[C] Besoin d'aide sur le dev d'un bot

Inscrit
10 Decembre 2022
Messages
9
Reactions
1
#1
Bonjour à tous. Ca fait longtemps que je voulais créer un bot et donc il y a quelques semaines je me suis lancé mais j'ai quelques problèmes.

Pour l'instant, j'arrive à recevoir les paquets provenant de dofus, et à les parser en décomposant le ethernet header, ip header, data payload. Il se pose maintenant la question de savoir si je fais un bot socket ou MITM.

Je voulais partir sur un bot socket, mais je me rend compte du travail titanesque puisque de ce que j'ai compris, si j'envoie un paquet faux, je me fais ban instantanément. Donc moi qui voulait juste envoyer des paquets pour cliquer sur l'écran et choper quelques infos sur les maps, je me retrouverais à recréer complétement la gestion d'envoie des paquets utilisé par le client. Notamment refaire chaque header des paquets, les bons numéros de ACK etc. Donc je ne pense pas que pour mon niveau actuel en réseau, c'est une bonne idée, sauf si j'ai mal compris quelque chose ou que j'ai omis d'utiliser un outil qui me faciliterait les choses.

Donc je pense faire un bot MITM. Je voulais en faire un sur dofus 2.0 mais j'ai vu que certains paquets sont protégés par le protocole TLS et je ne sais pas si ces paquets sont importants ou pas en terme d'informations concernant les maps/combats etc
Alors que sur dofus 1.29, ca à l'air plus simple et j'ai déjà le code source ( https://github.com/Sceat/Dofus je ne sais pas si il est encore d'actualité ) mais j'ai énormément de mal à trouver comment sont construis les paquets.

C'est pourquoi je fais ce topic, j'ai besoin de quelques conseils sur :
-en ayant un paquet, où chercher dans le code source pour pouvoir le décoder
-est-ce que un bot socket nécessite de recoder la gestion d'envoie de paquets du client
-est-ce que avec un bot MTIM, c'est plus simple de le faire sur dofus 1.29 ou dofus 2.0 et quelles sont les différences ?
-j'ai vu que certaines personnes redirigaient les paquets vers un proxy avec frida, est-ce que cela a un avantage par rapport à une simple écoute sur réseau ?

Merci d"avance
 
Dernière édition:
Inscrit
27 Septembre 2019
Messages
47
Reactions
250
#2
Hello, je parlerai que pour rétro mais j'imagine que certains de ces principes s'appliquent aussi pour 2.0. Pour ce qui concerne le parsing des packets pour retro tu peux retrouver ça ici.

Vu que tu parles de header ethernet et ip (layer data-link et network du modèle OSI) j'imagine que tu utilises une bibliothèque comme WinPcap (ou Wireshark directement), ce qui est rapide et pratique si tu veux voir le contenu des packets etc mais si tu veux en envoyer toi-même ça l'est un peu moins, d'où l'interêt de récupérer les packets au niveau des layers supérieurs via des hook API (bind/recv/send/connect), comme le fait Frida en injectant une DLL dans le processus-cible.

L'intêret de rediriger ton flux TCP vers ton propre serveur local c'est d'avoir le contrôle sur les packets entrant/sortant, gérer plusieurs clients facilement, déléguer toute la couche réseau à ton serveur, connecter tes clients à travers des proxies etc. Tu fais tourner ton serveur et il attend juste des connexions (que tu auras redirigé au préalable). Ensuite il s'agit que de récupérer et traiters les informations que tu reçois, surtout si tu veux faire un bot et j'imagine ajouter un moteur de scripts.

Je pense que retro est de loin le plus simple pour commencer: protocole texte et pas binaire, et surtout non chiffré.
Pour ce qui est de full socket vs MITM, je te conseillerais MITM du simple fait des mesures anti-bot mises en place, mais aussi parce que tu n'as pas à réimplémenter tous les packets du jeu donc plus rapide à mettre en place.

L'inconvénient principaux du MITM c'est que c'est assez lourd si tu comptes lancer plusieurs fenêtre sur une même machine:teeth:
Pour ce qui est du full socket il faut réimplémenter quasiment tous les packets et aussi l'interface externe Electron, mais ça te permet de lancer énormément de clients.
 
Inscrit
10 Decembre 2022
Messages
9
Reactions
1
#3
Merci pour ta réponse complète. Ce que je ne comprends pas, c'est que le client lorsqu'il envoie des paquets au serveur, il n'envoie pas seulement la couche TCP/UDP mais il envoie aussi la couche Ethernet et la couche Ip donc je ne comprends pas comment on obtient juste la couche TCP/UDP avec Frida ?

Sinon, voici ce que j'ai compris sur mon boulot à venir, qu'on me corrige si je dis n'importe quoi :

-Lancer le client dofus.exe en redirigeant sa connexion non pas vers dofus mais vers mon serveur local
-Une fois que mon serveur local a reçu la connexion d'un client, il ouvre une connexion au serveur dofus
-Transmettre les paquets du serveur dofus au client
-Récupérer les segments de la couche TCP émis par le client, les analyser et ensuite envoyer au serveur : soit un paquet bidouillé si c'est un bot socket, soit le paquet originel émis par le client, en ayant eu soin de rajouter les couches Ethernet et IP si c'est un MITM
 
Inscrit
27 Septembre 2019
Messages
47
Reactions
250
#4
Merci pour ta réponse complète. Ce que je ne comprends pas, c'est que le client lorsqu'il envoie des paquets au serveur, il n'envoie pas seulement la couche TCP/UDP mais il envoie aussi la couche Ethernet et la couche Ip donc je ne comprends pas comment on obtient juste la couche TCP/UDP avec Frida ?

Sinon, voici ce que j'ai compris sur mon boulot à venir, qu'on me corrige si je dis n'importe quoi :

-Lancer le client dofus.exe en redirigeant sa connexion non pas vers dofus mais vers mon serveur local
-Une fois que mon serveur local a reçu la connexion d'un client, il ouvre une connexion au serveur dofus
-Transmettre les paquets du serveur dofus au client
-Récupérer les segments de la couche TCP émis par le client, les analyser et ensuite envoyer au serveur : soit un paquet bidouillé si c'est un bot socket, soit le paquet originel émis par le client, en ayant eu soin de rajouter les couches Ethernet et IP si c'est un MITM
Oui mais ça c'est ton OS qui le gère, du point de vue d'une application tu utilises des API système (send/recv/connect), tu te préoccupes seulement de la payload, ensuite ton OS se charge de l'encapsulation de ces données pour les différents protocoles.
 
Inscrit
10 Decembre 2022
Messages
9
Reactions
1
#5
Ok j'ai compris, merci. Peux-tu me dire si mon petit résumé de ce que je dois faire au post au dessus est correct ?
 
Inscrit
27 Septembre 2019
Messages
47
Reactions
250
#6
Oui tu as compris le principe mais tu auras pas besoin d'encapsuler toi-même tes paquets, juste utiliser send/recv pour envoyer et recevoir
 
Inscrit
10 Février 2017
Messages
26
Reactions
43
#7
J'ai réussi à faire un bot 100% socket qui s'authentifie et gère une équipe de bots qui font des combats en boucle dans une zone donnée.
Je te dis que si tu comptes passer pas mal de temps sur ton projet, prends le temps de faire les choses correctement comme ça tu ne jetteras pas ton code dans la poubelle après.
Un bot socket est chronophage à mettre en place au départ, surtout si tu n'es pas un dev professionnel, mais il a beaucoup d'avantages.
L'avantage principal et de se passer du client qui est très gourmand en ressources, et donc de pouvoir lancer des dizaines voir des centaines de bots dans un pc gamer standard (à 36gb de ram j'ouvre jusqu'à 2xx bots dans mon cas).
Tu peux t'inspirer, comme je l'ai fait, du code source du client lui même pour architecturer ton code, ils ont mis en place un design que je trouve très bien fait.
Cela te prendra peut être 6mois avant d'avoir ton petit client simplifié mais, une fois ça de fait, ça t'ouvrira la porte pour toute sorte de bots.
Il faut voir un client comme un process qui tourne en tâche de fond et qui actualise une petite base de données (sous forme de dictionnaire python dans mon cas), selon les données échangées entre ton socket et celui du serveur. Ces données contiennent tout simplement ce qu'il y'a dans la Map courante que ce soit en combat ou en roleplay, donc rien de fous et il n'y a pas des masses de messages échangés qui concernent ces données et tu peux discard même beaucoups de messages par example qui traitent les succès, les maison, les prismes etc.. dans un premier temps du moins.
Au dessus de ce client tu peux alors ajouter tes propres 'frames' qui implémenterons les différents scénarios qu'exécutera ton bot dans le jeu en se basant en l'occurrence sur les données que t'as mis bien au propre le petit client et te les met à jour en live.
Je te conseille de regarder les classes suivantes dans le code source du jeu:
- ServerConnection : Gestion bas niveau des données reçus/envoyé
- tous les dossier frames que tu trouveras dans la partie 'logic' qui implémente comment sont traités les messages reçus par le client.
- authentificationFrame et authentificationManager qui gèrent la partie authentification
- decompile les sources du launcher (c'est de l'électron js) et regarde dans le app.js tu trouveras comment est obtenu le Haapi api key qui permet de générer le token du login
Bon courage
 
Dernière édition:
Inscrit
10 Decembre 2022
Messages
9
Reactions
1
#8
Merci pour ces conseils hadamard, je note :)

