Search code examples
pythoncode-injectionctypeskernel32

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


I am working with Grey Hat Python book at the moment. It describes on how to create a debugger in python. So far my debugger is able to start the process and attach to it. The problem happens when I try to retrieve a module handle from the process. According to OllyDbg the DLL is present in the program, but GetModuleHandleA fails to get a handle. I improved a code from the book a little bit so in case GetModuleHandleA fails to retrieve a handle the function will try to create a remote thread and force to load this module into the process. But even so it GetModuleHandleA fails (while everything else works fine). So maybe someone can take a quick glance at the code and see the problem in it?

def func_resolve(self,dll,function):
    handle  = kernel32.GetModuleHandleA(dll)
    print "%s module handle is at 0x%08x" % (dll, handle)
    error = kernel32.GetLastError()
    if error:
        print "There was an error in func_resolve::GetModuleHandleA(%s): %d" % (dll, error)
        print "Loading library into the process"
        pLibRemote = kernel32.VirtualAllocEx(self.h_process, 0, len(dll), 0x00001000, 0x04)
        print "Allocated %d bytes of memory at 0x%08x" % (len(dll), pLibRemote)
        written = c_int(0)
        kernel32.WriteProcessMemory(self.h_process, pLibRemote, dll, len(dll), byref(written))
        print "Written %d bytes" % written.value
        handle  = kernel32.GetModuleHandleA("kernel32.dll")
        print "Kernel module handle is 0x%08x" % handle
        address = kernel32.GetProcAddress(handle, "LoadLibraryA")
        print "LoadLibraryA address is 0x%08x" % address
        thread_id = c_ulong(0)
        kernel32.CreateRemoteThread(self.h_process, None, 0, address, pLibRemote, 0, byref(thread_id))
        print "Created thread %d" % thread_id.value
    handle  = kernel32.GetModuleHandleA(dll)
    address = kernel32.GetProcAddress(handle, function)
    kernel32.CloseHandle(handle)
    return address

The output looks like this:

[*] We have successfully launched the process!
[*] The Process ID I have is: 10380
Proces handle is 228
opengl32.dll module handle is at 0x00000000
There was an error in func_resolve::GetModuleHandleA(opengl32.dll): 126
Loading library into the process
Allocated 12 bytes of memory at 0x002c0000
Written 12 bytes
Kernel module handle is 0x772c0000
LoadLibraryA address is 0x772d498f
Created thread 11136
[*] Address of func: 0x00000000
[*] Setting breakpoint at: 0x00000000

The module handle is retrieved fine if it is used by python.exe (is among the imported list of python.exe process). But modules that are not in python.exe processes fail. Maybe that could be related somehow to OS Windows 7 (64 bit), but still application that I test against was compiled with a 32 bit compiler.

Update 2: According to recommendation in comments I wrote my own function:

def my_func_resolve(self, dll, function):
    module32 = MODULEENTRY32()
    CreateToolhelp32Snapshot = kernel32.CreateToolhelp32Snapshot
    CreateToolhelp32Snapshot.restype = HANDLE
    CreateToolhelp32Snapshot.argtypes = [DWORD, DWORD]
    Module32First = kernel32.Module32First
    Module32First.restype = BOOL
    Module32First.argtypes = [HANDLE, POINTER(MODULEENTRY32)]
    Module32Next = kernel32.Module32Next
    Module32Next.restype = BOOL
    Module32Next.argtypes = [HANDLE, POINTER(MODULEENTRY32)]
    thandle = 24
    while thandle == 24:
        thandle = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, self.pid)
    if thandle == 0 or thandle == 0xFFFFFFFF:
        print "Failed to create a snapshot. Error: %d" % kernel32.GetLastError()
        exit()
    if not Module32First(thandle, byref(module32)):
        print "Module32First failed. Error: %d" % kernel32.GetLastError()
        kernel32.CloseHandle(thandle)
        exit()
    while module32:
        print "DLL %s is loaded at 0x%08x" % (module32.szModule, module32.modBaseAddr)
        Module32Next(thandle, byref(module32))
    kernel32.CloseHandle(thandle)
    return True

but it fails with

[*] We have successfully launched the process!
[*] The Process ID I have is: 9584
Proces handle is 228
Failed create snapshot. Error: 299

Which is ERROR_PARTIAL_COPY and happens if we are trying to retrieve 64 bit process from 32 bit process. I have 32 bit python. My OS is 64 bit. I compiled testprog.exe using mingw 32 bit compiler. How that happened that I get this error now? For TH32CS_SNAPMODULE I used both 0x00000008 and 0x00000010

Just in case, the process is created in this way:

if kernel32.CreateProcessA(path_to_exe,
                            None,
                            None,
                            None,
                            None,
                            creation_flags,
                            None,
                            None,
                            byref(startupinfo),
                            byref(process_information)):
    print "[*] We have successfully launched the process!"
    print "[*] The Process ID I have is: %d" % \
                    process_information.dwProcessId
    self.pid = process_information.dwProcessId
    self.h_process = self.open_process(process_information.dwProcessId)
    print "Proces handle is %d" % self.h_process

Solution

  • 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()