【问题标题】:mprotect entire program, to run dangerous codemprotect 整个程序,运行危险代码
【发布时间】:2011-12-28 01:14:59
【问题描述】:

我有一个小程序,它映射有潜在危险的可执行代码(使用 PROT_EXEC),调用prctl(PR_SET_SECCOMP, 1),然后执行这个映射的代码。这一切都很好,并且允许我通过将 mmap'd 区域同步到磁盘来“保存”评估状态,并稍后重新加载(很可能在另一台机器上进行负载平衡)。然而,这种技术并不总是有效——因为这段代码可能已经对不在 mmap'd 区域中的程序进行了更改,并且这些信息将会丢失。

所以我想做的是在调用代码之前将所有内容(除了这个 mmap'd 区域)设为只读。这样我就可以保证可执行代码不能改变除了 mmap'd 区域以外的任何东西的状态,我可以随意序列化/反序列化。

顺便说一句,这是 x86_64 上的 Linux

谢谢

【问题讨论】:

  • 如果你成功将内存设为只读,你的程序在尝试写入时不会失败吗?
  • 如果它试图在其 mmap'd 区域之外写入,那么失败就可以了
  • hmmm...如果您有 mprotect 代码的权限,并且您在不放弃这些权限的情况下运行可执行代码,则可执行代码只需调用 mprotect 即可重新获得这些权限。
  • @gby 否,因为 prctl 调用禁用了执行系统调用的能力

标签: c linux posix sandbox mprotect


【解决方案1】:

首先,观察:没有什么说您必须mmap() 才能将机器指令放入内存或将它们保存回文件。 read()write() 也可以做到这一点,只是注意你应该为此目的创建一个可写和可执行的私有映射。

显然,如果要在同一进程中执行,您不能可靠地禁用对将调用您将加载的可执行代码的堆栈区域的写入,因为这会使堆栈不可用。您可以通过注释变量或使用程序集来解决此问题。

您的下一个选项是fork()。您可以将子级中的exec 放入一个特殊的包装器可执行文件中,该可执行文件允许恶意可执行代码(仅提供加载/转储)造成的损害和自省最小化,或者您可以通过让子级修改自身以达到相同效果来做同样的事情。这仍然不是 100% 安全的。

提案0

  • 创建与最小库 (-nodefaultlibs) 链接的独立二进制文件。
  • 在子级中的forkptrace(PTRACE_TRACEME) 之后(以便您可以可靠地读取内存内容并进行其他干预),并关闭除管道之外的所有句柄(为简单起见,仅在stdin 中)。 exec() 进入上述包装二进制文件。

在包装二进制文件中:

  • mmap 位于已知位置的私有区域,具有写入和执行权限。或者,如果大小固定,您可以静态分配该区域。
  • 将管道内容读入区域。
  • 关闭管道。现在该进程没有打开的句柄。
  • prctl(PR_SET_SECCOMP, 1)。现在唯一有效的系统调用是_exitsigreturn。既然进程不能raise,那么sigreturn应该没什么用。
  • 从主堆栈(应该是唯一的堆栈)中删除写权限。由于您无意返回,并且会立即跳转,因此您不需要再次触摸堆栈。
  • 跳转到区域内的起始位置。使用程序集执行此操作,或创建一个函数指针并调用它(如果您可以在不推入堆栈的情况下使其工作)。现在您应该正在执行一个内存区域,该区域是唯一可用的可写区域。主堆栈受到保护,由于缺乏库支持,不应使用堆。

在父级中:

  • 使用ptracewait,捕获错误或成功完成。
  • 通过/proc/<pid>/mem 或等效文件读取已知位置的映射区域。

【讨论】:

  • 这是一种不错的替代方式,但我看不出它如何解决我的问题。可执行代码仍可能在某处进行了更改,而这些更改未通过将映射区域写入文件来捕获。
  • 另外,顺便说一句,我使用 mmap 区域并在那里加载代码的原因是因为 Linux 不允许我为普通内存页面设置可执行权限,这是一个大问题用于加载反射/自我修改代码
  • @Heptic:不,它不能。关键是创建一个没有句柄的最小进程,只有崩溃或_exit 作为结束的可行方式。一旦结束,父进程就会转储可执行内存区域。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-08-24
  • 2023-04-07
  • 1970-01-01
  • 1970-01-01
  • 2012-02-15
  • 2019-04-26
  • 1970-01-01
相关资源
最近更新 更多