【问题标题】:GCC how to block system calls within a program?GCC如何阻止程序内的系统调用?
【发布时间】:2010-06-01 09:07:32
【问题描述】:

请问有人告诉我如何阻止程序中的某些特定系统调用吗?我正在构建一个系统,它需要一段 C 源代码,用 gcc 编译并运行它。出于安全考虑,我需要防止编译后的程序调用一些系统调用。有没有办法做到这一点,从源代码级别(例如剥离 gcc 的头文件,检测恶意外部调用,......)到可执行级别?

已编辑 #1: 添加有关恶意呼叫的详细信息。

已编辑 #2: 我的系统是 GNU/Linux 系统。

编辑#3:

我在几天内尝试了一些方法,以下是我目前得到的结论:

  1. 扫描源代码并不能解决主要问题,因为人们总是可以很好地混淆他/她的 C 源文件。
  2. “覆盖 C 符号”适用于库,但对于系统调用,我还没有达到我想要的效果。这个想法并没有死,但是,这样做肯定会导致我大量时间黑客(gcc 和/或 ld)。
  3. 权限降级就像一个魅力。我可以使用 fakeroot 或“来宾”用户来执行此操作。这种方法也是最容易实现的。

另一个是native client,我还没有尝试过,但由于项目和我的工作之间的共同点,我肯定会在不久的将来。

【问题讨论】:

  • 这看起来是一个相当大的挑战。毕竟,几乎所有可以想象的 C 程序都会非常合理地进行系统调用,例如:分配内存、读写文件、使用标准输入和标准输出等等。
  • 你认为什么样的程序永远不会进行系统调用?请发布一个示例。
  • @High Performance Mark:好吧,我只是想阻止一些特定的系统调用,因为它们是有限的,例如 fork()、open()、...等调用 @Neil Butterworth:我的意思是不所有系统调用,但恶意系统调用,例如execv() 可用于执行 BASH 脚本,该脚本会清除磁盘上的数据。
  • 为什么没有人提到seccomp

标签: c gcc system-calls


【解决方案1】:

正如其他人所指出的,程序不可能避免进行系统调用,它们遍及 C 库。

但是,如果您的平台支持(例如 Linux),您可以通过仔细使用 LD_PRELOAD 机制取得一些进展:您编写一个与 C 库中符号名称相同的共享库,称为而不是预期的 libc 函数。 (例如,Electric Fence 在基于 Debian 的系统上构建为共享库,并拦截对 mallocfree 等的调用。)

我怀疑您可以使用此机制来捕获或检查对您不喜欢的任何 libc 函数的调用,并可能注意那些您认为无条件安全的函数。然后扫描已编译的可执行文件以查找与INT 0x80 对应的代码以捕获任何进行原始系统调用的尝试(0xcd 0x80 - 尽管要注意误报)可能是合理的。但是我只考虑了几分钟,我很容易错过了一些东西,或者这可能会变得不切实际......

【讨论】:

  • 关于覆盖 C 符号的绝妙想法!这样我就可以完全控制和检测带有不良目的的呼叫。我一定会尝试这个以及其他两种方式(扫描源文件和权限降级),看看哪个最合适。
  • 我刚刚想到了一个更详细的例子来说明这种机制的威力:查看fakeroot 包。
  • 可以在不使用任何库的情况下执行系统调用,因此如果您希望构建一个针对恶意用户的安全系统,使用 LD_PRELOAD 是不够的。
【解决方案2】:

您可以通过从包装器中派生出已编译的程序来运行它,并使用 Linux ptrace(2) 工具来拦截和检查程序调用的所有系统调用。

以下示例代码显示了一个包装器,它运行 /usr/bin/w 命令,打印该命令调用的每个系统调用,并在它尝试调用 write(2) 系统调用时终止该命令。

