Nullz's MapleStory Packets: A Guide To Packets and Packet Editing

On martes, 7 de septiembre de 2010 0 comentarios

There exist a vague cloud of information regarding MapleStory's network communication; both how it happens, and why it occurs in the manner that it does. Though packet editing has now become something that can be done by the public, too few people understand what exactly packet editing is, how it works, the code involved, and the underlying concepts that make it possible. This is both a tutorial and a FAQ on these topics. I will begin with the basics, and then move forward from there, and finally end by with answers to both common and obscure questions that couldn't be answered elsewhere.

Network Communication and On-line Games

Though we often refer to MapleStory, like most games, in the singular when talking about our interaction with it, we actually use the game by interacting with several parts:

  • The Client - The game client is the executable that runs on your computer and is used to access the game itself. The very nature of the game client allows us to be completely unaware of the fact that there is a game server. The game client, upon execution, connects to the game server's IP address on a specific port, and then begin a loop of asynchronous reads and writes to and from that connection in order to communicate with the game server, and also to modify the game's state.
  • The Server - The game server resides somewhere unknown to you. It is meant to be addressed only by the game client, and doesn't expect to ever interact with any other type of program. Although you are made unaware of the background system of communication that occurs between itself and your game client, you can nonetheless determine the IP address and the port number of the server you are connected to, with almost no difficulty (Start > Run > cmd and type the command netstat -ano and you will see a list of the current TCP connections made by process ID; the ones that match MapleStory's process ID are the servers you are connected to).

Now, when these two entities need to communicate they do so over the connection between them; This connection is called a socket. The data in these communications is arranged in a logical format, but that format is usually only know by the client and the server only. In the case of MapleStory, packets exist as a 16-byte structure that contains 2 unknown values, the address of the actual packet data, and the size of that data.

Código:
/* MapleStory packet structure */
struct PACKET
{
    /* Unknown value */
    DWORD dwUnk1;
    /*
     * Pointer to the packet data.
     * Many coders manipulate this field as a singular
     * pointer of type void* or char*, but for the purpose
     * of flexibility, I manipulate this field in several
     * ways: void*, char*, and a anonymous struct pointer
     * that allows us to address the Data and the opcode
     * logically.
     */
    union
    {
        PVOID pvData;
        PBYTE pBytes;
        struct
        {
            WORD Opcode;
            BYTE Data[0];
        } *pPacket;
    };
    /* Length of packet data (in bytes) */
    DWORD dwLength;
    /* Unknown value */
    DWORD dwUnk2;
};

/*
 * When packets are intercepted before being sent
 * from the client to the server an additional field
 * is required.
 */
struct SENT_PACKET
{
    /* Return address for the packet data */
    DWORD dwReturnAddress;
    /* Packet struct */
    PACKET* ppPacket;
};
















The two most important parts of the packet are the opcode and the data associated with it. The opcode can be thought of as a command and the data can be thought of as it's arguments. The data usually must be viewed as a series of byte values, this makes it difficult to determine what that data actually is. Furthermore, these bytes are ordered in Little Endian format. This means that multi-byte integral data is has it's bytes ordered in the reverse of how it would be visualized.

Example:
The value 4,400 when used as a 4-byte integer becomes the bytes 00 00 11 30.

However, when that value is put in Little Endian format it becomes 30 11 00 00.

Because MapleStory's protocol changes so often it is very obscure to us. That is to say, we have almost no clue what most of the opcodes mean, nor do we know what the data associated with them really is. Most data is integral data and we cannot determine what it specifically is without knowing before hand where each integer is meant to begin and end.

Example:


Código:
Given the 4 bytes of: FF FF FF FF

Do we interpret those bytes as:
4 values of 255?
2 values of 65,535?
or 1 value of 4,294,967,295?
Strings (textual data) are a special case because we can parse that data in a programmatic way. In a MapleStory packet strings are length prefixed with single byte values representing each character.

