Functions call interception via replacement of header bytes by JMP or CALL instructions

function call interception by modyfying function headerI 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

 After modification:
  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.

 Call valve:
  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:
  HookFunction(...)
  {
    ...
    PFNORIFN pfn = (PFNORIFN)0xXXXXXXX0; // Call valve address
    pfn(...);
    ...
  }

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
    ...

IntercepringFunc:
    jmp     HookFunction
    ...

HookFunction:
    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

CallValveEx:
    restore registers and flags from the structure data
    call the original function 
    renew values of registers and flags in the data
    return to HookFunction

Conclusion

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.

Leave a Reply