【问题标题】:how could I intercept linux sys calls?我怎么能拦截 linux 系统调用?
【发布时间】:2010-09-09 08:24:49
【问题描述】:

除了 LD_PRELOAD 技巧和用您提供的系统调用替换某个系统调用的 Linux 内核模块之外,是否有可能拦截系统调用(例如打开),以便它首先通过您的函数,然后再到达实际打开?

【问题讨论】:

  • 这个问题需要澄清——它太模糊了。为什么 LD_PRELOAD 不够?
  • @Arafangion - LD_PRELOAD 允许您拦截库调用。但内核调用有所不同。

标签: c linux redirect hook system-calls


【解决方案1】:

为什么你不能/不想使用LD_PRELOAD trick

此处的示例代码:

/*
 * File: soft_atimes.c
 * Author: D.J. Capelis
 *
 * Compile:
 * gcc -fPIC -c -o soft_atimes.o soft_atimes.c
 * gcc -shared -o soft_atimes.so soft_atimes.o -ldl
 *
 * Use:
 * LD_PRELOAD="./soft_atimes.so" command
 *
 * Copyright 2007 Regents of the University of California
 */

#define _GNU_SOURCE
#include <dlfcn.h>
#define _FCNTL_H
#include <sys/types.h>
#include <bits/fcntl.h>
#include <stddef.h>

extern int errorno;

int __thread (*_open)(const char * pathname, int flags, ...) = NULL;
int __thread (*_open64)(const char * pathname, int flags, ...) = NULL;

int open(const char * pathname, int flags, mode_t mode)
{
    if (NULL == _open) {
        _open = (int (*)(const char * pathname, int flags, ...)) dlsym(RTLD_NEXT, "open");
    }
    if(flags & O_CREAT)
        return _open(pathname, flags | O_NOATIME, mode);
    else
        return _open(pathname, flags | O_NOATIME, 0);
}

int open64(const char * pathname, int flags, mode_t mode)
{
    if (NULL == _open64) {
        _open64 = (int (*)(const char * pathname, int flags, ...)) dlsym(RTLD_NEXT, "open64");
    }
    if(flags & O_CREAT)
        return _open64(pathname, flags | O_NOATIME, mode);
    else
        return _open64(pathname, flags | O_NOATIME, 0);
}

据我了解...这几乎是 LD_PRELOAD 技巧或内核模块。除非你想在一个模拟器下运行它,否则没有太多的中间立场,它可以捕捉到你的函数,或者在实际的二进制文件上重写代码以捕捉到你的函数。

假设您不能修改程序并且不能(或不想)修改内核,LD_PRELOAD 方法是最好的方法,假设您的应用程序相当标准并且实际上不是恶意尝试的方法越过你的拦截。 (在这种情况下,您将需要其他技术之一。)

【讨论】:

  • 程序确认 LD_PRELOAD 完全是可选的。并非每个程序都与 libc 链接。
  • @vipw 你能详细说明一下吗?程序如何绕过 LD_PRELOAD?如果使用 LD_PRELOAD 指定,则每个未与 libc 链接的程序都与链接器将在加载可执行文件时先加载给定库的事实无关。如果该库恰好有一个由可执行文件调用的函数,则程序首先查看 LD_PRELOAD 加载的库。后续库是否也实现了该功能并不重要。
  • @acib708 我的意思是程序可以在不使用 libc 的情况下进行系统调用。然后正在加载的库实际上并不重要,因为它没有被调用任何符号。相反,设置寄存器和创建中断的一小部分程序集可以进行调用。
  • @vipw 好的,是的,同意。
  • 100% 同意:我遇到了 Golang 的确切问题:我的存根库被加载,但除了构造函数之外没有任何调用......确实,golang 已决定不使用 libc,因为。 .. 原因。
【解决方案2】:

Valgrind 可用于拦截任何函数调用。如果您需要在成品中拦截系统调用,那么这将毫无用处。但是,如果您在开发过程中尝试拦截,那么它会非常有用。我经常使用这种技术来拦截散列函数,以便我可以控制返回的散列以进行测试。

如果您不知道,Valgrind 主要用于查找内存泄漏和其他与内存相关的错误。但底层技术基本上是一个 x86 模拟器。它模拟您的程序并拦截对 malloc/free 等的调用。好处是,您无需重新编译即可使用它。

Valgrind 有一个他们称之为Function Wrapping的特性,用来控制函数的拦截。有关详细信息,请参阅Valgrind manual 的第 3.2 节。您可以为您喜欢的任何功能设置功能包装。一旦调用被拦截,您提供的替代函数就会被调用。

【讨论】:

  • 所以它是用于调试目的的选项,但不适用于生产用途。
【解决方案3】:

一些应用程序可以欺骗 strace/ptrace 不运行,所以我唯一真正的选择是使用 systemtap

如果需要,Systemtap 可以拦截一堆系统调用,因为它的通配符匹配。 Systemtap 不是 C,而是一种单独的语言。在基本模式下,systemtap 应该可以防止你做愚蠢的事情,但它也可以在“专家模式”下运行,如果需要,它还允许开发人员使用 C。

它不需要你修补你的内核(或者至少不应该),一旦一个模块被编译,你可以从一个测试/开发框复制它并插入它(通过 insmod)到生产系统上.

我还没有找到找到解决/避免被 systemtap 捕获的方法的 linux 应用程序。

【讨论】:

  • 应用如何绕过 ptrace?
【解决方案4】:

我没有语法来优雅地使用 LKM 临时执行此操作,但这篇文章很好地概述了您需要做什么:http://www.linuxjournal.com/article/4378

您也可以只修补 sys_open 函数。从 linux-2.6.26 开始,它从 file/open.c 的第 1084 行开始。