Example:
Código:
Given the bytes: 05 00 48 65 6C 6C 6F
We can determine that this represents a string because the first two bytes
when cast to a 16-bit integer equal the number of bytes following it; and the
bytes following that value all represent printable characters.

Código:
05 00 = 5
   48 = 'H'
   65 = 'e'
   6C = 'l'
   6C = 'l'
   6F = 'o'
If you want to parse out the strings inside a packets data, here is a simple function that I created when I first made Z.

How To (C++)
Código:
DWORD NextString ( PPACKET ppPacket, PDWORD pdwOffset, PDWORD pdwLength )
{
    /*
     * If the packet contains only an opcode or if it data from the offset is not
     * sufficient to represent a string then when return the casted value of (-1)
     * to signify that.
     */
    if ( ( ppPacket->dwLength <= 2 ) || ( ( *pdwOffset + 2 ) >= ppPacket->dwLength ) )
        return (-1);

    /* Get the suspected length of the string data */
    DWORD dwLength = *(PWORD)( ppPacket->pBytes + *pdwOffset );

    /*
     * If that length is less than 1 then the data from the offset
     * could not represent string data.
     */
    if ( dwLength < 1 )
        return (-1);

    DWORD dwIndex = *pdwOffset + 2;
    DWORD dwCount = 0;

    /*
     * Iterate over the packet's data from pdwOffset and if the bytes in the range
     * represented by dwLength represent printable characters (0x20-0x7E) then end
     * the loop because those bytes are most likely a string.
     */
    for ( ; ( ( dwIndex + dwCount ) < ppPacket->dwLenngth ) && ( dwCount < dwLength ); dwCount++ ) {
        if ( ( ppPacket->pBytes[dwIndex + dwCount] > 0x1F ) && ( ppPacket->pBytes[dwIndex + dwCount] < 0x7F ) )
            break;
    }

    /*
     * If the number of bytes equal the number of characters suspected
     * to be in the string then set the value of pdwOffset to the next
     * byte after the string, pdwLength to the length of the string, and
     * return the offset of the first character from the start of the
     * packet data.
     */
    if ( dwCount == dwLength ) {
        *pdwOffset = ( dwIndex + dwCount - 1 );
        *pdwLength = dwCount;
        return dwOffset;
    }

    /* No string found, so we return the casted value of (-1) */
    return (-1);
}





















As you uncover the meaning and context of different opcodes you can begin to deduce what the data associated with it is meant to be.

Manipulating Game Network Data

When MapleStory receives a packet a series of events are triggered. The events take up the game's processing time. It is because of this behavior that various types of disconnection Tool are able to work.

Contrary to popular belief, when you spam packets to disconnect another user you are not actually sending those packets to that user, but rather to the game server. The game server, which does not care about intervals, relays all your packets to the target they were meant to be sent to (This can be though of as a type of Reflect Denial-of-Service Attack). The game client can only handle a certain number of incoming packets at a time, and when that limit is exceeded the connection is terminated. As that limit is reached all-at-once the client becomes very slow to respond, and eventually fails when the connection is terminated.

Blocking Packets

When you block a packet using a packet editor the packet is not actually blocked (at least this is the case with my PEs). What occurs is that the packet data is modified and turned into garbage. This causes the client to immediately discard the packet when it's received. This behavior allows one to prevent being DC Tool  because even when packets are spammed at you, your client is just throwing away the specified packets instead of attempting to process them.

How To: (C++)

Código:
void BlockPacket ( PPACKET ppPacket )
{
    /*
     * Even though the opcode is an unsigned value
     * we must set it to the casted value of -1 so
     * that the game will treat it as erroneous.
     */
    ppPacket->pPacket->wOpcode = (-1);

    /*
     * If the packet contains actual data instead of just
     * an opcode we must set those bytes to 0.
     */
    if ( dwLength > 2 ) {
        for ( DWORD dwOffset = 2; dwOffset < ppPacket->dwLength; dwOffset++ )
            ppPacket->pPacket->Data[dwOffset] = 0;
    }
}
Intercepting Packets