#include #include #include #include #include #include #include #define BAD_SYSCALL __NR_write int main(int argc, char *argv) { pid_t 孩子; int 状态,syscall_nr; 孩子=叉子(); 如果(孩子== 0){ /* 在孩子中。 */ ptrace(PTRACE_TRACEME, 0, NULL, NULL); execl("/usr/bin/w", NULL, NULL); // 还没到 } /* 在父级中。 */ 而(1){ 等待(&状态); /* 如果孩子已经退出,则中止循环。 */ 如果(WIFEXITED(状态)|| WIFSIGNALED(状态)) 休息; /* 从子进程上下文中获取系统调用号。 */ syscall_nr = ptrace(PTRACE_PEEKUSER, 孩子, 4 * ORIG_EAX, NULL); printf("孩子想要执行系统调用 %d:", syscall_nr); if (syscall_nr != BAD_SYSCALL) { /* 允许系统调用。 */ printf("允许。\n"); ptrace(PTRACE_SYSCALL,孩子,空,空); } 别的 { /* 终止孩子。 */ printf("不允许。终止子进程。\n"); ptrace(PTRACE_KILL,孩子,NULL,NULL); } } 退出(EXIT_SUCCESS); }

您可以使用 ptrace 做更强大的事情,例如检查和更改进程的地址空间(例如,获取和修改传递给系统调用的参数)。

可以在这个Linux Journal Article和它的follow-up找到一个很好的介绍。

【讨论】:

    【解决方案3】:

    你不能。

    即使是这个程序:

    #include <stdio.h>
    
    int main()
    {
        printf("Hello, World\n");
        return 0;
    }
    

    至少进行一次系统调用(将字符串“Hello, World\n”发送到标准输出)。系统调用是程序与外界交互的唯一方式。使用操作系统的安全模型来确保安全。

    编辑此评论:

    我指的不是所有系统调用,而是恶意系统调用,例如execv() 可用于执行 BASH 脚本,该脚本会清除磁盘上的数据。

    您的操作系统已经包含阻止此类事情发生的机制。例如,为了让 bash 脚本清除您的数据,该进程必须已经具有对该数据的写入权限。这意味着它必须是由您或 root 启动的。您唯一真正的选择是不要安装不可靠的软件。

    顺便说一句,根据您的平台,execv 不一定是系统调用。在 Linux 上,它是实际系统调用 (execve) 的 C 库包装器。

    【讨论】:

    • 既然您提到了文件系统权限,我通过在该目录中没有写入权限的自定义用户执行它来找到解决方案。我差点忘记了。谢谢你:-D。
    【解决方案4】:

    只是为了说明这是不可能的,下面的程序:

    int main() {
        return 0;
    }
    

    使用strace 报告进行了 20 多次系统调用。这些电话包括open(两次),这是您似乎想要阻止的电话之一。

    【讨论】:

    • 谢谢,但我指的是一些具体的,而不是全部。
    【解决方案5】:

    好吧,如果您只是想阻止特定的调用,为什么不在尝试编译之前通过源代码执行 grep 呢?并拒绝使用不安全系统调用的程序。

    【讨论】:

    • 我可以通过使用扫描源代码的程序自动完成吗?例如,我有一个源文件,其中包含可能导致恶意情况的“unistd.h”,另一个源文件的结构名为“unistd”,其中有一个名为“.h”的字段,我如何区分情况?
    • 您当然可以自动完成。我会从 grep 或 sed 开始,但如果这不是你的菜,那么任何通用编程语言,尤其是具有良好正则表达式功能的编程语言,都可以。
    • 是的,这可能可行,但我必须仔细设计算法以使用 grep 或内联 Perl 进行扫描,或者限制源代码命名约定。谢谢你:-)。
    • 我可以用任意方式转换我的源代码来尝试打败你的 grep。如果您尝试这样做,最好在预处理和实际编译之间进行。
    • 任何这样的源扫描器很可能是可以绕过的,尤其是如果它是使用正则表达式构建的(这意味着它不读取 C 语法)。你怎么知道这个看似无辜的整数常量数组是否实际上是调用execve系统调用的机器代码? (您实际上不需要任何头文件 - 甚至任何外部代码 - 来调用系统调用)。
    【解决方案6】:

    有些项目有类似的想法你可以看看nacl:http://code.google.com/p/nativeclient/

    【讨论】:

    • 值得一试,我试试看。
    猜你喜欢
    • 1970-01-01
    • 2020-03-12
    • 1970-01-01
    • 1970-01-01
    • 2011-02-26
    • 2012-10-31
    • 2011-06-09
    • 1970-01-01
    • 2013-09-08
    相关资源
    最近更新 更多