06 - PE Hooking

The objective of this tutorial is show how we can hook imported functions

Scripts and materials are available here: materials

By Romain Thomas - @rh0main


The targeted binary is a simple PE64 HelloWorld which prints the first argument on in the console:

#include "stdafx.h"
#include <stdio.h>

int main(int argc, char** argv) {
  printf("Hello: %s\n", argv[1]);
  return 0;
}
$ PE64_x86-64_binary_HelloWorld.exe World
$ Hello: World

Using LIEF, we will replace the function that prints the message in the console with a MessageBox

By disassembling the binary we can see that the print occurs in the function sub_140001030 and it uses two external functions: __acrt_iob_func and __stdio_common_vfprintf.

../_images/06_hooking_1.png
../_images/06_hooking_2.png

Due to the Microsoft x64 calling convention, the format is located in the rcx and the input message in the rdx register.

Basically the hooking code replaces the __acrt_iob_func function and shows a MessageBox with the rdx message.

hooking code
add rsp, 0x48         ; Stack unwind
xor rcx, rcx          ; hWnd
mov rdx, rdx          ; Message
mov r8,  0x0140009000 ; Title
xor r9, r9            ; MB_OK
mov rax, 0x014000A3E4 ; MessageBoxA address
call [rax]            ; MessageBoxA(hWnd, Message, Title, MB_OK)
xor rcx, rcx          ; exit value
mov rax, 0x014000A3d4 ; ExitProcess address
call [rax]            ; ExitProcess(0)
ret                   ; Never reached

Note

As for tutorial 02 - Create a PE from scratch, the address of MessageBoxA and ExitProcess can be found with the function:

Binary.predict_function_rva(self: _pylief.PE.Binary, library: str, function: str) → int

Try to predict the RVA of the given function name in the given import library name

First we create the .htext section which will hold the hooking code:

section_text                 = lief.PE.Section(".htext")
section_text.content         = code
section_text.virtual_address = 0x7000
section_text.characteristics = lief.PE.SECTION_CHARACTERISTICS.CNT_CODE | lief.PE.SECTION_CHARACTERISTICS.MEM_READ | lief.PE.SECTION_CHARACTERISTICS.MEM_EXECUTE

section_text = pe.add_section(section_text)

Then the .hdata section for the MessageBox title:

title   = "LIEF is awesome\0"
data =  list(map(ord, title))

section_data                 = lief.PE.Section(".hdata")
section_data.content         = data
section_data.virtual_address = 0x8000
section_data.characteristics = lief.PE.SECTION_CHARACTERISTICS.CNT_INITIALIZED_DATA | lief.PE.SECTION_CHARACTERISTICS.MEM_READ

section_data = pe.add_section(section_data)

As the ASLR is enabled we will disable it to avoid to deal with relocations:

binary.optional_header.dll_characteristics &= ~lief.PE.DLL_CHARACTERISTICS.DYNAMIC_BASE

We will also disable the NX protection:

binary.optional_header.dll_characteristics &= ~lief.PE.DLL_CHARACTERISTICS.NX_COMPAT

As ExitProcess is not imported in KERNEL32.dll we need to add it:

kernel32 = binary.get_import("KERNEL32.dll")
kernel32.add_entry("ExitProcess")

The MessageBoxA function is located in the user32.dll thus we have to add it:

user32 = binary.add_library("user32.dll")
user32.add_entry("MessageBoxA")

Then we proceed to the hook of the __acrt_iob_func function:

pe.hook_function("__acrt_iob_func", binary.optional_header.imagebase + section_text.virtual_address)

And finally we configure the Builder to create a new import table and to patch the original one with trampolines.

builder = lief.PE.Builder(binary)

builder.build_imports(True).patch_imports(True)

builder.build()

builder.write("lief_pe_hooking.exe")

Now we can run the final executable:

$ lief_pe_hooking.exe "Hooking World"
../_images/06_hooking_3.png