GetModuleHandleA fails to get modules not used by python.exe when attached to another process

Deadly 提交于 2019-12-06 04:10:43

GetModuleHandle looks for a module in the current process. To find a module in another process you need to use the PSAPI functions EnumProcessModulesEx & GetModuleBaseName or the Tool Help functions CreateToolhelp32Snapshot, Module32First, & Module32Next.

If the target process has the same architecture as the current process, then you can indirectly find procedure addresses in its loaded DLLs. First, load the DLL in the current process via LoadLibraryEx with DONT_RESOLVE_DLL_REFERENCES. Then call GetProcAddress with this local HMODULE to get the local address. Finally, adjust the local address relative to the module base address in the target process. Remember to call FreeLibrary to unload the DLL from the current process.

Note that HMODULE handles are actually pointers, so you'll need to set restype and argtypes for all ctypes functions. This prevents truncating 64-bit pointer values as 32-bit C int values.

Here's an example using the Tool Help functions.

import os
import ctypes
from ctypes import wintypes

kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
wintypes.LPBOOL = ctypes.POINTER(wintypes.BOOL)

ERROR_NO_MORE_FILES = 0x0012
ERROR_BAD_LENGTH    = 0x0018
ERROR_MOD_NOT_FOUND = 0x007E

INVALID_HANDLE_VALUE = wintypes.HANDLE(-1).value
DONT_RESOLVE_DLL_REFERENCES = 0x00000001
MAX_PATH = 260
MAX_MODULE_NAME32 = 255
TH32CS_SNAPMODULE   = 0x00000008

class MODULEENTRY32W(ctypes.Structure):
    _fields_ = (('dwSize',        wintypes.DWORD),
                ('th32ModuleID',  wintypes.DWORD),
                ('th32ProcessID', wintypes.DWORD),
                ('GlblcntUsage',  wintypes.DWORD),
                ('ProccntUsage',  wintypes.DWORD),
                ('modBaseAddr',   wintypes.LPVOID),
                ('modBaseSize',   wintypes.DWORD),
                ('hModule',       wintypes.HMODULE),
                ('szModule',      wintypes.WCHAR * (MAX_MODULE_NAME32 + 1)),
                ('szExePath',     wintypes.WCHAR * MAX_PATH))
    def __init__(self, *args, **kwds):
        super(MODULEENTRY32W, self).__init__(*args, **kwds)
        self.dwSize = ctypes.sizeof(self)

LPMODULEENTRY32W = ctypes.POINTER(MODULEENTRY32W)

def errcheck_bool(result, func, args):
    if not result:
        raise ctypes.WinError(ctypes.get_last_error())
    return args

def errcheck_ihv(result, func, args):
    if result == INVALID_HANDLE_VALUE:
        raise ctypes.WinError(ctypes.get_last_error())
    return args

kernel32.LoadLibraryExW.errcheck = errcheck_bool
kernel32.LoadLibraryExW.restype = wintypes.HMODULE
kernel32.LoadLibraryExW.argtypes = (wintypes.LPCWSTR,
                                    wintypes.HANDLE,
                                    wintypes.DWORD)

kernel32.FreeLibrary.errcheck = errcheck_bool
kernel32.FreeLibrary.argtypes = (wintypes.HMODULE,)

kernel32.GetProcAddress.errcheck = errcheck_bool
kernel32.GetProcAddress.restype = wintypes.LPVOID
kernel32.GetProcAddress.argtypes = (wintypes.HMODULE,
                                    wintypes.LPCSTR)

kernel32.CloseHandle.errcheck = errcheck_bool
kernel32.CloseHandle.argtypes = (wintypes.HANDLE,)

kernel32.CreateToolhelp32Snapshot.errcheck = errcheck_ihv
kernel32.CreateToolhelp32Snapshot.restype = wintypes.HANDLE
kernel32.CreateToolhelp32Snapshot.argtypes = (wintypes.DWORD,
                                              wintypes.DWORD)

kernel32.Module32FirstW.errcheck = errcheck_bool
kernel32.Module32FirstW.argtypes = (wintypes.HANDLE,
                                    LPMODULEENTRY32W)

kernel32.Module32NextW.errcheck = errcheck_bool
kernel32.Module32NextW.argtypes = (wintypes.HANDLE,
                                   LPMODULEENTRY32W)

def GetRemoteProcAddress(pid, filename, procname):
    procname = procname.encode('utf-8')
    hLocal = kernel32.LoadLibraryExW(filename, None,
                                     DONT_RESOLVE_DLL_REFERENCES)
    try:
        procaddr = kernel32.GetProcAddress(hLocal, procname)
    finally:
        kernel32.FreeLibrary(hLocal)
    modname = os.path.basename(filename)
    hRemote = GetRemoteModuleHandle(pid, modname)
    return hRemote - hLocal + procaddr

