Sniffer avec NodeJs

Inscrit
31 Aout 2020
Messages
9
Reactions
0
#1
Bonjour,

J'ai testé de sniffer les paquets de Dofus (version 2.56) avec un script javascript lancé avec NodeJs.
Voici par exemple ce que je récupère en format ASCII lorsqu'un message arrive dans le canal général : Å1♦TEST_M"t1eknh6zBS"4À♀Oufti-maladÍǤ
On peut donc y voir mon message, avec mon pseudo, et des caractères complètement wtf, j'imagine qu'ils correspondent à des codes pour signifier que c'est un message en canal général.
Voici le tableau de caractères
13, 197, 49, 0, 0, 4, 84, 69, 83, 84, 95, 77, 34, 165, 0, 8, 116, 49, 101, 107, 110, 104, 54, 122, 66, 83, 150, 34, 0, 52, 192, 0, 0, 12, 79, 117, 102, 116, 105, 45, 109, 97, 108, 97, 100, 101, 0, 0, 8, 205, 199, 164

J'aimerais comprendre ce sharabia.

Un autre exemple un peu plus approfondi, voici le message que je reçois quand je déplace mon personnage
14, 221, 22, 0, 5, 1, 114, 1, 100, 1, 87, 1, 73, 1, 45, 0, 3, 66, 83, 150, 34, 0, 52, 192, 0, 2, 192
Après avoir testé plusieurs fois le déplacement sur la map (pas le changement de map, juste le perso sur les cases du jeu) je remarque que le début de la séquence commence toujours par les nombres 14, 221
Ensuite le 3ème c'est assez spécial, si je me déplace d'une seule case en carré autour de moi il est égal à 16, de 2 cases il est égal à 18, et ainsi de suite.
Le 4ème nombre est toujours égal à 0
Le 5ème représente notre fameux nombre de case parcourus (+1)...

Enfin bref, vous voyez ce que je veux dire, est-ce qu'il existe quelque part une sorte de bible de cette traduction ?
Merci de m'avoir lu et bonne journée.
 
Inscrit
10 Mai 2017
Messages
26
Reactions
30
#2
https://cadernis.fr/index.php?threads/comprendre-le-protocole-de-dofus.115/#post-682
C'est la formule magique que tu cherches ^^

Si tu galères à comprendre ( ou que tu as la flemme de tout lire je vais te résumer le tout en quelque ligne )

