Background:
Writing a proof of concept that involves executing machine code within a python program. To do this on osx so I had to utilize ctypes and libc.dylib and the following function calls:
(With SIP disabled)
- valloc to allocate aligned memory
- mprotect to grant wrx permission on allocated memory
- memmove to copy executable code to allocated memory; cast; and execute...
Problem:
The issue arises at the mprotect function call, where it always return -1 for fail.
Script: (logic almost identical to a linux system as they both are posix family)
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)
The mmove will now fail with a segfault error message (b/c writing the memory space that probably only had read privilege), and program comes to a hault...
The problem is with mprotect, this method works very well in linux, I am now seeing that the results are very different for mac osx
Question:
Does Mac OSX have extra security features (even with SIP disabled) that restricts the mprotect type of operation? And if so, how can one bypass it?
UPDATE:
As per @DietrichEpp suggested in the comments, using use_errno=True on ctypes.CDLL call generated the errno. It evaluated to errno: 12, Cannot allocate memory. This errno is the value for ENOMEM on the mprotect man page.
Although there were a few ENOMEM on the man page, I suspect it is the last scenario: (b/c there were no error with the valloc call)
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.)
I suspect that osx have special restrictions, and have set the maximum mappings for each process, hence adding more permissions a new mapping of the same process will exceed such maximum limit (of how many mappings with exec/write privileges per process). If my assumptions were true, how can we work around that?