def GetRemoteModuleHandle(pid, modname):
    modname = modname.upper()
    if '.' not in modname:
        modname += '.DLL'
    while True:
        try:
            hProcessSnap = kernel32.CreateToolhelp32Snapshot(
                                TH32CS_SNAPMODULE, pid)
            break
        except OSError as e:
            if e.winerror != ERROR_BAD_LENGTH:
                raise
    try:
        modentry = MODULEENTRY32W()
        kernel32.Module32FirstW(hProcessSnap,
                                ctypes.byref(modentry))
        while True:
            if modentry.szModule.upper() == modname:
                return modentry.hModule
            try:
                kernel32.Module32NextW(hProcessSnap,
                                       ctypes.byref(modentry))
            except OSError as e:
                if e.winerror == ERROR_NO_MORE_FILES:
                    break
                raise
        raise ctypes.WinError(ERROR_MOD_NOT_FOUND)
    finally:
        kernel32.CloseHandle(hProcessSnap)

Here's a test that creates another Python process and verifies that kernel32.dll is loaded at the same address as the current process; that LoadLibraryExW is resolved at the same address; and that the first 1000 bytes are equal.

Note that I use a Windows Event object to wait for the child process to finish loading before attempting to read its module table. This avoids the problem with ERROR_PARTIAL_COPY. If the target is a GUI process with a message queue, you can instead use WaitForInputIdle.

if __name__ == '__main__':
    import sys
    import subprocess

    if len(sys.argv) > 1:
        # child process
        import time
        hEvent = int(sys.argv[1])
        kernel32.SetEvent(hEvent)
        time.sleep(120)
        sys.exit(0)

    wintypes.SIZE_T = ctypes.c_size_t
    kernel32.ReadProcessMemory.argtypes = (wintypes.HANDLE,
                                           wintypes.LPVOID,
                                           wintypes.LPVOID,
                                           wintypes.SIZE_T,
                                           ctypes.POINTER(wintypes.SIZE_T))

    class SECURITY_ATTRIBUTES(ctypes.Structure):
        _fields_ = (('nLength',              wintypes.DWORD),
                    ('lpSecurityDescriptor', wintypes.LPVOID),
                    ('bInheritHandle',       wintypes.BOOL))
    def __init__(self, *args, **kwds):
        super(SECURITY_ATTRIBUTES, self).__init__(*args, **kwds)
        self.nLength = ctypes.sizeof(self)

    WAIT_OBJECT_0 = 0
    CREATE_NO_WINDOW = 0x08000000

    sa = SECURITY_ATTRIBUTES(bInheritHandle=True)
    hEvent = kernel32.CreateEventW(ctypes.byref(sa), 0, 0, None)
    script = os.path.abspath(__file__)
    p = subprocess.Popen([sys.executable, script, str(hEvent)],
                         close_fds=False,
                         creationflags=CREATE_NO_WINDOW)
    try:
        result = kernel32.WaitForSingleObject(hEvent, 60 * 1000)
        if result != WAIT_OBJECT_0:
            sys.exit('wait failed')
        # kernel32 should load at the same address in a given session.
        hModule = GetRemoteModuleHandle(p.pid, 'kernel32')
        assert hModule == kernel32._handle
        remote_addr = GetRemoteProcAddress(p.pid,
                                           'kernel32',
                                           'LoadLibraryExW')
        local_addr = ctypes.c_void_p.from_buffer(
                        kernel32.LoadLibraryExW).value
        assert remote_addr == local_addr
        remote_bytes = (ctypes.c_char * 1000)()
        read = wintypes.SIZE_T()
        kernel32.ReadProcessMemory(int(p._handle),
                                   remote_addr,
                                   remote_bytes, 1000,
                                   ctypes.byref(read))
        local_bytes = ctypes.string_at(kernel32.LoadLibraryExW, 1000)
        assert remote_bytes[:] == local_bytes
    finally:
        p.terminate()
Chris Nauroth

According to the documentation on System Error Codes, error code 126 is ERROR_MOD_NOT_FOUND. You might want to review the DLL Search Path to make sure the DLL is installed in the right place. opengl32.dll is pretty common though, so I'd expect this to be available.

Another possibility could be that your code is calling GetModuleHandleA (the Windows code page or "ANSI" version of the function), but passing wide character Unicode strings. GetModuleHandleA would not be able to interpret Unicode strings properly, so it would search for the wrong module. If this were the case, then the fix would be to change your code to call GetModuleHandleW. Python 3 in particular uses Unicode for strings, so if you're running with Python 3, then this is likely to be relevant.

The Unicode in the Windows API documentation has more discussion of the A vs. W naming convention for functions and the distinction between functions capable of handling Windows code pages and functions capable of handling Unicode.

This previous question looks similar.

Call to GetModuleHandle on kernel32 using Python C-types

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!