[Guide] Extracting Built Packets in Silkroad

On viernes, 13 de agosto de 2010 0 comentarios

http://www.elitepvpers.de/forum/sro-guides-templates/271029-guide-extracting-built-packets-silkroad.html

Extracting Built Packets in Silkroad
 
I. Purpose

This guide will provide a complement to the previous guide of extracting the parsed packets in Silkroad. This time, we will learn how to extract packets the client sends the serer as they are built. Because most of the concepts in and theory are the same, this guide will be much shorter than the previous so please refer back to that article for all the nitty-gritty details.

 
An article like this is also just as important to understand as the previous was for any development that makes use of packets. If you are writing a client based tool, a clientless tool, or even a Silkroad emulator, you need to understand how packets are built and the format they follow. Using the techniques provided in this guide should help you achieve that.

 
I wanted to do make this a separate article as the previous one was getting pretty long itself. With all of the concepts explained already though, we can move through this one pretty fast. This lies in line with my first guide explaining how each article will relate to the others in one way or the other and once we have our base knowledge base established, we can do fun stuff. In addition, there are a few tricky parts in replicating the client's logic that we have not come across yet.

 
II. Requirements

 
If you have been keeping up with the previous guides, you should be able to follow everything presented here. There are no new concepts used. There is a bit more assembly logic used here though, but everything is based off our previously established concepts of codecaves and inline patching.

 
In order to be able to follow along this guide, you will need:
  • OllyDbg 1.10 (or equivalent)
  • Visual Studio 2008 (or equivalent) 
III. Theory

 
As I discussed in the previous guide, most games use code that greatly simplifies the programming challenges that lies ahead of them. When programming a networked game, there are a lot of things the developer must take into account when designing code. When it comes to packets, the developer has to figure out a way to make sure the client cannot spam the same packet over and over flooding the server.

 
Silkroad does just this with a simple packet opcode checking function. While I have not explored the internals of this function in a great degree at this time, just understanding that such a function exists can greatly help in our client reversing endeavors. The client will first check the opcode to see if such a packet can be sent. If it cannot, then the function usually skips execution by jumping over the packet building code.

 
Assuming the packet checking function is successful, the client will then build a new packet with the given opcode. Similarly to the opcode checking function, I’ve not actively explored the internals of how packet objects are constructed, but given I know C++ programming, it’s not hard to imagine how the process goes. We can hook this function to get the opcode of our current packet that is being built just as we were able to easily get the opcode of the packet that was being processed before.

 
Similarly to a ReadBytes function there is also a WriteBytes function. We can hook this function and simply rip out the data as it is being passed into the packet’s data buffer. By looking at multiple sections of code that build packets, we can also look for some final function that is called on the packet object to know when we are done building the packet. We know such a function has to exist because the client has to put in security bytes into the packets and actually dispatch the packet for sending to the server.

 
So, with this theory in mind as well as the knowledge gained in the previous guide, we can get started!

 
IV. Implementation

  
In order to get started, we need to know a packet opcode that is sent. By now, this information can easily be obtained, so there is no need to work from scratch to obtain one. If we had to, we would just breakpoint on a WSASend call and trace back the buffer until we could determine where the opcode was in the packet.

 
For this example, I will be using the character action packet, which uses an opcode of 0x7017. In order to find where the functionality lies for a packet that is sent to the server, we just have to search for the opcode! To begin our search, right click on the main assembly window and choose Search for -> All constants. Type in 7017 under the hexadecimal box and hit Ok. We get a new window that has search results in it and we can see we have a couple of entries. Double click on the first one to go to the client’s location.

 
We arrive in the main function that builds the 0x7017 packets. We know this because we see our opcode listed. Using what we learned in the theory section, we should first see a function call that checks to see if the packet can be used or not. We find that to be the case because there is a conditional jump after the first opcode but before the second that skips over a large part of the function. So far so good!

 
Next, we should be finding a function that writes bytes to the packet buffer. If we remember how the ReadBytes function was setup, it took two parameters, the buffer and the size. The WriteBytes should be no different. If we were to trace the function, we would quickly find the function and be able to identify the parameters.

Finally, we need to find a common function that is called after the packet building is complete. In order to do this, we will simply follow the call to the opcode setting function and look at all the other addresses that make calls to the function. We should be able to find a common function that is called in all of them and we do.

 
Here is an annotated screenshot of the entire function:
 

Now that we know the logic of how packets are built. All we have to do is setup the code to extract the data. This code is going to be really similar to the previous guide’s code, so I will not go through it in detail.


However, there were a few very tricky things to figure out during the process. Packets that are spanned across multiple opcodes for example, such as group spawn, make use of our packet writing functions! I came up with a little approach that should fix that issue for us.

Next, there was a problem replacing the client logic in the codecave. Since the code made use of the stack, it would incorrectly compile in Visual Studio this making the client disconnect. To fix this issue, I just used more commands to replicate the logic as it would be if it was done the long way.

Issues like these are not uncommon when working with game clients. When you have unexpected behavior and are sure your code is right, you have to be sure you do not have a problem like this. When I first wrote the code I was sure it was right and tracked the problem down to the WriteBytes codecave. I figured the stack operation should work fine, but it doesn’t.

After replacing the code, it worked perfectly! Chalk that up as a lesson learned of how tricky this development can get at times. The code for stealing the opcode seemed to work just fine, so I'm not really sure why this wouldn't either.

Here is our final DLL.cpp file:
 
#include


#include "../common/common.h"



// Global instance handle to this DLL

HMODULE gInstance = NULL;



// Function prototype