To intercept packets, we have to have a little knowledge of Assmebly as well as a tool for dissecting the game client process memory. These are required because in order to intercept the game client's packets we must modify the code executed when packets are sent and received (Note: I didn't use the word "hook" because technically what occurs in this particular case is not a hook, only an edit).

Intercepting Packets Received From The Game Server

To intercept packets received by the game client, we have to first locate the function that handles that event, and then locate a specific place to insert our code.

If you view MapleStory's (GMS-only) memory and look at the address 0072A41B you'll see one of the functions involved in handling the event where packets are received. It is inside this function that we must place code that will make a jump into our custom code, doing so will allow us to intercept the packet from MapleStory without disrupting the natural flow of execution.

Now inside this function you can actually modify as much or a little of the function's assembly code as you like as long as the function executes as expected and you place your jump somewhere inside it. I recommend placing your jump at address 0072A440.

:0072A440
Código:
...
0072A440 - pop ebx
0072A441 - leave
0072A442 - ret 0004
...
Those 3 instructions take up exactly 5 bytes of space, which is equal to the number of bytes needed for a JMP opcode and it's operand. However, since the flow of execution must remain the same, we have to reproduced those 5 bytes at the end of the code we jump to.

How To (C++)
Código:
/*
 * In this function ppRecvPacket is meant to be a pointer
 * of type PACKET* that represents the packet being intercepted.
 * and RecvPacketCallback is meant to be a user-defined function
 * that is executed when that packet is intercepted.
 */
void __declspec(naked) NewRecvFunc ( void )
{
    /*
     * The assembly code below is generic and can be done in multiple  ways.
     * The important thing is that the packet's address is located 12  bytes
     * down from EBP. How exactly you load that address into a variable  is
     * up to you.
     */
    __asm
    {
         lea ebx,[ebp-0x0C]
         mov ppRecvPacket,ebx
         pushad
         call RecvPacketCallback
         popad
         pop ebx  // Notice how we reproduce the assembly that was overwritten
         leave
         ret 4
    }
}
Now to insert our code we simply need to rewrite the assembly code at the address 0072A440; but before we do, we must make sure that a bypass (either full, or CRC) has been enacted and that we protect the area of memory we're about to overwrite before we do so.

How To (C++)
Código:
DWORD flOldProtect;

if ( VirtualProtect((LPVOID)0x0072A440, 5, PAGE_EXECUTE_READWRITE, &flOldProtect) ) {
    /* Write the opcode for the JMP instruction */
    *(LPBYTE)0x0072A440 = 0xE9;
    /* Write the relative address offset from our function to this address */
    *(PDWORD)( 0x0072A440 + 1 ) = ((LONG_PTR)NewRecvFunc)-((LONG_PTR)0x0072A440))- 5;
}
Now, whenever the game receives a packet, our code will be called first and we can determine what to do with the packet before the game ever knows it exists.

Intercepting Packets Sent To The Game Server

To intercept packets sent by the game client we are going to hook the function responsible for sending those packets. Unlike the received packets interception, we will actually be hooking this function because will we be placing our jump at the very head of the function's assembly code.

If you view MapleStory's (GMS only) memory in Cheat Engine and look at address 004A83AC you can see the function responsible for sending packets to the game server. We must overwrite the first instruction, which is conveniently 5 bytes in length (the same length of a JMP opcode and it's operand together). Because this is a function hook, the code we force the function to jump needs only to make a jump back to instruction to followed the jump to it.

:004A83AC
Código:
...
004A83AC - mov eax,00b1d368
004A83B1 - call 00afabf8
...
The first line is where we're going to rewrite to be our JMP instruction and it's operand, and the second line is where we're going to jump back to when our code is finished.

How To (C++)
Código:
DWORD dwOldSendFunc = 0x004A83B1; // 004A83AC + 5
DWORD dwNewSendFunc = 0x004A83AC;

