Extracting Built Packets in Silkroad
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.
- OllyDbg 1.10 (or equivalent)
- Visual Studio 2008 (or equivalent)
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