I solved this on my own. Maybe there's not much to say into it anyway.
I did a library for this. It's a small wrapping around linux mmap
-command.
mmap module provided by python weren't sufficient. I couldn't get the address out of an object. Instead I had to provide my own module for just doing that.
# -*- coding: utf-8 -*-
from ctypes import (
pythonapi, c_void_p, c_size_t, c_int, c_uint64,
c_byte, cast, POINTER, memmove, string_at,
)
import errno
mmap = pythonapi.mmap
mmap.restype = c_void_p
mmap.argtypes = [c_void_p, c_size_t, c_int, c_int, c_int, c_uint64]
munmap = pythonapi.munmap
munmap.restype = c_int
munmap.argtypes = [c_void_p, c_size_t]
errno_location = pythonapi.__errno_location
errno_location.restype = POINTER(c_int)
errormessage = lambda: errno.errorcode[errno_location()[0]]
PROT_NONE = 0
PROT_READ = 1
PROT_WRITE = 2
PROT_EXEC = 4
MAP_SHARED = 1
MAP_PRIVATE = 2
MAP_ANONYMOUS = 0x20
class RawData(object):
"Allocated with mmap -call, no file handles."
def __init__(self, length, prot):
flags = MAP_PRIVATE | MAP_ANONYMOUS
self.address = mmap(None, length, prot, flags, -1, 0)
if 0 == self.address:
raise Exception(errormessage())
self.__length = length
self.__accessor = cast(self.address, POINTER(c_byte))
def __len__(self):
return self.__length
def __getitem__(self, key):
assert key < len(self)
return self.__accessor[key]
def __setitem__(self, key, value):
assert key < len(self)
self.__accessor[key] = value
def close(self):
"the mapped memory must be freed manually"
if 0 != munmap(self.address, len(self)):
raise Exception(errormessage())
def poke(self, offset, data):
"poke data (from a tuple) into requested offset"
for i, byte in enumerate(data):
self[offset+i] = byte
def upload(self, data, offset=0):
"upload the data from a string"
data = data.tostring()
assert offset+len(data) <= len(self)
memmove(self.address+offset, data, len(data))
def tostring(self):
return string_at(self.address, len(self))
__all__ = [
'PROT_NONE',
'PROT_READ',
'PROT_WRITE',
'PROT_EXEC',
'RawData',
]
I also wrote an utility library for little-endian integers which supplements the toolchain:
# -*- coding: utf-8 -*-
TWOPOWER32 = 1 << 32
TWOPOWER64 = 1 << 64
TWOPOWER31 = TWOPOWER32 >> 1
TWOPOWER63 = TWOPOWER64 >> 1
def uint32(value):
assert 0 <= value < TWOPOWER32
return (
value >> 0 & 255,
value >> 8 & 255,
value >> 16 & 255,
value >> 24 & 255
)
def uint64(value):
assert 0 <= value < TWOPOWER64
return (
value >> 0 & 255,
value >> 8 & 255,
value >> 16 & 255,
value >> 24 & 255,
value >> 32 & 255,
value >> 40 & 255,
value >> 48 & 255,
value >> 56 & 255
)
def int32(value):
assert -TWOPOWER31 <= value < TWOPOWER31
return uint32((TWOPOWER32 + value) & (TWOPOWER32-1))
def int64(value):
assert -TWOPOWER63 <= value < TWOPOWER63
return uint64((TWOPOWER64 + value) & (TWOPOWER64-1))
__all__ = ['uint32', 'int32', 'uint64', 'int64']
It's simple stuff. Here's some usage example:
from ctypes import CFUNCTYPE, c_int
from array import array
#... bunch of imports
simple = array('B')
# x86 and x64 machine code (MOV eax, 0x10; RET)
simple.extend((0x81, 0xc0) + int32(0x10))
simple.append(0xc3)
program = RawData(len(simple), PROT_READ|PROT_WRITE|PROT_EXEC)
program.upload(simple)
procedure = CFUNCTYPE(c_int)(program.address)
print "result:", procedure()
# alters the first instruction
program.poke(2, int32(123))
print "result:", procedure()
# transforms that first instruction into (NOP,NOP,NOP,NOP,NOP,NOP)
program.poke(0, [0x90]*6)
print "result:", procedure()
I think I'll have fun with it. http://hg.boxbase.org/ will eventually host this module.
I'm Using opcode and instruction references to select instructions. Here's few such references: