Aujourd'hui, pour mon premier tuto, je vais vous apprendre à analyser les paquets que vous recevez.
Il y a déjà trois principaux tutoriels traitant de cela et qui m'ont servi de sources : https://cadernis.com/d/436-initiation-à-la-communication-avec-d, https://cadernis.com/d/143-[tuto]-identifier-les-packets et le plus ancien https://cadernis.com/d/115-comprendre-le-protocole-de-d20.
J'ai voulu faire le mien pour expliquer avec plus d'exemples (3 détaillés).
Je suis sous Mac OS et j'utilise wireshark comme sniffeur.
I) Capture des paquets
Tout d'abord, sélectionnez l'interface par laquelle vous vous connectez à Internet (dans mon cas, wifi = en1).
Ensuite, pour filtrer les paquets dofus, il va falloir créer un filtre (capture filter). Dans mon cas, je me connecte au port 5555 donc j'ai mis "tcp port 5555", et ça marche pour les serveurs de connexion et de jeu, donc pas besoin de chercher autre chose.
Vous sélectionnez start capture puis stop quand vous avez assez de paquets.
II) Analyse
Tout d'abord, sachez que wireshark intercepte TOUS les paquets, même les vides propres au protocole TCP qui, contrairement à l'UDP, accuse réception de chaque message, ce qui nous fait un PAQUET de paquets :)joke:).
On ne s'intéresse donc qu'aux paquets dont la taille est supérieure à 66 Bytes (taille d'un paquet sans data).
Ensuite, on regarde les deux premiers octets de la partie "Data" du message.
Ils sont sous la forme XX XX en hexadécimal. Pour débuter, je vous conseille http://sebastienguillon.com/test/javasc ... sseur.html pour vous retrouver dans les bases.
Il y a donc 16 bits dans ces 2 octets, mais quand vous convertissez en binaire, vous en aurez sûrement moins, donc considérez que ce sont des zéros qui manquent à gauche. (c'est pareil en base 10, vous allez écrire 42, et pas 042 ou 0042).
Les 14 premiers représentent l'id du paquet et les 2 derniers la taille de la taille du message.
Voici un joli schéma expliquant cela (merci Sorrow) :
Loading Image
En fait, on va prendre un exemple. Si les deux premiers bytes sont 00:7a, on va convertir en binaire (avec le temps, vous apprendrez sans même vous rendre compte les tables de conversion).
0=0000
7=4+2+1=0111
a=10=8+2=1010
donc on a reçu (0000 0000 0)111 1010.
docn l'id est 111 10 en base 2, (je laisse les espaces exprès) ce qui fait 1 1110 = 2+4+8+16 = 30.
La taille est 10 donc 2 en base 10.
Bien sûr, ça va plus vite avec le convertisseur. Mais si le premier octet est nul, voici deux autres méthodes qui permettent de le faire rapidement de tête :
Cliquez pour révéler
Cliquez pour masquer
Vous pouvez convertir directement de l'hexadécimal en décimal :
7a = 16*7 + 10 = 122 (il faut savoir multiplier, ou utiliser le convertisseur que j'ai donné).
On effectue ensuite une division euclidienne par 4. Le diviseur sera l'id et le reste la taille de la taille.
122 = 120 + 2 = 4 * 30 + 2 donc on retrouve les mêmes valeurs.
Enfin, méthode la plus rapide :
On multiplie le premier chiffre hexadécimal par 4 et on ajoute le quotient de la division par 4 du second pour obtenir l'id.
Pour la taille de la taille, on prend le reste de la division par 4 du second chiffre.
a= 10 = 4 * 2 + 2
id = 7*4+2 = 30
taille de la taille = 2.
Mais rassurez-vous, dès le prochain tuto, on va commencer à automatiser le processus et vous n'aurez plus à calculer, seulement à coder :D
Ces astuces vous serviront juste si vous êtes un masochiste qui programme en assembleur :twisted:
Après, on prend l'id, on ouvre les sources (pour les obtenir, décompilez DofusInvoker.swf avec JPEXS par exemple) avec un logiciel comme Sublime Text que m'a conseillé le très bienveillant [clignote]Moonlight-Angel[/clignote], que j'embête souvent par Skype avec mes questions, et à qui je dédicace ce tutoriel, et on recherche avec Maj+Cmd+F (ou Maj+ctrl+F sous Windows je suppose), ou encore TextMate que j'utilise, dans l'ensemble des sources le numéro de l'id.
Comme le même nombre peut apparaître ailleurs, je vous conseille de chercher "protocolId:uint = "+ l'id ; dans notre cas, "protocolId:uint = 30;".
Si vous voyez apparaître plusieurs résultats, ne considérez que le fichier situé dans messages, pas celui dans types. Celui dans types correspond à une structure de données, pas à un message.
Puis, intéressez-vous à la méthode de sérialisation pour un paquet envoyé et à la méthode de désérialisation pour un paquet reçu.
STOP, j'ai trop parlé dans le vide, place aux exemples !
III) Exemples
Exemple 1 : (paquet 30 = ServersListMessage)
paquet reçu
Cliquez pour révéler
Cliquez pour masquer
00:7a:02:da:00:34:00:13:03:00:01:00:00:00:00:00:00:00:00:00:00:10:03:00:01:00:00:00:00:00:00:00:00:00:13:89:
03:00:00:00:00:00:00:00:00:00:00:00:00:06:03:00:01:00:00:00:00:00:00:00:00:00:00:19:03:00:01:00:00:00:00:00:00:00:
00:00:0f:a3:03:00:00:00:00:00:00:00:00:00:00:00:00:0c:03:00:01:00:00:00:00:00:00:00:00:00:00:1f:03:00:00:00:00:00:00:
00:00:00:00:00:00:12:03:00:01:00:00:00:00:00:00:00:00:00:00:25:03:00:01:05:42:74:40:b7:01:90:e0:00:00:05:03:00:01:
00:00:00:00:00:00:00:00:00:00:18:03:00:01:00:00:00:00:00:00:00:00:00:0f:a2:03:00:00:00:00:00:00:00:00:00:00:00:00:0b
:03:00:01:00:00:00:00:00:00:00:00:00:00:1e:03:00:01:00:00:00:00:00:00:00:00:00:1b:59:03:00:00:00:00:00:00:00:00:00:
00:00:00:11:03:00:01:00:00:00:00:00:00:00:00:00:00:24:03:00:01:00:00:00:00:00:00:00:00:00:00:04:03:00:01:00:00:00:
00:00:00:00:00:00:00:17:03:00:01:00:00:00:00:00:00:00:00:00:0f:a1:03:00:00:00:00:00:00:00:00:00:00:00:00:0a:03:00:01
:00:00:00:00:00:00:00:00:00:00:1d:03:00:01:00:00:00:00:00:00:00:00:00:0f:a7:03:00:00:00:00:00:00:00:00:00:00:00:03:
e9:03:00:00:00:00:00:00:00:00:00:00:00:00:23:03:00:01:00:00:00:00:00:00:00:00:00:17:72:03:00:00:00:00:00:00:00:00:
00:00:00:00:03:03:00:01:00:00:00:00:00:00:00:00:00:00:16:07:00:00:00:00:00:00:00:00:00:00:00:00:29:03:00:00:00:00:
00:00:00:00:00:00:00:00:09:03:00:01:00:00:00:00:00:00:00:00:00:00:1c:03:00:01:00:00:00:00:00:00:00:00:00:23:29:03:
00:00:00:00:00:00:00:00:00:00:00:0f:a6:03:00:00:00:00:00:00:00:00:00:00:00:00:0f:03:00:01:00:00:00:00:00:00:00:00:00:
17:71:03:00:00:00:00:00:00:00:00:00:00:00:00:02:03:00:00:00:00:00:00:00:00:00:00:00:00:15:03:00:01:00:00:00:00:00:
00:00:00:00:00:28:03:00:00:00:00:00:00:00:00:00:00:00:00:08:03:00:00:00:00:00:00:00:00:00:00:00:00:1b:03:00:01:00:
00:00:00:00:00:00:00:00:0b:b9:03:00:00:00:00:00:00:00:00:00:00:00:0f:a5:03:00:00:00:00:00:00:00:00:00:00:00:00:0e:03
:00:01:00:00:00:00:00:00:00:00:00:00:21:03:00:01:00:00:00:00:00:00:00:00:00:00:01:07:00:00:00:00:00:00:00:00:00:00:
00:00:14:03:00:01:00:00:00:00:00:00:00:00:00:00:07:03:00:01:00:00:00:00:00:00:00:00:00:00:1a:03:00:01:00:00:00:00:
00:00:00:00:00:0f:a4:03:00:00:00:00:00:00:00:00:00:00:00:00:0d:03:00:01:00:00:00:00:00:00:00:00:00:00:20:03:00:01:00
:00:00:00:00:00:00:00:00
00 7a : en-tête = 0b1111010 donc paquet 30 et taille sur 2 octets
02 da : taille du message = 730 octets
Source :
public function deserializeAs_ServersListMessage(param1:IDataInput) : void {
var _loc4_:GameServerInformations = null;
var _loc2_:uint = param1.readUnsignedShort();
var _loc3_:uint = 0;
while(_loc3_ < _loc2_)
{
_loc4_ = new GameServerInformations();
_loc4_.deserialize(param1);
this.servers.push(_loc4_);
_loc3_++;
}
On voit qu'on peut déjà lire la dimension de la liste de serveurs.
00 34 : unsigned short nombre de serveurs = 52
On doit maintenant connaître comment est codée la classe GameServerInformations.
On remarque dans l'en-tête du fichier "import com.ankamagames.dofus.network.types.connection.GameServerInformations"
On regarde donc la source correspondante, spécialement la méthode de désérialisation.
Source :
public function deserializeAs_GameServerInformations(param1:IDataInput) : void {
this.id = param1.readUnsignedShort();
if(this.id < 0 || this.id > 65535)
{
throw new Error("Forbidden value (" + this.id + ") on element of GameServerInformations.id.");
}
else
{
this.status = param1.readByte();
if(this.status < 0)
{
throw new Error("Forbidden value (" + this.status + ") on element of GameServerInformations.status.");
}
else
{
this.completion = param1.readByte();
if(this.completion < 0)
{
throw new Error("Forbidden value (" + this.completion + ") on element of GameServerInformations.completion.");
}
else
{
this.isSelectable = param1.readBoolean();
this.charactersCount = param1.readByte();
if(this.charactersCount < 0)
{
throw new Error("Forbidden value (" + this.charactersCount + ") on element of GameServerInformations.charactersCount.");
}
else
{
this.date = param1.readDouble();
return;
}
}
00 13 : unsigned short id = 19
03 : byte status = 3
00 : byte completion = 0
01 : boolean isSelectable = 1
00 : byte charactersCount = 0
Après, on voit à quel point les programmeurs d'Ank@m@ se foutent d'utiliser des nombres à virgule en informatique.
00:00:00:00:00:00:00:00 : double date = 0
Donc les données de 1 serveur nous prennent 14 octets. Et 14*52=728 et si on ajoute les 2 octets du nombre de serveurs, ça fait bien 730 octets, soit la taille du message !
Je saute plusieurs serveurs pour arriver au serveur d'id 37 (en hexa 00 25)
00:25:03:00:01:05:42:74:40:b7:01:90:e0:00
Je traduis directement l'hexadécimal en décimal
id = 37
status = 3 on remarque presque tous les serveurs on un status de 3 donc ça doit être l'état disponible
completion = 0 je pense que ça veut dire qu'il n'est pas complet
isSelectable = 1
charactersCount = 5 j'ai bien 5 personnages sur ce serveur
La date est pour la première fois non nulle et vaut 42:74:40:b7:01:90:e0:00 = 1391761299726.0
A cette occasion, j'ai du programmer mon propre convertisseur hexa-binary64. Voici sa source en python 3 :
def hexdbl(ch):
if ':' in ch: ch=''.join(ch.split(':'))
if ' ' in ch: ch=''.join(ch.split(' '))
ch=bin(eval('0x'+ch))[2:]
ch=(64-len(ch))*'0'+ch
s=eval('0b'+ch[0])
e=eval('0b'+ch[1])
m=1+sum(int(ch[12+i])*2**-(i+1) for i in range(52))
return (-1)**s*m*2**(e-1023)
Exemple 2 : (paquet 40 = ServerSelectionMessage)
paquet envoyé
00:a1:02:00:25
00 a1 : en-tête = 0b10100001 donc paquet 0b101000 = 40 et taille encodée sur 1 octet
02 : taille du message = 2 octets
Source :
public function serializeAs_ServerSelectionMessage(param1:IDataOutput) : void {
param1.writeShort(this.serverId);
}
Message
00 25 : short serverId = 37
Exemple 3 : (paquet 42 = SelectedServerDataMessage)
paquet reçu
00:a9:37:00:25:00:0e:32:31:33:2e:32:34:38:2e:31:32:36:2e:38:35:15:b3:00:00:20:30:61:66:36:62:62:64:31:39:65:32:35:33:36:33:34:63:38:39:62:64:33:63:37:66:61:63:35:62:61:32:32
00 a9 : en-tête 0b10101001 donc paquet 0b101010=42 et taille encodée sur un octet
37 : taille du message = 55 octets
Source :
public function deserializeAs_SelectedServerDataMessage(param1:IDataInput) : void {
this.serverId = param1.readShort();
this.address = param1.readUTF();
this.port = param1.readUnsignedShort();
if(this.port < 0 || this.port > 65535)
{
throw new Error("Forbidden value (" + this.port + ") on element of SelectedServerDataMessage.port.");
}
else
{
this.canCreateNewCharacter = param1.readBoolean();
this.ticket = param1.readUTF();
return;
}
}
Message
00 25 : short serverId = 37
00 0e : short taille du reader utf = 14
32 31 33 2e 32 34 38 2e 31 32 36 2e 38 35 : utf address = 213.248.126.85
15 b3 : unsigned short port = 5555
00 : boolean canCreateNewCharacter = false
00 20 : short taille du reader utf = 32
30:61:66:36:62:62:64:31:39:65:32:35:33:36:33:34:63:38:39:62:64:33:63:37:66:61:63:35:62:61:32:32 : utf ticket = 0af6bbd19e253634c89bd3c7fac5ba22
Voilà, j'espère que ce tutoriel vous a plu, en tous cas, j'ai été content de l'écrire :)
Je suis par contre désolé de ne pas avoir pu mettre un image de wireshark, je sais pas comment les intégrer DANS le tutoriel.
EDIT : C'est bon, je crois savoir comment les héberger et les afficher dans le même post.
Si vous avez des questions, des remarques, surtout n'hésitez pas à commenter ou à m'envoyer un MP (enfin, pas trop quand même ;) ) !
Pour des explications complémentaires, se reporter à ce sujet : https://cadernis.com/d/1737 :)