微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

编译一个有限的库访问的程序

我想用gcc和glibc(或任何其他的c库)编译C程序,但我想限制程序访问某些function,例如,如果程序使用套接字或信号处理function,则不应编译程序。

任何想法我怎么能做到这一点?

顺便说一句,我想用这个简单的编程比赛裁判

谢谢

我如何在Linux上编译QScintilla和Eric6?

为Windows编译postgresql-8.4.7

忽略g ++编译错误以获得向后兼容性的额外资格

Windows Mingw出错 –

Windows Lua可执行文件

haskell中的latexpdf-esquefunction?

cifs挂载导致进程进入不间断的睡眠

用Windows SDK构buildGTK

ffmpeg与libsoxrparsing依赖关系

需要帮助编译Windows的libqxt

我想我晚了一点,但是我觉得到目前为止答案都是完全正确的。 事实上,可以按照你所要求的方式来限制一个程序的功能,并且这样做是明智的。

确实,防止对任意功能调用是可能的,也是毫无意义的 – 这就像是一个一个的把漏洞密封起来。 这也没有提出正确的问题 – 我猜想,您不希望阻止编码员计算一个数字的平方根,而是阻止他拥有该系统。 这意味着阻止他使系统做某些事情,而这些事情总是会涉及一个系统调用,所以把它们专注于它们而不是功能是有意义的。 用哪个函数打开一个socket是不重要的; 他们最终都使用socket系统调用

访问系统调用可以由内核控制。 Linux内核有一个称为seccomp的机制,被各种大型程序(如Firefox,Chrome和Adobe Flash)用来对其代码解释器和一些较小的程序(如vsftpd)进行沙盒处理,以便在攻击者设法发现一个远程代码执行漏洞(基于漏洞代码会发现自己严重受限而无法调用exec等)。

现在,在详细讨论之前:如果你要从你不认识的人(因此不能信任)采取代码,偏执狂就是理智。 Seccomp是好的,但在这种情况下是不够的,因为这种情况是攻击者的梦想。 最好是叠加防御,而不是微妙的。 所以,你需要做的前三件事是:

使用虚拟机

使用虚拟机

真的,使用虚拟机。

在虚拟机中运行所有程序使得利用主系统更加困难,因为攻击者除了必须做的所有其他事情之外,还必须打破虚拟机。 有这个免费的实现,很好地工作,并不是很难建立。 我大部分时间都使用VirtualBox

一旦你将一个Linux系统安装到你的虚拟机中, 把这个虚拟机一个快照,这样如果一个程序破坏了它,你可以回到它。

得到了所有的设置? 好。 现在,seccomp允许进程限制其使用系统调用的能力。 在设计上,限制是单向的; 以后再重新扩展进程的能力是不可能的。 seccomp可以放置的限制有点强大; 例如,进程不仅可以防止自己调用write ,还可以防止自己在除STDOUT_FILENO之外的任何文件描述符上调用write 。 由于内核API相当笨拙,我将在下面的代码示例中使用libseccomp 。 它有一个非常有用的帮助页面,可以帮助你解决细节问题,除非它很老,否则你的发行版可能会有一些软件包。 一个简单的例子来显示这是关于:

#include <seccomp.h> #include <stdio.h> #include <unistd.h> int main() { scmp_filter_ctx ctx; puts("foo"); // works as usual. (needed here because it forces fputs("barn",stderr); // some initialisation. More on that later) ctx = seccomp_init(SCMP_ACT_KILL); // default action: kill process seccomp_rule_add(ctx,SCMP_ACT_ALLOW,// allow SCMP_SYS(write),// calls to write 1,// under one condition: SCMP_A0(SCMP_CMP_EQ,STDOUT_FILENO)); // if the first argument // is STDOUT_FILENO seccomp_load(ctx); puts("foo"); // this will still work fputs("barn",stderr); // this will make the kernel kill the process fprintf(stderr,"barn"); // so would this fputc('b',stderr); // and this write(STDERR_FILENO,"barn",4); // and this // and any other write to anything but stdout return 0; }

所以我们对允许的系统调用进行了相当细致的控制,这很好。 它留下了识别系统调用的问题,需要允许程序的正确运行,其中几个是不重要的决定。 这是你必须回答自己的设计问题。 系统调用列在/usr/include/asm/unistd_64.h 。

那么我们如何将它应用于一个不可信来源的代码呢?

使用sed或类似的方式修补代码一个可能的想法,但对于安全关键型应用程序来说,这太不可靠了。 在用execv调用程序之前禁止系统调用的“安全加载器”会遇到无法禁止execve系统调用的问题,这是其中一个最想要禁止调用。 而且,在该程序的main函数被输入之前, execv需要一系列的其他系统调用(比如access , mmap , open , fstat , close , mprotect和arch_prctl )。 那么该怎么办?

重要更新:本节最初包含了使用LD_PRELOAD加载seccomp代码的尝试; @ virusdefender正确地指出,这有一个明显的漏洞,因为用户代码可以控制功能是否实际运行。 新的方法使运行时链接程序调用我们的函数关闭这个漏洞。

