The last time we discussed a very basic hook that would only work inside our own application. Today I'm going to show you the basic and universal hook called the jump hook. With the jump hook you can hook functions regardless of size, location, arguments and whether or not it was originally built in your project or not. The only downside to this hook is that most AC programs detect them quite easily. If there's a jump instruction at a location there shouldn't be one: you're caught.
To follow along:
*
Assembler knowledge is a must if you don't want to only leech the code
*A debugger or disassembler
*
C++/C or any language that can create dll files and console executables
Forward note:
The EIP (Extended Instruction Pointer) points to the instruction that is currently executed. Once the instruction is executed the EPI is advanced to the next instruction. This is how to processor keeps track of where it's working.
Understanding jumps
Jump instruction prototype:
Code:
JMP IMM32 // imminent operand 32 bit size
JMP MEM32 // Memory operand 32 bit zie
JMP REG32 // 32 bit register (eax, ecx, ebx, edx, esp, ebp, esi, edi)
All offsets are relative to the jump location, remember this!
Example:
401000: JMP 4 // jumps to an address 4 bytes from here
...
401004: // Execution will continue here.
To understand the jump hook we must first understand how the jump instruction works. When the processor encounters a jump instruction it adds the operand (IMM32, MEM32, REG32) to where the EIP is currently pointing. Consider:
Code:
Note: The operands to all instructions are in little-endian, this means that you have to read the number(two digits) from right to left.
eg: 00 10 is actually 10 00 and 10 00 00 00 is actually 00 00 00 10
E9 10 00 00 00
As you can see the processor adds the offset bytes (see red bold code above, 0x10) to the EIP register which points to the jump instruction being executed.
If you have any questions about how the JMP instruction works, you can post them below
The next step
Now that we know how the jump instruction works, we can advance to the next problem: How do we redirect the Sleep() function to our hook?
The answer is simple, we must write our jump code to a place were we are
100% sure it's going to be executed if we do that, every call to Sleep() will end in our Hook() >=D
The best place (where you are 100% sure) is of course the start of the Sleep() function, since every call to Sleep() will start there.
Every function has only 1 entry point, and all calls will start at this point
Now that we know where to place our hook, we can start writing code!
Create two projects, one console project named target and one dll project named hook
Target:
Add a file named main.cpp to your project and copy this into it:
Code:
#include <iostream>
#include <windows.h>
int main(){
printf("I'm going to sleep for a long while\n"); // to show us it's working
while(1){
Sleep(1000); // this sleep will get executed many times
}
return 0;
}
Hook:
Add a file named main.cpp to your project and copy this into it:
Code:
#include <windows.h>
#include <iostream>
#include <string.h>
void MainThread();
BOOL APIENTRY DllMain( HANDLE hModule, DWORD fdwReason, LPVOID lpReserved ){ // main() function inside the dll
if( fdwReason == DLL_PROCESS_ATTACH){
CreateThread(0, 0, (LPTHREAD_START_ROUTINE)&MainThread, NULL, NULL, NULL);
return TRUE;
}
return TRUE;
}
void __stdcall SleepHook(DWORD Timout){ // our hook
MessageBox(NULL, "Not sleeping!", "SCHiM", MB_OK); // display a messagebox instead of sleeping
return;
} // ret
void DoHook(DWORD* Address, DWORD* Hook){
/*
This time it's not as easy as in part 1 where we could just replace one pointer to point at our hook.
Here we need to replace 5 bytes with our jump code, E9 ** ** ** ** (where ** = offset bytes)
Luckily can just place them at the start of the Sleep() function so that we're sure they get executed
To do that we first need to change the memory protection so that we can write to it (we can't by default)
*/
DWORD OldProt; // -5 is there because the offset bytes of the jump instruction are relative to the address of the jump instruction, so the 5 bytes (length of the jump instruction) must be subtracted
DWORD HookOffset = (DWORD)Hook-(DWORD)Address-5; // calculate the offset bytes and store the result into hookoffset
VirtualProtect((void*) Address, 40, 0x40, &OldProt); // change the memory protection and give us write access.
char* CharPointer = (char*) Address; // sizeof(char) == 1, 0E9h == 1, we need a char for the size
*CharPointer = '\xE9'; // *CharPointer(Sleep()) now equals E9h (the jump opcode)
// we now need to place the operands
CharPointer++; // the size problem again, incrementing Address (Address++) would result in the pointer advancing 4 bytes
Address = (DWORD*)CharPointer; // Advance the pointer with 1 byte to where the offset bytes need to come
*Address = HookOffset; // the correct offset
VirtualProtect((void*) Address, 40, 0x40, &OldProt); // change the memory protection back to the old values (it doesn't really matter if we do this or not, but it's just proper)
return; // the hook is set!
}
void MainThread(){ // our main function to work with
DWORD* AddressOfSleep = (DWORD*) GetProcAddress(GetModuleHandle("Kernel32.dll"), "Sleep"); // get the address of the Sleep() function through the address of kernel32.dll
if( AddressOfSleep == NULL){ // oops
MessageBox(NULL, "Could not find the address of Sleep()!", "SCHiM", MB_OK);
return;
}
DoHook( AddressOfSleep, (DWORD*)&SleepHook); // hook it! >;)
return;
}
Compile/build the projects in release mode or something like it, as long as it's not debug mode
As usual the code is commented to help you understand it, I suggest you read it before compiling/continuing
If you have any problems with this code or if you don't understand something, post and I'll take a look
To test this code you run Target.exe and then you inject hook.dll into it. If all is well you should see a MessageBox saying: "Not sleeping!"
Reentry
If you've successfully tested the code above, you can continue here for there is yet another problem to be solved. In the code above we only show a MessageBox in our hook before returning. What if we instead want to change the timeout value of sleep and then resume the normal execution flow of the program executing Sleep()?
We've replaced 5 bytes at the start of the Sleep() function, so we can't just call Sleep() nor can we call Sleep()+5 (skipping the jump) for we have overwritten critical prologue code, without this prologue code the program will crash if you try to execute Sleep().
This problem is the problem of reentry in all it's glory: You can't know exactly what prologue code you've overwritten so you can't hardcode a solution, nor can you just jump back-in and you can't call it directly either.
The more absolute readers probably see the way out by now, and there's only one
good solution to this problem: we have to save the bytes at the start of the function and execute them before we jump back into the Sleep() function.
But that isn't the end of our problems, there's one last thing I kept back as to not discourage you at the start of this tutorial. Remember you need a debugger or a disassembler? Well here's why:
That prologue code I talked about with the Sleep() function? That's also in your Hook(), and in your Main() and in every other function you ever made or used. At the start of a function the prologue code does two things: It pushes the original base pointer to the stack and then moves the current stack pointer to the base pointer. There's also a matching Epilogue code undoing what the prologue code did, it moves the base pointer to the stack pointer, and then pops the previous base pointer from the stack. This Epilogue code is executed right before a Return; statement.
To keep it simple I'll only tell you about the foremost reason why this is a problem to us: At the end of a function when we Return; the processor thinks that the stack pointer is pointing to the return address. So it can properly return the execution to where it came from. However we're not going to return we're going to jump back into the Sleep() function therefore the epilogue will never be executed.
Because of that the stack pointer is NOT pointing at the return address. The function will fail when it's ready to return because it finds that our prologue code is where it's return address should be.
Luckily we can hardcode our own epilogue code because it will not change from hook to hook (we only have one hook function, remember?)
Here's how my compiler implements the prologue code:
Code:
10001049 55 PUSH EBP
1000104A 8BEC MOV EBP,ESP
push ebx
push esi
push edi
My epilogue code looks like this:
Code:
pop edi
pop esi
pop ebx
100010A2 8BE5 MOV ESP,EBP
100010A4 5D POP EBP
You'll have to find how your compiler implements pro/epi code but this is how it generally looks.
If you have any questions about this section, you should try playing around in a debugger a bit with epilogue/prologue code before asking, because I have the feeling a lot of people won't get this.
Now that we know what to do with the prologue and epilogue code, lets get back to the source codes!!
The final step
hook:
Just c+p this over the code that is in main.cpp previously
The green code is specific to my environment, yours may differ. Instructions on how to find your won epilogue code can be found in the "Reentry" section
Code:
#include <windows.h>
#include <iostream>
#include <string.h>
void MainThread();
DWORD* gPrologPointer; // this pointer will point at the prologue code of Sleep()
BOOL APIENTRY DllMain( HANDLE hModule, DWORD fdwReason, LPVOID lpReserved ){ // main() function inside the dll
if( fdwReason == DLL_PROCESS_ATTACH){
CreateThread(0, 0, (LPTHREAD_START_ROUTINE)&MainThread, NULL, NULL, NULL);
return TRUE;
}
return TRUE;
}
void __stdcall SleepHook(DWORD Timout){ // our hook
MessageBox(NULL, "Not sleeping!", "SCHiM", MB_OK); // display a messagebox instead of sleeping
Timout = 5000; // make it sleep longer 5 seconds instead of 1
__asm{
pop edi
pop esi
pop ebx
mov esp, ebp // MY epilogue code, to match MY prologue code, note that this is suited to MY environment, yours may differ
pop ebp
mov eax, gPrologPointer
jmp eax // jump to Sleep's prologue code
}
return;
} // ret
void DoHook(DWORD* Address, DWORD* Hook){
/*
This time it's not so easy as in part 1 where we could just replace one pointer.
Now we need to replace 5 bytes with our jump code, E9 ** ** ** ** (where ** = offset bytes)
Luckely can just place them at the start of the Sleep() function so that we're sure they get executed
To do that we first need to change the memory protection so that we can write to it.
We also need to save the prologue code at the start of the Sleep() function and execute them after our own Epilogue code.
Luckily I've done all this for you, and the only thing you have to do is C+P :p
*/
DWORD OldProt;
DWORD* OffsetPointer;
char* CharPointer;
gPrologPointer = (DWORD*) new char[10]; // we only need 10 bytes, sizeof(char) = 1
CharPointer = (char*) gPrologPointer; // size control again
memcpy((void*)CharPointer, (void*)Address, 5); // copy the porlogue code of sleep to our buffer
CharPointer[5] = '\xE9'; // this code will jump from the prologue code to the Sleep() function
CharPointer += 6; // advance the pointer
OffsetPointer = (DWORD*)CharPointer; // Offsetpointer now points at where to offset bytes need to come
DWORD JumpTwoOffset = (DWORD)Address-(DWORD)OffsetPointer+1; // no -5 this time, because we want to skip the jump on the other end( jump back into the sleep() not in an endless loop)
*OffsetPointer = JumpTwoOffset; // fix this jump
VirtualProtect((void*)gPrologPointer, 40, 0x40, &OldProt); // we need to be able to execute the prologue code
// -5 is there because the offset at the jump instruction is relative to the address of the jump instruction, so the 5 bytes (length of the jump instruction) must be substracted
DWORD HookOffset = (DWORD)Hook-(DWORD)Address-5; // calculate the offset bytes and store the result into hookoffset
VirtualProtect((void*) Address, 40, 0x40, &OldProt); // change the memory protection and give us write access.
CharPointer = (char*) Address; // sizeof(char) == 1, 0E9h == 1, we need a char for the size
*CharPointer = '\xE9'; // *CharPointer(Sleep()) now equals E9h (the jump opcode)
// we now need to place the operands
CharPointer++; // the size problem again, incrementing Address (Address++) would result in the pointer advancing 4 bytes
Address = (DWORD*)CharPointer; // Advance the pointer with 1 byte to where the offset bytes need to come
*Address = HookOffset; // the correct offset
VirtualProtect((void*) Address, 40, 0x40, &OldProt); // change the memory protection back to the old values (it doesn't really matter if we do this or not, but it's just proper)
return; // the hook is set!
}
void MainThread(){ // our main function to work with
DWORD* AddressOfSleep = (DWORD*) GetProcAddress(GetModuleHandle("Kernel32.dll"), "Sleep"); // get the address of the Sleep() function through the address of kernel32.dll
if( AddressOfSleep == NULL){ // oops
MessageBox(NULL, "Could not find the address of Sleep()!", "SCHiM", MB_OK);
return;
}
DoHook( AddressOfSleep, (DWORD*)&SleepHook); // hook it! >;)
return;
}
Overview
Because this is a daunting scheme I'll post an overview of what's going on here:
This hasty scrawling of mine should be enough to show you what we're doing here.
Now to address a question that will be poping up in some of you:
You may have noticed some hackers doing things like this:
Code:
Hook(foo, bar){
foobar = foo;
bar();
...
...
...
Return oFoo(foo, bar);
}
What they are doing here is this: They are calling the original (the real) function in the hooked function, and return the return value to the caller.
This may look simpler and cleaner too you and it probably is, however it's
not better because this is more easily detected then our function (which cleans the stack as though it has never been there). If the AC developers have even a slight idea of what they are doing they can check if the all came from the right function (by using the return address). If they see your hook's return address: you're caught!
Ca is not that good though, so can try this, I knew a few games were this was detected from day 1 however.
Any questions can be asked as long as they are not too stupid
-SCHiM