python ctypes memory read stops at first zero integer
Question:
To all ctypes experts:
I am running this code in Python to read 0x400 entries from memory.
Unfortunately the returned list only contains ~20 entries. It seems to stop reading at the first entry with the value equal to 0.
read_buffer = (ctypes.c_char * buffsize)()
lp_buffer = ctypes.byref(read_buffer)
n_size = ctypes.sizeof(read_buffer)
lp_number_of_bytes_read = ctypes.c_ulong(0)
ctypes.windll.kernel32.ReadProcessMemory(self.handle, ctypes.c_void_p(lp_base_address), lp_buffer, n_size, lp_number_of_bytes_read)
return read_buffer.value
Is there any explanation/fix for this?
Here is an example output from my C++ implementation, where all 0x400 entries were read:
Answers:
.value
reads a null-terminated string from the buffer. Use .raw
and trim to the known returned size.
Here’s a comparison:
>>> import ctypes
>>> read_buffer = (ctypes.c_char * 20)(bytes([1,2,3,0,4,5,6]))
>>> read_buffer.value # reads until null byte
b'x01x02x03'
>>> read_buffer.raw # entire 20-byte buffer
b'x01x02x03x00x04x05x06x00x00x00x00x00x00x00x00x00x00x00x00x00'
>>> read_buffer.raw[:7] # trimmed to a known size
b'x01x02x03x00x04x05x06'
This example read’s CPython’s own memory space and compares the memory read using two methods. Note that in CPython, id()
returns the memory address of a Python object:
import sys
import ctypes as ct
import ctypes.wintypes as w
k32 = ct.WinDLL('kernel32')
ReadProcessMemory = k32.ReadProcessMemory
ReadProcessMemory.argtypes = w.HANDLE, w.LPCVOID, w.LPVOID, ct.c_size_t, ct.POINTER(ct.c_size_t)
ReadProcessMemory.restype = w.BOOL
GetCurrentProcess = k32.GetCurrentProcess
GetCurrentProcess.argtypes = ()
GetCurrentProcess.restype = w.HANDLE
s = b'ABCx00DEF' # object to example
obj_len = sys.getsizeof(s) # total object length (not string length)
data = ct.create_string_buffer(obj_len)
read = ct.c_size_t()
if ReadProcessMemory(GetCurrentProcess(), id(s), data, len(data), ct.byref(read)):
print(f'{data.raw[:read.value]}')
print(ct.string_at(id(s), obj_len))
Output:
b'x02x00x00x00x00x00x00x00xb0xb10xa8xf8x7fx00x00x07x00x00x00x00x00x00x00xc2xe23-xeax99xddx97ABCx00DEFx00'
b'x02x00x00x00x00x00x00x00xb0xb10xa8xf8x7fx00x00x07x00x00x00x00x00x00x00xc2xe23-xeax99xddx97ABCx00DEFx00'
To all ctypes experts:
I am running this code in Python to read 0x400 entries from memory.
Unfortunately the returned list only contains ~20 entries. It seems to stop reading at the first entry with the value equal to 0.
read_buffer = (ctypes.c_char * buffsize)()
lp_buffer = ctypes.byref(read_buffer)
n_size = ctypes.sizeof(read_buffer)
lp_number_of_bytes_read = ctypes.c_ulong(0)
ctypes.windll.kernel32.ReadProcessMemory(self.handle, ctypes.c_void_p(lp_base_address), lp_buffer, n_size, lp_number_of_bytes_read)
return read_buffer.value
Is there any explanation/fix for this?
Here is an example output from my C++ implementation, where all 0x400 entries were read:
.value
reads a null-terminated string from the buffer. Use .raw
and trim to the known returned size.
Here’s a comparison:
>>> import ctypes
>>> read_buffer = (ctypes.c_char * 20)(bytes([1,2,3,0,4,5,6]))
>>> read_buffer.value # reads until null byte
b'x01x02x03'
>>> read_buffer.raw # entire 20-byte buffer
b'x01x02x03x00x04x05x06x00x00x00x00x00x00x00x00x00x00x00x00x00'
>>> read_buffer.raw[:7] # trimmed to a known size
b'x01x02x03x00x04x05x06'
This example read’s CPython’s own memory space and compares the memory read using two methods. Note that in CPython, id()
returns the memory address of a Python object:
import sys
import ctypes as ct
import ctypes.wintypes as w
k32 = ct.WinDLL('kernel32')
ReadProcessMemory = k32.ReadProcessMemory
ReadProcessMemory.argtypes = w.HANDLE, w.LPCVOID, w.LPVOID, ct.c_size_t, ct.POINTER(ct.c_size_t)
ReadProcessMemory.restype = w.BOOL
GetCurrentProcess = k32.GetCurrentProcess
GetCurrentProcess.argtypes = ()
GetCurrentProcess.restype = w.HANDLE
s = b'ABCx00DEF' # object to example
obj_len = sys.getsizeof(s) # total object length (not string length)
data = ct.create_string_buffer(obj_len)
read = ct.c_size_t()
if ReadProcessMemory(GetCurrentProcess(), id(s), data, len(data), ct.byref(read)):
print(f'{data.raw[:read.value]}')
print(ct.string_at(id(s), obj_len))
Output:
b'x02x00x00x00x00x00x00x00xb0xb10xa8xf8x7fx00x00x07x00x00x00x00x00x00x00xc2xe23-xeax99xddx97ABCx00DEFx00'
b'x02x00x00x00x00x00x00x00xb0xb10xa8xf8x7fx00x00x07x00x00x00x00x00x00x00xc2xe23-xeax99xddx97ABCx00DEFx00'