(Link to AcmlmWiki) Offline: thank ||bass
Register | Login
Views: 13,040,846
Main | Memberlist | Active users | Calendar | Chat | Online users
Ranks | FAQ | ACS | Stats | Color Chart | Search | Photo album
05-15-24 04:29 AM
0 users currently in The Pit of Despair.
Acmlm's Board - I3 Archive - The Pit of Despair - DirectX API calls in assembler, step-by-step. New poll | | Thread closed
Add to favorites | Next newer thread | Next older thread
User Post
blackhole89
Moronic Thread Bodycount: 17
(since 2006-08-21 09:50 EST)
F5 F5 F5 F5 F5


 





Since: 12-31-69
From: Dresden/SN/DE

Last post: 6297 days
Last view: 6295 days
Skype
Posted on 05-21-06 10:51 AM Link
What would one need this for?
The particular goal I had in mind when working this out was looking up the call of a specific Direct3D call in the disassembly of a Ragnarok Online client (I wanted to hack it to change the scene's background colour, so it was a call to IDirect3DDevice::Clear). But of course, you can ultimately do all you want with it, up to writing your own DirectX applications in assembler.

What tools does one need?
- A C/C++ header of whatever DirectX API you are working on. In the example I give here, it is d3d.h.
- The disassembler of your choice. I recommend IDA or SoftICE (google for that one).
- A program that can search using either wildcards or regular expressions. This is not necessary, but might come in very handy if you have to handle larger amounts of code.

So how would this be done?
First off, disassemble your binary to a text file or, if you use an integrated environment like IDA, just disassemble it.

Next, let's assume you are, like me, looking for a call of IDirect3DDevice::Clear.

Let's assume our application uses the IDirect3DDevice7 interface. There are multiple ways to guess the version of the interface used (like looking up DLL dependencies), but version 7 seems to be the most common nowadays anyway.

Open up your header - in our case, it's d3d.h - and look up your interface definition; find the function you are looking for in it.


DECLARE_INTERFACE_(IDirect3DDevice7, IUnknown)
{
/*** IUnknown methods ***/
STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID * ppvObj) PURE;
STDMETHOD_(ULONG,AddRef)(THIS) PURE;
STDMETHOD_(ULONG,Release)(THIS) PURE;

/*** IDirect3DDevice7 methods ***/
STDMETHOD(GetCaps)(THIS_ LPD3DDEVICEDESC7) PURE;
STDMETHOD(EnumTextureFormats)(THIS_ LPD3DENUMPIXELFORMATSCALLBACK,LPVOID) PURE;
STDMETHOD(BeginScene)(THIS) PURE;
STDMETHOD(EndScene)(THIS) PURE;
STDMETHOD(GetDirect3D)(THIS_ LPDIRECT3D7*) PURE;
STDMETHOD(SetRenderTarget)(THIS_ LPDIRECTDRAWSURFACE7,DWORD) PURE;
STDMETHOD(GetRenderTarget)(THIS_ LPDIRECTDRAWSURFACE7 *) PURE;
STDMETHOD(Clear)(THIS_ DWORD,LPD3DRECT,DWORD,D3DCOLOR,D3DVALUE,DWORD) PURE;
[...]

As you see, Clear is the 11th method from above. Memorize this number.
It takes 6 parameters. Memorize that too.

At this point, you should know Microsoft's COM interfaces all work in a similar way: when you initialize them using a DLL's API function, your small container structure is given a pointer to a virtual function table ("void *vf_ptr" in most implementations, but that doesn't matter). It points at an array of function pointers to the single interface functions somewhere in memory. x86 pointers are, natively, 32 bits - 4 bytes - long, so you can expect the pointer to Clear - remember it was the 11th method in the interface definition? - to be at (11-1)*4 = 40d = 28h. (We subtract 1 from 11 because the first method is at (vf_ptr+0).)

Sadly, the way vf_ptr takes throughout the application is very different from one binary to another, so we can't realistically trace it. Nevertheless, the only way in x86 architecture to call an offset relative to a pointer is the opcode FF xx yy - call dword ptr [register xx + yy].

The next problem is the fact the compiler could have used any register to store vf_ptr in for the call. This is where the wildcard function of your disassembly viewer comes in handy: Search for call dword ptr [*+28h] (where * is the wildcard).
You will get a number of hits, of which some might be not what you are looking for. A relatively safe way to figure out whether you have actually stumbled upon a DirectX API call is checking the number of push operations done before the call.

Note that
- push operations for a function call can go quite far back in the code flow.
- It is also worth checking whether the type of arguments plus one matches (in this example, we expect 7 push operations). The additional argument is from the this pointer which is always added as a first argument to DirectX interface calls.
- The order of the pushed arguments is reversed, so a call to f(a,b,c) looks like
push c
push b
push a
call f

In the beforementioned example, we will stumble upon the following piece of code at some point.


.text:00404CB9 xor eax, eax
.text:00404CBB mov [ebp+var_10], eax
.text:00404CBE mov [ebp+var_C], eax
.text:00404CC1 push eax
.text:00404CC2 mov eax, [ebp+arg_0]
.text:00404CC5 mov [ebp+var_8], edx
.text:00404CC8 mov edx, [ecx+8]
.text:00404CCB mov ecx, [ecx+3Ch]
.text:00404CCE push 3F800000h
.text:00404CD3 jmp short loc_404D26
.text:00404CD3 push eax
.text:00404CD4 nop
.text:00404CD5 lea eax, [ebp+var_10]
.text:00404CD8 push 3
.text:00404CDA mov [ebp-4], edx
.text:00404CDD mov edx, [ecx]
.text:00404CDF push eax
.text:00404CE0 push 1
.text:00404CE2 push ecx
.text:00404CE3 call dword ptr [edx+28h]

As you see, the number of preceding push operations matches 7. Furthermore, the first argument obviously is a this pointer, as we know edx, which stores our vf_ptr here, is [ecx]; in x86 assembly, [ecx] means nothing else than "the contents of memory at the address in ecx".
The two DWORDs are pushed as immediates, 1 and 3; the LPD3DRECT is passed from eax. Let's trace back eax's way.

.text:00404CB9 xor eax, eax
.text:00404CBB mov [ebp+var_10], eax
.text:00404CD5 lea eax, [ebp+var_10]
.text:00404CDF push eax

As everybody knows, XORing something with itself effectively sets it to zero; this seemingly exotic procedure is very common among x86 compilers.
For some reason, it is stored to var_10 on the stack while other arguments are being processed; before being pushed, it is retrieved from it again. Hence, we are passing 0 (NULL) as the LPD3DRECT parameter, thus clearing the entire viewport.

For the D3DCOLOR value, we pass something passed as an argument to the surrounding subroutine.

The D3DVALUE, which is just Direct3D API alias for IEEE 32-bit single-precision floating-point numbers, is passed as 3F800000h; if you have the free time and will to, you can get a hex editor and verify it is the DWORD representation of 1.0f, but as it is, you just will have to believe me on this one.

As the last DWORD, our previously nulled eax is pushed.

With this information, we can reconstruct the call that happened there to something like the following:

(ourd3dinterfacepointer)->Clear(1,NULL,3,arg_0,1.0f,0);

Based on this information, you could place hooks in that code and modify virtually anything about the call; in my modification of the client, I hooked in a piece of code that sets the D3DCOLOR to the value of the location in the binary's virtual address space where the current scene's fog colour is stored.

Hopefully this will actually be of use to someone... half of my motivation to post this was to give an example of what this new forum section is good for, as I was the one who suggested it being added in the first place.

-- blackhole89.
Add to favorites | Next newer thread | Next older thread
Acmlm's Board - I3 Archive - The Pit of Despair - DirectX API calls in assembler, step-by-step. | Thread closed


ABII

Acmlmboard 1.92.999, 9/17/2006
©2000-2006 Acmlm, Emuz, Blades, Xkeeper

Page rendered in 0.012 seconds; used 359.99 kB (max 470.63 kB)