C# Assemblage des segments TCP pour reconstituer les paquets D2

Inscrit
14 Mai 2019
Messages
66
Reactions
22
#1
Hello,

Je suis en train de coder un Sniffer pour D2 utilisant SharpPcap et PacketNet, cela m'évite de passer par un hook/detour en mode MITM et je trouve ces libs plutot cools enfin bref. Je rencontre un petit probleme concernant la construction de mes paquets D2. J'ai bien conscience que je recois mes données sous forme d'un flux d'octets. 1646562466383.png
J'ai egalement conscience que dans une reception coté sniffer je peux avoir 1 paquet complet + le bout d'un second paquet ou 1 paquet complet ou la suite d'un paquet. Cependant il semblerait que la manière dont je build mes paquets n'est pas bonne dans le cas d'un paquet split en plusieurs receptions :teeth:. J'ai étudié la methode lowReceive du fichier ServerConnection.as. Voici mon code si quelqu'un pourrait m'éclairer sur ce probleme de splitting ca serait super ! :D

DofusPacketBuilder:
public class DofusPacketBuilder
{

    private int? _header;

    private int? _payloadLen;

    private uint? _instanceId;

    private int? MessageId => _header >> 2;

    private int? LenType => _header & 0b11;

    private byte[] _buffer;

    private byte[] _temp = Array.Empty<byte>();
  
    public bool TryBuild(byte[] data, out DofusPacket packet, bool isClient)
    {
        packet = default;

        /* Adding previous bytes remaining after the previous packet construction */
        if (_temp.Length > 0)
        {
            data = data.Concat(_temp).ToArray();
            _temp = Array.Empty<byte>();
        }
      
        IDataReader reader = new BigEndianReader(data);

        /* Constructing the header - metadata */
        if (reader.BytesAvailable >= 2 && !_header.HasValue)
        {
            _header = reader.ReadUShort();

            _instanceId = isClient ? reader.ReadUInt() : 0;
        }
      
        if (LenType.HasValue && reader.BytesAvailable >= LenType && !_payloadLen.HasValue)
        {
            if (LenType is < 0 or > 3)
                throw new Exception(
                    "Malformated Message Header, invalid bytes number to read message length (inferior to 0 or superior to 3)");

            _payloadLen = 0;

            for (int i = LenType.Value - 1; i >= 0; i--) _payloadLen |= reader.ReadByte() << (i * 8);
        }
        /* End of metadata information */
      
        // first case : no data read
        if (_buffer == null && _payloadLen.HasValue)
        {
            if (_payloadLen == 0) _buffer = Array.Empty<byte>();

            // enough bytes in the buffer to build a complete message
            if (reader.BytesAvailable >= _payloadLen)
                _buffer = reader.ReadBytes(_payloadLen.Value);

            // not enough bytes, so we read what we can
            else if (_payloadLen > reader.BytesAvailable) _buffer = reader.ReadBytes((int) reader.BytesAvailable);
        }

        //second case : the message was split and it missed some bytes
        if (_buffer != null && _payloadLen.HasValue && _buffer.Length < _payloadLen)
        {
            int bytesToRead =  _buffer.Length + reader.BytesAvailable < _payloadLen /* still miss some bytes */
                ? (int) reader.BytesAvailable
                : _payloadLen.Value - _buffer.Length; /* enough bytes in the buffer to complete the message*/

            if (bytesToRead != 0) /* adding missing bytes to the buffer */
            {
                int oldLength = _buffer.Length;
                Array.Resize(ref _buffer, _buffer.Length + bytesToRead);
                Array.Copy(reader.ReadBytes(bytesToRead), 0, _buffer, oldLength, bytesToRead);
            }
        }

        /* We can construct our packet */
        if (_header.HasValue && _payloadLen.HasValue && _instanceId.HasValue && _payloadLen == _buffer?.Length)
        {
            DofusPacketMetadata metadata = new(MessageId!.Value, LenType!.Value, _payloadLen!.Value, _instanceId!.Value);
            packet = new DofusPacket(metadata, _buffer);

            /* Reset building state */
            _header = null;
            _payloadLen = null;
            _instanceId = null;
            _buffer = null;

            /* Adding the remaining data bytes for the next packet construction */
            if (reader.BytesAvailable > 0)
                _temp = reader.ReadBytes((int) reader.BytesAvailable);

            return true;
        }

        return false;
    }
Code appelant :

Code appelant:
 var ipPacket = (IPPacket) ((EthernetPacket) packet).PayloadPacket;

        Packet payloadPacket = ipPacket.PayloadPacket;

        byte[] data = payloadPacket.PayloadData;

        if (data.Length <= 0 && (data.Length != 1 || data[0] == 0x00)) return;

        bool isClient = Equals(ipPacket.SourceAddress, IPAddress.Parse("192.168.1.33"));

