I'm trying to call inlined machine code from pure Python code on Linux. To this end, I embed the code in a bytes literal
code = b"\x55\x89\xe5\x5d\xc3"
and then call mprotect()
via ctypes
to allow execution of the page containing the code. Finally, I try to use ctypes
to call the code. Here is my full code:
#!/usr/bin/python3
from ctypes import *
# Initialise ctypes prototype for mprotect().
# According to the manpage:
# int mprotect(const void *addr, size_t len, int prot);
libc = CDLL("libc.so.6")
mprotect = libc.mprotect
mprotect.restype = c_int
mprotect.argtypes = [c_void_p, c_size_t, c_int]
# PROT_xxxx constants
# Output of gcc -E -dM -x c /usr/include/sys/mman.h | grep PROT_
# #define PROT_NONE 0x0
# #define PROT_READ 0x1
# #define PROT_WRITE 0x2
# #define PROT_EXEC 0x4
# #define PROT_GROWSDOWN 0x01000000
# #define PROT_GROWSUP 0x02000000
PROT_NONE = 0x0
PROT_READ = 0x1
PROT_WRITE = 0x2
PROT_EXEC = 0x4
# Machine code of an empty C function, generated with gcc
# Disassembly:
# 55 push %ebp
# 89 e5 mov %esp,%ebp
# 5d pop %ebp
# c3 ret
code = b"\x55\x89\xe5\x5d\xc3"
# Get the address of the code
addr = addressof(c_char_p(code))
# Get the start of the page containing the code and set the permissions
pagesize = 0x1000
pagestart = addr & ~(pagesize - 1)
if mprotect(pagestart, pagesize, PROT_READ|PROT_WRITE|PROT_EXEC):
raise RuntimeError("Failed to set permissions using mprotect()")
# Generate ctypes function object from code
functype = CFUNCTYPE(None)
f = functype(addr)
# Call the function
print("Calling f()")
f()
This code segfaults on the last line.
Why do I get a segfault? The
mprotect()
call signals success, so I should be permitted to execute code in the page.Is there a way to fix the code? Can I actually call the machine code in pure Python and inside the current process?
(Some further remarks: I'm not really trying to achieve a goal -- I'm trying to understand how things work. I also tried to use 2*pagesize
instead of pagesize
in the mprotect()
call to rule out the case that my 5 bytes of code fall on a page boundary -- which should be impossible anyway. I used Python 3.1.3 for testing. My machine is an 32-bit i386 box. I know one possible solution would be to create a ELF shared object from pure Python code and load it via ctypes
, but that's not the answer I'm looking for :)
Edit: The following C version of the code is working fine:
#include <sys/mman.h>
char code[] = "\x55\x89\xe5\x5d\xc3";
const int pagesize = 0x1000;
int main()
{
mprotect((int)code & ~(pagesize - 1), pagesize,
PROT_READ|PROT_WRITE|PROT_EXEC);
((void(*)())code)();
}
Edit 2: I found the error in my code. The line
addr = addressof(c_char_p(code))
first creates a ctypes char*
pointing to the beginning of the bytes
instance code
. addressof()
applied to this pointer does not return the address this pointer is pointing to, but rather the address of the pointer itself.
The simplest way I managed to figure out to actually get the address of the beginning of the code is
addr = addressof(cast(c_char_p(code), POINTER(c_char)).contents)
Hints for a simpler solution would be appreciated :)
Fixing this line makes the above code "work" (meaning it does nothing instead of segfaulting...).