Search code examples
pythonc++memorybufferctypes

python ctypes memory read stops at first zero integer


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:

enter image description here


Solution

  • .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'\x01\x02\x03'
    >>> read_buffer.raw     # entire 20-byte buffer
    b'\x01\x02\x03\x00\x04\x05\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
    >>> read_buffer.raw[:7] # trimmed to a known size
    b'\x01\x02\x03\x00\x04\x05\x06'
    

    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'ABC\x00DEF'  # 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'\x02\x00\x00\x00\x00\x00\x00\x00\xb0\xb10\xa8\xf8\x7f\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\xc2\xe23-\xea\x99\xdd\x97ABC\x00DEF\x00'
    b'\x02\x00\x00\x00\x00\x00\x00\x00\xb0\xb10\xa8\xf8\x7f\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\xc2\xe23-\xea\x99\xdd\x97ABC\x00DEF\x00'