01Hook FunctionsHook functions are predefined hooks in a program that allow you to attach or register a hook function when needed, making it available for the target.If there are no hooks, you can also obtain function pointers to encapsulate functions, but this can only be attached before or after the function runs, not during its execution.02Using Hook AttachmentsYou can refer to the design pattern of the GNU C Library, setting a hook pointer. When the hook pointer is detected to be non-null, it jumps to execute the function pointed to by the hook pointer.
The following example is a function entry point hook
void *
__libc_malloc (size_t bytes)
{
mstate ar_ptr;
void *victim;
void *(*hook) (size_t, const void *)
= atomic_forced_read (__malloc_hook);
if (__builtin_expect (hook != NULL, 0))
return (*hook)(bytes, RETURN_ADDRESS (0));
arena_get (ar_ptr, bytes);
victim = _int_malloc (ar_ptr, bytes);
……
The following is an example of a hook function, designed with a hook inside the myPrintf function <span><span>__myPrintf_hook</span></span>, which attaches myPrintfPlus through the hook:
#include <stdio.h>
#include <libio.h>
// myPrintf function hook marker
void (*__myPrintf_hook)(char* string) = NULL;
void myPrintf(char* string){
// Check if hook marker is not NULL, then jump to execute hook function
void (*hook)(char*) = __myPrintf_hook;
if (hook != NULL){
return hook(string);
}
printf("%s:\t", "NoHook");
printf("%s\n", string);
}
void myPrintfPlus(char *string){
printf("%s:\t", "Hook");
printf("%s\n", string);
}
int main(int argc, char const *argv[])
{
// Normal call
myPrintf("test");
// Using hook
__myPrintf_hook = myPrintfPlus;
myPrintf("test");
return 0;
}
03Using Function Pointer Encapsulation
#include <stdio.h>
#include <libio.h>
void myPrintf(char* string){
printf("%s:\t", "NoHook");
printf("%s\n", string);
}
void myPrintfPlus(char *string){
printf("%s:\t", "Hook");
printf("%s\n", string);
}
int main(int argc, char const *argv[])
{
void (*myPrintf)(char*) = myPrintfPlus;
myPrintf("test");
return 0;
}
04Dynamic Link Library Hijacking
LD_PRELOAD Environment Variable
<span><span>LD_PRELOAD</span></span>: Prioritizes loading specific function libraries, and later loaded functions with the same name will not overwrite the previous ones.The specified function library will be loaded regardless of whether it is called.<span><span>LD_LIBRARY_PATH</span></span>: Prioritizes searching for the path of function libraries<span><span>LD_DEBUG</span></span>: An environment variable for glibc’s own debugging. By setting this environment variable, the loading process of the loader is output.Priority: LD_PRELOAD > LD_LIBRARY_PATH > /etc/ld.so.cache > /lib > /usr/libRewrite functions called at runtime, compiled into dynamic library files, and injected into the program through LD_xx environment variables.
0x00 Obtaining Functions Called by the Program
Obtain the functions called by the program at runtime and select suitable functions for rewriting and injection. There are many methods to obtain this, here is one:
LD_DEBUG=all ./your_program
……
22507: symbol=strcmp; lookup in file=./app [0]
22507: symbol=strcmp; lookup in file=/lib/x86_64-linux-gnu/libc.so.6 [0]
……
Then obtain the function prototype for rewriting, which can be queried through <span><span>man strcmp</span></span><span><span>, or look at the glibc source code:</span></span><pre><code class="language-c">#ifndef STRCMP
# define STRCMP strcmp
#endif
int STRCMP (const char *p1, const char *p2){……}
0x01 Rewriting Functions
//gcc -fPIC -shared -o libmyhook.so myhook.c -ldl
#include <stdio.h>
#include <dlfcn.h>
/*
hook's target is strcmp, so typedef a STRCMP function pointer
The purpose of the hook is to control function behavior, obtaining the strcmp pointer from the original library libc.so.6, saving it as old_strcmp for later calls
*/
// Define strcmp function prototype pointer type
typedef int(*STRCMP)(const char*, const char*);
// Rewrite strcmp function internal logic
int strcmp(const char *s1, const char *s2)
{
// Parse original prototype function pointer from the original runtime library
static void *handle = NULL;
static STRCMP old_strcmp = NULL;
if( !handle )
{
// When the library is loaded, the returned handle is used as the first parameter for dlsym() to obtain the symbol's address in the library
handle = dlopen("libc.so.6", RTLD_LAZY);
old_strcmp = (STRCMP)dlsym(handle, "strcmp");
}
// Custom operation
printf("oops!!! hack function invoked. s1=<%s> s2=<%s>\n", s1, s2);
// Run the original function
return old_strcmp(s1, s2);
}
<span><span>void* dlopen(const char* libfile,int flag);</span></span>- libfile: function library
- flag: function address loading mode
For testing program main.c:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
//sleep(10);
if( strcmp(argv[1], "test") )
{
printf("Incorrect password\n");
}
else
{
printf("Correct password\n");
}
return 0;
}
0x02 GCC Specific Syntax
Using the above hijacking method to handle pre-call, post-call, and return values of a single function in the function library,if you need to implement hijacking operations for any (or unspecified) function, you can use GCC specific syntax, such as:<span>__attribute__ </span>
#include <stdio.h>
__attribute__((constructor)) static void setup(void) {
fprintf(stderr, "hook libc is setup.\n");
}
<span><span>__attribute__((constructor))</span></span> marked functions will run when the library is loaded, and will run before the program.
0x03 Persistent Injection
Using LD_PRELOAD is for thread entry injection, and the child thread obtained by the program fork() will also be injected.Using LD_PRELOAD process injection requires writing to the environment variable, then restarting the program orstarting a new process.Depending on the actual situation, it is necessary to cancel the environment variable in the so source code. To write the malicious function library into the target, it is necessary to trigger the backdoor when setenv writes the environment variable, and after the malicious function library is called, unsetenv is used to improve concealment & avoid exceptions in other thread calls:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
__attribute__ ((__constructor__)) void setup (void){
unsetenv("LD_PRELOAD");
……//exploit
}
Modify .bashrc, .zshrc, profile, etc. terminal configurations to write LD_PRELOAD:
export LD_PRELOAD=/path/to/your/libhook.so
This modification can directly inject into various threads of the system.
eBPF
BPF provides a mechanism for safely injecting code when kernel events and user program events occur. Write bpf programs to inject into the kernel, hooking or monitoring specified functions from kernel space.eBPF development tools: BCC, bpftrace, libbpf, among which BCC provides a large number of available small tool demos.Hijacking and monitoring the open system call can view the BCC source library: https://github.com/iovisor/bcc/blob/master/tools/opensnoop.py.
#!/usr/bin/python
#
# This is a Hello World example that uses BPF_PERF_OUTPUT.
from bcc import BPF
from bcc.utils import printb
# define BPF program
prog = """
#include <linux/sched.h>
// define output data structure in C
struct data_t {
u32 pid;
u64 ts;
char comm[TASK_COMM_LEN];
};
BPF_PERF_OUTPUT(events);
int hello(struct pt_regs *ctx) {
struct data_t data = {};
data.pid = bpf_get_current_pid_tgid();
data.ts = bpf_ktime_get_ns();
bpf_get_current_comm(&data.comm, sizeof(data.comm));
events.perf_submit(ctx, &data, sizeof(data));
return 0;
}
"""
# load BPF program
b = BPF(text=prog)
b.attach_kprobe(event=b.get_syscall_fnname("open"), fn_name="hello")
# header
print("%-18s %-16s %-6s %s" % ("TIME(s)", "COMM", "PID", "MESSAGE"))
# process event
start = 0
def print_event(cpu, data, size):
global start
event = b["events"].event(data)
if start == 0:
start = event.ts
time_s = (float(event.ts - start)) / 1000000000
printb(b"%-18.9f %-16s %-6d %s" % (time_s, event.comm, event.pid,
b"Hello, perf_output!"))
# loop with callback to print_event
b["events"].open_perf_buffer(print_event)
while 1:
try:
b.perf_buffer_poll()
except KeyboardInterrupt:
exit()