设为首页 - 加入收藏 ASP站长网(Aspzz.Cn)- 科技、建站、经验、云计算、5G、大数据,站长网!
热搜: 数据 手机 公司
当前位置: 首页 > 服务器 > 搭建环境 > Windows > 正文

使用Ptrace去拦截和仿真Linux系统调用(3)

发布时间:2018-08-25 17:42 所属栏目:117 来源:Chris Wellons
导读:为演示需要,我同时构建了一个非常小的接口,这在实践中并不是个好主意。它与 OpenBSD 的 pledge(2) 稍有一些相似之处,它使用了一个 字符串接口。 事实上 ,设计一个健壮且安全的权限集是非常复杂的,正如在 pledg

为演示需要,我同时构建了一个非常小的接口,这在实践中并不是个好主意。它与 OpenBSD 的 pledge(2) 稍有一些相似之处,它使用了一个 字符串接口。事实上,设计一个健壮且安全的权限集是非常复杂的,正如在 pledge(2) 的手册页面上所显示的那样。下面是对被跟踪进程的系统调用的完整接口实现:

  1. #define _GNU_SOURCE
  2. #include <unistd.h>
  3.  
  4. #define XPLEDGE_RDWR (1 << 0)
  5. #define XPLEDGE_OPEN (1 << 1)
  6.  
  7. #define xpledge(arg) syscall(SYS_xpledge, arg)

如果给它传递个参数 0 ,仅允许一些基本的系统调用,包括那些用于去分配内存的系统调用(比如 brk(2))。 PLEDGE_RDWR 位允许 各种 读和写的系统调用(read(2)readv(2)pread(2)preadv(2) 等等)。PLEDGE_OPEN 位允许 open(2)

为防止发生提升权限的行为,pledge() 会拦截它自己 —— 但这样也防止了权限撤销,以后再细说这方面内容。

在 xpledge 跟踪器中,我需要去检查这个系统调用:

  1. /* Handle entrance */
  2. switch (regs.orig_rax) {
  3. case SYS_pledge:
  4. register_pledge(regs.rdi);
  5. break;
  6. }

操作系统将返回 ENOSYS(函数尚未实现),因为它不是一个真实的系统调用。为此在退出时我用一个 success(0) 去覆写它。

  1. /* Handle exit */
  2. switch (regs.orig_rax) {
  3. case SYS_pledge:
  4. ptrace(PTRACE_POKEUSER, pid, RAX * 8, 0);
  5. break;
  6. }

我写了一小段测试程序去打开 /dev/urandom,做一个读操作,尝试去承诺后,然后试着第二次打开 /dev/urandom,然后确认它能够读取原始的 /dev/urandom 文件描述符。在没有承诺跟踪器的情况下运行,输出如下:

  1. $ ./example
  2. fread("/dev/urandom")[1] = 0xcd2508c7
  3. XPledging...
  4. XPledge failed: Function not implemented
  5. fread("/dev/urandom")[2] = 0x0be4a986
  6. fread("/dev/urandom")[1] = 0x03147604

做一个无效的系统调用并不会让应用程序崩溃。它只是失败,这是一个很方便的返回方式。当它在跟踪器下运行时,它的输出如下:

  1. >$ ./xpledge ./example
  2. fread("/dev/urandom")[1] = 0xb2ac39c4
  3. XPledging...
  4. fopen("/dev/urandom")[2]: Operation not permitted
  5. fread("/dev/urandom")[1] = 0x2e1bd1c4

这个承诺很成功,第二次的 fopen(3) 并没有进行,因为跟踪器用一个 EPERM 阻塞了它。

可以将这种思路进一步发扬光大,比如,改变文件路径或返回一个假的结果。一个跟踪器可以很高效地 chroot 它的被跟踪进程,通过一个系统调用将任意路径传递给 root 从而实现 chroot 路径。它甚至可以对用户进行欺骗,告诉用户它以 root 运行。事实上,这些就是 Fakeroot NG 程序所做的事情。

仿真外部系统

假设你不满足于仅拦截一些系统调用,而是想拦截全部系统调用。你就会有了 一个打算在其它操作系统上运行的二进制程序,无需系统调用,这个二进制程序可以一直运行。

使用我在前面所描述的这些内容你就可以管理这一切。跟踪器可以使用一个假冒的东西去代替系统调用号,允许它失败,以及为系统调用本身提供服务。但那样做的效率很低。其实质上是对每个系统调用做了三个上下文切换:一个是在入口上停止,一个是让系统调用总是以失败告终,还有一个是在系统调用退出时停止。

从 2005 年以后,对于这个技术,PTrace 的 Linux 版本有更高效的操作:PTRACE_SYSEMU。PTrace 仅在每个系统调用发出时停止一次,在允许被跟踪进程继续运行之前,由跟踪器为系统调用提供服务。

  1. for (;;) {
  2. ptrace(PTRACE_SYSEMU, pid, 0, 0);
  3. waitpid(pid, 0, 0);
  4.  
  5. struct user_regs_struct regs;
  6. ptrace(PTRACE_GETREGS, pid, 0, &regs);
  7.  
  8. switch (regs.orig_rax) {
  9. case OS_read:
  10. /* ... */
  11.  
  12. case OS_write:
  13. /* ... */
  14.  
  15. case OS_open:
  16. /* ... */
  17.  
  18. case OS_exit:
  19. /* ... */
  20.  
  21. /* ... and so on ... */
  22. }
  23. }

(编辑:ASP站长网)

网友评论
推荐文章
    热点阅读