views:

35

answers:

1

I have a simple byte array I've filled with a x86 -program. Which I need to execute at runtime.

"""
    Produces a simple callable procedure which returns a constant.
"""
from array import array

simple = array('B')

# mov rax, 0x10
simple.extend((0x81, 0xc0, 0x10, 0x0, 0x0, 0x0))
# ret
simple.append(0xc3)

Now, to get this running, I'll need to offload it into a memory region in my process that has PROT_EXEC flags. Also need to know the address of that memory region so I can call it. How could I do what I just described?

from ctypes import CFUNCTYPE, c_int

procedure = CFUNCTYPE(c_int)(program.address)
print "result correct: %r" % (procedure() == 0x10)
print "result: %r" % procedure()

Also, it might be useful to do this:

program[2] = 15

print "result correct: %r" % (procedure() == 15)
print "result: %r" % procedure()
A: 

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:

Cheery