Search code examples
pythonctypesreadprocessmemoryopenprocess

ReadProcessMemory returns nothing


I was following this Stackoverflow question here. I'm trying to read the data stored in notepad.exe memory space. But my get_data function seems to return nothing. I have some text stored in notepad, i would like to retrieve that text from RAM and store it in a variable in Python. This is the code:

import os
from ctypes import *
from ctypes.wintypes import *

def get_pid(exe_name):
    x = os.popen('tasklist /FI "ImageName eq '+process_name+'"').read()
    if not x.find("No tasks are running") >= 0:
        return int(list(filter(None, x[x.find(process_name):-1].split(" ")))[1])
    return -1

def get_data(PROCESS_ID, PROCESS_HEADER_ADDR, STRLEN=255, PROCESS_VM_READ=0x0010):
    k32 = WinDLL('kernel32')
    k32.OpenProcess.argtypes = DWORD,BOOL,DWORD
    k32.OpenProcess.restype = HANDLE
    k32.ReadProcessMemory.argtypes = HANDLE,LPVOID,LPVOID,c_size_t,POINTER(c_size_t)
    k32.ReadProcessMemory.restype = BOOL

    process = k32.OpenProcess(PROCESS_VM_READ, 0, PROCESS_ID)
    buf = create_string_buffer(STRLEN)
    s = c_size_t()
    if k32.ReadProcessMemory(process, PROCESS_HEADER_ADDR, buf, STRLEN, byref(s)):
        return (s.value,buf.raw)


process_name = "notepad.exe"
pid = get_pid(process_name)
process_header_addr = 0x7FF79A1E0000 # address from VMMap

data = get_data(pid, process_header_addr)

when i run this code, there is no data it just prints nothing:

>>> print(data)
None
>>> 

how can I retrieve the data?


Solution

  • [MS.Docs]: ReadProcessMemory function states:

    If the function fails, the return value is 0 (zero). To get extended error information, call GetLastError.

    Here's a small example.

    code00.py:

    #!/usr/bin/env python
    
    import sys
    import ctypes as ct
    from ctypes import wintypes as wt
    
    
    PROCESS_VM_READ = 0x0010
    
    
    def main(*argv):
        kernel32 = ct.WinDLL("kernel32")
    
        OpenProcess = kernel32.OpenProcess
        OpenProcess.argtypes = [wt.DWORD, wt.BOOL, wt.DWORD]
        OpenProcess.restype = wt.HANDLE
    
        ReadProcessMemory = kernel32.ReadProcessMemory
        ReadProcessMemory.argtypes = [wt.HANDLE, wt.LPCVOID, wt.LPVOID, ct.c_size_t, ct.POINTER(ct.c_size_t)]
        ReadProcessMemory.restype = wt.BOOL
    
        GetLastError = kernel32.GetLastError
        GetLastError.argtypes = []
        GetLastError.restype = wt.DWORD
    
        CloseHandle = kernel32.CloseHandle
        CloseHandle.argtypes = [wt.HANDLE]
        CloseHandle.restype = wt.BOOL
    
        np_pid = 34376  # Got it from a process monitoring tool
        np = OpenProcess(PROCESS_VM_READ, 0, np_pid)
        if not np:
            print("OpenProcess failed: {0:d}".format(GetLastError()))
            return
    
        buf_len = 0x0F # 0xFF  # Lower value for display purposes
        buf = ct.create_string_buffer(buf_len)
        read = ct.c_size_t()
        addr = 0x00001CF26F20000  # Got a readable address from VMMap as well, but I don't know the one where the actual text is stored
    
        res = ReadProcessMemory(np, addr, buf, buf_len, ct.byref(read))
        if res:
            print("Read ({0:d} bytes) from process ({1:d}) address 0x{2:016X}:".format(read.value, np_pid, addr))
            text = ""
            for i in range(read.value):
                text += " 0x{0:02X}".format(ord(buf[i]))
            print(text)
        else:
            print("ReadProcessMemory failed: {0:d}".format(GetLastError()))
    
        CloseHandle(np)
    
    
    if __name__ == "__main__":
        print("Python {0:s} {1:d}bit on {2:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")), 64 if sys.maxsize > 0x100000000 else 32, sys.platform))
        main(*sys.argv[1:])
        print("\nDone.")
    

    Output:

    [cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q063273381]> "e:\Work\Dev\VEnvs\py_pc064_03.07.06_test0\Scripts\python.exe" code00.py
    Python 3.7.6 (tags/v3.7.6:43364a7ae0, Dec 19 2019, 00:42:30) [MSC v.1916 64 bit (AMD64)] 64bit on win32
    
    Read (15 bytes) from process (34376) address 0x000001CF26F20000:
     0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0xC2 0x3B 0x78 0x62 0xE6 0xFA 0x00
    
    Done.
    


    Update #0

    I don't know how Notepad organizes its memory internally. I can assume that the text is stored in a buffer (or maybe more, could be one per line, ...) which should reside in the heap area. But where exactly I can't say. You could inspect the process memory using a tool (I know that CheatEngine can do that) do a match between the memory contents and the text, and get that address, and paste it in the code, but I think that would:

    • Beat the very purpose of the script (as scripts are used for automation, to do the work instead of the user)
    • Not be reliable. A buffer is allocated with a specific length. If the user keeps typing stuff in Notepad, eventually that buffer will get full and (behind the scenes) it will be relocated which will (most likely) change its address


    All in all, I don't think this is the way to go. You could search for alternatives, like using WinAPIs to send messages (maybe WM_GETTEXT) to the Notepad window to get the text. I don't know exactly how to do it, but I remember I was able to programmatically insert characters in Notepad using WM_CHAR.
    Or you could send a Ctrl + A, Ctrl + C, and then get the clipboard contents.