Warning
This tutorial is no longer working as the PE hooking functions has been removed from LIEF.
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 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
.
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.
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:
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"