20

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

4

10 回答 10

20

为什么你不能/不想使用LD_PRELOAD 技巧

这里的示例代码:

/*
 * 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 方法是最好的方法,假设您的应用程序相当标准并且实际上不是恶意试图通过的方法你的拦截。(在这种情况下,您将需要其他技术之一。)

于 2008-09-16T06:45:24.633 回答
7

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

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

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

于 2008-09-16T10:59:13.073 回答
5

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

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

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

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

于 2008-09-17T04:13:04.087 回答
2

我没有语法来优雅地使用 LKM 副手,但这篇文章很好地概述了你需要做什么: http ://www.linuxjournal.com/article/4378

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

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

于 2008-09-16T07:05:04.977 回答
2

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

于 2008-09-16T14:03:14.520 回答
2

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

于 2008-09-16T23:32:13.527 回答
1

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

于 2008-09-16T07:02:28.477 回答
1

听起来你需要审计。

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

于 2011-01-20T06:12:51.653 回答
1

使用 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)
于 2019-08-29T15:29:05.940 回答
1

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

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

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

重写代码

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

探针

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

跟踪

ptrace是 GDB 等调试器用来进行调试的 API。有一个PTRACE_SYSCALL选项可以在系统调用之前/之后暂停执行。从那里你可以像 GDB 一样做任何你喜欢的事情。这是一篇关于如何使用 ptrace 修改系统调用参数的文章。然而,它显然有很高的开销。

赛康

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

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

我认为这可能是最好的选择。这是与上述作者相同的一篇关于它的文章请注意,他们使用经典 BPF 而不是 eBPF,但我想您也可以使用 eBPF。

编辑:实际上你只能使用经典的 BPF,而不是 eBPF。有一篇关于它的 LWN 文章

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

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

于 2021-11-05T10:53:34.213 回答