[Guide] Extracting Parsed Packets in Silkroad

On viernes, 13 de agosto de 2010 0 comentarios

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

I think you guys are really going to love this guide the most. Using this guide, you will be able to accomplish the holy grail of any game and that's being able to figure out packet formats with very little work! Once again I must give thanks to clockwork for showing me the basics of this approach when I was getting started with this stuff in the past.

Extracting Parsed Packets in Silkroad

 
I. Purpose

 
This guide will be especially helpful to anyone that is working with Silkroad packets in their programs. Writing a simple proxy to dump the packets from Silkroad is pretty easy, anyone can do it. However, that information is not too useful because you must then struggle to figure out how to parse the packets yourself. This is a time consuming process and as a result it hampers development efforts.  

 
This guide will show how you can easily extract packets as they are parsed in the client so you do not have any hard work of trying to figure out the parsing yourself! In the past, I released a few programs that did this for you but I don’t think I released the source because it was part of a larger framework that had too much in it to release.

 
Now with our own simple injector and DLL, we can implement the logic ourselves and speed up development! I should also mention that I did not come up with this original idea. Credits for this goes to clockwork as he figured out the packet parsing stuff in the client long before I even knew anything fun! After having learned the networking stuff and the Silkroad security process, I went back and relearned this stuff as I didn’t understand it the first time I saw it.

 
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)

 
This guide contains a lot more assembly than the previous and less higher level programming logic. That is because the higher level logic is for you to implement to save the data in a format that best suits you. For the sake of this guide I am just dumping it all to the console. 

 
III. Theory

 
Most modern games make use of code that makes the developers’ lives as easy as possible. With Silkroad, a custom stream processing class was designed to allow the game to easily parse data out of the data stream that arrives on the TCP channel. As a result, this code can be hooked to intercept the data that is currently being processed.

 
In this guide, I will show you how to do this task so you can see data as the client does when it is parsing packets. If you have ever seen the source code of my older sr33 or edx33 projects, you might remember a custom packet reader class or packet builder class. If you do, then that is the code that we are reversing in the client. If you don’t, that’s ok as it’s not really important for now.

 
All we have to do is locate the stream processing functionality in the client and spend some time figuring out how it works. Once we do that, we can then setup our code in our DLL to utilize the client’s existing functionality to siphon out parsed data.

 
As mentioned in previous guides, actually discovering this kind of stuff just takes some experience, patience, and luck. If you were to search for commonly used words related to packet handling code, you would definitely stumble upon a few of these locations. There is no real trick to this stuff other than being creative and working until you figure it out.

 
That's about it for the theory section. It is quite small compared to some of the other articles, but there really is not too much going on here. It's just a matter of figuring out the right code to do what we want.

 
IV. Implementation

 
The first thing we need to do is find our main packet handling function. Typically, this is going to be a huge switch statement as most games implement their packet dispatching this way. Locating it in Silkroad can be found via two ways. Either you trace the raw packet data from the WSARecv functions and eventually hit the handler or you stumble upon it through searching the client’s referenced text strings.

 
Here is a screenshot of the function in OllyDbg. You can easily find this in any client if you search for a referenced text string that is shown.


That is quite a bit of assembly! However, if we look at the top of the function we can see a huge range of packet opcodes exist in a switch statement as seen in the predefined Olly comment. Further looking down the function and seeing the referenced text strings that are listed, we can have a pretty good idea we are in the right area.


Now just by intuition and looking at the start of the function, we can kind of assume the current packet opcode is stored into the EAX register on the 4th line of the function. This is important to remember since we can hook this location and save the current packet’s opcode if needed.

When we have functions like this, it’s good to understand the exit points so we can get a feel of where we need to perform our codecaves at. I have placed breakpoints on 3 out of the 4 exit points above. If you were to run Silkroad, you should not be able to trigger the 1st two breakpoints under normal circumstances and the 3rd breakpoint only triggers when you exit the client. The unmarked return seems to be the normal exit point. This is important information to keep in mind.

