10.13 High Sierra OSX - Python mprotect 在使用 ENOMEM 授予执行权限时总是失败

10.13 High Sierra OSX - Python mprotect always fails when granting exec permission, with ENOMEM

背景:

编写涉及在 python 程序中执行 mac 内部代码的概念证明。要在 osx 上执行此操作,所以我必须使用 ctypes 和 libc.dylib 以及以下函数调用:

(禁用 SIP)

  1. valloc 分配对齐内存
  2. mprotect 授予 wrx 分配内存的权限
  3. memmove 将可执行代码复制到分配的内存;投掷;和 执行...

问题:

问题出现在 mprotect 函数调用中,它总是 return -1 表示失败。

脚本:(逻辑几乎与 linux 系统相同,因为它们都是 posix 系列)

import ctypes

buf = "machine code..."
libc = cytpes.CDLL('libc.dylib')
size = len(buf)
buf_ptr = ctypes.c_char_p(buf)

# allocate aligned memory space
alloc_space = ctypes.c_void_p(ctypes.valloc(size))

# this always evaluates true, and mprotect fails every attempt
if 0 != libc.mprotect(alloc_space, size, 1 |2 |4):
  print "mprotect failed"

ctypes.mmove(alloc_space, buf_ptr, size)

mmove 现在将失败并出现段错误消息(b/c 正在写入可能只有读取权限的内存 space),并且程序出现故障...

问题出在 mprotect 上,此方法在 linux 中非常有效,我现在看到 mac osx

的结果非常不同

问题:

Mac OSX 是否有限制 mprotect 操作类型的额外安全功能(即使禁用 SIP)?如果是这样,如何绕过它?

更新:

根据@DietrichEpp 在评论中的建议,在 ctypes.CDLL 调用上使用 use_errno=True 生成了错误号。它评估为错误号:12,无法分配内存。此 errno 是 mprotect 手册页上 ENOMEM 的值。

尽管手册页上有一些 ENOMEM,但我怀疑这是最后一种情况:(b/c valloc 调用没有错误)

   ENOMEM Changing the protection of a memory region would result in the
          total number of mappings with distinct attributes (e.g., read
          versus read/write protection) exceeding the allowed maximum.
          (For example, making the protection of a range PROT_READ in
          the middle of a region currently protected as
          PROT_READ|PROT_WRITE would result in three mappings: two
          read/write mappings at each end and a read-only mapping in the
          middle.)

我怀疑 osx 有特殊限制,并为每个进程设置了最大映射,因此添加更多权限同一进程的新映射将超过这样的最大限制(有多少映射与 exec/write 每个进程的特权)。如果我的假设是正确的,我们该如何解决?

Apple 的手册页不再在线,但请参阅 POSIX man page for mprotect:

The behavior of this function is unspecified if the mapping was not established by a call to mmap().

似乎 Linux 在这方面更宽容,并允许您或多或少地在您想要的任何内存上调用 mprotect()。 Darwin 更严格,如果您想调用 mprotect(),则要求您使用 mmap() 中的内存。这就是从头到尾阅读整个手册页值得的原因之一。

仔细想想,这是一个合理的要求。 valloc() 提供的内存由分配器管理,稍后必须通过 free() 返回给分配器,而 mprotect() 在某种意义上绕过分配器并改变内存的工作方式。 mmap() 和 munmap() 不是这种情况,它们与 mprotect() 属于同一系列系统调用。