Linux hook函数简单实践

#起因
最近对hook函数比较感兴趣,希望能捕获所有某种系统调用,看网上有一篇文章(http://opensourceforu.com/2011/08/lets-hook-a-library-function/) 介绍hook malloc函数,但可能内核实现不同,4.4.0上有问题。特写一篇文章进行更新。

原文中,hook函数中调用printf,其实printf、fprintf等函数都会再调用malloc函数,导致segment fault。正确的方式应该是用snprintf写入内存,然后调用write函数输出。同理,如果在hook open函数的时候,调用了open函数,也会造成segment fault的问题。

#示例
a.c:

#include<stdio.h>
#include<malloc.h>
#include<stdlib.h>
int main(void)
{
int p;
printf("calling from main...\n");
p=(int *)malloc(10);
if(!p)
{
printf("Got allocation error...\n");
exit(1);
}
printf("returning to main...\n");
free(p); /
freeing memory from heap */
printf("freeing memory...\n");
return 0;
}

prog2.c

#define _GNU_SOURCE
#include <stdio.h>
#include <stdint.h>
#include <stdarg.h>
#include <string.h>
#include <dlfcn.h> /* header required for dlsym() */
#include <unistd.h>

/* lcheck() is for memory leak check; its code is not shown
here /
void lcheck(void);
void print(const char
format, ...);

void* malloc(size_t size)
{
static void* (my_malloc)(size_t) = NULL;
print("inside shared object...\n");
if (!my_malloc)
my_malloc = dlsym(RTLD_NEXT, "malloc"); /
returns the object reference for malloc /
void *p = my_malloc(size); /
call malloc() using function pointer my_malloc /
print("malloc(%lu) = %p\n", size, p);
lcheck(); /
calling do_your_stuff function */
print("returning from shared object...\n");

return p;

}

void free(void* p){
static void (my_free)(void ) = NULL;
if (!my_free)
my_free = dlsym(RTLD_NEXT, "free"); /* returns the object reference for free /
my_free(p); /
call malloc() using function pointer my_free */
print("free %p\n", p);
return;

}

int open(const char pathname, int flags, mode_t mode){
static int (
my_open)(const char *, int , mode_t ) = NULL;
if (!my_open)
my_open = dlsym(RTLD_NEXT, "open"); /* returns the object reference for open /
int res = my_open(pathname, flags, mode); /
call malloc() using function pointer my_open */
print("open %s, flags: %d, mode: %u\n", pathname, flags, mode);
return res;
}

ssize_t read(int fd, void buf, size_t count){
static int (
my_read)(int ,void*, size_t) = NULL;
if (!my_read)
my_read = dlsym(RTLD_NEXT, "read"); /* returns the object reference for open /
ssize_t size= my_read(fd, buf, count); /
call malloc() using function pointer my_open */
print("read fd %d, buf: %s, count: %u, read size: %u\n", fd, buf, count, size);
return size;
}

ssize_t recv(int sockfd, void buf, size_t len, int flags){
static int (
my_recv)(int ,void*, size_t, int) = NULL;
if (!my_recv)
my_recv = dlsym(RTLD_NEXT, "recv"); /* returns the object reference for open /
ssize_t size= my_recv(sockfd, buf, len, flags); /
call malloc() using function pointer my_open */
print("recv fd: %d, buf: %s, len: %u, flags: %d, read size: %u\n", sockfd, buf, flags, size);
return size;
}

int accept(int sockfd, struct sockaddr addr, socklen_t *addrlen){
static int (
my_accept)(int ,struct sockaddr*, socklen_t) = NULL;
if (!my_accept)
my_accept = dlsym(RTLD_NEXT, "accept"); /
returns the object reference for open /
ssize_t size= my_accept(sockfd, addr, addrlen); /
call malloc() using function pointer my_open */
print("accept fd: %d, addr: %u, len: %u, fd: %u\n", sockfd, addr, addrlen, size);
return size;
}

