下面例子是函数入口点钩子
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);
……
__myPrintf_hook
,通过钩子挂接 myPrintfPlus :#include <stdio.h>
#include <libio.h>
// myPrintf 函数 hook 标记
void (*__myPrintf_hook)(char* string) = NULL;
void myPrintf(char* string){
// 判断 hook 标记不为 NULL 则跳转执行 hook 函数
void (*hook)(char*) = __myPrintf_hook;
if (hook != NULL){
return hook(string);
}
printf("%s:t", "NoHook");
printf("%sn", string);
}
void myPrintfPlus(char *string){
printf("%s:t", "Hook");
printf("%sn", string);
}
int main(int argc, char const *argv[])
{
// 正常调用
myPrintf("test");
// 使用 hook
__myPrintf_hook = myPrintfPlus;
myPrintf("test");
return 0;
}
#include <stdio.h>
#include <libio.h>
void myPrintf(char* string){
printf("%s:t", "NoHook");
printf("%sn", string);
}
void myPrintfPlus(char *string){
printf("%s:t", "Hook");
printf("%sn", string);
}
int main(int argc, char const *argv[])
{
void (*myPrintf)(char*) = myPrintfPlus;
myPrintf("test");
return 0;
}
LD_PRELOAD 环境变量
LD_PRELOAD
:优先加载特定函数库,后面加载函数库内同名函数不会覆盖前面的。指定函数库无论是否被调用,都会被加载。LD_LIBRARY_PATH
:优先检索函数库的路径LD_DEBUG
:glibc 为了自身调试的环境变量。通过设置这个环境变量,输出 loader 的加载过程。0x00 获取程序调用的函数
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]
……
man strcmp
查询,或者看 glibc 源码:#ifndef STRCMP
# define STRCMP strcmp
#endif
int STRCMP (const char *p1, const char *p2){……}
0x01 重写函数
//gcc -fPIC -shared -o libmyhook.so myhook.c -ldl
#include <stdio.h>
#include <dlfcn.h>
/*
hook的目标是strcmp,所以typedef了一个STRCMP函数指针
hook的目的是要控制函数行为,从原库libc.so.6中拿到strcmp指针,保存成old_strcmp以备调用
*/
//定义 strcmp 函数原型指针类型
typedef int(*STRCMP)(const char*, const char*);
//重写 strcmp 函数内部逻辑
int strcmp(const char *s1, const char *s2)
{
//从原运行库解析原型函数指针
static void *handle = NULL;
static STRCMP old_strcmp = NULL;
if( !handle )
{
//当库被装入后,返回的句柄作为给 dlsym() 的第一个参数,以获得符号在库中的地址
handle = dlopen("libc.so.6", RTLD_LAZY);
old_strcmp = (STRCMP)dlsym(handle, "strcmp");
}
//自定义操作
printf("oops!!! hack function invoked. s1=<%s> s2=<%s>n", s1, s2);
//运行原函数
return old_strcmp(s1, s2);
}
-
void* dlopen(const char* libfile,int flag);
-
libfile :函数库 -
flag :函数地址加载模式
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
//sleep(10);
if( strcmp(argv[1], "test") )
{
printf("Incorrect passwordn");
}
else
{
printf("Correct passwordn");
}
return 0;
}
0x02 GCC 特定语法
__attribute__
#include <stdio.h>
__attribute__((constructor)) static void setup(void) {
fprintf(stderr, "hook libc is setup.n");
}
__attribute__((constructor))
标记的函数会在函数库加载时就运行,且会先于程序。0x03 持久化注入
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
__attribute__ ((__constructor__)) void setup (void){
unsetenv("LD_PRELOAD");
……//exploit
}
export LD_PRELOAD=/path/to/your/libhook.so
eBPF
#!/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()
原文始发于微信公众号(山石网科安全技术研究院):常用的Linux Hooking技术总结