1.函数
函数介绍参见http://msdn.microsoft.com/en-us/library/ms644898(v=VS.85).aspx。可以调用SetWindowLongPtr(hWnd,GWLP_WNDPROC,MySubclassProc),告诉系统所有发到hWnd窗口的消息,应该由MySubclassProc来处理,而不是由该窗口的标准窗口过程来处理。也就是当系统需要向指定窗口的WndProc发送消息的时候,会先查看窗口过程的地址,然后通过该地址来调用WndProc。LONG_PTR WINAPI SetWindowLongPtr( __in HWND hWnd, __in int nIndex, __in LONG_PTR dwNewLong );
问题:请看下面代码,函数过程在进程B中实现。
void SomeFunc(void){
HWND hWnd = FindWindow(TEXT("Class-A"),NULL);
SetWindowLongPtr(hWnd,GWLP_WNDPROC,MySubclassProc);
}
LRESULT MySubclassProc(HWND hWnd,UINT Msg,...){}
这里B调用了SetWindowLongPtr,试图改变A进程对应窗口的WndProc地址,结果就是返回NULL。因为SetWindowLongPtr中的代码会检查一个进程试图修改的WndProc的地址是否属于另一个进程创建的窗口,如果是这种情况的话,函数会直接忽略该调用。
2.函数 HHOOK WINAPI SetWindowsHookEx(
__in int idHook,
__in HOOKPROC lpfn,
__in HINSTANCE hMod,
__in DWORD dwThreadId
);
函数介绍参见http://msdn.microsoft.com/en-us/library/ms644990(VS.85).aspx。调用SetWindowsHookEx(WH_GETMESSAGE,GetMsgProc,hInstDll,0),WH_GETMESSAGE表示监控发送到消息队列中的消息,GetMsgProc挂钩的处理函数,如果dwThreadId参数是零或指定不同进程创建的一个线程标识符,lpfn参数必须指向DLL中的钩子函数。否则,lpfn指向一个当前进程相关的钩子函数。hInstDll为包含GetMsgProc过程的DLL,其值是进程地址空间中的DLL被映射到的虚拟内存地址。
HHOOK WINAPI SetWindowsHookEx(
__in int idHook,
__in HOOKPROC lpfn,
__in HINSTANCE hMod,
__in DWORD dwThreadId
);
3.函数
HANDLE WINAPI CreateRemoteThread(
__in HANDLE hProcess,
__in LPSECURITY_ATTRIBUTES lpThreadAttributes,
__in SIZE_T dwStackSize,
__in LPTHREAD_START_ROUTINE lpStartAddress,
__in LPVOID lpParameter,
__in DWORD dwCreationFlags,
__out LPDWORD lpThreadId
);
函数介绍参见
http://msdn.microsoft.com/en-us/library/ms682437(VS.85).aspx
。参数pfnStartAddr是线程函数的在远程进程的内存地址。要执行DLL注入,让远程线程载入我们的DLL,需要让该线程调用LoadLibrary函数。因此我们只需要执行如下代码:
HANDLE hThread = CreateRemoteThread(hProcessRemote,NULL,0,LoadLibraryW,L"C:\\MyLib.dll",0,NULL);
如果我们想要使用ANSI版本的,那么代码将是
HANDLE hThread = CreateRemoteThread(hProcessRemote,NULL,0,LoadLibraryA,"C:\\MyLib.dll",0,NULL)。
这里有两个问题:
1.直接把LoadLibraryW或LoadLibraryA作为第4个参数传给CreateRemoteThread,原因不是那么明显。
在编译和链接一个程序的时候,生成的二进制文件中会包含一个导入段,这个段由一系列转换函数构成,这些转换函数用来跳转到导入的函数。因此在调用CreateRemoteThread的时候直接引用LoadLibraryW,该引用会被解析为我们模块的导入段中的LoadLibraryW转换函数的地址,这就可能导致访问违规。为了强制调用LoadLibraryW函数,我们必须通过调用GetProcAddress来得到LoadLibraryW的确切地址。由于每个 应用程序都需要Kernel32.dll,而系统每个进程都会将Kernel32.dll映射到同一个地址,所以必须如下调用CreateRemoteThread:
// Get the real address of LoadLibraryW in Kernel32.dll. PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE) GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW"); HANDLE hThread = CreateRemoteThread(hProcessRemote, NULL, 0, pfnThreadRtn, L"C:\\MyLib.dll", 0, NULL);
如果在ANSI下,对应函数调用如下:
// Get the real address of LoadLibraryA in Kernel32.dll. PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE) GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryA"); HANDLE hThread = CreateRemoteThread(hProcessRemote, NULL, 0, pfnThreadRtn, "C:\\MyLib.dll", 0, NULL);
LPVOID WINAPI VirtualAllocEx( __in HANDLE hProcess, __in_opt LPVOID lpAddress, __in SIZE_T dwSize, __in DWORD flAllocationType, __in DWORD flProtect );
释放该内存的函数为:
BOOL WINAPI VirtualFreeEx(
__in HANDLE hProcess,
__in LPVOID lpAddress,
__in SIZE_T dwSize,
__in DWORD dwFreeType
);
一旦为字符串分配了一块内存,我们就需要将字符串从本地进程的地址空间复制到远程进程的地址空间中去。Windows提供了一些函数,可以让一个进程对另一个进程的地址空间进行读写:
4.修改模块的导入段来拦截APIBOOL WINAPI ReadProcessMemory( __in HANDLE hProcess, __in LPCVOID lpBaseAddress, __out LPVOID lpBuffer, __in SIZE_T nSize, __out SIZE_T *lpNumberOfBytesRead ); BOOL WINAPI WriteProcessMemory( __in HANDLE hProcess, __in LPVOID lpBaseAddress, __in LPCVOID lpBuffer, __in SIZE_T nSize, __out SIZE_T *lpNumberOfBytesWritten );
为了拦截一个特定的函数,我们所需要做的就是要修改它在模块中的导入段的地址。
void CAPIHook::ReplaceIATEntryInOneMod(PCSTR pszCalleeModName,
PROC pfnCurrent, PROC pfnNew, HMODULE hmodCaller) {
// Get the address of the module's import section
ULONG ulSize;
// An exception was triggered by Explorer (when browsing the content of
// a folder) into imagehlp.dll. It looks like one module was unloaded...
// Maybe some threading problem: the list of modules from Toolhelp might
// not be accurate if FreeLibrary is called during the enumeration.
PIMAGE_IMPORT_DESCRIPTOR pImportDesc = NULL;
__try {
pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR) ImageDirectoryEntryToData(
hmodCaller, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &ulSize);
}
__except (InvalidReadExceptionFilter(GetExceptionInformation())) {
// Nothing to do in here, thread continues to run normally
// with NULL for pImportDesc
}
if (pImportDesc == NULL)
return; // This module has no import section or is no longer loaded
// Find the import descriptor containing references to callee's functions
for (; pImportDesc->Name; pImportDesc++) {
PSTR pszModName = (PSTR) ((PBYTE) hmodCaller + pImportDesc->Name);
if (lstrcmpiA(pszModName, pszCalleeModName) == 0) {
// Get caller's import address table (IAT) for the callee's functions
PIMAGE_THUNK_DATA pThunk = (PIMAGE_THUNK_DATA)
((PBYTE) hmodCaller + pImportDesc->FirstThunk);
// Replace current function address with new function address
for (; pThunk->u1.Function; pThunk++) {
// Get the address of the function address
PROC* ppfn = (PROC*) &pThunk->u1.Function;
// Is this the function we're looking for?
BOOL bFound = (*ppfn == pfnCurrent);
if (bFound) {
if (!WriteProcessMemory(GetCurrentProcess(), ppfn, &pfnNew,
sizeof(pfnNew), NULL) && (ERROR_NOACCESS == GetLastError())) {
DWORD dwOldProtect;
if (VirtualProtect(ppfn, sizeof(pfnNew), PAGE_WRITECOPY,
&dwOldProtect)) {
WriteProcessMemory(GetCurrentProcess(), ppfn, &pfnNew,
sizeof(pfnNew), NULL);
VirtualProtect(ppfn, sizeof(pfnNew), dwOldProtect,
&dwOldProtect);
}
}
return; // We did it, get out
}
}
} // Each import section is parsed until the right entry is found and patched
}
}
PCSTR pszCalleeModName:调用模块(含要被替换的函数)。
PROC pfnCurrent:调用模块中被替换的函数。
PROC pfnNew:需要执行的函数。
HMODULE hmodCaller:调用模块。