Welcome, guest | Sign In | My Account | Store | Cart

This particular example pertains to running 64-bit assembly code under 64-bit Python, however if you are fortunate enough to be running on a 32-bit Linux platform you may get away with doing a lot less. Since the introduction of DEP you can no longer just shove some code in a buffer and execute it. You must first allow execute permission. Further, you cannot change memory protection settings for anything other than an entire page, so I've padded the memory allocated to include a page before and a page after, so I can seek back to the start of page for the start of my assembler, then change memory protection from there secure in the knowledge that I won't change protection for anything beyond the end of my allocated region. I could probably have used valloc() instead.

After that it's simply a matter of changing protection. There is some doubt over whether you can leave this memory read/writable after you set the execute bit, I haven't played with that.

Finally, it's important to reset the memory protection before the memory gets freed/reused by Python because Python has no knowledge of the hackery you've just been up to.

For a Windows version, use VirtualProtect() to achieve similar results. I could have conditionally added this and made the code portable, however I rarely do any assembler on Windows, so it's left as an exercise for the reader.

Python, 53 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#!/usr/bin/env python

import subprocess, os, tempfile
from ctypes import *

PAGE_SIZE = 4096    

class AssemblerFunction(object):
  
  def __init__(self, code, ret_type, *arg_types):
    # Run Nasm
    fd, source = tempfile.mkstemp(".S", "assembly", os.getcwd())
    os.write(fd, code)
    os.close(fd)
    target = os.path.splitext(source)[0]
    subprocess.check_call(["nasm",source])
    os.unlink(source)
    binary = file(target,"rb").read()
    os.unlink(target)
    bin_len = len(binary)

    # align our code on page boundary.
    self.code_buffer = create_string_buffer(PAGE_SIZE*2+bin_len)
    addr = (addressof(self.code_buffer) + PAGE_SIZE) & (~(PAGE_SIZE-1))
    memmove(addr, binary, bin_len)    
  
    # Change memory protection
    self.mprotect = cdll.LoadLibrary("libc.so.6").mprotect
    mp_ret = self.mprotect(addr, bin_len, 4)   # execute only. 
    if mp_ret: raise OSError("Unable to change memory protection")
    
    self.func = CFUNCTYPE(ret_type, *arg_types)(addr)
    self.addr = addr
    self.bin_len = bin_len
    
  def __call__(self, *args):
    return self.func(*args)
    
  def __del__(self):
    # Revert memory protection
    if hasattr(self,"mprotect"):
      self.mprotect(self.addr, self.bin_len, 3)   

 
if __name__ == "__main__":
  add_func = """ BITS 64
		    mov rax, rdi    ; Move the first parameter
		    add rax, rsi    ; add the second parameter
		    ret 		; rax will be returned 
		    """
		    
  Add = AssemblerFunction(add_func, c_int, c_int, c_int)
  print Add(1, 2)