That’s just a general overview of our main packet handling function. We do not yet know where our packets are parsed or how to get the functions for individual packets. We can do that now. Let’s say we had done some preliminary packet research and saw the client sends us a packet with opcode 0x3667 on a chat message. Let’s say we just had a proxy program that dumped out all the packets for us. The first thing we would want to do is search the client for that opcode!

To begin our search, right click on the main assembly window and choose Search for -> All constants. Type in 3667 under the hexadecimal box and hit Ok. We get a new window that has search results in it and we can see we have one entry. Double click on it to go to the client’s location.

Eureka! This looks interesting! We can see a pattern of opcodes and function addresses. We can only assume one thing now! I’ve cheated a little and already labeled the function, but here is a screenshot for reference:
 

So, how do we read this stuff? The opcode is listed first, 0x3667. The function that should be called when the packet is received comes next, in this case 0x775D40. Let’s go ahead and go to that address in Olly to check it out. There is nothing too exciting at first glance. If you had no idea of what you were looking at, you would just set a breakpoint on the entry and trace through it when you got a packet in the client.

However, this is indeed our chat packet processing function. If we look down a ways, we see a switch that has cases from 1 to 16 (0x10) and these represent the different chat types. I’m not going to list out all of the various chat types and their associated values, but rest assured it’s all there.

Now we need to figure out which function calls this packet’s handler. If we were to set a breakpoint on the function and log in to the game and received a chat packet, we would hit our breakpoint. Here’s a look at the stack when we hit the breakpoint:
 

Immediately we can see the function that called us. If we hit Enter on the line, we will trace back to the calling function. Here is that function:



This makes sense to have a function pointer call because we know a different function is called for each packet opcode. We assume there is a map of opcode function mappings that is stored in the client. To assume this, you would have to had some programming experience with a map/dictionary data type. Let’s go back one more level to see what called this function. We will want to highlight the second return line in the stack and hit Enter on the line.

Lo’ and behold, we arrive at our main packet handling function we discovered earlier! We now know that the function right before our common return exit point is what dispatches the packet handler for whichever packet we are currently processing. This is good news now since we now know almost everything we need to about how the client processes packets.

What’s left is figuring out how the client actually parses them. If we were to study a few of the packet processing functions, we should notice a recurring pattern. There seems to be code that pushes a constant, pushes an address, and then calls the same function. If we were to investigate this function and check the parameters on entry, we would discover that this is the packet reading function! Here is a small snippet as an example from our chat processing function:
 

I went ahead and labeled the function, but you can do that yourself. If you don’t remember how, just click on the function call, hit enter to follow it, hit Shift + ; to add a label to the line, and finally hit the number pad - to go back.

Once we add our label and revisit any of our packet processing functions, we can now see lines of ReadBytes calls as well as how many bytes are being read. In some cases, there is no constant, which means it is a variable read.

At this point, we now know everything we should for being able to extract the packets as they are being parsed. We can now move on to the specific code we need to accomplish our task.

We first need to figure out how we are going to save our current opcode. This is pretty easy as it turns out because we have a 5 byte line to codecave at the beginning of the function. We can just rip the current opcode from EAX! As you see, our codecave function is really small since all we have to do is save the opcode into our variable and execute the original code.

Once we have our opcode saved, we now need to think about how we want to handle the packet reading functions. If we were to place a codecave in the function itself, we might have issues as it is a global function and might be used in more than one place for non packet parsing. Since we cannot rule this out, we will do what we did in our previous article to toggle the codecave for the function.

By setting up a codecave on top of the function call, we can easily toggle our logic on and off without interfering with other uses of the function. So far, so good. Now, we need to create another codecave specific for the packet reading function. Since we know we have a variable buffer and size, we have a little work to do. First, we have to codecave the start of the function to save the output buffer and the output size. Next, we need to let the function execute to parse the packet into that buffer. Finally, we need to process the final result using the stored buffer and then allow the function to return normally.

The logic above takes some time to come up with, but if you think about solving the problem, you should eventually reach the same conclusion on the best way to go about it.

