Hooking Non-Virtual Functions, Part 2

The second method (this one PM gave me the idea for) for hooking non-virtual functions is with the jmp operator, rather than a call. This has a few immediate bonuses: the stack frame is left in tact, and we have more control over program flow. In other terms, we gain re-entrancy, and we don’t have to keep repatching code. In this article we’ll work on a generic hook, rather than a hardcoded one.

For beginners, let’s say we disassemble any function. In the HL2SDK for Win32, most non-virtual member fuctions will look like this (compiled with MSVC):

push ebx
push esi
push edi
...

Generally, the first four-five bytes of the function are simple pushes, and unless the function is less than five bytes, we won’t have a problem caching and overwriting the old code. So, let’s begin.

First, let’s make an imaginary data structure to hold all the information we’ll need for this. We’ll need to save the first six bytes of the function (that is, the amount of bytes a far, mem32 jmp takes). We’ll also need the eip to return to (function addr + 6), the function address, the calling convention, the stack size for the parameter list, and a few more little things we’ll get to. Since MSVC + HL2SDK compiles thiscalls with the __stdcall calling convention, I’ll demonstrate both GCC and MSVC options. Note you’d need a third to deal with varargs (maybe this will be a 3rd article).

enum CallConvention
{
   Call_cdecl=0,
   Call_stdcall=1,
};
//You must call mprotect on the address beforehand
//Returns a block of memory identifying the hook
//Pass a static address that contains the address of the function
void *hook_function(void *function, void **handler, int stacskize, int calltype);

Now we get to the assembly. As always, we are going to have at least two functions. The basic function gate that is never called, and the gate assembler that copies the basic gate into memory, then alters it.

cdecl_gate_open:
  push esi
  push edi
.params1
  mov ecx, 0 ;We will replace this with the number of bytes in the parameter stack
  lea esi, [esp+12] ;Get address of last stack frame
  lea edi, [esp-ecx] ;Point edi to bottom of the next call stack
  rep movsb
  pop edi
  pop esi
.params2
  sub esp, 0 ;Modify the stack frame to be N+8, again this will be copied
;The parameter stack has now been copied.  We can call our handler
;This is an E8 call, we'll replace the last four bytes with the address later
.call
  db 0e8h, 000h, 000h, 000h, 000h 
  add esp, 0; Another copy, for restoring the stack frame to N+8
  ret

That was easy enough. Now all hook_function has to do is copy correct values into the function. How to call the original, however? We need another, quick function assembled in memory.

cdecl_gate_close:
   ;Copy exactly the first six bytes of the original function into here
   times 6 db 0
   ;Construct a jump call.  Fill it with an address containing the eip (function addr + 6).
   db 0FFh, 025h, 000h, 000h, 000h, 000h

And how to keep track of all this information? Simple, we allocate a nice looking struct:

struct callgate
{
   void *function;
   void *orig_eip;
   void *gate_open;
   void *gate_close;
   int call_style;
   int num_params;
};

What’s going on here? We’re intercepting the function by telling it to jump somewhere else. That other location pushes the stack parameters, calls the handler, then returns. The second function simply executes the original 6 bytes, then jumps back to the original position.

If you get a little more creative, you can include handler lists (i.e., plugins) and a system for overriding return values. Tomorrow I’ll wrap this up with stdcall versions. To anyone actually reading this, you can make the actual gate creation function as an exercise. And, lastly, see if you can find how this will utterly fail! This will also be corrected in the next article.

HINT to the first question – All you need to do is allocate memory for those two code blocks, then overwrite the first six bytes of the target function with 0xFF 0×25 <4 bytes>, where the 4bytes hold the address of the callgate::gate_open pointer.

Leave a Reply

You must be logged in to post a comment.