常用的Linux Hooking技术总结

渗透技巧 11个月前 admin
228 0 0
01
钩子函数

在程序中预定义好的钩子,在需要的时候,将 hook 函数挂接或者注册(register)到钩子里,使得对目标可用。
如果没有钩子,也可以获取函数指针,对函数进行封装,但视乎只能挂接在函数运行前后,不能挂接在函数运行中。

02
使用钩子挂接

可以参考 GNU C Library 设计模式,设置 hook 指针,当检测 hook 指针不为 null 时,跳转执行 hook 指针指向得到函数。
下面例子是函数入口点钩子
void *
__libc_malloc (size_t bytes)
{
  mstate ar_ptr;
  void *victim;

  void *(*hook) (size_tconst void *)
    = atomic_forced_read (__malloc_hook);
  if (__builtin_expect (hook != NULL0))
    return (*hook)(bytes, RETURN_ADDRESS (0));

  arena_get (ar_ptr, bytes);

  victim = _int_malloc (ar_ptr, bytes);
……
下面是钩子函数例子,在 myPrintf 函数内部设计有钩子 __myPrintf_hook ,通过钩子挂接 myPrintfPlus :
#include <stdio.h>
#include <libio.h>

// myPrintf 函数 hook 标记
void (*__myPrintf_hook)(charstring) = NULL;


void myPrintf(charstring){
    // 判断 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;
}


03
使用函数指针封装

#include <stdio.h>
#include <libio.h>

void myPrintf(charstring){
 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;
}


04
动态链接库劫持

LD_PRELOAD 环境变量

LD_PRELOAD :优先加载特定函数库,后面加载函数库内同名函数不会覆盖前面的。指定函数库无论是否被调用,都会被加载。
LD_LIBRARY_PATH :优先检索函数库的路径
LD_DEBUG :glibc 为了自身调试的环境变量。通过设置这个环境变量,输出 loader 的加载过程。
优先级别:LD_PRELOAD > LD_LIBRARY_PATH > /etc/ld.so.cache > /lib > /usr/lib
重写程序运行时调用的函数,编译为动态链接库文件,通过 LD_xx 环境变量注入程序。

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 :函数地址加载模式
用于测试程序 main.c :
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
 
//sleep(10);
  ifstrcmp(argv[1], "test") )
  {
    printf("Incorrect passwordn");
  }
  else
  {
    printf("Correct passwordn");
  }
  return 0;
}

0x02 GCC 特定语法

使用上面劫持方法对函数库单个函数的前调、后调、返回值进行处理,如果需要实现任意(或不指定)函数的劫持操作,可以使用 GCC 特定语法,比如:__attribute__ 
#include <stdio.h>

__attribute__((constructor)) static void setup(void) {
  fprintf(stderr"hook libc is setup.n");
}
__attribute__((constructor)) 标记的函数会在函数库加载时就运行,且会先于程序。

0x03 持久化注入

通过 LD_PRELOAD 是对线程进入注入,程序 fork() 得到子线程也会被注入
使用 LD_PRELOAD 进程注入需要写入环境变量后,重启程序或者开启新进程
根据实际情况需要在 so 源码取消环境变量。将恶意函数库写入目标,需要触发后门时 setenv 写入环境变量,恶意函数库调用完毕后 unsetenv ,提高隐蔽度&避免其他线程调用出现异常:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

__attribute__ ((__constructor__)) void setup (void){
    unsetenv("LD_PRELOAD");
    ……//exploit
}
修改 .bashrc、.zshrc、profile 等等终端配置,将 LD_PRELOAD 写入:
export LD_PRELOAD=/path/to/your/libhook.so
修改这种方式可以直接注入系统各个线程。

eBPF

BPF 提供了一种在内核事件和用户程序事件发生时安全注入代码的机制。编写 bpf 程序注入内核,从内核空间对指定函数进行 hook 或者监控。
eBPF 开发工具:BCC、bpftrace、libbpf ,其中 BCC 提供大量可用小工具 demo 。
劫持监控 open 系统调用,可以查看 BCC 源码库: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()
       

原文始发于微信公众号(山石网科安全技术研究院):常用的Linux Hooking技术总结

版权声明:admin 发表于 2023年6月15日 上午10:35。
转载请注明:常用的Linux Hooking技术总结 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...