gpt4 book ai didi

c - 使用LD_PRELOAD覆盖从另一个libc函数调用的libc函数

转载 作者:太空狗 更新时间:2023-10-29 15:07:36 26 4
gpt4 key购买 nike

我有一个项目旨在运行php-cgi chrooted 进行大规模虚拟主机(超过10k个虚拟主机),并且每个虚拟主机在Ubuntu Lucid x86_64下都具有自己的chroot。

我想避免在每个chroot内为/dev/null,/dev/zero,语言环境,图标...以及php模块认为它们在chroot之外运行所需的任何内容创建必要的环境。

的目标是使php-cgi在chroot内运行,但允许他访问chroot 以外的文件,只要这些文件(对于大多数文件)以只读模式打开并在允许的列表中(/dev/log,/dev/zero,/dev/null,语言环境路径...)

一种明显的方法似乎是创建(或使用)内核模块,该模块可以在chroot之外钩住并重定向受信任的open()路径。
但我认为这不是最简单的方法:

  • 我从未做过内核模块,所以我没有正确估计难度。
  • 似乎有多个syscall来挂接文件“打开”(打开,连接,mmap ...),但是我想对于与文件打开有关的所有事情都有一个通用的内核功能。

  • 我确实想减少对php或其模块的补丁数量,以减少每次将平台更新到最新的稳定PHP版本(并因此更频繁,更快速地从上游PHP版本更新)时所需的工作量。我发现从外部修补PHP的行为更好(因为我们有特定的设置,因此修补PHP并向上游建议修补是无关紧要的)。

    相反,我当前正在尝试一个用户级解决方案:使用LD_PRELOAD钩接libc函数,该函数在大多数情况下都可以正常工作,并且实现起来很快,但是遇到了一个我无法单独解决的问题。
    (其想法是与在chroot外部运行的守护进程进行对话,并使用ioctl SENDFD和RECVFD从该守护进程获取文件描述符)。

    当我调用syslog()(首先不带openlog())时,syslog() 调用connect()打开文件

    例子:
    folays@phenix:~/ldpreload$ strace logger test 2>&1 | grep connect
    connect(3, {sa_family=AF_FILE, path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
    connect(3, {sa_family=AF_FILE, path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory)
    connect(1, {sa_family=AF_FILE, path="/dev/log"}, 110) = 0

    到目前为止,到目前为止,我尝试钩住libc的connect()函数,但未成功。
    我还尝试在预加载库的_init()函数内的dlopen()中放置一些标志,以测试其中一些标志是否可以使这项工作成功,但没有成功

    这是我的预载库的相关代码:
    void __attribute__((constructor)) my_init(void)
    {
    printf("INIT preloadz %s\n", __progname);
    dlopen(getenv("LD_PRELOAD"), RTLD_NOLOAD | RTLD_DEEPBIND | RTLD_GLOBAL |
    RTLD_NOW);
    }

    int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
    {
    printf("HOOKED connect\n");
    int (*f)() = dlsym(RTLD_NEXT, "connect");
    int ret = f(sockfd, addr, addrlen);
    return ret;
    }

    int __connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
    {
    printf("HOOKED __connect\n");
    int (*f)() = dlsym(RTLD_NEXT, "connect");
    int ret = f(sockfd, addr, addrlen);
    return ret;
    }

    但是libc的connect()函数仍然优先于我的:
    folays@phenix:~/ldpreload$ LD_PRELOAD=./lib-preload.so logger test
    INIT preloadz logger
    [...] no lines with "HOOKED connect..." [...]
    folays@phenix:~/ldpreload$

    查看syslog()的代码(apt-get source libc6,glibc-2.13/misc/syslog.c),似乎在misc/syslog.c第386行调用了openlog_internal,而后者又调用了__connect():
                if (LogFile != -1 && !connected)
    {
    int old_errno = errno;
    if (__connect(LogFile, &SyslogAddr, sizeof(SyslogAddr))
    == -1)
    {

    好吧,objdump在libc的动态符号表中向我展示了connect和__connect:
    folays@phenix:~/ldpreload$ objdump -T /lib/x86_64-linux-gnu/libc.so.6 |grep -i connec
    00000000000e6d00 w DF .text 000000000000005e GLIBC_2.2.5 connect
    00000000000e6d00 w DF .text 000000000000005e GLIBC_2.2.5 __connect

    但是动态重定位条目中没有连接符号,因此我猜想它解释了为什么我无法成功覆盖openlog_internal()使用的connect(),它可能不使用动态符号重定位,并且可能具有__connect()的地址很难发挥作用(相对于-fPIC偏移量?)。
    folays@phenix:~/ldpreload$ objdump -R /lib/x86_64-linux-gnu/libc.so.6 |grep -i connec
    folays@phenix:~/ldpreload$

    connect是__connect的弱别名:
     eglibc-2.13/socket/connect.c:weak_alias (__connect, connect)

    gdb仍然能够在libc的libc连接符号上断点:
    folays@phenix:~/ldpreload$ gdb logger
    (gdb) b connect
    Breakpoint 1 at 0x400dc8
    (gdb) r test
    Starting program: /usr/bin/logger

    Breakpoint 1, connect () at ../sysdeps/unix/syscall-template.S:82
    82 ../sysdeps/unix/syscall-template.S: No such file or directory.
    in ../sysdeps/unix/syscall-template.S
    (gdb) c 2
    Will ignore next crossing of breakpoint 1. Continuing.

    Breakpoint 1, connect () at ../sysdeps/unix/syscall-template.S:82
    82 in ../sysdeps/unix/syscall-template.S
    (gdb) bt
    #0 connect () at ../sysdeps/unix/syscall-template.S:82
    #1 0x00007ffff7b28974 in openlog_internal (ident=<value optimized out>, logstat=<value optimized out>, logfac=<value optimized out>) at ../misc/syslog.c:386
    #2 0x00007ffff7b29187 in __vsyslog_chk (pri=<value optimized out>, flag=1, fmt=0x40198e "%s", ap=0x7fffffffdd40) at ../misc/syslog.c:274
    #3 0x00007ffff7b293af in __syslog_chk (pri=<value optimized out>, flag=<value optimized out>, fmt=<value optimized out>) at ../misc/syslog.c:131

    当然,我自己可以通过执行openlog()来完全跳过此特定问题,但是我猜想其他一些函数将遇到相同类型的问题。

    我不太了解为什么openlog_internal不使用动态符号重定位来调用__connect(),以及是否甚至有可能通过使用简单的LD_PRELOAD机制来挂接此__connect()调用。

    我看到的其他方式是如何做到的:
  • 使用dlopen从LD_PRELOAD加载libc.so,使用dlsym()获取libc的__connect的地址,然后修补函数(明智地使用ASM)以使钩子(Hook)正常工作。看来确实是过度杀伤和容易出错。
  • 使用针对PHP的经过修改的自定义libc在源头直接解决这些问题(打开/连接/mmap函数...)
  • 对LKM进行编码,以将文件访问重定向到我想要的位置。优点:不需要ioctl(SENDFD),也不需要chroot外的守护进程。

  • 如果可能的话,我将非常感谢您学习如何仍然可以挂接到openlog_internal发出的__connect()的调用,建议或与syscall挂接和重定向有关的内核文档的链接。

    我的Google搜索与“hook syscalls”相关,找到了很多对 LSM的引用,但似乎只允许ACL回答"is"或“否”,但没有重定向open()路径。

    谢谢阅读。

    最佳答案

    如果不构建自己的经过大量修改的libc,使用LD_PRELOAD绝对是不可能的,在这种情况下,您最好将重定向hacks直接放入内部。不一定需要调用openconnect等。取而代之的是,可能会在库创建时绑定(bind)一个类似的隐藏函数(不可动态重新绑定(bind)),甚至是内联syscall,这当然会随着版本的变化而变化。

    您可以选择一个内核模块,或者在“chroot”内部的所有内容上使用ptrace,并在跟踪过程遇到需要修补的过程时修改syscall的参数。听起来都不容易...

    或者您可以接受,您需要在chroot中存在最少的关键设备节点和文件集,这样它才能正常工作。如果可能的话,使用其他libc代替glibc将有助于您最大程度地减少所需的其他文件数量。

    关于c - 使用LD_PRELOAD覆盖从另一个libc函数调用的libc函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7612724/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com