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:

 C | 
 
 copy code |
?

01
02
#include<stdio.h>
03
#include<malloc.h>
04
#include<stdlib.h>
05
int main(void)
06
{
07
    int <em>p;
08
    printf("calling from main...\n");
09
    p=(int *)malloc(10);
10
    if(!p)
11
    {
12
        printf("Got allocation error...\n");
13
        exit(1);
14
    }
15
    printf("returning to main...\n");
16
    free(p);                           /</em> freeing memory from heap */
17
    printf("freeing memory...\n");
18
    return 0;
19
}
20

prog2.c

 C | 
 
 copy code |
?

01
02
#define _GNU_SOURCE
03
#include &lt;stdio.h>
04
#include &lt;stdint.h>
05
#include &lt;stdarg.h>
06
#include &lt;string.h>
07
#include &lt;dlfcn.h>                               /* header required for dlsym() */
08
#include &lt;unistd.h>
09
 
10
/* lcheck() is for memory leak check; its code is not shown
11
 here <em>/
12
void lcheck(void);
13
void print(const char</em> format, ...);
14
 
15
void* malloc(size_t size)
16
{
17
    static void* (<em>my_malloc)(size_t) = NULL;
18
    print("inside shared object...\n");
19
    if (!my_malloc)
20
        my_malloc = dlsym(RTLD_NEXT, "malloc");  /</em> returns the object reference for malloc <em>/
21
    void *p = my_malloc(size);               /</em> call malloc() using function pointer my_malloc <em>/
22
    print("malloc(%lu) = %p\n", size, p);
23
    lcheck();                                /</em> calling do_your_stuff function */
24
    print("returning from shared object...\n");
25
 
26
[crayon-5b2c6eb5c9d00597224900/]
27
 
28
}
29
 
30
void free(void* p){
31
    static void (<em>my_free)(void</em> ) = NULL;
32
    if (!my_free)
33
        my_free = dlsym(RTLD_NEXT, "free");  /* returns the object reference for free <em>/
34
    my_free(p);               /</em> call malloc() using function pointer my_free */
35
    print("free %p\n",  p);
36
    return;
37
 
38
}
39
 
40
int open(const char <em>pathname, int flags, mode_t mode){
41
    static int (</em>my_open)(const char *, int , mode_t ) = NULL;
42
    if (!my_open)
43
        my_open = dlsym(RTLD_NEXT, "open");  /* returns the object reference for open <em>/
44
    int res = my_open(pathname, flags, mode);               /</em> call malloc() using function pointer my_open */
45
    print("open %s, flags: %d, mode: %u\n",  pathname, flags, mode);
46
    return res;
47
}
48
 
49
ssize_t read(int fd, void <em>buf, size_t count){
50
    static int (</em>my_read)(int ,void*, size_t) = NULL;
51
    if (!my_read)
52
        my_read = dlsym(RTLD_NEXT, "read");  /* returns the object reference for open <em>/
53
    ssize_t size= my_read(fd, buf, count);               /</em> call malloc() using function pointer my_open */
54
    print("read fd %d, buf: %s, count: %u, read size: %u\n",  fd, buf, count, size);
55
    return size;
56
}
57
 
58
ssize_t recv(int sockfd, void <em>buf, size_t len, int flags){
59
    static int (</em>my_recv)(int ,void*, size_t, int) = NULL;
60
    if (!my_recv)
61
        my_recv = dlsym(RTLD_NEXT, "recv");  /* returns the object reference for open <em>/
62
    ssize_t size= my_recv(sockfd, buf, len, flags);               /</em> call malloc() using function pointer my_open */
63
    print("recv fd: %d, buf: %s, len: %u, flags: %d, read size: %u\n",  sockfd, buf, flags, size);
64
    return size;
65
}
66
 
67
int accept(int sockfd, struct sockaddr <em>addr, socklen_t *addrlen){
68
    static int (</em>my_accept)(int ,struct sockaddr*, socklen_t<em>) = NULL;
69
    if (!my_accept)
70
        my_accept = dlsym(RTLD_NEXT, "accept");  /</em> returns the object reference for open <em>/
71
    ssize_t size= my_accept(sockfd, addr, addrlen);               /</em> call malloc() using function pointer my_open */
72
    print("accept fd: %d, addr: %u, len: %u, fd: %u\n",  sockfd, addr, addrlen, size);
73
    return size;
74
}
75
 
76
ssize_t write(int fd, const void <em>buf, size_t count){
77
    static int (</em>my_write)(int ,const void*, size_t) = NULL;
78
    if (!my_write)
79
        my_write = dlsym(RTLD_NEXT, "write");
80
    ssize_t size= my_write(fd, buf, count);
81
    print("write %d, buf: %s, count: %u, write size: %u\n",  fd, buf, count, size);
82
    return size;
83
}
84
 
85
void lcheck(void)
86
{
87
    print("displaying memory leaks...\n");
88
    /* do required stuff here */
89
}
90
 
91
void print(const char* format, ...){
92
    static char buf[1024*2];
93
    char tmp[1024];
94
 
95
[crayon-5b2c6eb5c9d0d254286865/]
96
 
97
}
98

然后编译,运行:

 shell | 
 
 copy code |
?

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

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

 shell | 
 
 copy code |
?

01
02
root@node10:~/hook# LD_PRELOAD=/home/liuwenmao/hook/libprog2.so  /usr/sbin/sshd -D -p 9999
03
04
#------客户端----
05
liuwenmao@node10:~$ ssh localhost -p 9999 -l test
06
test@localhost's password:
07
Permission denied, please try again.
08
test@localhost's password:
09
close[31596] 4
10
Welcome to Ubuntu 16.04.3 LTS (GNU/Linux 4.4.0-78-generic x86_64)
11
12
13
  • Documentation: https://help.ubuntu.com
  • 14
  • Management: https://landscape.canonical.com
  • 15
  • Support: https://ubuntu.com/advantage
  • 16
    
    
    17
    18
    1 package can be updated.
    
    19
    0 updates are security updates.
    
    20
    21
    *** System restart required ***
    
    22
    Last login: Fri Sep 29 15:51:02 2017 from ::1
    
    23
    write error: Bad file descriptor
    
    24
    write error: Bad file descriptor
    
    25
    write error: Bad file descriptor
    
    26
    write error: Bad file descriptor
    
    27
    test@node10:~$
    
    28

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

     shell | 
     
     copy code |
    ?

    01
    02
    liuwenmao@node10:~/hook$ LD_PRELOAD=/home/liuwenmao/hook/libprog2.so sudo  /usr/sbin/sshd -D -p 9999
    
    03
    04
    #------客户端----
    
    05
    liuwenmao@node10:~$ ssh localhost -p 9999 -l test
    
    06
    test@localhost's password:
    
    07
    Welcome to Ubuntu 16.04.3 LTS (GNU/Linux 4.4.0-78-generic x86_64)
    
    08
    09
    10
  • Documentation: https://help.ubuntu.com
  • 11
  • Management: https://landscape.canonical.com
  • 12
  • Support: https://ubuntu.com/advantage
  • 13
    
    
    14
    15
    1 package can be updated.
    
    16
    0 updates are security updates.
    
    17
    18
    *** System restart required ***
    
    19
    Last login: Fri Sep 29 15:51:20 2017 from ::1
    
    20
    test@node10:~$
    
    21

    猜测可能是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 *