J'ai une question concernant les paquets émis et reçus. Si je prends l'inventaire, pourquoi est-ce que quand je l'ouvre, je ne reçois ou n'émet aucun paquet me disant par exemple mon nombre de pods utilisé ? Comment obtenir cette information ?

Plus généralement, quelles sont les actions qui ne générent aucun paquet ? Et du coup comment obtenir les informations
 
Dernière édition:
Inscrit
10 Février 2017
Messages
26
Reactions
43
#9
Merci pour ces conseils hadamard, je note :)

J'ai une question concernant les paquets émis et reçus. Si je prends l'inventaire, pourquoi est-ce que quand je l'ouvre, je ne reçois ou n'émet aucun paquet me disant par exemple mon nombre de pods utilisé ? Comment obtenir cette information ?

Plus généralement, quelles sont les actions qui ne générent aucun paquet ? Et du coup comment obtenir les informations
Merci pour ces conseils hadamard, je note :)

J'ai une question concernant les paquets émis et reçus. Si je prends l'inventaire, pourquoi est-ce que quand je l'ouvre, je ne reçois ou n'émet aucun paquet me disant par exemple mon nombre de pods utilisé ? Comment obtenir cette information ?

Plus généralement, quelles sont les actions qui ne générent aucun paquet ? Et du coup comment obtenir les informations
A ta première connexion au serveur de jeu. Celui-ci t'enverra le message Inventory content message, qui constituera si tu veux la valeur initiale dans ta base de données locale. Ensuite, il t'enverra des messages ponctuellement quand il y'a un changement dans l'inventaire. Par exemple, lors de l'obtention d'un objet tu recevras le message 'ObjectAddedMessage' avec le guid ainsi que la quantité reçue qui permet au client de mettre à jour les données de l'inventaire.
On retrouvera le même principe pour les données de la Map courante, les caractéristiques, la Map de combat etc.
 
