I was describing a method of functions call interception by means of an import table in one of my previous articles. This method is more universal, since it gives an opportunity to intercept almost any calls (please see the limitations list below). However, this one is more complicated, since header modification code needs disassembling skills function, as I will show below.
The method’s essence is in setting JMP or CALL instruction in the first few bytes (5 bytes, if one sets an instruction for a jump or a call with 32-bytes offset relative to EIP) of a function header with an address of hook function.
One can see the method limitations right away. A modified function (or a function plus alignment bytes after it, if available) should be longer than the code that is being set. Otherwise the code of the next function will be spoiled, which can lead to the app crash.
The original function:
004013E0 55 push ebp
004013E1 8B EC mov ebp,esp
004013E3 81 EC F4 00 00 00 sub esp,0F4h
004013E9 53 push ebx
004013EA 56 push esi
004013E0 E9 xx xx xx xx jmp HookFunction
004013E5 F4 00 00 00 db 0F4h, 0, 0, 0 ; Bytes that are left from
; a partially rewritten
; instruction "sub esp,0F4h"
004013E9 53 push ebx
004013EA 56 push esi
There is no problem if one does not need to call the original function. It’s just necessary to prepare an array for jump instructions bytes, and then write the path for them in the function header, using the method that I described in my article “Self-modifying programs”. All this is much more complicated if it is necessary to call the original function…
Why is that? Because if we are overwriting the first bytes of the function header, it is necessary to create a call valve for a correct call, which doesn’t lead to application crash. The call valve is a copy of the first header instructions of the original function that are to be overwritten, and then a command to jump to the first unspoiled header instruction.
XXXXXXX0 55 push ebp
XXXXXXX1 8B EC mov ebp,esp
XXXXXXX3 81 EC F4 00 00 00 sub esp,0F4h
XXXXXXX9 E9 xx xx xx xx jmp 4013E9h
Call for the original handler:
PFNORIFN pfn = (PFNORIFN)0xXXXXXXX0; // Call valve address
If the first header code’s bytes contain jump/branch instruction(s) relative to EIP, the offsets have to be recalculated. One should need to convert the instruction into a long jump command if a short jump command was used.
The original function with jump commands in the header:
77f6054e 39 c8 cmp eax,ecx
77f60550 74 02 je 77f60554h
77f60552 50 push eax
77f60553 52 push edx
77f60554 33 c0 xor eax,eax
Call valve in this case:
XXXXXXX0 39 c8 cmp eax,ecx
XXXXXXX2 8B EC je XXXXXXXA
XXXXXXX4 50 push eax
XXXXXXX5 E9 xx xx xx xx jmp 77f60553h
XXXXXXXA E9 xx xx xx xx jmp 77f60554h
As one can see in from the above examples, creation of a call valve needs function header disassembling “on the fly” and particular assembler skills for recalculation of offsets and creation of long jumps. This is definitely necessary if one doesn’t rely on some constant header kind. Assuming that header content is fixed can be dangerous. The code will stop working sooner or later.
Save and restore registers values
The author of this article wrote an interception code for Windows 98 and Windows ME kernel drivers functions calls by means of this method. I guess some of you still remember such operation systems and possibly know that drivers for them were written in Assembler and parameters were transferred in registers. I remind you about this in order to draw your attention to the fact that if an interception function is written in a high-level language, the original function should be called and the original function is sensitive to processor flags and to registers content, which are considered to be “rubbish” by a chosen high-level language, and then all the above is not sufficient for effective work. It is also necessary to save registers and flags at the entrance to a new handler, to be able to set them according to the required values before calling the original function and also before returning to the calling code.
mov eax, 1
call IntercepringFunc // Calling the function that we intercept
save registers and flags in some structure
change values in this structure if necessary
call the original function via extended call valve(CallValveEx)
restore registers and flags
return to the calling function
restore registers and flags from the structure data
call the original function
renew values of registers and flags in the data
return to HookFunction
The full-featured version of the library for calls interception with a built-in disassembler to support arbitrary function headers is available for download via this website. The library supports both user-level applications and system kernel drivers.
The application sample that is attached to this article uses a simplified version of header modification method, which can work only with a limited number of instructions.