ssize_t write(int fd, const void buf, size_t count){
static int (
my_write)(int ,const void*, size_t) = NULL;
if (!my_write)
my_write = dlsym(RTLD_NEXT, "write");
ssize_t size= my_write(fd, buf, count);
print("write %d, buf: %s, count: %u, write size: %u\n", fd, buf, count, size);
return size;
}

void lcheck(void)
{
print("displaying memory leaks...\n");
/* do required stuff here */
}

void print(const char* format, ...){
static char buf[1024*2];
char tmp[1024];

static int logfd = -1;
if (!my_open)
    my_open = dlsym(RTLD_NEXT, "open");

if (!my_write)
    my_write = dlsym(RTLD_NEXT, "write");

if (!my_close)
    my_close = dlsym(RTLD_NEXT, "close");

if(logfd <0 || lseek(logfd, 0, SEEK_CUR)<0){
    logfd = my_open(logfile, O_RDWR | O_CREAT| O_APPEND, 600);
}


va_list argptr;
va_start(argptr, format);
vsprintf(buf, format, argptr);
va_end(argptr);
//int res = my_write(1, buf, strlen(buf));
int res = my_write(logfd, buf, strlen(buf));
if(res <0){
    perror("write error");
}

}

然后编译,运行:


gcc prog2.c -o libprog2.so -ldl -fPIC -shared
LD_PRELOAD=/home/liuwenmao/hook/libprog2.so /usr/sbin/sshd -D -p 9999

很奇怪的是sshd会有问题,root下面运行客户端会有错误:

root@node10:~/hook# LD_PRELOAD=/home/liuwenmao/hook/libprog2.so /usr/sbin/sshd -D -p 9999

#------客户端----
liuwenmao@node10:~$ ssh localhost -p 9999 -l test
test@localhost's password:
Permission denied, please try again.
test@localhost's password:
close[31596] 4
Welcome to Ubuntu 16.04.3 LTS (GNU/Linux 4.4.0-78-generic x86_64)

  • Documentation: https://help.ubuntu.com
  • Management: https://landscape.canonical.com
  • Support: https://ubuntu.com/advantage

1 package can be updated.
0 updates are security updates.

*** System restart required ***
Last login: Fri Sep 29 15:51:02 2017 from ::1
write error: Bad file descriptor
write error: Bad file descriptor
write error: Bad file descriptor
write error: Bad file descriptor
test@node10:~$

但是在普通用户下sudo是ok的:

liuwenmao@node10:~/hook$ LD_PRELOAD=/home/liuwenmao/hook/libprog2.so sudo /usr/sbin/sshd -D -p 9999

#------客户端----
liuwenmao@node10:~$ ssh localhost -p 9999 -l test
test@localhost's password:
Welcome to Ubuntu 16.04.3 LTS (GNU/Linux 4.4.0-78-generic x86_64)

  • Documentation: https://help.ubuntu.com
  • Management: https://landscape.canonical.com
  • Support: https://ubuntu.com/advantage

1 package can be updated.
0 updates are security updates.

*** System restart required ***
Last login: Fri Sep 29 15:51:20 2017 from ::1
test@node10:~$

猜测可能是ssh有一些suid等操作,对LD_PRELOAD有一些影响。

探讨

作为检测方来说,其实用户态函数hook并不是一种要的选择,因为
1. 攻击者可以检测环境变量,如果存在LD_PRELOAD,就可以认定有监测程序,作为处置方式也很简单,要不unset掉这个环境变量,这样检测方就无法hook了;要不直接放弃这台主机,避免被捕获下一步的攻击样本。
2. 检测方法并不可扩展,一方面,需要hook很多很多很多函数,例如新建文件描述符就涉及到open、accept、listen等函数,如果漏掉一个,就可能监控不全;另一方面,很多程序是非预期的,例如docker新启动一个容器,就可能会将所有的环境变量清空,这样默认场景下是无法监控到容器内部的操作的(虽然容器里的进程是docker run的子进程)
3. hook函数编写较为复杂,至少它调用的方法不能是hook函数之一。

Leave a Comment

Your email address will not be published. Required fields are marked *