Inscrit
10 Decembre 2022
Messages
9
Reactions
1
#10
Ok merci, je note. J'ai un autre problème désormais. Vu que je ne redirige pas la connexion de la socket, je reçois des paquets qui ne correspondent pas forcément à des messages. J'essaie de comprendre le fichier ServerConnection.as et surtout la fonction receive / lowReceive mais je n'arrive pas à comprendre comment il reconstitue les messages à partir des paquets réseau. Surtout que je ne commence pas à sniffer en lancant le jeu mais en cours de route ce qui ajoute une difficulté. Des idées ?
 
Inscrit
10 Février 2017
Messages
26
Reactions
43
#11
Ça dépend de quelle méthode de sniffing tu utilises.
Dans un framework de sniffing par du code ce que tu vas avoir en général c'est une boucle infini qui to fourni packet par packet les packets reçus.
Après, par une méthode du framework tu peux récupéré la partie TCP du packet par par example paquet.tcp.
Dans la couche TCP, il y'a un champs 'seq' qui permet d'ordonner les paquets, je te laisse aller voir de ton côté et comprendre comment est utilisé le champs 'seq'.
Une fois maitrisé, ce que tu devrais être capable de faire c'est une fonction qui queue les packets désorganisé. Et une juste après qui reconstitue les stream en ordre.
De ce stream ordonné plus le protocol du jeu tu vas pouvoir parse tes messages.
Voici la couche haute de mon sniffer :

