<p><code>GetModuleHandle</code>在当前进程中查找模块。要在另一个进程中查找模块,您需要使用PSAPI函数<code>EnumProcessModulesEx</code>&;<code>GetModuleBaseName</code>或工具帮助函数<code>CreateToolhelp32Snapshot</code>,<code>Module32First</code>,<code>Module32Next</code>。在</p>
<p>如果目标进程具有与当前进程相同的体系结构,则可以在其加载的DLL中间接查找过程地址。首先,通过<code>LoadLibraryEx</code>和<code>DONT_RESOLVE_DLL_REFERENCES</code>在当前进程中加载DLL。然后用这个本地的<code>HMODULE</code>调用<code>GetProcAddress</code>来获得本地地址。最后,在目标进程中调整相对于模块基址的本地地址。记住调用<code>FreeLibrary</code>从当前进程卸载DLL。在</p>
<p>注意,<code>HMODULE</code>句柄实际上是指针,因此您需要为所有ctypes函数设置<code>restype</code>和{<cd14>}。这可以防止将64位指针值截断为32位C<code>int</code>值。在</p>
<p>下面是一个使用工具帮助函数的示例。在</p>
<pre><code>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)
</code></pre>
<p>下面是一个测试,它创建另一个Python进程,并验证kernel32.dll是否加载到与当前进程相同的地址;<code>LoadLibraryExW</code>是否在同一地址解析;前1000个字节是否相等。在</p>
<p>请注意,我使用Windows事件对象等待子进程完成加载,然后再尝试读取其模块表。这避免了<code>ERROR_PARTIAL_COPY</code>的问题。如果目标是具有消息队列的GUI进程,则可以改为使用<a href="https://msdn.microsoft.com/en-us/library/ms687022" rel="nofollow">^{<cd18>}</a>。在</p>
^{pr2}$