【问题标题】:Difference in behavior when hooking a library function via LD_PRELOAD on Ubuntu and CentOS在 Ubuntu 和 CentOS 上通过 LD_PRELOAD 挂钩库函数时的行为差异
【发布时间】:2021-07-27 14:27:54
【问题描述】:

有一个钩子函数socketHook.c可以拦截socket()调用:

#include <stdio.h>
int socket(int domain, int type, int protocol)
{
    printf("socket() has been intercepted!\n");
    return 0;
}
gcc -c -fPIC socketHook.c
gcc -shared -o socketHook.so socketHook.o

还有一个简单的程序getpwuid.c (1),它只调用getpwuid() 函数:

#include <pwd.h>

int main()
{
    getpwuid(0);
    return 0;
}
gcc getpwuid.c -o getpwuid

getpwuid() 在内部进行 socket() 调用。 在 CentOS 上:

$ strace -e trace=socket ./getpwuid
socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3
socket(AF_UNIX, SOCK_STREAM, 0)         = 4

在 Ubuntu 上:

$ strace -e trace=socket ./getpwuid
socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 5
socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 5

运行(1)时,socket()在CentOS上被拦截,在Ubuntu上不被拦截。

CentOS. 来自 socketHook.cprintf() 存在:

$ uname -a
Linux centos-stream 4.18.0-301.1.el8.x86_64 #1 SMP Tue Apr 13 16:24:22 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

$ LD_PRELOAD=$(pwd)/socketHook.so ./getpwuid
socket() has been intercepted!

Ubuntu(Xubuntu 20.04)。 socketHook.c 中的 printf() 不存在:

$ uname -a
Linux ibse-VirtualBox 5.8.0-50-generic #56~20.04.1-Ubuntu SMP Mon Apr 12 21:46:35 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

$ LD_PRELOAD=$(pwd)/socketHook.so ./getpwuid
$

所以我的问题是:

  1. 它取决于什么?我认为这是由于 socket() 不是直接从可执行文件调用,而是从 getpwuid() 调用,如果我理解正确的话,它又从 libc.so 调用。李>
  2. 如何在 CentOS 中实现与在 Ubuntu 中相同的行为?我不想拦截来自 libc 的间接调用

【问题讨论】:

    标签: linux ubuntu centos hook ld-preload


    【解决方案1】:

    它取决于什么?

    有两个问题要问:

    1. 哪个函数实际调用了socket系统调用?
    2. 如何调用该函数。

    你可以看到如何socket系统调用是通过在GDB下运行你的程序并使用catch syscall socket命令来调用的。在 Ubuntu 上:

    (gdb) catch syscall socket    
    Catchpoint 1 (syscall 'socket' [41])
    (gdb) run 
    Starting program: /tmp/a.out 
    
    Catchpoint 1 (call to syscall socket), 0x00007ffff7ed3477 in socket () at ../sysdeps/unix/syscall-template.S:120
    120     ../sysdeps/unix/syscall-template.S: No such file or directory.
    (gdb) bt
    #0  0x00007ffff7ed3477 in socket () at ../sysdeps/unix/syscall-template.S:120
    #1  0x00007ffff7f08010 in open_socket (type=type@entry=GETFDPW, key=key@entry=0x7ffff7f612ca "passwd", keylen=keylen@entry=7) at nscd_helper.c:171
    #2  0x00007ffff7f084fa in __nscd_get_mapping (type=type@entry=GETFDPW, key=key@entry=0x7ffff7f612ca "passwd", mappedp=mappedp@entry=0x7ffff7f980c8 <map_handle+8>) at nscd_helper.c:269
    #3  0x00007ffff7f0894f in __nscd_get_map_ref (type=type@entry=GETFDPW, name=name@entry=0x7ffff7f612ca "passwd", mapptr=mapptr@entry=0x7ffff7f980c0 <map_handle>, 
        gc_cyclep=gc_cyclep@entry=0x7fffffffda0c) at nscd_helper.c:419
    #4  0x00007ffff7f04fb7 in nscd_getpw_r (key=0x7fffffffdaa6 "0", keylen=2, type=type@entry=GETPWBYUID, resultbuf=resultbuf@entry=0x7ffff7f96520 <resbuf>, 
        buffer=buffer@entry=0x5555555592a0 "", buflen=buflen@entry=1024, result=0x7fffffffdb60) at nscd_getpw_r.c:93
    #5  0x00007ffff7f05412 in __nscd_getpwuid_r (uid=uid@entry=0, resultbuf=resultbuf@entry=0x7ffff7f96520 <resbuf>, buffer=buffer@entry=0x5555555592a0 "", buflen=buflen@entry=1024, 
        result=result@entry=0x7fffffffdb60) at nscd_getpw_r.c:62
    #6  0x00007ffff7e9e95d in __getpwuid_r (uid=uid@entry=0, resbuf=resbuf@entry=0x7ffff7f96520 <resbuf>, buffer=0x5555555592a0 "", buflen=buflen@entry=1024, 
        result=result@entry=0x7fffffffdb60) at ../nss/getXXbyYY_r.c:255
    #7  0x00007ffff7e9dfd3 in getpwuid (uid=0) at ../nss/getXXbyYY.c:134
    #8  0x0000555555555143 in main () at t.c:5
    
    (gdb) info sym $pc
    socket + 7 in section .text of /lib/x86_64-linux-gnu/libc.so.6
    (gdb) up
    #1  0x00007ffff7f08010 in open_socket (type=type@entry=GETFDPW, key=key@entry=0x7ffff7f612ca "passwd", keylen=keylen@entry=7) at nscd_helper.c:171
    171     nscd_helper.c: No such file or directory.
    (gdb) x/i $pc-5
       0x7ffff7f0800b <open_socket+59>:     callq  0x7ffff7ed3470 <socket>
    

    由此我们可以看出

    1. 函数socket 被调用。使用nm -D /lib/x86_64-linux-gnu/libc.so.6 | grep ' socket',我们可以确认该函数是从libc.so.6 导出的,因此应该是可插入的。
    2. 调用者调用socket@plt(即不使用procedure linkage table),因此LD_PRELOAD将不起作用。

    open_socket()socket() 的调用自 2004 年以来一直是不可插入的,因此 这个 调用很可能在 CentOS 上也没有被拦截,但有一些 other em> 调用是。可能是您strace 输出中的第三个。

    使用上述方法,您应该能够知道 调用来自何处。


    我不想拦截来自 libc 的间接调用

    在这种情况下,LD_PRELOAD 可能是使用错误的机制。

    如果您只想拦截来自您自己代码的 socket() 调用,将它们重定向到例如mysocket() 不需要LD_PRELOAD

    您可以通过添加例如在源代码级别做到这一点

    #define socket mysocket
    

    到您的所有文件,或在编译时使用-Dsocket=mysocket 参数。

    或者,使用链接器--wrap=socket 将在不重新编译的情况下进行重定向。

    【讨论】:

    • "调用者没有调用socket@plt,所以LD_PRELOAD没有作用。"@plt是什么? LD_PRELOAD 拦截是否有必要工作?我在哪里可以找到更多相关信息?
    • @ibse 我已经更新了答案。您可以在此处阅读有关 PLT 的信息:reverseengineering.stackexchange.com/a/1993 并点击链接。
    猜你喜欢
    • 2019-07-22
    • 1970-01-01
    • 1970-01-01
    • 2021-06-09
    • 2014-03-11
    • 2019-03-04
    • 2011-12-20
    • 2022-01-24
    • 2020-12-11
    相关资源
    最近更新 更多