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