这是拦截系统调用的好方法吗?

时间:2012-05-21 16:08:23

标签: c linux gcc x86-64 glibc

我正在写一个工具。该工具的一部分是它能够记录系统调用的参数。好吧,我可以将ptrace用于此目的,但ptrace非常慢。我想到的一种更快的方法是修改glibc。但这变得越来越困难,因为gcc神奇地将自己的内置函数作为系统调用包装器插入,而不是使用glibc中定义的代码。使用-fno-builtin也无济于事。

所以我提出了编写共享库的想法,其中包括每个系统调用包装器,例如mmap,然后在调用实际的系统调用包装函数之前执行日志记录。例如,我的mmap看起来像的伪代码如下所示。

int mmap(...)
{
 log_parameters(...);
 call_original_mmap(...);
 ...
}

然后我可以使用LD_PRELOAD首先加载这个库。你认为这个想法会起作用,还是我错过了什么?

4 个答案:

答案 0 :(得分:3)

您在用户空间中可能想到的任何方法都无法与任何应用程序无缝协作。幸运的是,已经支持在内核中完成您想要做的事情。 Kprobes和Kretprobes允许您在系统调用之前和之后检查机器的状态。

此处的文档:https://www.kernel.org/doc/Documentation/kprobes.txt

答案 1 :(得分:0)

来自用户空间的所有系统调用都通过中断处理程序切换到内核模式,如果你找到这个处理程序,你可能会在那里添加一些东西。

编辑我发现了http://cateee.net/lkddb/web-lkddb/AUDITSYSCALL.html。 Linux内核:2.6.6-2.6.39,3.0-3.4支持系统调用审计。这是必须启用的内核模块。如果不混淆,也许你可以查看这个模块的来源。

答案 2 :(得分:0)

正如其他人所提到的,如果二进制文件是静态链接的,动态链接器将跳过使用libdl拦截函数的任何尝试。相反,您应该考虑自己启动该过程并将入口点绕过您想要拦截的函数。

这意味着自己启动过程,拦截它的执行,并重写它的内存,将一个函数在内存中定义开头的跳转指令放到你控制的新函数中。

如果要拦截实际的系统调用而不能使用ptrace,则必须找到每个系统调用的执行站点并重写它,否则您可能需要覆盖系统调用表在内存中过滤掉除了你想要控制的过程之外的所有内容。

答案 3 :(得分:0)

如果您正在开发的代码与流程相关,有时您可以在不破坏现有代码的情况下开发替代实现。如果您正在重写一个重要的系统调用,并且想要一个用于调试它的全功能系统,这将非常有用。

对于您的情况,您正在重写mmap()算法以利用令人兴奋的新功能(或使用新功能进行增强)。除非您在第一次尝试时完成所有操作,否则调试系统并不容易:非功能性mmap()系统调用肯定会导致系统无法运行。一如既往,有希望。

通常,保留剩余的算法并在侧面构建替换是安全的。您可以通过使用用户ID(UID)作为条件来实现此目的,以决定使用哪种算法:

if (current->uid != 7777) {
/* old algorithm .. */
} else {
/* new algorithm .. */
}

除UID 7777之外的所有用户都将使用旧算法。您可以使用UID 7777创建一个特殊用户来测试新算法。这使得测试与流程相关的关键代码变得更加容易。