Python:
#!/usr/bin/python
import os
from types import FunctionType
import pyshark
import socket
from whistle import Event, EventDispatcher
from pydofus2.com.ankamagames.dofus.network.MessageReceiver import MessageReceiver
from pydofus2.com.ankamagames.jerakine.logger.Logger import Logger
from pydofus2.com.ankamagames.jerakine.network.CustomDataWrapper import ByteArray
from pydofus2.com.ankamagames.jerakine.network.ServerConnection import ServerConnection
from snifferApp.network.message import Message
import threading

logger = Logger()
LOW_LEVEL_DEBUG = os.environ['LOW_LEVEL_DEBUG']


class SnifferBuffer:
    def __init__(self):
        self.memory = list()
        self.buffer = ByteArray()
        self.nextSeq = None


    def getSeqRw(self, tcp_packet):
        return int(tcp_packet.seq), tcp_packet.payload.binary_value

    def updateFromMemory(self):
        self.memory.sort(key=lambda e: e.seq)
        poped = []
        for packet in self.memory:
            seq, raw = self.getSeqRw(packet)
            if seq == self.nextSeq:
                poped.append(seq)
                self.buffer += raw
                self.nextSeq = seq + len(raw)
        self.memory = [p for p in self.memory if p.seq not in poped]

    def write(self, tcp_packet):
        if LOW_LEVEL_DEBUG:
            logger.debug(f"nextSeq {self.nextSeq}, lenBuffer {len(self.buffer)}, lenMemory {len(self.memory)}")
        seq, data = self.getSeqRw(tcp_packet)
        if LOW_LEVEL_DEBUG:
            logger.debug(f"Write (seq {seq}, len data {len(data)})")
        self.updateFromMemory()
        if self.nextSeq is None or seq == self.nextSeq:
            self.buffer += data
            self.nextSeq = seq + len(data)
        else:
            self.memory.append(tcp_packet)
        self.updateFromMemory()
        if LOW_LEVEL_DEBUG:
            logger.debug(
                f"new next seq {self.nextSeq}, new buffer len {len(self.buffer)}, new memory len {len(self.memory)}"
            )


class ServerMsgHandler:
    def __init__(self, process: FunctionType):
        self.process = process


class PacketEvent(Event):
    def __init__(self, p):
        super().__init__()
        self.packet = p