        if (!packetBuilder.TryBuild(data, out dofusPacket, isClient))
            return;

      
        Message msg = MessageReceiver.ComputeMessage(dofusPacket.Metadata.MessageId, dofusPacket.Payload);
        Type downcastType = msg.GetType();
        object downcast = Convert.ChangeType(msg, downcastType);


        builder.AppendLine(
            $"> {(isClient ? "Client sent" : "Server sent")}: {downcastType.Name}, Metadata = {dofusPacket.Metadata}");
      
        builder.AppendLine(JsonSerializer.Serialize(downcast, serializerOptions));

        Console.WriteLine(builder.ToString());

        builder.Clear();
Erreur coté console :
1646562841069.png

EDIT : J'ai l'impression d'avoir un decalage sur la data que je traite (mes IO sont goods ils ont été testées)

Bonne journée et merci pour votre aide

Ps : l'image provient de ce blog Dofus et le reverse-engineering (2/2) (jaichange.fr)
 
Dernière édition:
Inscrit
6 Mars 2022
Messages
6
Reactions
3
#2
J'ai une approche différente personnellement

J'ajoute les données reçues dans un buffer et j'ai une méthode Receive sur laquelle j'applique un while tant qu'elle me retourne un message complet désérialisé, que je traite par la suite

Dans cette méthode Receive,

si >= 2 octets je lis l'entête,
si >= (entête & 3) octets, je lis la taille du message,
si >= taille du message, je retourne le message et supprime ses données contenues dans le buffer

si aucune condition n'est réunie, je retourne null en attendant de recevoir plus de données (le début des données sont donc toujours présentes dans le buffer)
essentiellement je ne stocke pas des segments de messages, j'attend qu'il soit complet et je retourne uniquement dans ce cas

cette méthode est plus pratique je trouve
si t'as besoin d'aide hésite pas à mp
 
Inscrit
14 Mai 2019
Messages
66
Reactions
22
#3
J'ai une approche différente personnellement

J'ajoute les données reçues dans un buffer et j'ai une méthode Receive sur laquelle j'applique un while tant qu'elle me retourne un message complet désérialisé, que je traite par la suite

Dans cette méthode Receive,

si >= 2 octets je lis l'entête,
si >= (entête & 3) octets, je lis la taille du message,
si >= taille du message, je retourne le message et supprime ses données contenues dans le buffer

si aucune condition n'est réunie, je retourne null en attendant de recevoir plus de données (le début des données sont donc toujours présentes dans le buffer)
essentiellement je ne stocke pas des segments de messages, j'attend qu'il soit complet et je retourne uniquement dans ce cas

cette méthode est plus pratique je trouve
si t'as besoin d'aide hésite pas à mp
Hello, je vois le fonctionnement de la methode Receive mais j'ai pas compris par rapport à la boucle while tu la déclenches à quel niveau ?
 
Inscrit
6 Mars 2022
Messages
6
Reactions
3
#4
tu auras un byte[] dans ta méthode receive socket

tu ajoutes ce byte[] à un buffer
typiquement list<byte> addrange

MonPacket packet = null;

while((packet = Receive(buffer) != null) ou encore while((packet = Receive(buffer)) is { } != null)
// traitement du packet
 
Dernière édition:
Inscrit
14 Mai 2019
Messages
66
Reactions
22
#5
Ok je vois mieux ce qui donnerait qque chose comme ça :
C#:
 public bool TryBuild(byte[] data, out DofusPacket packet, bool isClient)
    {
    
packet = default;

/* Adding previous bytes remaining after the previous packet construction */
if (_temp.Length > 0)
{
    data = data.Concat(_temp).ToArray();
    _temp = Array.Empty<byte>();
}

IDataReader reader = new BigEndianReader(data);

if (!_header.HasValue)
{
    if (reader.BytesAvailable >= 2)
    {
        _header = reader.ReadUShort();
        _instanceId = isClient ? reader.ReadUInt() : 0;
    }
    else
    {
        _buffer = _buffer.Concat(data).ToArray();
        return false;
    }
}

if (!_payloadLen.HasValue)
{
    if (reader.BytesAvailable >= LenType)
    {
        _payloadLen = 0;

        for (int i = LenType.Value - 1; i >= 0; i--) _payloadLen |= reader.ReadByte() << (i * 8);
    }
    else
    {
        _buffer = _buffer.Concat(data).ToArray();
        return false;
    }

}

if (reader.BytesAvailable >= _payloadLen)
{
    byte[] payload = reader.ReadBytes(_payloadLen.Value);
    
    DofusPacketMetadata metadata = new(MessageId!.Value, LenType!.Value, _payloadLen!.Value, _instanceId!.Value);
    packet = new DofusPacket(metadata, payload);

    /* Reset building state */
    _header = null;
    _payloadLen = null;
    _instanceId = null;
    _buffer = null;

    /* Adding the remaining data bytes for the next packet construction */
    if (reader.BytesAvailable > 0)
        _temp = reader.ReadBytes((int) reader.BytesAvailable);
    
    return true;
}

_buffer = _buffer.Concat(data).ToArray();

return false;
    }
Mais du coup on aurait pas besoin de la condition avec le second buffer _temp qui recupère les bytes restants apres avoir construit un packet dans le cadre où dans mon pipeline j'aurais un message et le debut d'un autre message tu l'as impl ici ?

J'ai une approche différente personnellement

J'ajoute les données reçues dans un buffer et j'ai une méthode Receive sur laquelle j'applique un while tant qu'elle me retourne un message complet désérialisé, que je traite par la suite

Dans cette méthode Receive,

si >= 2 octets je lis l'entête,
si >= (entête & 3) octets, je lis la taille du message,
si >= taille du message, je retourne le message et supprime ses données contenues dans le buffer

si aucune condition n'est réunie, je retourne null en attendant de recevoir plus de données (le début des données sont donc toujours présentes dans le buffer)
essentiellement je ne stocke pas des segments de messages, j'attend qu'il soit complet et je retourne uniquement dans ce cas

cette méthode est plus pratique je trouve
si t'as besoin d'aide hésite pas à mp
 
Dernière édition:
Inscrit
6 Mars 2022
Messages
6
Reactions
3
#6
Je t'ai fait une petite démo à l'arrache de ce que ça peut donner
et oui donc, pas de temp buffer, on travaille uniquement avec le buffer de réception
1.png 2.png
3.png 4.png
 
Dernière édition:
Inscrit
14 Mai 2019
Messages
66
Reactions
22
#7
Ok merci pour ta démo je comprends mieux finalement j'ai qque chose comme ça et ca à l'air good :

C#:
public bool TryBuild(List<byte> buffer, out DofusPacket packet, bool isClient)
    {
        packet = default;
        IDataReader reader = new BigEndianReader(buffer);

        if (reader.Remaining < 2) return false;

        ushort header = reader.ReadUShort();

        int messageId = header >> 2;

        int lenType = header & 3;

        uint instanceId = isClient ? reader.ReadUInt() : 0;
        
        int len = lenType switch
        {
            0 => 0,
            1 => reader.ReadByte(),
            2 => reader.ReadShort(),
            3 => ((reader.ReadByte() & 255) << 16) + ((reader.ReadByte() & 255) << 8) + (reader.ReadByte() & 255),
            _ => throw new InvalidDataException("Length type must be inside [0;3]")
        };

        if (reader.Remaining < len) return false;

        byte[] payload = reader.ReadBytes(len);
        
        DofusPacketMetadata metadata = new(messageId, lenType, len, instanceId);
        
        packet = new(metadata, payload);
        
        buffer.RemoveRange(0, reader.BytesRead);
        
        return true;
    }
et le code appelant :

C#:
 DofusPacket dofusPacket = default;
    try
    {
        var ipPacket = (IPPacket) ((EthernetPacket) packet).PayloadPacket;

        Packet payloadPacket = ipPacket.PayloadPacket;

        byte[] data = payloadPacket.PayloadData;

        if (data.Length <= 0 && (data.Length != 1 || data[0] == 0x00)) return;

        bool isClient = Equals(ipPacket.SourceAddress, IPAddress.Parse("192.168.1.33"));

        buffer.AddRange(data);
        
        while (packetBuilder.TryBuild(buffer, out dofusPacket, isClient))
        {
            Message msg = MessageReceiver.ComputeMessage(dofusPacket.Metadata.MessageId, dofusPacket.Payload);
            Type downcastType = msg.GetType();
            object downcast = Convert.ChangeType(msg, downcastType);


            builder.AppendLine(
                $"> {(isClient ? "Client sent" : "Server sent")}: {downcastType.Name}, Metadata = {dofusPacket.Metadata}");

            builder.AppendLine(JsonSerializer.Serialize(downcast, serializerOptions));

            Console.WriteLine(builder.ToString());

            builder.Clear();
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Exception : {dofusPacket?.Metadata} | {ex}");
    }
Il me reste quand meme une petite erreur coté console
1646581705636.png
Mais je pense que c'est du à mon reader

Merci :D
 
Inscrit
6 Mars 2022
Messages
6
Reactions
3
#8
No soucis, j'ajouterais, vérfie aussi reader.Remaining < (header & 3) j'ai oublié sur la démo
imaginons le cas ou tu reçois un message, et juste le header du prochain, bon en vrai ça arrive pas mais on sait jamais lol

EDIT: pareil pour l'instanceId (4; sizeof(int))
 
Dernière édition:
Inscrit
14 Mai 2019
Messages
66
Reactions
22
#9
E
No soucis, j'ajouterais, vérfie aussi reader.Remaining < (header & 3) j'ai oublié sur la démo
imaginons le cas ou tu reçois un message, et juste le header du prochain, bon en vrai ça arrive pas mais on sait jamais lol

EDIT: pareil pour l'instanceId (4; sizeof(int))
En effet merci ^^
 
Haut Bas