To kick things off, I am writing about some useful techniques that I used in the past but are still something that no one else has really dedicated resources to for Silkroad. This article is the first part in a series, so you might not find an immediate use for this information yet. A couple of other articles still need to be written to build the foundation to make use of this knowledge.
Here's an older screenshot from the past as to why this guide is relevant, as well as what I am making my first series of guides for:
With that said, here's the first article I have written for the year. It is being posted first exclusively here on epvp, so I hope you enjoy!
Locating Silkroad’s Direct3D Objects
I. Purpose
The purpose of this guide is to show a quick and easy way to obtain the Direct3D objects Silkroad uses. This guide shows how to accomplish the task with Silkroad, but the concepts are applicable to any Direct3D game. The primary reason such a task is needed is to be able to integrate in a secondary Direct3D GUI into the game through hooking the appropriate functions. Alternatives do exist, such as creating a Direct3D proxy DLL, but that method is much more complicated and requires a different strategy for implementing additional logic into the host.
II. Requirements
This guide is written as an intermediate to advanced guide. It is expected you have basic knowledge of C++/ASM and how to use OllyDbg.
In order to be able to follow along, you will need:
• OllyDbg 1.10 (or equivalent)
• Visual Studio 2008 (or equivalent)
• Microsoft’s DirectX March 2009 SDK (or equivalent)
You may use other disassembles, but it is up to you to figure out the appropriate equivalent commands shown. Any modern version of Visual Studio will work as well. Likewise with the DirectX SDK, you just need a modern version that supports DirectX 9. It is up to you to properly setup those tools. Once you have all of those requirements fulfilled, you may continue on to the next section.
III. Theory
The theory of what we are about to do is very creative and extremely useful in problem solving. I originally thought of using this method a few years back when trying to search the client for Direct3D calls to hook the drawing functions to implement my own GUI in the game. Basically, what we are going to do is create a small program that mimics what Silkroad does internally to locate calls to an external DLL.
The reason why we want to approach the problem this way is because the Silkroad client is huge, over 9 MB and actually trying to search through all that code is impossible. I had studied the client for a number of years and was still finding new things I did not know about on a weekly basis before I switched to packet based applications. By creating a smaller program that we can control the code of, we can easily write out specific function calls and then mark them in the code and look at what functions they call in an external DLL.
The reason why we can take this approach is because of how DLLs work on Windows. I won’t get too deep into the workings, but because the DLL is compiled with a fixed address, we can always obtain an offset to specific functions when the DLL is loaded into memory. On one computer, the function might be at address 0x12345678 in memory and on another computer the address might be at address 0x87654321. It wouldn’t matter because we can always calculate the address to the function based on the offset in the DLL.
Now in this specific example, we don’t have to worry about what address the functions are loaded at because we are only interested in finding the client related code that calls these functions. As a result, we have less work cut out for us compared to if we needed to patch the DLL itself!
IV. Implementation
Now we are ready to get started. The first thing we will be doing is creating our simple Direct3D program so we can get an idea of how the Direct3D code looks like for the Silkroad client. I just found the simplest tutorial code I could online to use. I will be modifying the code from this tutorial: . I would also strongly recommend you follow the tutorial to get an idea of some Direct3D basics before you continue as well if needed.
Create a new Win32 project in Visual Studio and add in a new CPP file with the code presented at the end of the tutorial (click on the link that says [Show Code]). You should be able to compile that code with no changes and be able to run it with no problems. If you do have any problems, make sure you setup Direct3D correctly and have the appropriate project settings in place.
Here is a cleaned up version of that code for reference:
Code:
#include <windows.h> #include <windowsx.h> #include <d3d9.h> #pragma comment (lib, "d3d9.lib") LPDIRECT3D9 d3d; LPDIRECT3DDEVICE9 d3ddev; void initD3D(HWND hWnd); void render_frame(void); void cleanD3D(void); LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { HWND hWnd; WNDCLASSEX wc; ZeroMemory(&wc, sizeof(WNDCLASSEX)); wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WindowProc; wc.hInstance = hInstance; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)COLOR_WINDOW; wc.lpszClassName = L"WindowClass"; RegisterClassEx(&wc); hWnd = CreateWindowEx(NULL, L"WindowClass", L"Our First Direct3D Program", WS_OVERLAPPEDWINDOW, 300, 300, 800, 600, NULL, NULL, hInstance, NULL); ShowWindow(hWnd, nCmdShow); initD3D(hWnd); MSG msg; while(TRUE) { while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } if(msg.message == WM_QUIT) break; render_frame(); } cleanD3D(); return msg.wParam; } LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch(message) { case WM_DESTROY: { PostQuitMessage(0); return 0; } break; } return DefWindowProc (hWnd, message, wParam, lParam); } void initD3D(HWND hWnd) { d3d = Direct3DCreate9(D3D_SDK_VERSION); D3DPRESENT_PARAMETERS d3dpp; ZeroMemory(&d3dpp, sizeof(d3dpp)); d3dpp.Windowed = TRUE; d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; d3dpp.hDeviceWindow = hWnd; d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &d3ddev); } void render_frame(void) { d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 40, 100), 1.0f, 0); d3ddev->BeginScene(); d3ddev->EndScene(); d3ddev->Present(NULL, NULL, NULL, NULL); } void cleanD3D(void) { d3ddev->Release(); d3d->Release(); }
The functions we will want to be marking are the calls to Direct3DCreate9, CreateDevice, Clear, BeginScene, EndScene, Present, and Release. To mark these, we will just define a macro that emits our NOPs and place the macro around the code.
Here is what our macro looks like:
Code:
#define OLLYDBGMARKING \ __asm nop \ __asm nop \ __asm nop \ __asm nop \ __asm nop
Code:
void initD3D(HWND hWnd) { OLLYDBGMARKING d3d = Direct3DCreate9(D3D_SDK_VERSION); D3DPRESENT_PARAMETERS d3dpp; ZeroMemory(&d3dpp, sizeof(d3dpp)); d3dpp.Windowed = TRUE; d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; d3dpp.hDeviceWindow = hWnd; d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &d3ddev); OLLYDBGMARKING } void render_frame(void) { OLLYDBGMARKING d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 40, 100), 1.0f, 0); d3ddev->BeginScene(); d3ddev->EndScene(); d3ddev->Present(NULL, NULL, NULL, NULL); OLLYDBGMARKING } void cleanD3D(void) { OLLYDBGMARKING d3ddev->Release(); d3d->Release(); OLLYDBGMARKING }
Once we get the file opened in OllyDbg we will want to find our marked areas. To do this, we will want to right click on the main assembly pane and choose Search For -> All Sequences. In the new dialog that pops up, we will want to simply type in our 5 NOPs. In case you need a reference image:
Click Find and you will get a list of all of the code blocks that contain the matching pattern. In this case we will get 6 since we marked the start and end of 3 code segments. Right click on the window and choose Set breakpoint on every command. Now all our regions are marked and we are ready to run.
Since we compiled this code ourselves, chances are we left in the code listing for the exe and we will be able to see the code in the OllyDbg preview pane right above the dump window. This will help us for this guide, but such information is rare to come by in the real world.
Go ahead and hit F9 to run the application. The first breakpoint we will hit will be at the entry of the initD3D function. Once again, since we compiled this program ourselves, we can see a lot more information available. Usually tracking down the Direct3DCreate9 API function is pretty easy, so for now we will not worry about this section. Hit F9 again to hit the end marker and then F9 once more so we land inside the render_frame function.
Here is a reference screenshot of what we should be looking at:
You might notice some interesting things about this code. First, we have only function pointer calls. This is because of how Direct3D uses a COM interface. As a result, any Direct3D calls will pull a function pointer from the base object and setup the function call. By studying this pattern, you can easily identify the D3D device object in another executable if you were to find some point of reference.
For now, place a breakpoint on the line: 01061121 FFD2 CALL NEAR EDX and press F9 to run to it. Hit Enter once on the line to follow the call. Press Shift + ; to be able to set a label for the function. If we reference our original code, we will know our first function call was: d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 40, 100), 1.0f, 0); , so we can label this function Clear. Press the number pad * key to go back to the current line of code. We should now see our label in the comment section of the listing. We have found the address of the first function!
Go ahead and set a breakpoint on the next function: 01061131 FFD2 CALL NEAR EDX and press F9 to run to it. If we reference our program again, we know this call is the BeginState function, so we can once again hit Enter to follow the call and Shift + ; to set the function label. We can repeat this process two more times to obtain the address of the EndScene function and then finally the Present function.
Once we have all four of these functions addresses and understand the process, we can just do the same thing for the Release functions as well as the previous CreateDevice and Direct3DCreate9 functions we skipped over. I will just continue on from here though.
For now, clear out the breakpoints in this section (the 4 function calls and two NOP markers) so we can let the program run normally. Once you do that, go ahead and close the program so we can hit the release functions. When you hit the last NOP marker breakpoint set the final two breakpoints on the remaining two functions.
The first function is our DeviceRelease specific function. Label it and continue to the next function, which is the SDKRelease function. Label that one as well but do not leave the module yet. If you do leave the module just hit Enter again to go back into the d3d9 module. Right click on the main assembly pane and choose Search for -> User-defined labels. In the new window that opens, we will see out 6 functions we marked (since we skipped the first two initially). Here is what my table looks like:
Code:
User-defined labels Label Address Disassembly <DeviceRelease> 503F6BB1 MOV EDI,EDI <SDKRelease> 503FA1E0 MOV EDI,EDI <Clear> 5040F206 MOV EDI,EDI <EndScene> 50410F67 MOV EDI,EDI <BeginScene> 50411E31 MOV EDI,EDI <Present> 5042DBA5 MOV EDI,EDI
Now we can apply our new findings to the Silkroad client to see if it did us any good. Load up sro_client.exe in Olly. Click on Debug -> Arguments and enter “0 /18 0 0” for the command line. Hit Ok and the press Ctrl + F2 to reload the executable so it will use our command line. We don’t need this for this guide, but we will for later guides.
Now, we need to set a breakpoint on the loader check. To find the check, right click on the main assembly pane and choose Search for -> All referenced text strings. It will take a moment to search so when it is done, hit Ctrl + Home to go to the top of the search results. Right click on the window area and choose Search for text. Type in “Please execute” and make sure case sensitive is not checked and hit Ok.
The first result we will see is related to our Loader check: 00733B55 |. 68 9CF6C400 PUSH sro_clie.00C4F69C ; |Text = "Please Execute the "Silkroad.exe."". We will want to set a breakpoint a few lines above on the JNZ. Here is a screenshot for reference:
Now we are ready to apply our knowledge. What we will want to do is just hit F9 once so we hit the breakpoint we just set. When the breakpoint has been set, we can go ahead and identify our Direct3D calls.
For now, we will just mark the rendering related functions. Start out with the Clear function. Hit Ctrl + g to go to the address of where your Clear function is located. Set the function label so we know what function it is and set a breakpoint on the address. Repeat this process for the three other functions.
When you are done hit the numpad * key to return to our current line of code, which is the launcher check. Hit enter to follow the jump and set the new line of code as the current EIP. To do this you can press Ctrl + * (on the numpad) or right click on the line and choose New origin here. Hit F9 again to resume running of the client.
After the client loads, we should first hit the Clear function, just as we had seen in our own program. So far so good, remove the breakpoint and hit F9 to continue. Now we hit the BeginScene, so remove the breakpoint and hit F9 to continue. Repeat this for the EndScene and Present functions.
At this point, we have found the main D3D functions we will need to hook. We removed our breakpoints so we could let the client load. If by chance the client disconnected, simply restart it and don’t set breakpoints there yet.
After you get to the login screen, you will want to apply a breakpoint to the EndScene function address. We do it here at the login screen since the smaller window for the startup logo also makes use of these functions and we do not care about that code. To go to the address of the EndScene function you can type in the address via the go to dialog (Ctrl + g) or you can type in the label name EndScene!
When you are at the function address, place a breakpoint. Immediately the client breaks. Click on the top line in the Call Stack and hit Enter to follow the call back. Here is a reference screenshot of what you should be seeing now. I added a comment to the function call:
Does anything look familiar? It certainly should! Based on what we saw in our previous program’s assembly output, we can see the game’s device object at address 0xA5F050. The line after next loads the EndScene function pointer from the object. Set a breakpoint on the function call line. Hit the numpad * key to return to the current breakpoint inside the EndScene function and remove it. Press Ctrl+ F2 to restart the application and run it again.
We will notice the breakpoint is triggered when the logo starts up, so remove the breakpoint and run normally until you are at the login screen again. We have finally found where the client’s function for invoking the D3D function is. We can repeat this process as needed for the other functions as well.
Congratulations! We are done now! Go ahead and close Olly.
V. Conclusion
By now, you should understand the basics of locating the Direct3D objects Silkroad uses. In doing so, you will now be able to hook those objects and implement additional rendering logic on top of the game screen. Making use of this specific knowledge will come in a later guide, so this guide is just a beginning stepping stone to bigger things.
Furthermore, you can now apply this knowledge to other programs and APIs if you are able to create a simple demo of the functionality you wish to find! This makes often perceived hard tasks relatively simply using this approach.
That wraps up this guide. As mentioned before, this is just preliminary information to understand for larger purposes. I hope you found this guide informational and beneficial. Stay tuned for future guides.
Drew “pushedx” Benton
edxLabs