class Provider(threading.Thread):
    def __init__(self) -> None:
        super().__init__()
        self.killsig = threading.Event()
        self.dispatcher: EventDispatcher = EventDispatcher()
        self.clientBuffer = SnifferBuffer()
        self.serverBuffer = SnifferBuffer()
        self.lastSeq = 0
        self.prevPaLen = 0
        self.LOCAL_IP = self.getLocalIp()
        if LOW_LEVEL_DEBUG:
            logger.debug(f"LOCAL_IP: {self.LOCAL_IP}")

    def getLocalIp(self):
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        try:
            s.connect(("10.255.255.255", 1))
            local_ip = s.getsockname()[0]
        except:
            local_ip = "127.0.0.1"
        finally:
            s.close()
        return local_ip

    def isFromClient(self, pa):
        dst = pa.ip.dst
        src = pa.ip.src
        if LOW_LEVEL_DEBUG:
            logger.debug(f"src: {src}, dst: {dst}, LOCAL_IP: {self.LOCAL_IP}")
        if src == self.LOCAL_IP:
            return True
        elif dst == self.LOCAL_IP:
            return False
        raise Exception(f"Packet origin unknown\nsrc: {src}\ndst: {dst}\nLOCAL_IP: {self.LOCAL_IP}")

    def run(self):
        capture = pyshark.LiveCapture(bpf_filter="tcp port 5555")
        try:
            for p in capture.sniff_continuously():
                if self.killsig.is_set():
                    return True
                try:
                    p.tcp.payload.binary_value
                    if int(p.tcp.seq) == 1:
                        self.reset()
                    isfromClient = self.isFromClient(p)
                    if isfromClient:
                        self.clientBuffer.write(p.tcp)
                        self.dispatcher.dispatch("Client packet received", PacketEvent(p))
                    else:
                        self.serverBuffer.write(p.tcp)
                        self.dispatcher.dispatch("Server packet received", PacketEvent(p))
                except AttributeError as e:
                    pass
        except Exception as e:
            logger.error(f"Error: {e}", exc_info=True)

    def interrupt(self):
        self.killsig.set()

    def reset(self):
        self.clientBuffer = SnifferBuffer()
        self.serverBuffer = SnifferBuffer()
        self.lastSeq = 0
        self.prevPaLen = 0


class DofusSniffer:
    def __init__(self, callback):
        self.servConn = ServerConnection()
        self.servConn.rawParser = MessageReceiver()
        self.servConn.handler = ServerMsgHandler(self.processServerMsg)
        self.provider = Provider()
        self.servConn._id = "ServerSniffer"
        self.provider.dispatcher.add_listener("Client packet received", self.onClientPacketReceived, 0)
        self.provider.dispatcher.add_listener("Server packet received", self.onServerPacketReceived, 0)
        self.handle = callback
        self.running = False

    def processServerMsg(self, msg):
        if msg.__class__.__name__ == "SelectedServerDataMessage":
            self.provider.reset()
        self.handle(msg)

    def onClientPacketReceived(self, event: PacketEvent):
        while True:
            msg = Message.fromRaw(
                self.provider.clientBuffer.buffer,
                True,
                src=event.packet.ip.src,
                dst=event.packet.ip.dst,
            )
            if msg:
                self.handle(msg.deserialize(), True)
            else:
                break

    def onServerPacketReceived(self, event: PacketEvent):
        self.servConn.receive(self.provider.serverBuffer.buffer)

    def start(self):
        self.provider.start()
        self.running = True

    def stop(self):
        self.provider.interrupt()
        self.provider.join()
        self.running = False


if __name__ == "__main__":

    def handle(msg):
        print(msg)

    mySniffer = DofusSniffer(handle)
    mySniffer.start()
 
Inscrit
10 Decembre 2022
Messages
9
Reactions
1
#12
Si je ne dis pas de bêtises, seq est juste le numéro du premier octet de la partie data. Je ne comprends donc pas très bien en quoi cela nous aiderait à reconstruire un message puisque pour moi, ce nombre sert juste à vérifier que l'envoie s'est bien déroulé. A moins que tu veuilles que je ré-organise les paquets dans l'ordre temporel d'envoie mais ca je n'ai pas besoin de le faire, ils sont déjà ordonnés avec libpcap en C

Mon problème vient de la traduction des paquets : Par exemple, si je prends les 3 premiers Bytes d'un paquet émis par le serveur :

