I've made a little update for the new (unpacked) client. This version should be a lot easier to update in the future as opposed to the original version posted. This guides code is also combined with the other guides for extracting built packets. This is the simple version of the analyzer that just has the code for console logging and is ready for you to do with it what you want.
DLL.h
Code:
#pragma once
// Bin: 33 C0 89 46 10 89 46 14 C7 46 08 00 00 00 00 C7 46 04 00 00 00 00 8B C6 5E C2 04 00 CC
// First line in function
#define CC_ExtractSentPacket_Address 0x535BA0
// 56 57 8D 9B 00 00 00 00 8B 73 08 81 E6 FF 0F 00 80 8B FD
// MOV EBX,ECX
// MOV DWORD PTR SS:[ESP+10],EAX
#define CC_ExtractSentPacket_Address_1 0x504C4C
// 64 A1 00 00 00 00 50 B8 08 10 00 00
// MOV EAX, 1008
#define CC_ExtractSentPacket_Address_2 0x76F9DE
// 2nd call address under "SR_RESET_CLIENT"; Case 3369 of switch 007794AA
// CALL sro_clie.006DC6F0 <--
#define CC_ExtractPacket_Address_1 0x6DC6F0
// End of ReadBytes function
// POP ESI
// MOV EAX,EBX
// POP EBX
// RETN 8
#define CC_ExtractPacket_Address_2 0x4F312C
// First CMP above "MSGID:0x%04X Wait My Char Data !!!"
//CMP EAX, 3369
#define CC_ExtractPacket_Address_3 0x779B1A
// Line of second call below "SR_RESET_CLIENT"; Case 3369 of switch 007794AA
// --> 00779555 . E8 262BF6FF CALL 200_clie.006DC080
#define CC_ExtractPacket_Address_4 0x779BC5
DLL.cpp
Code:
#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include "../common/common.h"
#include "DLL.h"
//-------------------------------------------------------------------------
// Global instance handle to this DLL
HMODULE gInstance = NULL;
// Function prototype
void UserOnInject();
void UserOnDeinitialize();
//-------------------------------------------------------------------------
// 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);
}
else if(ulReason == DLL_PROCESS_DETACH)
{
UserOnDeinitialize();
}
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("[ClientToServer][%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(CC_ExtractSentPacket_Address_1, 6, codecave_WriteBytes);
// Hook the post build processing function
edx::CreateCodeCave(CC_ExtractSentPacket_Address_2, 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(CC_ExtractSentPacket_Address_1, patch1, 6);
// Restore the post build processing function code
static BYTE patch2[] = {0xB8, 0x08, 0x10, 0x00, 0x00};
edx::WriteBytes(CC_ExtractSentPacket_Address_2, patch2, 5);
}
void Setup()
{
edx::CreateCodeCave(CC_ExtractSentPacket_Address, 5, codecave_SetOpcode);
}
}
//-------------------------------------------------------------------------
namespace CC_ExtractPacket
{
FARPROC fpHandler = (FARPROC)CC_ExtractPacket_Address_1;
DWORD currentOpcode;
LPBYTE currentBuffer;
DWORD currentSize;
void OnProcessDataStart()
{
printf("[ServerToClient][%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(CC_ExtractPacket_Address_2, 7, codecave_ReadBytes);
}
void DisableParseHook()
{
static BYTE patch[] = {0x5E, 0x8B, 0xC3, 0x5B, 0xC2, 0x08, 0x00};
edx::WriteBytes(CC_ExtractPacket_Address_2, 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(CC_ExtractPacket_Address_3, 5, codecave_ExtractPacket);
edx::CreateCodeCave(CC_ExtractPacket_Address_4, 5, codecave_InvokeHandlers);
}
}
//-------------------------------------------------------------------------
// The function where we place all our logic
void UserOnInject()
{
// Create a debugging console
edx::CreateConsole("edxAnalyzer 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();
CC_ExtractPacket::Setup();
}
//-------------------------------------------------------------------------
void UserOnDeinitialize()
{
}
//-------------------------------------------------------------------------
I've also attached a GUI version of the analyzer from edx33 so the packets log to the GUI window instead of the console. That version is a much nicer development tool, but it's just a temporary solution for now. For users of the 2008 EE, you can't compile the project because of the resource editing limitations in EE, so I've also attached a binary version. If it doesn't work for you, your unpacked client might be different from mine.
[Only registered and activated users can see links. Click Here To Register...]
One important thing to note about this method is that some packets don't display correctly or some seem parsed weird. The reason for this has to do with how the client works. This method is not a 100% accurate way of how the client parses the packets because the client maintains packet states internally so it can discard some data.
That means some packets might parse unexpectedly or only show portions of their data payload rather than the full packet. An example is B505, where if you cast Grasswalk, you won't see 10 bytes but rather 5, but if you cast another buff, you'll see 10 bytes. if you look at the raw packets in edx33, you'd have seen both packets actually contained 10 bytes. Just keep that in mind before relying solely on this feature because it can have an effect on your resulting code.
I am rewriting a lot of my previous released code, so I'll try to get updates posted to the important guides when I can, but as you can imagine, I'm pretty busy ;)