Search code examples
pythonpython-3.xmemory-addresspywin32

Python3 get process base-address from PID


I am trying to get the base-address of a process in Windows (64-bit), with Python3, assuming to know the PID. I looked over all questions here on stack, but the solutions are old/not working.

I assume to have the PID of the process in a variable called pid.

One of the many pieces of code I tried is

PROCESS_ALL_ACCESS = 0x1F0FFF
processHandle = win32api.OpenProcess(PROCESS_ALL_ACCESS, False, pid)
modules = win32process.EnumProcessModules(processHandle)
fileName = win32process.GetModuleFileNameEx(processHandle, modules[0])
base_address = win32api.GetModuleHandle(fileName)
processHandle.close()

But I get error on GetModuleHandle: 'Impossible to find the specified module'.

Thank you for the help.


Solution

  • According to [MS.Docs]: GetModuleHandleW function (emphasis is mine):

    Retrieves a module handle for the specified module. The module must have been loaded by the calling process.

    That means that it will work fine for the current process, but for any other one you'd get Undefined Behavior, because you try retrieving:

    1. The .dll (or .exe) name from the other process (GetModuleFileNameEx call)
    2. The handle for the name at previous step (GetModuleHandle call) but in the current process (if loaded), which makes no sense

    Although there's no clear documentation on this topic (or at least I couldn't find any), the handle is the base address. This is a principle that you also rely on (calling GetModuleHandle), but you can use the values returned by EnumProcessModules directly (look at the example below, the values are the same).

    If you want to be rigorous, you could use [MS.Docs]: GetModuleInformation function. Unfortunately, that's not exported by PyWin32, and an alternative is using [Python 3.Docs]: ctypes - A foreign function library for Python.

    code00.py:

    #!/usr/bin/env python3
    
    import sys
    import win32api as wapi
    import win32process as wproc
    import win32con as wcon
    import ctypes as ct
    from ctypes import wintypes as wt
    import traceback as tb
    
    
    class MODULEINFO(ct.Structure):
        _fields_ = [
            ("lpBaseOfDll", ct.c_void_p),
            ("SizeOfImage", wt.DWORD),
            ("EntryPoint", ct.c_void_p),
        ]
    
    get_module_information_func_name = "GetModuleInformation"
    GetModuleInformation = getattr(ct.WinDLL("kernel32"), get_module_information_func_name, getattr(ct.WinDLL("psapi"), get_module_information_func_name))
    GetModuleInformation.argtypes = [wt.HANDLE, wt.HMODULE, ct.POINTER(MODULEINFO)]
    GetModuleInformation.restype = wt.BOOL
    
    
    def get_base_address_original(process_handle, module_handle):
        module_file_name = wproc.GetModuleFileNameEx(process_handle, module_handle)
        print("    File for module {0:d}: {1:s}".format(module_handle, module_file_name))
        module_base_address = wapi.GetModuleHandle(module_file_name)
        return module_base_address
    
    
    def get_base_address_new(process_handle, module_handle):
        module_info = MODULEINFO()
        res = GetModuleInformation(process_handle.handle, module_handle, ct.byref(module_info))
        print("    Result: {0:}, Base: {1:d}, Size: {2:d}".format(res, module_info.lpBaseOfDll, module_info.SizeOfImage))
        if not res:
            print("    {0:s} failed: {1:d}".format(get_module_information_func_name, getattr(ct.WinDLL("kernel32"), "GetLastError")()))
        return module_info.lpBaseOfDll
    
    
    def main(*argv):
        pid = int(argv[0]) if argv and argv[0].isdecimal() else wapi.GetCurrentProcessId()
        print("Working on pid {0:d}".format(pid))
        process_handle = wapi.OpenProcess(wcon.PROCESS_ALL_ACCESS, False, pid)
        print("Process handle: {0:d}".format(process_handle.handle))
        module_handles = wproc.EnumProcessModules(process_handle)
        print("Loaded modules: {0:}".format(module_handles))
        module_index = 0  # 0 - the executable itself
        module_handle = module_handles[module_index]
        get_base_address_funcs = [
            #get_base_address_original,  # Original behavior moved in a function
            get_base_address_new,
        ]
        for get_base_address in get_base_address_funcs:
            print("\nAttempting {0:s}".format(get_base_address.__name__))
            try:
                module_base_address = get_base_address(process_handle, module_handle)
                print("    Base address: 0x{0:016X} ({1:d})".format(module_base_address, module_base_address))
            except:
                tb.print_exc()
        process_handle.close()
        #input("\nPress ENTER to exit> ")
    
    
    if __name__ == "__main__":
        print("Python {0:s} {1:d}bit on {2:s}\n".format(" ".join(item.strip() for item in sys.version.split("\n")), 64 if sys.maxsize > 0x100000000 else 32, sys.platform))
        main(*sys.argv[1:])
        print("\nDone.")
    

    Output:

    e:\Work\Dev\StackOverflow\q059610466>"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
    
    Working on pid 59608
    Process handle: 452
    Loaded modules: (140696816713728, 140714582343680, 140714572513280, 140714535354368, 140714547544064, 140713592946688, 140714443341824, 140714557898752, 140714556325888, 140714550362112, 140714414964736, 140714562486272, 140714532798464, 140714555473920, 140714548592640, 140714533322752, 140714531946496, 140714553769984, 140714555670528, 140714558750720, 140714581426176, 140714556129280, 140714546036736, 140714518052864, 140714532601856, 140714524737536, 140714210361344, 1797128192, 140714574151680, 140714535026688, 140714557046784, 140714538172416, 140714531291136, 140714530963456, 140714530766848, 140714530832384, 1796931584, 140714561044480, 140714573299712, 140714215014400, 140714529849344, 1798438912, 140714559995904, 140714167042048)
    
    Attempting get_base_address_new
        Result: 1, Base: 140696816713728, Size: 110592
        Base address: 0x00007FF687C80000 (140696816713728)
    
    Done.
    
    e:\Work\Dev\StackOverflow\q059610466>:: Attempting to run with Task Manager pid
    e:\Work\Dev\StackOverflow\q059610466>"e:\Work\Dev\VEnvs\py_pc064_03.07.06_test0\Scripts\python.exe" code00.py 22784
    Python 3.7.6 (tags/v3.7.6:43364a7ae0, Dec 19 2019, 00:42:30) [MSC v.1916 64 bit (AMD64)] 64bit on win32
    
    Working on pid 22784
    Process handle: 480
    Loaded modules: (140699900903424, 140714582343680, 140714572513280, 140714535354368, 140714547544064, 140714573299712, 140714531946496, 140714550362112, 140714562486272, 140714532798464, 140714530963456, 140714530766848, 140714556981248, 140714557898752, 140714556325888, 140714555473920, 140714365222912, 140714548592640, 140714496753664, 140714533322752, 140714553769984, 140714574151680, 140714535026688, 140714557046784, 140714538172416, 140714581426176, 140714558750720, 140714531291136, 140714530832384, 140714546036736, 140714444521472, 140714567467008, 140714532601856, 140714468966400, 140714452385792, 140714267115520, 140714510843904, 140714478731264, 140713698263040, 140714510254080, 140714556129280, 140714565435392, 140714110091264, 140714491379712, 140714455007232, 140714514382848, 140714459529216, 140714281140224, 140714370859008, 140714471260160, 140714566746112, 140713839362048, 140714555670528, 140714171695104, 140714508615680, 140714514841600, 140714029154304, 140714036625408, 140714329636864, 140714447011840, 140714434691072, 140714470866944, 140714561044480, 140714520870912, 140714469883904, 140714494787584, 140714293592064, 140713999335424, 140714400743424, 140714497605632, 140714502193152, 140714197254144, 140714415030272, 140714035576832, 140714065854464, 140714513006592, 140714529652736, 140714512809984, 140714495049728, 140714038657024, 140714371448832, 140714421911552, 140714325966848, 140714196074496, 140714057924608, 140714058317824, 140714064281600, 140714058121216, 140714519756800, 140714327539712, 140714311614464, 140714501079040, 140714546167808, 140714531422208, 140714531553280, 140714557767680, 140714518052864, 140714524737536, 140714167631872, 140714528669696, 140714331865088, 140714310369280, 140714310238208, 140714520018944, 140714458939392, 2018133999616, 140714401988608, 2018141863936, 140714514644992, 140714454810624, 140714294640640)
    
    Attempting get_base_address_new
        Result: 1, Base: 140699900903424, Size: 1105920
        Base address: 0x00007FF73F9D0000 (140699900903424)
    
    Done.
    



    Update #0

    According to [MS.Docs]: MODULEINFO structure (Remarks section, emphasis still mine):

    The load address of a module is the same as the HMODULE value.

    So, things seem to be pretty straightforward.

    code01.py:

    #!/usr/bin/env python3
    
    import sys
    import win32api as wapi
    import win32process as wproc
    import win32con as wcon
    
    
    def main(*argv):
        pid = int(argv[0]) if argv and argv[0].isdecimal() else wapi.GetCurrentProcessId()
        print("Working on pid {0:d}".format(pid))
        process_handle = wapi.OpenProcess(wcon.PROCESS_ALL_ACCESS, False, pid)
        print("  Process handle: {0:d}".format(process_handle.handle))
        module_handles = wproc.EnumProcessModules(process_handle)
        module_handles_count = len(module_handles)
        print("  Loaded modules count: {0:d}".format(module_handles_count))
        module_index = 0  # 0 - the executable itself
        if module_index > module_handles_count:
            module_index = 0
        module_handle = module_handles[module_index]
        module_file_name = wproc.GetModuleFileNameEx(process_handle, module_handle)
        print("  File [{0:s}] (index {1:d}) is loaded at address 0x{2:016X} ({3:d})".format(module_file_name, module_index, module_handle, module_handle))
        process_handle.close()
    
    
    if __name__ == "__main__":
        print("Python {0:s} {1:d}bit on {2:s}\n".format(" ".join(item.strip() for item in sys.version.split("\n")), 64 if sys.maxsize > 0x100000000 else 32, sys.platform))
        main(*sys.argv[1:])
        print("\nDone.")
    

    Output:

    e:\Work\Dev\StackOverflow\q059610466>"e:\Work\Dev\VEnvs\py_pc064_03.07.06_test0\Scripts\python.exe" code01.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
    
    Working on pid 7184
      Process handle: 456
      Loaded modules count: 43
      File [e:\Work\Dev\VEnvs\py_pc064_03.07.06_test0\Scripts\python.exe] (index 0) is loaded at address 0x00007FF687C80000 (140696816713728)
    
    Done.
    
    e:\Work\Dev\StackOverflow\q059610466>:: Attempting to run with Task Manager pid
    e:\Work\Dev\StackOverflow\q059610466>"e:\Work\Dev\VEnvs\py_pc064_03.07.06_test0\Scripts\python.exe" code01.py 22784
    Python 3.7.6 (tags/v3.7.6:43364a7ae0, Dec 19 2019, 00:42:30) [MSC v.1916 64 bit (AMD64)] 64bit on win32
    
    Working on pid 22784
      Process handle: 624
      Loaded modules count: 111
      File [C:\WINDOWS\system32\taskmgr.exe] (index 0) is loaded at address 0x00007FF73F9D0000 (140699900903424)
    
    Done.