52 FD 06, ca correspond au message d'id 5311, le message length est censé être 6 mais il y a beaucoup plus que 6 bytes dans la partie data, j'imagine donc que ce paquet émis par le serveur n'est pas le vrai message mais une partie seulement ?
 
Inscrit
10 Février 2017
Messages
26
Reactions
43
#13
Si je ne dis pas de bêtises, seq est juste le numéro du premier octet de la partie data. Je ne comprends donc pas très bien en quoi cela nous aiderait à reconstruire un message puisque pour moi, ce nombre sert juste à vérifier que l'envoie s'est bien déroulé. A moins que tu veuilles que je ré-organise les paquets dans l'ordre temporel d'envoie mais ca je n'ai pas besoin de le faire, ils sont déjà ordonnés avec libpcap en C

Mon problème vient de la traduction des paquets : Par exemple, si je prends les 3 premiers Bytes d'un paquet émis par le serveur :

52 FD 06, ca correspond au message d'id 5311, le message length est censé être 6 mais il y a beaucoup plus que 6 bytes dans la partie data, j'imagine donc que ce paquet émis par le serveur n'est pas le vrai message mais une partie seulement ?
Je sais que TCP est très compliqué comme protocol. Je vais essayer de te l'expliquer rapidement :
Supposant je reçois un paquet P1 avec un seq number SEQ1 et qui contient lent(P1) bytes, le prochain paquet qui va avec doit avoir un seq number SEQ2 = SEQ1 + len(P1).
Du coup tu peux stocker les packet tant que tu n'as pas reçus le bon et quand tu le reçois tu le colles au premier et tu redéroule depuis ce que t'as emmagasiné.
 
Inscrit
10 Février 2017
Messages
26
Reactions
43
#14
A moins que tu veuilles que je ré-organise les paquets dans l'ordre temporel d'envoie mais ca je n'ai pas besoin de le faire, ils sont déjà ordonnés avec libpcap en C
?
Ca m'éttonerais car la plupart des sniffers permettent juste de voir les paquets recues un après l'autre sans ordonnancement.
Mais si c'est le cas ca tombe bien.


A moins que tu veuilles que je ré-organise les paquets dans l'ordre temporel d'envoie mais ca je n'ai pas besoin de le faire, ils sont déjà ordonnés avec libpcap en C
?
C'est pas un problème si il y'a plus de data qu'il en faut dans un paquet pour faire un message. Toi tu stocks les binaires et quand t'as autant de bytes que tu veux tu les trim du stream. Juste après le paquet ID, tu recois le paquet length et après t'attends d'avoir reçus au moins le paquet length ensuite tu prends paquetLength data du buffer.
 
Inscrit
10 Decembre 2022
Messages
9
Reactions
1
#15
J'ai continué mon analyse de paquet et j'ai enfin compris pourquoi certains paquets avaient un surplus de data, c'est parce qu'ils contiennent plusieurs messages.
Cependant , je n'explique toujours pas pourquoi pour les ObjectQuantityMessage, le paquet me dit qu'il comporte un message de taile 8 alors que si je regarde la fonction de deserialize de ce message, il lit 2 fois un int et 1 fois un byte donc je devrais avoir 9 bytes de long pour le message. Une idée ?
 
Dernière édition:
Inscrit
10 Février 2017
Messages
26
Reactions
43
#16
Alors j'ai continué d'analyser les paquets et je me suis rendu compte que certains paquets avaient plus de Bytes data que nécessaire, notamment les messages d'ID 5311 ( ObjectQuantityMessage ) et 4644 ( BasicAckMessage ). D'autres ont exactement le bon nombre de Byte, c'est à dire le nombre donné par le 3è byte ( si type len vaut 1 ). Ca rejoint donc ce que tu dis sur le fait de devoir couper le surplus de Bytes. Cependant, même en prenant en compte qu'il y a un surplus de data des fois, je n'explique pas que pour un ObjectQuantityMessage, le paquet me dit qu'il comporte un message de taile 8 alors que si je regarde la fonction de deserialize de ce message, il lit 2 fois un int et 1 fois un byte donc je devrais avoir 9 bytes de long pour le message ? Une idée ?
il y'a des fields optionnels dans certains message faut faire attention à ça
 
