The objective of this tutorial is to hook an imported function in an ELF binary.
Scripts and materials are available here: materials
By Romain Thomas - @rh0main
Hooking imported functions by infecting the .got
section is a well-known technique [1] [2] and this tutorial will be focused on its implementation using LIEF.
These figures illustrate the plt/got
mechanism:
Basically the infection is done in two steps:
Firstly, we inject our hook
Secondly, we redirect the targeted function to our hook by patching the
got
It can be summed up by the following figure:
As example, we will use a basic crackme which performs a memcmp(3) on the flag and user’s input.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Damn_YoU_Got_The_Flag
char password[] = "\x18\x3d\x31\x32\x03\x05\x33\x09\x03\x1b\x33\x28\x03\x08\x34\x39\x03\x1a\x30\x3d\x3b";
inline int check(char* input);
int check(char* input) {
for (int i = 0; i < sizeof(password) - 1; ++i) {
password[i] ^= 0x5c;
}
return memcmp(password, input, sizeof(password) - 1);
}
int main(int argc, char **argv) {
if (argc != 2) {
printf("Usage: %s <password>\n", argv[0]);
return EXIT_FAILURE;
}
if (strlen(argv[1]) == (sizeof(password) - 1) && check(argv[1]) == 0) {
puts("You got it !!");
return EXIT_SUCCESS;
}
puts("Wrong");
return EXIT_FAILURE;
}
The flag is xored with 0x5C
. To validate the crackme, the user has to enter Damn_YoU_Got_The_Flag
:
$ crackme.bin foo
Wrong
$ crackme.bin Damn_YoU_Got_The_Flag
You got it !!
The hook will consist in printing arguments of memcmp
and returning 0
:
#include "arch/x86_64/syscall.c"
#define stdout 1
int my_memcmp(const void* lhs, const void* rhs, int n) {
const char msg[] = "Hook memcmp\n";
_write(stdout, msg, sizeof(msg));
_write(stdout, (const char*)lhs, n);
_write(stdout, "\n", 2);
_write(stdout, (const char*)rhs, n);
_write(stdout, "\n", 2);
return 0;
}
As the hook is going to be injected into the crackme, it must have the following requirements:
Assembly code must be position independant (compiled with -fPIC
or -pie/-fPIE
flags)
Don’t use external libraries such as libc.so
(-nostdlib -nodefaultlibs
flags)
Due to the requirements, the hook is compiled with: The first step is to inject the hook into the binary. To do so we will add a All assembly code of the hook stands in the first Once the hook added, its virtual address is LIEF provides a function to easily patch the Overloaded function. Patch the imported symbol’s name with the Patch the imported The offset of the Finally we can patch the And rebuild it: As a check on the input size is performed before checking the flag value, we have to provide an input with the correct length (no matter its content): Referencesgcc -nostdlib -nodefaultlibs -fPIC -Wl,-shared hook.c -o hook
.Injecting the hook¶
Segment
:import lief
crackme = lief.parse("crackme.bin")
hook = lief.parse("hook")
segment_added = crackme.add(hook.segments[0])
LOAD
segment of hook
.virtual_address
of segment_added
and we can processed to the got
patching.Patching the
got
¶got
entry associated with a Symbol
:patch_pltgot(self, symbol_name: str, address: int) -> None
address
patch_pltgot(self, symbol: lief._lief.ELF.Symbol, address: int) -> None
Symbol
with the address
memcmp
function is stored in the value
attribute of the associated dynamic symbol. Thus its virtual address will be:my_memcpy
: value
+ segment_added.virtual_address
my_memcmp = hook.get_symbol("my_memcmp")
my_memcmp_addr = segment_added.virtual_address + my_memcmp.value
memcmp
from the crakme with this value:crackme.patch_pltgot('memcmp', my_memcmp_addr)
crackme.write("crackme.hooked")
Run¶
$ crackme.hooked XXXXXXXXXXXXXXXXXXXXX
Hook add
Damn_YoU_Got_The_Flag
XXXXXXXXXXXXXXXXXXXXX
You got it !!