Sei sulla pagina 1di 9

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:

Code:
#include <windows.h>
#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

Potrebbero piacerti anche