一种方法是使用共享库,除了构造函数和析构函数分别在加载和卸载时运行外,其他都没有。 链接器将在从二进制代码运行代码之前加载库,所以构造函数将被运行并且在用户代码控制之前安装过滤器。

代码如下:

#include <seccomp.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> static scmp_filter_ctx ctx; // Macro just to make error handling simple. Error handling is // very important here. You don't want this to silently fail. #define ADD_SECCOMP_RULE(ctx,...) do { if(seccomp_rule_add(ctx,__VA_ARGS__) < 0) { perror("Could not add seccomp rule"); seccomp_release(ctx); exit(-1); } } while(0) // Constructor. This sets up the seccomp filter. static void __attribute__((constructor)) seccomp_load_init(void) { ctx = seccomp_init(SCMP_ACT_KILL); if(ctx == NULL) { perror("Could not open seccomp context"); exit(-1); } // Rules for system calls here. ADD_SECCOMP_RULE(ctx,SCMP_SYS(exit ),0); ADD_SECCOMP_RULE(ctx,SCMP_SYS(exit_group),SCMP_SYS(write ),1,SCMP_A0(SCMP_CMP_EQ,STDOUT_FILENO)); ADD_SECCOMP_RULE(ctx,STDERR_FILENO)); ADD_SECCOMP_RULE(ctx,SCMP_SYS(read ),STDIN_FILENO)); // This is needed for dynamic memory allocation ADD_SECCOMP_RULE(ctx,SCMP_SYS(brk ),0); // These are needed for stdio initialisation. Workarounds to this are ugly,and the // syscalls are not terribly critical because they require file descriptors. We // restrict the program's ability to obtain those. ADD_SECCOMP_RULE(ctx,SCMP_SYS(mmap ),SCMP_SYS(fstat ),0); if(seccomp_load(ctx) < 0) { perror("Could not load seccomp context"); exit(-1); } } // Destructor; run at unload time. Just cleanup here. static void __attribute__((destructor)) seccomp_load_free(void) { seccomp_release(ctx); }

这将需要被编译到共享库中:

gcc -fPIC -shared -o libmyfilter.so myfilter.c

它将需要链接到不可信的代码,以便链接器在程序启动时加载它:

gcc -o untrustworthy_program untrustworthy_code.c -L/path/to/myfilter -lmyfilter -lseccomp

然后你可以把那些不可靠的程序放在一个不安全的地方(在你的VM里面)

LD_LIBRARY_PATH=/path/to/myfilter ./untrustworthy_program

Where /path/to/myfilter libmyfilter.so是包含libmyfilter.so的目录。

由于过滤器库使用libc(和libseccomp)中的函数,所以在安装seccomp过滤器之前将会完成libc的启动。 这是故意的(并且是原始尝试背后的基本原理的一部分),因为libc在启动时会执行许多操作,例如打开文件,以防止用户代码的执行。 如果您希望允许使用另一个在启动时执行过滤器应该稍后阻止的库,则可以使用LD_PRELOAD使链接器在过滤器之前加载它。

我不打算出去到目前为止,说这将使利用不可能,但攻击者会,如果你明智地设计你的系统调用过滤器,必须在Linux内核中找到可利用的错误(无论是在seccomp或者你允许它使用的内核的子集)和你的虚拟机,这很可能是非常困难的。 在更可能的情况下,我忽略了再次想到的东西,虚拟机仍然是一个有用的防线。

你不能可靠地限制访问某些功能,因为一个积极的开发人员总是可以找到解决办法。 例如,他可以使用dlsym在运行时查找某个函数的地址,或者使用调用一些系统调用(或使用缓冲区溢出技术)的asm代码,或者假定libc二进制文件的特定版本并计算一些函数指针(例如,通过偏移一些合法的libc函数的地址,如带有内置偏移量的printf ),或者将一些文字字符串(包含合适的机器操作码)转换为函数指针等。

但是,您可能会考虑自定义编译器(例如,如果使用最近的GCC进行编译 ,请使用MELT扩展名进行自定义)来检测常见情况(但不是全部)。 这可能意味着开发这种编译器定制工作数周。

您也可以链接到您特制的libc ,使用LD_PRELOAD或ptrace等。

为了可靠地禁止某些行为,你应该在一些虚拟容器中运行。

PS。 静态地(完全可靠地)检测到某些源代码永远不会调用给定的一组函数是不可判定的 ,因为相当于暂停问题 。

请不要这样做。 即使你找到一种方法禁止某些功能,如execl ,有很多方法解决这种限制。 例如,一个程序可以用内联汇编或其他技巧自己调用操作系统。

有几件事你可以做:

锁定环境。

在某种你不需要的东西时终止程序的某个管理程序运行程序。 有一个Linux工具包来做到这一点,但我忘了它的名字。

如果你只是想检测一个程序使用了不允许的函数,那么你可以在编译器创建的二进制文件上运行nm ,并检查是否有任何不允许的函数名称出现。 注意并不是所有的函数都有一个等于它们名字的符号名称

这是如何在程序使用printf时防止编译的一个例子

#include <stdio.h> #define printf int main() { printf("%sn","Hello Worldn"); return 0; }

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。

相关推荐