Ronteg
Ronteg

Reputation: 123

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.

Upvotes: 2

Views: 2462

Answers (1)

CristiFati
CristiFati

Reputation: 41137

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.

Upvotes: 3

Related Questions