Silkroad Multiclient Tutorial
In this tutorial you will learn to patch sro_client.exe and Silkroad.exe
to allow starting of multiple clients.
First you will need the following things
- OllyDbg 1.10
- Visual C++ 2005 or later
- EXEInfo PE
- StripperX
- 32bit version of Windows
- Winject or another DLL injector
Part 1: Analyze the client
If you open sro_client.exe in OllyDbg you should notice that it is packed. To find out
what it is packed with we will use EXEInfo PE. You can find this here http://www.exeinfo.xwp.pl/
EXEInfo PE tells us sro_client.exe is packed with ASprotect ver 2.1 / 2.^. This is quite simple to unpack
and requires only a few clicks.
Part 2: Unpacking the client
Start stripper v9 and click the open button. It will give you an open file dialog. Select your client
and click unpack. Once it does it's thing sro_client may crash (ignore this) and click on save in the
new window that has appeared. StripperX will save the unpacked file as _sro_client.exe
Part 3: Analyzing and patching the Silkroad launcher
Start two Silkroad.exe's and you will get this error.
Start OllyDbg and open Silkroad.exe. Right click -> Search for -> All referenced text strings
Search for the word "already" until you find "%s is already executed!!".
Double click the first one and you should see some similar code to this.
We need to just completely jump over the MessageBoxA error to do this we will need to change
the JNZ SHORT 00437ACB to a JMP by double clicking on the JNZ and changing it to a JMP.
Go back to the text string window and find the other one we found. Patch it the same way by locating
the CMP EAX,0x0B7.
Next we need to find
Now right click -> Copy to executable -> All modifications
On the next window click Copy All. And on the next window right click -> Save file and save it as the
same file name.
Now we will be able to run Silkroad.exe while sro_client is running.
Part 4: Patching the client
Move the unpacked client to your Silkroad directory and start OllyDbg and open the newly unpacked
sro_client. Let OllyDbg analyze the exe. It'll be easier to look at and save us some time.
Now hit Shift+F9 this will run the program. You should an error message that looks like this.
Click the << rewind button in OllyDbg (it's directly beside the open file button). sro_client has now been
reloaded in Olly.
We obviously need to find this error message within the program. Right click -> Search for -> All referenced text strings.
In the new window that has appeared right click -> Search for text
Remember the error message? Type in "Please Execute" this should be sufficient enough to find it.
Double click that line and it will take you to the string. As you can see it's just a simple MessageBoxA call.
This is exactly what we need to bypass.
Double click the JNZ and change it to a JMP as such.
Scroll up a little and you should see a text string that says something about a MapTool.
If you have every tried to run another client while one was already running it would bring the already
running client to the front and close the one you just ran. Well this is that code.
If we take a close look at the code we can see that it creates a mutex called "Silkroad Client" then
calls GetLastError() to see if it failed and if it did it will bring the other Silkroad client to the front
and terminate (JNZ = JMP if false)
As you can see the EAX holds the return value of GetLastError() from looking at CMP EAX,0xB7.
So simply double click the JNZ after CMP EAX,0xB7 and change it to a JMP like we did before.
Now lets save the file and see if it worked correctly. Right click -> Copy to executable -> All modifications
Select copy all in the next window.
Right click in the new window -> Save file
Save it in the same location and same file name.
Now click the << rewind button. Find the file and double click it twice to launch two clients.
Eventually you should get an error like this with some Korean letters.
We need to bypass this error too. Like we did before. Right click -> Search for -> All referenced text strings
We need to find "Silkroad" because we cannot type the letters into OllyDbg. Once you find the string it should
be directly below "GateWay : %s"
Double click it and the code should look like this.
Change the JNZ to a JMP and save the file exactly like before. Run the modified client twice and there shouldn't
be any problems. Now try to login two characters. One will get in and oh noes C10 error :o
The C10 error is caused by multiple logins trying to using the same MAC (Media Access Control) address it's
basically the unique ID of your network adapter. Silkroad MUST get your MAC address from some where. The best
way to do this would be with a call to GetAdaptersInfo()
Right click in OllyDbg -> Search for -> All intermodular calls
Find any calls to iphlpapi.GetAdaptersInfo
You can either set break points here and look for your MAC address in the register's or I can tell you
that MOV EDX,DWORD PTR DS:[EDI+0x194] will move your MAC address into the EDX register. This is what we
need to patch.
The easiest way to patch this is to make a DLL and code cave it.
Part 5: Creating the DLL
Create a new prject in VC++ and name it macChanger. Disable pre-compiled headers and change the character-set
to multi byte. We will use the WriteJMP code from my other tutorial. Make sure this code goes above your DllMain.
Code:
void WriteBytesASM(DWORD destAddress, LPVOID patch, DWORD numBytes)
{
DWORD oldProtect = 0;
DWORD srcAddress = PtrToUlong(patch);
VirtualProtect((void*)(destAddress), numBytes, PAGE_EXECUTE_READWRITE, &oldProtect);
__asm
{
nop // Filler
nop // Filler
nop // Filler
mov esi, srcAddress // Save the address
mov edi, destAddress // Save the destination address
mov ecx, numBytes // Save the size of the patch
Start:
cmp ecx, 0 // Are we done yet?
jz Exit // If so, go to end of function
mov al, [esi] // Move the byte at the patch into AL
mov [edi], al // Move AL into the destination byte
dec ecx // 1 less byte to patch
inc esi // Next source byte
inc edi // Next destination byte
jmp Start // Repeat the process
Exit:
nop // Filler
nop // Filler
nop // Filler
}
VirtualProtect((void*)(destAddress), numBytes, oldProtect, &oldProtect);
}
void WriteJMP(DWORD destAddress, VOID (*func)(VOID), BYTE nopCount)
{
DWORD offset = (PtrToUlong(func) - destAddress) - 5;
BYTE nopPatch[0xFF] = {0};
BYTE patch[5] = {0xE9, 0x00, 0x00, 0x00, 0x00};
memcpy(patch + 1, &offset, sizeof(DWORD));
WriteBytesASM(destAddress, patch, 5);
if(nopCount == 0)
return;
memset(nopPatch, 0x90, nopCount);
WriteBytesASM(destAddress + 5, nopPatch, nopCount);
}
We need to generate 5 random numbers for our MAC address. Create a 6 BYTE array at the very top of the code
but below the includes.
Now create a DWORD below the byte array for our return address. It's always easier to just copy pasta the ASM
from Silkroad into C++ and return a few addresses after it so there won't be any problems.
Code:
DWORD dwRet = 0x0046C791;
In the DllMain function add. This will generate 5 random bytes for our MAC address. Write the code cave
after the MAC address has been generated.
Code:
for(int x = 1; x < 5; ++x) //Skip the first byte and generate random values
{
srand(GetTickCount()); //Initialize a random seed
MAC[x] = rand() % 0xFF; //Generate random BYTE
}
//Code cave
WriteJMP(0x0046C787, MACChange, 0);
Next we will write our naked MAC change function. Remember that EDX will contain our MAC address and
EDI+194 holds our original MAC address.
Code:
__declspec(naked) void MACChange()
{
__asm
{
LEA EDX, DWORD PTR DS:[MAC] //Move the MAC byte array into the EDX register
MOV EAX,DWORD PTR SS:[ESP+0x18] //Copied from Silkroad
JMP dwRet //Return to original code
}
}
Here's the full code if you couldn't follow that.
Code:
#include "stdio.h"
#include "windows.h"
BYTE MAC[6] = {0};
DWORD dwRet = 0x0046C791;
void WriteJMP(DWORD destAddress, VOID (*func)(VOID), BYTE nopCount);
__declspec(naked) void MACChange()
{
__asm
{
MOV EDX, DWORD PTR DS:[MAC] //Move the MAC byte array into the EDX register
MOV EAX,DWORD PTR SS:[ESP+0x18] //Copied from Silkroad
JMP dwRet //Return to original code
}
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call,LPVOID lpReserved)
{
if(ul_reason_for_call == DLL_PROCESS_ATTACH)
{
DisableThreadLibraryCalls(hModule);
srand(GetTickCount()); //Initialize a random seed
for(int x = 1; x < 5; ++x) //Skip the first byte and generate random values
{
MAC[x] = rand() % 0xFF; //Generate random BYTE
}
//Code cave
WriteJMP(0x0046C787, MACChange, 0);
}
return true;
}
void WriteBytesASM(DWORD destAddress, LPVOID patch, DWORD numBytes)
{
DWORD oldProtect = 0;
DWORD srcAddress = PtrToUlong(patch);
VirtualProtect((void*)(destAddress), numBytes, PAGE_EXECUTE_READWRITE, &oldProtect);
__asm
{
nop // Filler
nop // Filler
nop // Filler
mov esi, srcAddress // Save the address
mov edi, destAddress // Save the destination address
mov ecx, numBytes // Save the size of the patch
Start:
cmp ecx, 0 // Are we done yet?
jz Exit // If so, go to end of function
mov al, [esi] // Move the byte at the patch into AL
mov [edi], al // Move AL into the destination byte
dec ecx // 1 less byte to patch
inc esi // Next source byte
inc edi // Next destination byte
jmp Start // Repeat the process
Exit:
nop // Filler
nop // Filler
nop // Filler
}
VirtualProtect((void*)(destAddress), numBytes, oldProtect, &oldProtect);
}
void WriteJMP(DWORD destAddress, VOID (*func)(VOID), BYTE nopCount)
{
DWORD offset = (PtrToUlong(func) - destAddress) - 5;
BYTE nopPatch[0xFF] = {0};
BYTE patch[5] = {0xE9, 0x00, 0x00, 0x00, 0x00};
memcpy(patch + 1, &offset, sizeof(DWORD));
WriteBytesASM(destAddress, patch, 5);
if(nopCount == 0)
return;
memset(nopPatch, 0x90, nopCount);
WriteBytesASM(destAddress + 5, nopPatch, nopCount);
}
void WriteCALL(DWORD destAddress, VOID (*func)(VOID), BYTE nopCount)
{
DWORD offset = (PtrToUlong(func) - destAddress) - 5;
BYTE nopPatch[0xFF] = {0};
BYTE patch[5] = {0xE8, 0x00, 0x00, 0x00, 0x00};
memcpy(patch + 1, &offset, sizeof(DWORD));
WriteBytesASM(destAddress, patch, 5);
if(nopCount == 0)
return;
memset(nopPatch, 0x90, nopCount);
WriteBytesASM(destAddress + 5, nopPatch, nopCount);
}
Part 6: Testing
Rename the patched _sro_client.exe to sro_client.exe Start two clients that you have already
patched with Silkroad.exe and inject the DLL we made into one or both of them. Now try to login
with both at the same time. They both should continue with the login process as normal and not
get the C10 error.
© ProjectHax.com