10.13 High Sierra OSX - 使用ENOMEM授予exec权限时,Python mprotect始终失败

时间:2018-06-04 04:45:14

标签: python c macos mprotect enomem

背景

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

(禁用SIP)

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

    问题出现在mprotect函数调用中,它总是返回-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写入可能只具有读取权限的内存空间),并且程序变得很糟糕......

    问题在于mprotect,这种方法在linux中运行得很好,我现在看到mac osx的结果非常不同

    问题:

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

    更新:

    根据@DietrichEpp在评论中建议,使用use_errno = True对ctypes.CDLL调用生成errno。它评估为errno: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有特殊的限制,并为每个进程设置了最大映射,因此添加了更多权限,同一进程的新映射将超过这样的最大限制(每个进程有多少次执行/写权限映射) 。如果我的假设是真的,我们如何解决这个问题?

1 个答案:

答案 0 :(得分:1)

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

  

如果未通过调用mmap()建立映射,则未指定此函数的行为。

看起来Linux在这方面更宽容,并允许你或多或少地在你想要的任何内存上调用mprotect()。 Darwin更严格,如果你想调用mprotect(),则需要你使用mmap()中的内存。这是为什么从开始到结束阅读整个手册页都是值得的。

如果你考虑一下,这是一个合理的要求。由valloc()给出的内存由分配器管理,并且必须稍后使用free()返回到分配器,并且mprotect()在某种意义上绕过分配器的后面并改变内存的工作方式。对于mmap()和munmap(),情况并非如此,它们与mprotect()位于同一系列调用中。