void UserOnInject();



// Main DLL entry point

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ulReason, LPVOID lpReserved)

{

UNREFERENCED_PARAMETER(lpReserved);

if(ulReason == DLL_PROCESS_ATTACH)

{

gInstance = hModule;

// Do not notify this DLL of thread based events

DisableThreadLibraryCalls(hModule);

}

return TRUE;

}



// This is the main function that is called when the DLL is injected into the process

extern "C" __declspec(dllexport) void OnInject(DWORD address, LPDWORD bytes)

{

// Restore the original bytes at the OEP

DWORD wrote = 0;

WriteProcessMemory(GetCurrentProcess(), UlongToPtr(address), bytes, 6, &wrote);



// Call our user function to keep this function clean

UserOnInject();

}



namespace CC_ExtractSentPacket

{

DWORD currentOpcode;

LPBYTE currentBuffer;

DWORD currentSize;



void OnProcessDataStart()

{

printf("=== %X ===\n", currentOpcode);

}



void ProcessData()

{

for(DWORD x = 0; x < currentSize; ++x)

{

printf("%.2X ", currentBuffer[x]);

if((x+1)%16 == 0)

printf("\n");

}

printf("\n");

}



void OnProcessDataEnd()

{

printf("\n\n");

}



void EnableParseHook();

void DisableParseHook();



DWORD codecave_SetOpcode_ReturnAddress = 0;

__declspec(naked) void codecave_SetOpcode()

{

__asm pop codecave_SetOpcode_ReturnAddress

__asm MOV AX,WORD PTR SS:[ESP + 0x04] // Original code

__asm mov currentOpcode, eax

__asm pushad

EnableParseHook(); // Start hooking writes

__asm popad

__asm push codecave_SetOpcode_ReturnAddress

__asm ret

}



DWORD codecave_WriteBytes_ReturnAddress = 0;

__declspec(naked) void codecave_WriteBytes()

{

__asm pop codecave_WriteBytes_ReturnAddress



__asm mov currentBuffer, eax

__asm mov currentSize, ebp



__asm pushad

ProcessData();

__asm popad



// Even though this comes first, we will use EBX and then fix it up

// afterwards. This is ok since ECX is not disturbed.

//__asm MOV EBX, ECX



// This line seems to give the client problems when compiled in VS,

// so we have to rewrite it a little. This might be one of the hardest

// things to figure out if you were doing this yourself and not

// too experienced.

//__asm MOV DWORD PTR SS:[ESP + 0x10],EAX



__asm

{

mov ebx, esp

add ebx, 0x10

mov [ebx], eax

}



// Now set our EBX value to what it should be

__asm MOV EBX, ECX



__asm push codecave_WriteBytes_ReturnAddress

__asm ret

}



DWORD codecave_PostProcessPacket1_ReturnAddress;

__declspec(naked) void codecave_PostProcessPacket1()

{

__asm pop codecave_PostProcessPacket1_ReturnAddress



// We choose this line because it is the easiest to codecave and won't likely change

__asm MOV EAX, 0x1008 // Remember #s have to be in HEX! 1008 != 0x1008



__asm pushad

DisableParseHook();

OnProcessDataEnd();

__asm popad



__asm push codecave_PostProcessPacket1_ReturnAddress

__asm ret

}



void EnableParseHook()

{

// A little trick I came up with to filter out the packets received

// that make use of this functionality. Not sure if it'll always work,

// but it "should" since the higher 4 bytes of any C->S packet in the

// executable will be 0 since they are hard coded into the client!

DWORD result = currentOpcode & 0xFFFF0000;

//printf("Result = %i\n", result); // Debugging

if(result == 0)

{

// Hook the byte writing function

edx::CreateCodeCave(0x4D66CC, 6, codecave_WriteBytes);



// Hook the post build processing function

edx::CreateCodeCave(0x741CBE, 5, codecave_PostProcessPacket1);



// Let the user know we have an new packet being built

OnProcessDataStart();

}

}



void DisableParseHook()

{

// Restore the byte writing function code

static BYTE patch1[] = {0x8B, 0xD9, 0x89, 0x44, 0x24, 0x10};

edx::WriteBytes(0x4D66CC, patch1, 6);



// Restore the post build processing function code

static BYTE patch2[] = {0xB8, 0x08, 0x10, 0x00, 0x00};

edx::WriteBytes(0x741CBE, patch2, 5);

}



void Setup()

{

edx::CreateCodeCave(0x507B40, 5, codecave_SetOpcode);

}

}



// The function where we place all our logic

void UserOnInject()

{

// Create a debugging console

edx::CreateConsole("SilkroadFramework Debugging Console");



// Mutex for the launcher, no patches required to start Silkroad now

CreateMutexA(0, 0, "Silkroad Online Launcher");

CreateMutexA(0, 0, "Ready");



CC_ExtractSentPacket::Setup();

}

You should be able to compile and run. Here is an example screenshot of the program in action:



V. Conclusion

You should not have a good idea of how it is possible to let your game client do most of the hard work for you, whether it is extracting the received or sent packets. By following this guide, you should be able to greatly speed up packet related development efforts as all you have to do is trigger packets and save how the client parses them. Using this information, you can further your understanding of the client and track down the specific areas in the client responsible for generating certain packets. All you have to do is search for the opcode of interest!

That wraps up this guide! Between this guide and the previous, you should be able to work a lot more efficiently now with building a packet database for use in your programs. I hope you found this it informational and beneficial. Stay tuned for future guides!

Drew “pushedx” Benton
edxLabs

0 comentarios:

Publicar un comentario