/*
 * In this function ppSendPacket is meant to be a pointer
 * of type SENT_PACKET* that represents the packet being intercepted.
 * and SendPacketCallback is meant to be a user-defined function
 * that is executed when that packet is intercepted.
 */
void __declspec(naked) NewSendFunc ( void )
{
    __asm
    {
        mov [ppSendPacket],esp
        pushad
        call SendPacketCallback
        popad
        jmp dword ptr [dwOldSendFunc]
    }
}



Now to insert our code we simply need to rewrite the assembly code at the address 004A83AC; but before we do, we must make sure that we protect the area of memory we're about to overwrite before we do so. This modification to the game's memory doesn't require that we have any kind of bypass at all.

How To (C++)

Código:
DWORD flOldProtect;

if ( VirtualProtect((LPVOID)0x004A83AC, 5, PAGE_EXECUTE_READWRITE,  &flOldProtect) ) {
    /* Write the opcode for the JMP instruction */
    *(LPBYTE)0x004A83AC = 0xE9;
    /* Write the relative address offset from our function to this  address */
    *(PDWORD)( 0x004A83AC + 1 ) =  ((LONG_PTR)NewSendFunc)-((LONG_PTR)0x004A83AC))- 5;
}
Now, whenever the game sends a packet, our code will be called first and we can determine what to do with the packet before the game ever sends it.

Injecting Packets

When we say a packet is to be "injected" what we actually mean is that we're going to trick the game client into believing a specific packet has been received or sent by using the game client's native functions for those events.

In the case of sending your own custom packets, you must first locate the address of the function used to send packets to the game server. However, that function is a method of a class, and as such it is bound to a specific instance of that class. So you have it actually locate both the function's address and the address of the class instance in process memory.

If you view MapleStory's (GMS only) memory in Cheat Engine and look at the address 0047D2A5 you will see an address being moved into the ECX register. This is where the address of the class instance is being passed to the function call.

:0047D2A5
Código:
...
0047D2A5 - mov ecx,[00c9a004]
...
We need to get just the address by itself, so we must disregard the first two bytes (the opcode and the first operand) and then de-reference the last 4 bytes (the second operand), and that will give us the address of the class instance.

How To (C++)
Código:
DWORD dwSendClass = *(PDWORD)(0047D2A5+2);
Now we can send our own custom packets. All we must do is define a function pointer type that matches the function signature of MapleStory's own function for sending packets, and invoke MapleStory's native function with our packet passed as the argument.

How To (C++)
Código:
/*
 * Although we're defining a pointer to the Send function as a
 * __stdcall, the actual function is really a __thiscall because
 * it is a method of an instance of a class.
 */
typedef DWORD ( __stdcall *PFNSENDPROC ) ( __in PPACKET ppPacket );

DWORD SendPacket ( PBYTE pBytes, DWORD dwLength )
{
    /*
     * Although we're using allocated memory from our own code in pBytes,
     * the game itself will release that memory when it is no longer being
     * used. So once it's address is assigned below, we don't need to worry
     * about causing a memory leak regardless of how the memory pointed at
     * by pBytes was allocated.
     */
    PACKET pPacket;

    ZeroMemory(&pPacket, sizeof(PACKET));

    /* Assign the necessary fields to the packet. */
    pPacket.pBytes   = pBytes;
    pPacket.dwLength = dwLength;

    /*
     * As stated before, this is actually a __thiscall. So to make
     * the function call work, ECX (the "this" ptr) must contain the
     * address of the instance of the class object.
     */
    __asm
    {
        mov ecx,dwSendClass
        mov ecx,[ecx]
    }

    /*
     * If you wanted to recapture your own custom packets you
     * would just use dwNewSendFunc instead of dwOldSendFunc.
     */
    /* Call the native MapleStory Send function */
    (*(PFNSENDPROC)dwOldSendFunc)(&pPacket);
}















Frequently Asked Questions

Coming Soon...

The FAQ section will be written when I have the time. I hope this guide has helped you.

~The Nullz

0 comentarios:

Publicar un comentario