勾取dll源码详解:
- 首先在创建时候来保存原始IAT地址到全局变量,然后通过Hook_iat函数来进行iat函数的勾取
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { switch( fdwReason ) { case DLL_PROCESS_ATTACH : //保存原始API(后面脱钩会用到此地址) g_pOrgFunc = GetProcAddress(GetModuleHandle(L"user32.dll"), "SetWindowTextW"); // # hook // 用hookiat.mysetwindowText()勾取user32.serwindowTextW(); hook_iat("user32.dll", g_pOrgFunc, (PROC)MySetWindowTextW); break; case DLL_PROCESS_DETACH : // # unhook // 把calc.exe的iat恢复原来的值 hook_iat("user32.dll", (PROC)MySetWindowTextW, g_pOrgFunc); break; } return TRUE; }
-
hook_iat参数1,dll名称,
- 参数2,原始的API地址(需要被勾取的)
- 参数3,自己写的替换API地址的函数(勾取函数)
// hook_iat // 负责iat的勾取 BOOL hook_iat(LPCSTR szDllName, PROC pfnOrg, PROC pfnNew) { HMODULE hMod; LPCSTR szLibName; PIMAGE_IMPORT_DESCRIPTOR pImportDesc; PIMAGE_THUNK_DATA pThunk; DWORD dwOldProtect, dwRVA; PBYTE pAddr; //*首先进行PE文件头信息的读取*/ // hMod, pAddr = ImageBase of calc.exe // = VA to MZ signature (IMAGE_DOS_HEADER) hMod = GetModuleHandle(NULL); pAddr = (PBYTE)hMod; // pAddr = VA to PE signature (IMAGE_NT_HEADERS) pAddr += *((DWORD*)&pAddr[0x3C]); // dwRVA = RVA to IMAGE_IMPORT_DESCRIPTOR Table dwRVA = *((DWORD*)&pAddr[0x80]); // pImportDesc = VA to IMAGE_IMPORT_DESCRIPTOR Table 如果想要找到IAT首先要找到导入表对应的位置 pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hMod+dwRVA); /*通过循环来比较找到user32.dll中的导入表结构*/ for( ; pImportDesc->Name; pImportDesc++ ) { // szLibName = VA to IMAGE_IMPORT_DESCRIPTOR.Name szLibName = (LPCSTR)((DWORD)hMod + pImportDesc->Name); if( !_stricmp(szLibName, szDllName) ) { // pThunk = IMAGE_IMPORT_DESCRIPTOR.FirstThunk // = VA to IAT(Import Address Table) pThunk = (PIMAGE_THUNK_DATA)((DWORD)hMod + pImportDesc->FirstThunk); // pThunk->u1.Function = VA to API for( ; pThunk->u1.Function; pThunk++ ) { if( pThunk->u1.Function == (DWORD)pfnOrg ) { // 更改内存属性为E/R/W //由于计算器原有IAT内存区域是只可读的 //所以勾取之前通过VirtualProtect函数将相应的IAT的内存区域更改为可读写模式 VirtualProtect((LPVOID)&pThunk->u1.Function, 4, PAGE_EXECUTE_READWRITE, &dwOldProtect); //修改IAT值(勾取)把原有指向user32.dll/Setwindowtext的值指向我们自己的函数MySetWindowTextW pThunk->u1.Function = (DWORD)pfnNew; // 恢复内存属性 VirtualProtect((LPVOID)&pThunk->u1.Function, 4, dwOldProtect, &dwOldProtect); return TRUE; } } } } return FALSE; }
- 自写的勾取函数主要功能是将阿拉伯数字转换为中文数字,具体如下:
BOOL WINAPI MySetWindowTextW(HWND hWnd, LPWSTR lpString) { wchar_t* pNum = L"零一二三四五六七八九"; wchar_t temp[2] = {0,}; int i = 0, nLen = 0, nIndex = 0; nLen = wcslen(lpString); for(i = 0; i < nLen; i++) { // 将阿拉伯数字转换为中文数字 // lpString是宽字符的字符串 if( L'0' <= lpString[i] && lpString[i] <= L'9' ) { temp[0] = lpString[i]; nIndex = _wtoi(temp); lpString[i] = pNum[nIndex]; } } // 调用user32.setwindowsTextW()api // 修改lpstring缓冲区内的内容 return ((PFSETWINDOWTEXTW)g_pOrgFunc)(hWnd, lpString); }
全dll代码:
// include #include "stdio.h" #include "wchar.h" #include "windows.h" // typedef typedef BOOL (WINAPI *PFSETWINDOWTEXTW)(HWND hWnd, LPWSTR lpString); // globals FARPROC g_pOrgFunc = NULL; BOOL WINAPI MySetWindowTextW(HWND hWnd, LPWSTR lpString) { wchar_t* pNum = L"零一二三四五六七八九"; wchar_t temp[2] = {0,}; int i = 0, nLen = 0, nIndex = 0; nLen = wcslen(lpString); for(i = 0; i < nLen; i++) { // 将阿拉伯数字转换为中文数字 // lpString是宽字符的字符串 if( L'0' <= lpString[i] && lpString[i] <= L'9' ) { temp[0] = lpString[i]; nIndex = _wtoi(temp); lpString[i] = pNum[nIndex]; } } // 调用user32.setwindowsTextW()api // 修改lpstring缓冲区内的内容 return ((PFSETWINDOWTEXTW)g_pOrgFunc)(hWnd, lpString); } // hook_iat // 负责iat的勾取 BOOL hook_iat(LPCSTR szDllName, PROC pfnOrg, PROC pfnNew) { HMODULE hMod; LPCSTR szLibName; PIMAGE_IMPORT_DESCRIPTOR pImportDesc; PIMAGE_THUNK_DATA pThunk; DWORD dwOldProtect, dwRVA; PBYTE pAddr; //*首先进行PE文件头信息的读取*/ // hMod, pAddr = ImageBase of calc.exe // = VA to MZ signature (IMAGE_DOS_HEADER) hMod = GetModuleHandle(NULL); pAddr = (PBYTE)hMod; // pAddr = VA to PE signature (IMAGE_NT_HEADERS) pAddr += *((DWORD*)&pAddr[0x3C]); // dwRVA = RVA to IMAGE_IMPORT_DESCRIPTOR Table dwRVA = *((DWORD*)&pAddr[0x80]); // pImportDesc = VA to IMAGE_IMPORT_DESCRIPTOR Table 如果想要找到IAT首先要找到导入表对应的位置 pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hMod+dwRVA); /*通过循环来比较找到user32.dll中的导入表结构*/ for( ; pImportDesc->Name; pImportDesc++ ) { // szLibName = VA to IMAGE_IMPORT_DESCRIPTOR.Name szLibName = (LPCSTR)((DWORD)hMod + pImportDesc->Name); if( !_stricmp(szLibName, szDllName) ) { // pThunk = IMAGE_IMPORT_DESCRIPTOR.FirstThunk // = VA to IAT(Import Address Table) pThunk = (PIMAGE_THUNK_DATA)((DWORD)hMod + pImportDesc->FirstThunk); // pThunk->u1.Function = VA to API for( ; pThunk->u1.Function; pThunk++ ) { if( pThunk->u1.Function == (DWORD)pfnOrg ) { // 更改内存属性为E/R/W //由于计算器原有IAT内存区域是只可读的 //所以勾取之前通过VirtualProtect函数将相应的IAT的内存区域更改为可读写模式 VirtualProtect((LPVOID)&pThunk->u1.Function, 4, PAGE_EXECUTE_READWRITE, &dwOldProtect); //修改IAT值(勾取)把原有指向user32.dll/Setwindowtext的值指向我们自己的函数MySetWindowTextW pThunk->u1.Function = (DWORD)pfnNew; // 恢复内存属性 VirtualProtect((LPVOID)&pThunk->u1.Function, 4, dwOldProtect, &dwOldProtect); return TRUE; } } } } return FALSE; } BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { switch( fdwReason ) { case DLL_PROCESS_ATTACH : //保存原始API(后面脱钩会用到此地址) g_pOrgFunc = GetProcAddress(GetModuleHandle(L"user32.dll"), "SetWindowTextW"); // # hook // 用hookiat.mysetwindowText()勾取user32.serwindowTextW(); hook_iat("user32.dll", g_pOrgFunc, (PROC)MySetWindowTextW); break; case DLL_PROCESS_DETACH : // # unhook // 把calc.exe的iat恢复原来的值 hook_iat("user32.dll", (PROC)MySetWindowTextW, g_pOrgFunc); break; } return TRUE; }
然后我们通过一个DLL注入程序来注入我们的DLL
BOOL InjectDll(DWORD dwPID, LPCTSTR szDllName) { HANDLE hProcess, hThread; LPVOID pRemoteBuf; DWORD dwBufSize = (DWORD)(_tcslen(szDllName) + 1) * sizeof(TCHAR); LPTHREAD_START_ROUTINE pThreadProc; //#1.使用dwPID获取目标进程句柄 if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) ) { DWORD dwErr = GetLastError(); return FALSE; } //#2.在目标进程内存中分配注入szdllName大小的内存 pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE); //#3.将dll路径写入分配的内存 WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllName, dwBufSize, NULL); //#4.获取loadLibraryW()API的地址 pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryW"); //#5.在计算器进程中运行线程 hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL); WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); CloseHandle(hProcess); return TRUE; }
有注入就有卸载,那我们一起来看看DLL卸载的源码解析:
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID)
使用
CreateToolhelp32Snapshot
可以获取加载到进程的模块信息,将获取的hSnapshot 句柄传递给Module32First/Module32Next函数后,即可设置与MODLUEENTRY32结构体相关的模块信息
循环比较出需要卸载的Dll文件名称
bMore = Module32First(hSnapshot, &me); for( ;bMore ;bMore = Module32Next(hSnapshot, &me) ) { if( !_tcsicmp(me.szModule, szDllName) || !_tcsicmp(me.szExePath, szDllName) ) { bFound = TRUE; break; } }
获取目标进程的句柄
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)
在目标进程中载入远程线程
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "FreeLibrary"); hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, me.modBaseAddr, 0, NULL); WaitForSingleObject(hThread, INFINITE);
完整DLL卸载函数如下:
BOOL EjectDll(DWORD dwPID, LPCTSTR szDllName) { BOOL bMore = FALSE, bFound = FALSE; HANDLE hSnapshot, hProcess, hThread; MODULEENTRY32 me = { sizeof(me) }; LPTHREAD_START_ROUTINE pThreadProc; //dwPID =进程ID //使用TH32CS_SNAPMODULE参数,获取加载到进程的DLL名称 if( INVALID_HANDLE_VALUE == (hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID)) ) return FALSE; bMore = Module32First(hSnapshot, &me); for( ;bMore ;bMore = Module32Next(hSnapshot, &me) ) { if( !_tcsicmp(me.szModule, szDllName) || !_tcsicmp(me.szExePath, szDllName) ) { bFound = TRUE; break; } } if( !bFound ) { CloseHandle(hSnapshot); return FALSE; } if( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) ) { CloseHandle(hSnapshot); return FALSE; } pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "FreeLibrary"); hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, me.modBaseAddr, 0, NULL); WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); CloseHandle(hProcess); CloseHandle(hSnapshot); return TRUE; }