您可能还会看到是否不能使用 inotify、systemtap 或 SELinux 为您完成所有这些日志记录,而无需构建新系统。

【讨论】:

  • 我们如何使用 SELinux 来拦截系统调用?
【解决方案5】:

如果您只想观看打开的内容,则需要查看 ptrace() 函数或命令行 strace 实用程序的源代码。如果你真的想拦截调用,也许让它做其他事情,我认为你列出的选项 - LD_PRELOAD 或内核模块 - 是你唯一的选择。

【讨论】:

  • 这里观看拦截有什么区别?我使用 ptrace 来拦截(停止、更改内容和继续)系统调用。
【解决方案6】:

如果您只是为了调试目的而这样做,请查看 strace,它内置于 ptrace(2) 系统调用之上,它允许您在系统调用完成时连接代码。请参阅手册页的 PTRACE_SYSCALL 部分。

【讨论】:

    【解决方案7】:

    如果您真的需要一个解决方案,您可能会对完成此任务的 DR rootkit 感兴趣,http://www.immunityinc.com/downloads/linux_rootkit_source.tbz2 此处有关于它的文章http://www.theregister.co.uk/2008/09/04/linux_rootkit_released/

    【讨论】:

    • 当存在其他更传统的替代方法时,为什么要提出一种晦涩难懂的方法? LD_PRELOAD 是最常见的。
    • 因为他不是在寻找更传统的,或者至少那是我从他最初的问题中收集到的
    【解决方案8】:

    听起来你需要审计。

    Auditd 允许全局跟踪所有系统调用或对文件的访问,并带有日志记录。您可以为您感兴趣的特定事件设置密钥。

    【讨论】:

      【解决方案9】:

      使用 SystemTap 可能是一种选择。

      对于 Ubuntu,请按照https://wiki.ubuntu.com/Kernel/Systemtap 中的说明进行安装。

      然后只需执行以下操作,您将监听所有 openat 系统调用:

      # stap -e 'probe syscall.openat { printf("%s(%s)\n", name, argstr) }'
      openat(AT_FDCWD, "/dev/fb0", O_RDWR)
      openat(AT_FDCWD, "/sys/devices/virtual/tty/tty0/active", O_RDONLY)
      openat(AT_FDCWD, "/sys/devices/virtual/tty/tty0/active", O_RDONLY)
      openat(AT_FDCWD, "/dev/tty1", O_RDONLY)
      

      【讨论】:

        【解决方案10】:

        首先让我们消除一些其他人给出的非答案:

        • 使用LD_PRELOAD。是的,您在问题中说“除了LD_PRELOAD ...”,但显然这对某些人来说还不够。这不是一个好的选择,因为它仅在程序使用 libc 时才有效,但不一定如此。
        • 使用 Systemtap。是的,您在问题中说“除了... Linux内核模块”,但显然这对某些人来说还不够。这不是一个好的选择,因为您必须加载自定义内核模块,这很麻烦,而且还需要 root。
        • 瓦尔格林德。这确实有点工作,但它可以模拟 CPU,所以它真的很慢而且真的很复杂。如果您只是为了一次性调试而这样做,那很好。如果您正在做一些值得生产的事情,这并不是一个真正的选择。
        • 各种系统调用审计事情。我不认为记录系统调用算作“拦截”它们。我们显然想修改系统调用参数/返回值或通过一些其他代码重定向程序。

        但是,这里还没有提到其他可能性。请注意,我对所有这些东西都是新手,还没有尝试过任何东西,所以我可能对某些事情有误。

        重写代码

        理论上,您可以使用某种自定义加载程序来重写系统调用指令以跳转到自定义处理程序。但我认为实施起来绝对是一场噩梦。

        kprobes

        kprobes 是某种内核检测系统。它们只有对任何内容的只读访问权限,因此您不能使用它们来拦截系统调用,只能记录它们。

        ptrace

        ptrace 是 GDB 等调试器用来进行调试的 API。有一个PTRACE_SYSCALL 选项将在系统调用之前/之后暂停执行。从那里你可以像 GDB 一样做任何你喜欢的事情。 Here's an article about how to modify syscall paramters using ptrace。然而,它显然有很高的开销。

        Seccomp

        Seccomp 是一个旨在让您过滤系统调用的系统。您无法修改参数,但您可以阻止它们或返回自定义错误。 Seccomp 过滤器是 BPF 程序。如果您不熟悉,它们基本上是用户可以在内核空间 VM 中运行的任意程序。这避免了用户/内核上下文切换,这使得它们比 ptrace 更快。

        虽然您不能直接从 BPF 程序修改参数,但您可以返回 SECCOMP_RET_TRACE,这将触发 ptraceing 父级中断。所以它基本上与PTRACE_SYSCALL 相同,只是您可以在内核空间中运行一个程序来决定是否要根据其参数实际拦截系统调用。因此,如果您只想拦截一些系统调用(例如带有特定路径的open()),它应该会更快。

        我认为这可能是最好的选择。 Here's an article about it from the same author as the one above请注意,他们使用经典 BPF 而不是 eBPF,但我想你也可以使用 eBPF。

        编辑:实际上你只能使用经典的 BPF,而不是 eBPF。有a LWN article about it

        这里有一些相关的问题。第一个绝对值得一读。

        还有一篇关于通过 ptrace here 操作系统调用的好文章。

        【讨论】:

          猜你喜欢
          • 2015-10-04
          • 2022-07-07
          • 1970-01-01
          • 2012-12-02
          • 2021-07-06
          • 2012-02-27
          • 2012-03-06
          • 1970-01-01
          • 2013-06-29
          相关资源
          最近更新 更多