GetModuleHandleA fails to get modules not used by

2019-05-20 22:30发布

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

2条回答
爷的心禁止访问
2楼-- · 2019-05-20 22:55

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()
查看更多
时光不老,我们不散
3楼-- · 2019-05-20 23:03

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

查看更多
登录 后发表回答