Inscrit
10 Février 2017
Messages
26
Reactions
43
#17
Code:
import os

import uuid

from pydofus2.com.ankamagames.dofus.network.MessageReceiver import MessageReceiver

from pydofus2.com.ankamagames.jerakine.logger.Logger import Logger

from pydofus2.com.ankamagames.jerakine.network.CustomDataWrapper import ByteArray

from pydofus2.com.ankamagames.jerakine.network.NetworkMessage import NetworkMessage

from pydofus2.com.ankamagames.jerakine.network.parser.NetworkMessageClassDefinition import (

     NetworkMessageClassDefinition,

 )

 from pydofus2.com.ankamagames.jerakine.network.parser.ProtocolSpec import ProtocolSpec

 

 logger = Logger("Dofus2")

 

 

 class Message:

     def __init__(self, m_id, data, count=None, from_client=None, src=None, dst=None):

         self.id = m_id

         self.raw = data

         self.count = count

         self.from_client = from_client

         self.src_ip = src

         self.dst_ip = dst

 

     def __str__(self):

         ans = str.format(

             "{}(m_id={}, data={}, count={})",

             self.__class__.__name__,

             self.id,

             self.raw,

             self.count,

         )

         return ans

 

     def __repr__(self):

         ans = self.__str__()

         return ans

 

     @staticmethod

     def readMessageLength(staticHeader: int, src: ByteArray) -> int:

         byteLenDynamicHeader: int = staticHeader & NetworkMessage.BIT_MASK

         messageLength: int = int.from_bytes(src.read(byteLenDynamicHeader), "big")

         return messageLength

 

     @staticmethod

     def getMessageId(firstOctet: int) -> int:

         return firstOctet >> NetworkMessage.BIT_RIGHT_SHIFT_LEN_PACKET_ID

 

     @classmethod

     def fromRaw(cls, buf: ByteArray, from_client: bool, src=None, dst=None):

         """Read a message from the buffer and empty the beginning of the buffer.

         Dofus2 msg fields spec:

              id      |    data len  |   data

            1 bytes   |    1 bytes   |  len bytes

         """

         if not buf:

             return

 

         if buf.remaining() < 2:

             logger.debug(f"Not enough data to read the header, byte available : {buf.remaining()} (needed : 2)")

             return None

 

         staticHeader = buf.readUnsignedShort()

         id = Message.getMessageId(staticHeader)

         byteLenDynamicHeader = staticHeader & NetworkMessage.BIT_MASK

         count = None

         if from_client:

             if buf.remaining() >= 4:

                 count = buf.readUnsignedInt()

             else:

                 logger.debug(f"Not enough data to read the count, byte available : {buf.remaining()} (needed : 4)")

                 return None

         if buf.remaining() >= byteLenDynamicHeader:

             lenData = Message.readMessageLength(staticHeader, buf)

             if buf.remaining() >= lenData:

                 data = buf.read(lenData)

             else:

                 logger.debug(

                     f"Not enough data to read the data, byte available : {buf.remaining()} (needed : {lenData})"

                 )

                 buf.position = 0

                 return None

         else:

             logger.debug(

                 f"Not enough data to read the data length, byte available : {buf.remaining()} (needed : {byteLenDynamicHeader})"

             )

             buf.position = 0

             return None

 

         if id == 2:

             newbuffer = ByteArray(data.readByteArray())

             newbuffer.uncompress()

             msg = Message.fromRaw(newbuffer, from_client)

             if not msg or newbuffer.remaining():

                 raise Exception("Unable to parse Message")

 

         res = cls(id, data, count, from_client, src, dst)

         buf.trim()

         return res

 

     @property

     def name(self):

         if not self.from_client:

             return MessageReceiver._messagesTypes[self.id].__class__.__name__

         else:

             return ProtocolSpec.getClassSpecById(self.id)["name"]

 

     def lenlenData(self):

         if len(self.raw) > 65535:

             return 3

         if len(self.raw) > 255:

             return 2

         if len(self.raw) > 0:

             return 1

         return 0

 

     def serialize(self) -> ByteArray:

         header = 4 * self.id + self.lenlenData()

         ans = ByteArray()

         ans.writeUnsignedShort(header)

         if self.count is not None:

             ans.writeUnsignedInt(self.count)

         ans += len(self.raw).to_bytes(self.lenlenData(), "big")

         ans += self.raw

         return ans

 

     def deserialize(self) -> NetworkMessage:

         try:

             return NetworkMessageClassDefinition(self.name, self.raw).deserialize()

         except:

             logger.error(f"Unable to parse message {self.name} with id {self.id}")

             os.makedirs("./messagesUnableToParse", exist_ok=True)

             with open(f"./messagesUnableToParse/messageFail_{uuid.uuid4().hex}.bin", "wb") as f:

                 f.write(self.serialize())

             raise
 