Le protocol Dofus ( les données envoyer ) sont écris en BigEndian ( je te laisse le plaisir de taper sur google si tu n'as pas lu le post )
Pour lire le protocol c'est :
- un short pour le header ( ou est stocké le protocol id et une approxymation de la taille )
- un uint pour l'instanceId ( uniquement du côté des messages envoyé par le client )
- la taille des données dans le message ( qui est variable , soit c'est 1 byte ou 1 short ou 1 byte + 1 short )
- les données dans le message ( le reste des données )

Après dans tes données données reçu il peut y avoir plusieurs messages ou un message pas fini ( le reste du message est dans la prochaine données que tu reçois )

Pour lire les données dans le message il faut avoir en main les classes du protocol ( que tu peux trouver dans le DofusInvoker.swf , ou sinon il y a une gentil personne sur le forum qui à fait ça https://cldine.gitlab.io/-/protocol-autoparser/-/jobs/691246963/artifacts/protocol.json si tu veux avoir un aperçu )
 
Dernière édition:
Inscrit
31 Aout 2020
Messages
9
Reactions
0
#3
Merci pour cette réponse fournie, j'ai lu le topic et farfouillé un peu dans le fichier avec les ids.

Je ne sais pas si tu t'y connais en Javascript avec NodeJs, j'utilie cette librairie pour sniffer : https://github.com/mscdex/cap

Du coup je ne sais pas si ce que tu me dis par rapport au protocole est déjà géré par la lib, car dans le code d'exemple, on a déjà une espèce d'interprétation du header qui nous donne le protocole avec les adresses & ports de source et destination (IPv4 + TCP)

Et une fois ces données retirées du buffer, il reste les données du message à proprement parlé, est-ce que le short dont tu parles pour le header avec protocol id + taille est là, ou alors tu parles en fait du truc global de réception de paquets ?
Moi mon avis c'est que ce message ne contient que les données du jeu (ton dernier point), j'ai bon ?

J'ai continué un peu mes expérimentations lors d'un déplacement sur la map, j'en ai conclu que le jeu indiquait chaque cellule par un id, puis à la fin toujours la même séquence, je ne sais pas encore trop à quoi cela correspond, mais j'en avait assez pour commencer mes expérimentations ailleurs.

Je cherche à comprendre le système de forgemagie, mais quand je fais une recherche (ctrl+f) sur le fichier que tu as partagé, je ne trouve rien en rapport avec "forgemagie" ou "rune", tu saurais m'éclairer ?
 
Inscrit
31 Aout 2020
Messages
9
Reactions
0
#4
Voici le message que je reçois quand je passe une Rune Cha qui passe en succès critique sur un Marteau de Forgeur de Grattoir
96, 177, 29, 2, 244, 77, 0, 4, 0, 70, 123, 7, 0, 70, 170, 3, 1, 0, 70, 158, 3, 1, 0, 82, 96, 5, 7, 141, 187, 110, 1, 1, 90, 137, 9, 2, 244, 77, 211, 129, 148, 175, 147, 5, 47, 85, 29, 0, 63, 244, 77, 0, 4, 0, 70, 123, 7, 0, 70, 170, 3, 1, 0, 70, 158, 3, 1, 0, 82, 96, 5, 7, 141, 187, 110, 1, 47, 5, 7, 240, 206, 1, 0, 179, 222, 1, 2, 192

Donc ici on a 96 au début qui est le code d'en-tête qui signifie qu'un a fusionné une rune ?

J'ai presque le même message quand j'en repasse une et que ça fait échec, visiblement c'est le 4ème nombre qui donne cette info :
- 2 = succès
- 1 = échec
 
Inscrit
10 Mai 2017
Messages
26
Reactions
30
#5
Tu as assez mal interprété ce que j'ai écris , et visiblement tu n'as pas très bien compris le sujet que bouh2 à écrit ^^
Je vais essayer d'être le plus clair possible.

Je ne connais pas trop le Js donc ce que je dit peux être faux , mais le header dont toi tu parles c'est une sorte de donnée concernant le client/serveur et non les données envoyé par le jeu.
Moi je te parle vraiment des données envoyées par le jeu ( en l'occurrence le tableau de byte que tu nous montre (un tableau de byte = une liste de nombre allant de 0 à 255 = une liste d'octet/8bit ) )

Le header dont moi je te parle fait parti de cette liste d'octet
Et pour lire correctement cette liste d'octet , il ne faut pas lire mannuellement en se basant sur du hasard , ou changer le tout en ASCII ( car toute les données présent dans cette liste d'octet n'est pas de l'ASCII )

Faut savoir que :
-1 byte = 1 octet = 1 nombre dans la liste
-1 short = 2 octets = 2 nombres dans la liste
-1 int = 4 octets = 4 nombres dans la liste

Ducoup je vais un peu me réexpliquer sur ce que j'ai écrit plus haut :
Quand tu as ta liste d'octet ce que tu vas devoir faire c'est :

- Prendre les 2 premiers octet pour le header du message , qui contient : Le protocol Id et une APPROXYMATION de la taille des données*
- Prendre les 4 octets suivants pour l'InstanceID UNIQUEMENT SI le tableau d'octet provient du client
- Prendre 1 ou 2 ou 3 octets ( en fonction de la taille des données* )
- Prendre le reste d'octets -> Ce qui correspond au données du message

Après pour lire les données du message comment on fait ?

Il te faudra le protocol ( le lien que j'ai mis en haut : https://cldine.gitlab.io/-/protocol-autoparser/-/jobs/691246963/artifacts/protocol.json te donne un aperçu mais comme je te l'ai déjà dit , toute les classes sont dans DofusInvoker.swf , après pour le chemin c'est
com.ankamagames.dofus.network.messages pour les messages et com.ankamagames.dofus.network.types pour les types ( j'explique un peu plus bas ) tu peux aussi avoir le protocol sous forme de plusieurs class ( c'est ce qui est présent dans le DofusInvoker dailleurs )

Pour lire les données je vais te faire un exemple car c'est assez dur d'expliquer sans

Déjà pour les messages de déplacement le message en question c'est (sur le lien que je t'ai données ):
Code:
{
      "fields": [
        {
          "default_value": "0",
          "name": "forcedDirection",
          "position": 1,
          "type": "int",
          "write_method": "writeShort"
        },
        {
          "bounds": {
            "low": "-9007199254740992.000000",
            "up": "9007199254740992.000000"
          },
          "default_value": "0",
          "name": "actorId",
          "position": 2,
          "type": "Number",
          "write_method": "writeDouble"
        },
        {
          "bounds": {
            "low": "0"
          },
          "is_vector": true,
          "name": "keyMovements",
          "position": 0,
          "type": "uint",
          "write_length_method": "writeShort",
          "write_method": "writeShort"
        }
      ],
      "name": "GameMapMovementMessage",
      "namespace": "com.ankamagames.dofus.network.messages.game.context",
      "protocolID": 951,
      "super": "NetworkMessage",
      "super_serialize": false,
      "supernamespace": "com.ankamagames.jerakine.network",
      "use_hash_function": false
    }
Avec ça tu si tu tri par leur position tu as :
0 : keyMovements
1 : forcedDirection
2 : actorId

keyMovements c'est un tableau de Short ( la taille du tableau est un Short )
forcedAction c'est un Short
actorId c'est un Double

à partir de la pour lire les données tu vas faire comme ça :

- Prendre 2 octets pour la taille de keyMovements
- Prendre 2 octets * taille de keyMovements pour avoir chaque keyMovements
- Prendre 2 octets pour forcedDirection
- Prendre ? octets pour actorId ( Je ne sais plus sur combien d'octet est encodé un double x) )

Et c'est la même chose pour tout les message. ( si tu veux avoir un exemple d'implémentation en Js je n'ai pas de lien mais https://cadernis.fr/index.php?threads/client-qui-prend-le-pas-sur-un-autre.2572/#post-25737 dans la class Client il y a une implémentation en C# , dans la fonction Send() c'est la logique d'envoye et non de reception , mais il suffit de faire l'inverse , la logique de reception c'est dans la class Buffer )

Pour les Types , c'est juste des classes qui seront directement lu comme en haut mais sans la partie header/instanceid/taille données car ils seront présent dans les données.

J'ai essayé d'être le plus clair possible , excuse moi si t'as pas bien compris.

*: j'ai volontairement différencier le tableau d'octet des données , car quand je te parle des données c'est tout ce qu'il faut savoir dans le message

PS : si tu veux déjà faire un grand pas , au lieu d'afficher les tableau d'octet tu devrais afficher le protocolId de chaque message que tu reçois
C'est à dire que tu prend les 2 premiers octets (sous forme d'un entier encodé sur un short en BigEndian) et tu fais un décallage de 2 vers la droite. ( header >> 2 ) après il te suffit de faire une recherche ctrl + f sur le protocol que je t'ai envoyer pour savoir à quel message ça correspond
Mais n'oublie pas de lire le tout en BigEndian sinon tu auras des valeurs un peu farfelu
 
Dernière édition:
Inscrit
31 Aout 2020
Messages
9
Reactions
0
#6
Okay j'ai lu tout ça, merci c'est déjà plus clair pour moi, je vais continuer de faire mon code Javascript, je reviendrais poster quand j'aurai du nouveau :) !
 
Inscrit
31 Aout 2020
Messages
9
Reactions
0
#7
J'ai bien galéré mais j'ai fini par trouver les outils Buffer / BigEndian en Javascript, je connaissais pas trop ça, maintenant je comprend mieux comment convertir les bits un peut comme on veut.

J'ai donc réussi à choper le Protocol Id correctement, quel sentiment de satisfaction => Real Hacker mdr... On se calme x)

Bon je continue l'expérimentation, merci pour tes explications, je partagerais un code plus tard pour ceux qui sont curieux de comment ça fonctionne en Javascript ;) .
 
Inscrit
31 Aout 2020
Messages
9
Reactions
0
#8
Bon je bloque à nouveau...

Là je suis en train d'analyser le message ExchangeObjectAddedMessage lorsqu'on ajoute un item dans l'interface de FM.
Voici donc la "doc"
Code:
      {
        "fields": [
          {
            "name": "object",
            "position": 0,
            "self_serialize_method": "serializeAs_ObjectItem",
            "type": "ObjectItem",
            "type_namespace": "com.ankamagames.dofus.network.types.game.data.items"
          }
        ],
        "name": "ExchangeObjectAddedMessage",
        "namespace": "com.ankamagames.dofus.network.messages.game.inventory.exchanges",
        "protocolID": 5516,
        "super": "ExchangeObjectMessage",
        "super_serialize": true,
        "supernamespace": "com.ankamagames.dofus.network.messages.game.inventory.exchanges",
        "use_hash_function": false
      }

Donc là je comprend qu'après avoir récupéré Protocole + Taille du message, je dois récupérer un objet du type ObjectItem

Code:
      {
        "fields": [
          {
            "bounds": {
              "low": "0"
            },
            "default_value": "0",
            "name": "objectGID",
            "position": 1,
            "type": "uint",
            "write_method": "writeVarShort"
          },
          {
            "default_value": "63",
            "name": "position",
            "position": 0,
            "type": "uint",
            "write_method": "writeShort"
          },
          {
            "bounds": {
              "low": "0"
            },
            "default_value": "0",
            "name": "objectUID",
            "position": 3,
            "type": "uint",
            "write_method": "writeVarInt"
          },
          {
            "is_vector": true,
            "name": "effects",
            "position": 2,
            "prefixed_by_type_id": true,
            "self_serialize_method": "serialize",
            "type": "ObjectEffect",
            "type_namespace": "com.ankamagames.dofus.network.types.game.data.items.effects",
            "write_length_method": "writeShort",
            "write_type_id_method": "writeShort"
          },
          {
            "bounds": {
              "low": "0"
            },
            "default_value": "0",
            "name": "quantity",
            "position": 4,
            "type": "uint",
            "write_method": "writeVarInt"
          }
        ],
        "name": "ObjectItem",
        "namespace": "com.ankamagames.dofus.network.types.game.data.items",
        "protocolID": 37,
        "super": "Item",
        "super_serialize": true,
        "supernamespace": "com.ankamagames.dofus.network.types.game.data.items",
        "use_hash_function": false
      }

Qui contient lui même ce qui m'intéresse le plus dans mon objectif : un ObjectEffect
Code:
{
        "fields": [
          {
            "bounds": {
              "low": "0"
            },
            "default_value": "0",
            "name": "actionId",
            "position": 0,
            "type": "uint",
            "write_method": "writeVarShort"
          }
        ],
        "name": "ObjectEffect",
        "namespace": "com.ankamagames.dofus.network.types.game.data.items.effects",
        "protocolID": 76,
        "super": "",
        "super_serialize": false,
        "supernamespace": "",
        "use_hash_function": false
      }

Mais là est mon soucis, je n'arrive pas à bien segmenter le buffer pour récupérer correctement les informations...
De plus je ne comprend pas ce qu'est actionId

Voici ce que j'arrive à récupérer lorsque je met un Anneau du Piou Vert
Code:
Protocol ID : 5516 (ExchangeObjectAddedMessage)
Length (type/value) : 1 / 21
Message : [ 0, 63, 65533,  64, 0, 2, 0, 70, 124, 8, 0, 70, 65533, 3, 1, 65533, 65533, 65533, 70, 1, 2, 65533 ]
Selon la "doc" :
- 1er short position nous donne 63, j'imagine l'id de la cellule de FM ? Je ne sais pas trop, enfin ce n'est pas très important dans tous les cas.
- 2ème short objectGID (en BigEndian) nous donne 40512, l'id global de l'item (différent de UID qui rend l'item unique comme vu ici https://cadernis.fr/index.php?threads/suis-je-sur-la-bonne-voie.2033/ )
Mais là y'a un concept de writeVarShort que je n'arrive pas à bien comprendre, cela veut dire que ce n'est pas juste 2 octets ? ça peut être + ou - ?

Bref quand je regarde le message j'arrive quand même à trouver des valeurs cohérentes à certains endroits qui m'ont permis de sortir ça
Code:
{
  position: 63,
  objectGID: 40512,
  statsLength: 2,
  stats: [
    { something: 70, id: 124, value: 8 },
    { something: 70, id: 172, value: 3 }
  ]
}
Mais c'est très inconsistant, des fois selon l'items y'a comme un octet en + qui décale tout et qui met le bazar mais je n'arrive pas à définir quand il est là où pas, je pense à cause du concept de writeVarShort.
 
Inscrit
10 Mai 2017
Messages
26
Reactions
30
#9
T'as pas vraiment compris ce que j'ai expliqué enfaite.
Pour le header , tu t'es déjà bien débrouillé , mais pour toute la suite , il faudra ouvrir le DofusInvoker et essayé de lire tout ce qu'il y a à l'intérieur, toute les réponses à tes questions ce trouve dedans.

Concernant ta façon de lire les données , elle est un peu bancal , parce que tu sautes des propriétés , ducoup ça décalle forcément tout.
Et tu interprete mal aussi certaine propriété, par exemple position dans objectItem , c'est pas du tout l'emplacement FM , mais l'emplacement dans l'inventaire et 63 correspond à NOT_EQUIPED. (si tu veux les autres position c'est dans CharacterInventoryPositionEnum )
Comme je ne connais rien de la façon dont tu as implémenter les choses , il se peu aussi que tu ai une erreur à ce niveau la (surtout au niveau des messages qui sont fragmenté, ou même plusieurs messages dans un seul tableau de byte ).

Pour te familiariser avec tout ça , je te conseille avant tout d'observer TOUT LE PROTOCOL , ( enum , message , type ) , observer différent packet envoyé/reçu ( même sans leur contenu ) pour savoir quoi envoyé et à quel moment, et aller LIRE certaines fonction du DofusInvoker ( ça ne dépendra que de ce dont tu as besoin , mais pour le VarShort , si tu veux la fonction original , c'est dans le DofusInvoker , sinon tu cherches dans les sources d'un bot ou un émulateur )
( Il ne faut pas négliger la lecture avant tout , et surtout les noms/types des variables , ça te permetra d'avoir une vision d'ensemble sur les différents élément utiliser par le protocol )

Pour finir , ça ne sert à rien de direct s'attaquer à des trucs que tu ne comprend même pas.
Essaye d'abord de faire des choses simples , d'analyser des petits packets. ( Comme par exemple le packet des mouvements qui est relativement facile à lire , ou même le changement d'orientation )

Je vais refaire une petite explication de comment lire les données des types :

Il faut savoir que la façon dont les types et les messages sont lu , c'est exactement la même chose.

Pour les messages :
Il faut d'abord lire le header et le reste des données qui ne sont pas des propriétées du messages , puis lire les données qui sont des propriétées du messages
Pour les types :
Lire le protocolId du types (ce n'est obligatoire que dans certains cas , dans le protocol en json c'est prefixed_by_type_id qui va définir si on lit l'id ou pas , dans d'autre cas il faut directement passé à la lecture ) , puis il faut lire les données qui sont les proprétées du types. ( de la même façon que tu lis les propriétées d'un message )

Si un messages ou un type à une propriété qui est un type lui aussi , il te suffit de refaire la même chose.
 
Dernière édition:
Inscrit
31 Aout 2020
Messages
9
Reactions
0
#10
La lecture des paquet de mouvements je l'ai déjà plus où moins expérimenté, c'est pour ça que je suis allé directement à l'essentiel : la FM.
Aussi je ne cherche pas à envoyer de paquet, seulement lire ceux que je reçois, c'est aussi la raison pour laquelle je ne m'attarde pas à tout comprendre.

Je ne sais pas où se trouve le DofusInvoker ni comment le lire, c'est quoi son extension ?

D'ailleurs maintenant c'est plutôt ExchangeObjectModifiedMessage que je cherche à analyser.
 
Inscrit
31 Aout 2020
Messages
9
Reactions
0
#11
Pour donner + de contexte, ce que je cherche à faire c'est connaître les stats de l'item que je forgemage après avoir passé une rune.
 
Inscrit
10 Mai 2017
Messages
26
Reactions
30
#12
C'est pas la partie la plus dur si tu prenais le temps de comprendre.
Le DofusInvoker se situe dans le dossier du jeu au même endroit que Dofus.exe , pour le lire il te faudra un logiciel capable d'ouvrir les fichier swf ( JPEXS est gratuit si tu veux )
 
Inscrit
31 Aout 2020
Messages
9
Reactions
0
#13
J'ai ouvert le fichier avec JPEXS, j'ai trouvé des choses dans d2hooks mais ça ne m'avance à rien pour l'instant, genre quand j'ouvre "ExchangeObjectModified" y'a juste une déclaration de classe avec un super() dedans.
Où sont les types / messages dont tu parles dans le DofusInvoker ?
 
Inscrit
10 Mai 2017
Messages
26
Reactions
30
#14
com.ankamagames.dofus.network pour le protocol
com.ankamagames.jerakine.network pour les readers/writers

Tu peux utiliser la recherche ( ctrl + f ) pour rechercher directement les classes que tu veux dans le DofusInvoker.
 
Haut Bas