Native Hooks in Python
Posted on Sat 30 June 2018 in Python
Sometimes it can be helpful to alter the functionality of a native program. This is often achieved through function hooks. These hooks come in a few different flavors depending on how they are installed. For this post we'll be focusing on "inline" hooks and describe how they can be installed to replace a native function (likely written in C) with a Python implementation. The steps to achieve this are as follows:
- Identify / resolve the address of the original function. This is where the hook will be placed.
- Write a prototype of the function using Python's ctypes module including the return and argument types.
- Write a replacement function in Python.
- Apply the prototype (from step #2) to the replacement function and cast it to a void * to obtain it's address.
- Install a hook to jump to the address (from step #4) in the original function (from step #1).
For this tutorial, we'll install a simple hook to replace the user32!GetClipboardData with a Python implementation that will simply display a message box. This function is ideal because it can be triggered on demand by a user by trying to paste (Ctrl+V) into the process's window. Since Python and the hook will need to exist in the target process, we'll be using the Mayhem project's Python Injector tool to run the script in a target process.
An inline hook is installed by overwriting the first few instructions of the target function with a long jump. These first instructions that are overwritten are often the function's preamble. In 32-bit Windows it's often:
1 2 3
mov edi, edi ; 2-byte NOP push ebp ; save ebp on the stack mov ebp, esp ; copy esp into ebp
The first instruction mov edi, edi is a NOP (no operation) instruction used to reserve two bytes for hot patching. The next two backup ebp before moving esp into it for referencing arguments passed on the stack.
Python Hook Logic*
Python's ctypes module can be used to define function prototypes in a few ways. Like in C, a function's prototype is a definition of a function's arguments, return type, and calling convention. With the ctypes module, once a function prototype has been defined it is a callable object. This callable prototype object can then be called with a few different things including integers, library exports, and Python functions. Integers and library exports are used to access native functions from within Python while the last can be used to make a Python function available in a native function. This is intended for defining Python functions that can be used as callbacks for native functions.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
# define the prototype for user32!GetClipboardData pro_GetClipboardData = ctypes.WINFUNCTYPE( ctypes.wintypes.HANDLE, # return type ctypes.wintypes.UINT # argument type ) # define the Python implementation def py_GetClipboardData(uFormat): # display a message box for demonstration purposes ctypes.windll.user32.MessageBoxW( None, 'Hello World!', 'Hello World', 0 ) # the return type is HANDLE return None # apply the prototype to the Python implementation nat_GetClipboardData = pro_GetClipboardData(py_GetClipboardData) address = ctypemayhems.cast(nat_GetClipboardData, ctypes.c_void_p).value
Installing The Hook*
Inline hooks are relatively simple to install in comparison to table based hooks such as Import Address Table (IAT) hooks that require a lot of parsing logic. An inline hook only requires the installer to know the location of the function, which in the case of user32!GetClipboardData is as simple as resolving it using the traditional kernel32!GetModuleHandle / kernel32!GetProcAddress combination.
Once the address is known a push eax followed by a jmp eax instruction can be written over the functions preamble to call the hook. The EAX register is ideal for use for the following reasons:
- EAX is not used to pass any arguments in any of the common calling conventions.
- EAX is considered volatile and will eventually be modified by the function to pass the return value back to the caller.
The reason we want to jump instead of calling the hook is that a jump instruction will not modify the stack. This allows the hook to simply return to the caller directly. In this scenario the original function which is hooked is never fully executed, and it's functionality is effectively replaced. While inline hooks are easy to install, for this reason they are not particularly well suited for scenarios where the user may want to alter the original functionality.
It's simple to assembly a single instruction to move a DWORD (for x86) or QWORD (for x64) into a specific register. In Python, use the struct module to pack the address value in little-endian order and concatenate it with the other assembled instruction bytes.
1 2 3 4
def assemble_hook(address): hook = '\xb8' + struct.pack('I', address) # mov eax, address hook += b'\xff\xe0' # jmp eax return hook
Testing The Hook*
Once the hook has been installed, any call to the user32!GetClipboardData function will execute the replacement stub and jump to the replacement function (the Python implementation). The return value of the replacement function is placed in RAX / EAX as defined by the calling conventions and the stack maintains it's alignment allowing it to return directly to the caller.
For testing the user32!GetClipboardData function the user can attempt to paste data into the target application using Ctrl+V. Once the paste keyboard combination is used, the replacement function should display a window and no data should be pasted since the original function was replaced.
A complete example of an injectable script is available in the examples directory of the Mayhem project.