One thing I did not explicitly mention is how to find the location to codecave. This can only be done if you trace the function without stepping into any calls and seeing what is the latest point in execution that you can reach while still having the data accessible. In my case, I managed to find a spot where the buffers were intact, however the codecave extend to the end of the function so I had to integrate that into the codecave itself.

You should understand why this had to be done if you read through the codecave tutorial! Remember you have to check all the locations of the possible jmps in the function to ensure you do not create an invalid instruction for another branch.

Once we have that code in place, we should be set for a test! Here is our final code for DLL.cpp:
 
#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_ExtractPacket

{

FARPROC fpHandler = (FARPROC)0x6AE8F0;



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");

}



DWORD codecave_ExtractPacket_ReturnAddress = 0;



__declspec(naked) void codecave_ExtractPacket()

{

__asm pop codecave_ExtractPacket_ReturnAddress

__asm mov currentOpcode, eax

__asm pushad

OnProcessDataStart();

__asm popad

__asm CMP EAX, 0x3369 // Original code

__asm push codecave_ExtractPacket_ReturnAddress

__asm ret

}



DWORD codecave_ReadBytes_ReturnAddress = 0;



__declspec(naked) void codecave_ReadBytes()

{

__asm pop codecave_ReadBytes_ReturnAddress



__asm mov currentBuffer, eax

__asm mov currentSize, ebx



__asm pushad

ProcessData();

__asm popad



// Emulate the rest of the function since our codecave overlaps it all

__asm POP ESI

__asm MOV EAX,EBX

__asm POP EBX

__asm RET 8

}



void EnableParseHook()

{

edx::CreateCodeCave(0x4C42FC, 7, codecave_ReadBytes);

}



void DisableParseHook()

{

static BYTE patch[] = {0x5E, 0x8B, 0xC3, 0x5B, 0xC2, 0x08, 0x00};

edx::WriteBytes(0x4C42FC, patch, 7);

OnProcessDataEnd();

}



DWORD codecave_InvokeHandlers_ReturnAddress;

__declspec(naked) void codecave_InvokeHandlers()

{

__asm pop codecave_InvokeHandlers_ReturnAddress



__asm pushad

EnableParseHook();

__asm popad



// We have to use this trick as VS does not support calling direct memory addresses

__asm call fpHandler



__asm pushad

DisableParseHook();

__asm popad



__asm push codecave_InvokeHandlers_ReturnAddress

__asm ret

}



void Setup()

{

edx::CreateCodeCave(0x74BDDA, 5, codecave_ExtractPacket);

edx::CreateCodeCave(0x74BE85, 5, codecave_InvokeHandlers);

}

}



// 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_ExtractPacket::Setup();

}

When we run the program, we should notice we do not get any login server packets. This is because we only hooked the world server packet logic. We do not really need to worry about login server packet parsing as it is really easy to do by hand and there are not many packets at all. Packet should be flying by on the console. While it’s not easy to read unless you go to a quiet area, this should show the example in action. Here’s a screenshot of the console:



Woot! We are all done now. I designed the code so you can easily change up how you process the packets through the OnProcessDataStart, ProcessData, and OnProcessDataEnd functions. You might want to save to a file or route to a GUI, be creative.


V. Conclusion

Hopefully by now you have a good idea of how it is possible to let your game client do the hard work for you. 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. If you play around with this some, you might notice some oddities in some specific packets such as group spawn, character data, and storage. Try and reason out those issues yourself to think what the client is doing under the hood.

There is still a few more things that can be learned in this process by analyzing the client code, but that requires a bit of experience with assembly and understanding how code is generated. For the most part, you should be able to discover most of what you need to know only using the parsings. However, for some packets, you do need to look at the client code to figure out what is going on.

That wraps up this guide! As you can hopefully see, it's not that much code overall once you have a good idea of what to do. I hope you found this it informational and beneficial. Stay tuned for future guides, I will see what I can come up with next.

Drew “pushedx” Benton
edxLabs

0 comentarios:

Publicar un comentario