我怎样才能拦截Linux系统调用?
- 2024-10-30 08:36:00
- admin 原创
- 59
问题描述:
除了 LD_PRELOAD 技巧和用您提供的系统调用替换某个系统调用的 Linux 内核模块之外,是否有可能拦截系统调用(例如打开),以便它在到达实际打开之前先经过您的函数?
解决方案 1:
首先让我们消除其他人给出的一些非答案:
使用
LD_PRELOAD
。是的,你在问题中说的是“此外LD_PRELOAD
……”,但显然这对某些人来说还不够。这不是一个好的选择,因为它仅在程序使用 libc 时才有效,但情况并非如此。使用 Systemtap。是的,你在问题中说“除了……Linux 内核模块”,但显然这对某些人来说还不够。这不是一个好选择,因为你必须加载自定义内核模块,这非常麻烦,而且需要 root 权限。
Valgrind。这确实有用,但它是通过模拟 CPU 来工作的,所以它真的很慢而且很复杂。如果你只是为了一次性调试而这样做,那没问题。如果你正在做一些值得投入生产的事情,那不是一个真正的选择。
各种系统调用审计。我认为记录系统调用不算作“拦截”它们。我们显然想要修改系统调用参数/返回值或通过其他代码重定向程序。
但是这里还没有提到其他可能性。请注意,我对这些东西都很陌生,还没有尝试过,所以有些事情我可能错了。
重写代码
理论上,您可以使用某种自定义加载器来重写系统调用指令,以跳转到自定义处理程序。但我认为这绝对是一场噩梦。
探针
kprobes是某种内核检测系统。它们对任何内容都只有只读权限,因此您不能使用它们来拦截系统调用,只能记录它们。
跟踪
ptrace是 GDB 等调试器用来进行调试的 API。有一个PTRACE_SYSCALL
选项可以在系统调用之前/之后暂停执行。从那里你可以像 GDB 一样做任何你想做的事情。这里有一篇关于如何使用 ptrace 修改系统调用参数的文章。然而,它显然开销很大。
赛康普
Seccomp是一个旨在允许您过滤系统调用的系统。您无法修改参数,但可以阻止它们或返回自定义错误。Seccomp 过滤器是 BPF 程序。如果您不熟悉,它们基本上是用户可以在内核空间 VM 中运行的任意程序。这避免了用户/内核上下文切换,这使得它们比 ptrace 更快。
虽然您无法直接从 BPF 程序修改参数,但您可以返回,SECCOMP_RET_TRACE
这将触发ptrace
ing 父级中断。因此,它基本上与PTRACE_SYSCALL
您在内核空间中运行程序以根据其参数决定是否要实际拦截系统调用相同。因此,如果您只想拦截某些系统调用(例如open()
具有特定路径),它应该会更快。
我认为这可能是最好的选择。这是一篇与上述文章同一位作者的文章。请注意,他们使用的是经典 BPF 而不是 eBPF,但我想你也可以使用 eBPF。
编辑:实际上你只能使用经典 BPF,而不能使用 eBPF。有一篇关于它的 LWN 文章。
以下是一些相关问题。第一个问题绝对值得一读。
eBPF 可以修改系统调用的返回值或参数吗?
仅拦截使用 PTRACE_SINGLESTEP 的系统调用
这是拦截系统调用的好方法吗?
无需修改内核即可以最小开销拦截系统调用
这里还有一篇关于通过 ptrace 操作系统调用的好文章。
解决方案 2:
为什么您不能/不想使用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 方法就是最好的方法,前提是您的应用程序相当标准,并且实际上不是恶意试图绕过您的拦截的应用程序。(在这种情况下,您将需要其他技术之一。)
解决方案 3:
Valgrind可用于拦截任何函数调用。如果您需要在成品中拦截系统调用,那么这将毫无用处。但是,如果您尝试在开发过程中进行拦截,那么它将非常有用。我经常使用这种技术来拦截哈希函数,以便我可以控制返回的哈希以进行测试。
如果您还不知道,Valgrind 主要用于查找内存泄漏和其他与内存相关的错误。但其底层技术基本上是一个 x86 模拟器。它模拟您的程序并拦截对 malloc/free 等的调用。好处是,您无需重新编译即可使用它。
Valgrind 有一个功能,他们称之为“函数包装”,用于控制函数的拦截。有关详细信息,请参阅Valgrind 手册第 3.2 节。您可以为任何您喜欢的函数设置函数包装。一旦拦截了调用,就会调用您提供的替代函数。
解决方案 4:
有些应用程序可以欺骗 strace/ptrace 不运行,所以我唯一真正的选择就是使用 systemtap
Systemtap 可以根据其通配符匹配功能在必要时拦截大量系统调用。Systemtap 不是 C,而是一种独立的语言。在基本模式下,systemtap 应该可以阻止您做蠢事,但它也可以在“专家模式”下运行,如果需要,可以返回到允许开发人员使用 C。
它不需要您修补内核(或者至少不应该),并且一旦模块被编译,您就可以从测试/开发箱中复制它并将其插入(通过 insmod)生产系统。
我还没有找到一个 Linux 应用程序能够找到办法解决/避免被 systemtap 捕获。
解决方案 5:
我没有使用 LKM 轻松完成此操作的语法,但这篇文章很好地概述了您需要做什么: http://www.linuxjournal.com/article/4378
您也可以只修补 sys_open 函数。从 linux-2.6.26 开始,它从 file/open.c 的第 1084 行开始。
您可能还会发现,如果您无法使用 inotify、systemtap 或 SELinux 为您完成所有这些日志记录,而无需构建新系统。
解决方案 6:
如果您只想查看打开的内容,则需要查看 ptrace() 函数或命令行 strace 实用程序的源代码。如果您确实想拦截该调用,也许让它执行其他操作,我认为您列出的选项 - LD_PRELOAD 或内核模块 - 是您唯一的选择。
解决方案 7:
如果您只是为了调试目的而这样做,请查看 strace,它内置于 ptrace(2) 系统调用之上,允许您在系统调用完成时挂接代码。请参阅手册页的 PTRACE_SYSCALL 部分。
解决方案 8:
如果你真的需要一个解决方案,你可能会对实现这一点的 DR rootkit 感兴趣,http://www.immunityinc.com/downloads/linux_rootkit_source.tbz2关于它的文章在这里http://www.theregister.co.uk/2008/09/04/linux_rootkit_released/
解决方案 9:
听起来你需要审计。
Auditd 允许全局跟踪所有系统调用或文件访问,并记录日志。您可以为您感兴趣的特定事件设置键。
解决方案 10:
使用 SystemTap 可能是一个选择。
对于 Ubuntu,请按照https://wiki.ubuntu.com/Kernel/Systemtap中的指示进行安装。
然后只需执行以下操作即可监听所有openat
系统调用:
# stap -e 'probe syscall.openat { printf("%s(%s)
", 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)
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件