Inscrit
10 Février 2017
Messages
26
Reactions
43
#18
Regarde la fonction de parsing des messages, que j'utilise dans mon sniffer, elle reprend la logique de low receive du module ServerConnection d'une manière simplifiée notamment sans parser les grand messages de manière asynchrone.
C'est la fonction fromRaw, tu retrouveras à la fin buffer.trim() qui tronque le buffer une fois la partie qui contient le message est lue.
Tu verras aussi que si la fonction ne trouve pas assez de bytes pour lire, que ce soit le header d'abord, puis le message length et finalement la payload, elle retourne sans rien faire et en repositionnant le curseur du bytes array vers le début avec buffer.position = 0.
En effet ton socket, ou ton sniffer, à chaque fois qu'il reçoit un packet va le append à un bytes array (Buffer) et appeler cette fonction.
Je te conseille de garder le buffer en ordre en vérifiant au moins les seq ids et lever une exception si jamais le seq id du packet que tu viens de recevoir n'est pas égale à celui de l'ancien plus ça taille. De mon expérience j'ai jamais trouvé un sniffer qui garde les packets en ordre tout le temps sans jamais se tromper.
 
Dernière édition:
Inscrit
10 Decembre 2022
Messages
9
Reactions
1
#19
Ok merci, je vais regarder tout ca. Au passage, c'est indiqué où si un field est optionnel ? Je n'ai jamais repéré ca dans les fichiers. Sinon je pense que maintenant je devrais pouvoir faire mon parser. La prochaine étape sera de rediriger la socket de dofus vers mon proxy. Le code https://cadernis.com/index.php?threads/génération-z-p-token.2867/page-4#post-28019 est toujours d'actualité ? Ca me permettrait d'avoir déjà une base pour m'aider à comprendre le fonctionnement des fichier .as relatifs à l'authentification
 
Dernière édition:
Inscrit
4 Janvier 2017
Messages
19
Reactions
12
#20
Ok merci, je vais regarder tout ca. Au passage, c'est indiqué où si un field est optionnel ? Je n'ai jamais repéré ca dans les fichiers. Sinon je pense que maintenant je devrais pouvoir faire mon parser. La prochaine étape sera de rediriger la socket de dofus vers mon proxy. Le code https://cadernis.com/index.php?threads/génération-z-p-token.2867/page-4#post-28019 est toujours d'actualité ? Ca me permettrait d'avoir déjà une base pour m'aider à comprendre le fonctionnement des fichier .as relatifs à l'authentification
Si ça marche, il suffit de modifier les user-agent

{ "user-agent", "Zaap 3.7.4" }

Captura de pantalla 2023-01-08 194659.png
 
Haut Bas