Anti HookAPI学习笔记
Hookapi分为2种,ring3 下挂钩 API 基本上也就是修改导入表,和Inline hook 修改前5个字节这几种方法,关于挂钩API 请参见:www.xfocus.net/articles/200403/681.html
部分引用如下:
看到了吧,关键点是 进程载入器会在程序代码里调用所使用的IAT里填入直接跳转的jmp指令 hookapi就是修改这个里面写得地址 将其修改成hook函数得地址引用:
这里对我们比较重要的是.idata部分的导入地址表(IAT)。这个部分包含了导入的相关信息和导入函数的地址。有一点很重要的是我们必须知道PE文件 是如何创建的。当在编程语言里间接调用任意API(这意味着我们是用函数的名字来调用它,而不是用它的地址),编译器并不直接把调用连接到模块,而是用 jmp指令连接调用到IAT,IAT在系统把进程调入内存时时会由进程载入器填满。这就是我们可以在两个不同版本的Windows里使用相同的二进制代码 的原因,虽然模块可能会加载到不同的地址。进程载入器会在程序代码里调用所使用的IAT里填入直接跳转的jmp指令。所以我们能在IAT里找到我们想要挂 钩的指定函数,我们就能很容易改变那里的jmp指令并重定向代码到我们的地址。完成之后每次调用都会执行我们的代码了。
最简单得防护方法 调用下层API
比如RegSetValueA 偶得代码其实就是
引用:
77DB6F49 > 8BFF mov edi,edi
77DB6F4B . 55 push ebp
77DB6F4C . 8BEC mov ebp,esp
77DB6F4E . 51 push ecx
77DB6F4F . 53 push ebx
77DB6F50 . 33DB xor ebx,ebx
77DB6F52 . 817D 08 04000080 cmp dword ptr ss:[ebp+8],80000004
77DB6F59 . 895D FC mov dword ptr ss:[ebp-4],ebx
77DB6F5C . 0F84 B7020200 je ADVAPI32.77DD7219
77DB6F62 . 837D 10 01 cmp dword ptr ss:[ebp+10],1
77DB6F66 . 75 78 jnz short ADVAPI32.77DB6FE0
77DB6F68 . 395D 14 cmp dword ptr ss:[ebp+14],ebx
77DB6F6B . 74 73 je short ADVAPI32.77DB6FE0
77DB6F6D . 56 push esi
77DB6F6E . 8D45 FC lea eax,dword ptr ss:[ebp-4]
77DB6F71 . 50 push eax
77DB6F72 . FF75 08 push dword ptr ss:[ebp+8]
77DB6F75 . E8 46F9FEFF call ADVAPI32.77DA68C0
77DB6F7A . 8BF0 mov esi,eax
77DB6F7C . 3BF3 cmp esi,ebx
77DB6F7E . 8975 08 mov dword ptr ss:[ebp+8],esi
77DB6F81 . 0F84 96020200 je ADVAPI32.77DD721D
77DB6F87 . 8B45 14 mov eax,dword ptr ss:[ebp+14]
77DB6F8A . 8D50 01 lea edx,dword ptr ds:[eax+1]
77DB6F8D > 8A08 mov cl,byte ptr ds:[eax]
77DB6F8F . 40 inc eax
77DB6F90 . 3ACB cmp cl,bl
77DB6F92 .^ 75 F9 jnz short ADVAPI32.77DB6F8D
77DB6F94 . 2BC2 sub eax,edx
77DB6F96 . 57 push edi
77DB6F97 . 8D78 01 lea edi,dword ptr ds:[eax+1]
77DB6F9A . 8B45 0C mov eax,dword ptr ss:[ebp+C]
77DB6F9D . 3BC3 cmp eax,ebx
77DB6F9F . 0F85 80020200 jnz ADVAPI32.77DD7225
77DB6FA5 > 8975 10 mov dword ptr ss:[ebp+10],esi
77DB6FA8 > 57 push edi ; /BufSize
77DB6FA9 . FF75 14 push dword ptr ss:[ebp+14] ; |Buffer
77DB6FAC . 6A 01 push 1 ; |ValueType = REG_SZ
77DB6FAE . 53 push ebx ; |Reserved
77DB6FAF . 53 push ebx ; |ValueName
77DB6FB0 . FF75 10 push dword ptr ss:[ebp+10] ; |hKey
77DB6FB3 . E8 2F7CFFFF call ADVAPI32.RegSetValueExA ; \RegSetValueExA
77DB6FB8 . 8BF0 mov esi,eax
77DB6FBA . 8B45 10 mov eax,dword ptr ss:[ebp+10]
77DB6FBD . 3B45 08 cmp eax,dword ptr ss:[ebp+8]
77DB6FC0 . 0F85 7E020200 jnz ADVAPI32.77DD7244
77DB6FC6 > 5F pop edi
77DB6FC7 > 395D FC cmp dword ptr ss:[ebp-4],ebx
77DB6FCA . 0F85 81020200 jnz ADVAPI32.77DD7251
77DB6FD0 > 8BC6 mov eax,esi
77DB6FD2 . 5E pop esi
77DB6FD3 > 5B pop ebx
77DB6FD4 . C9 leave
77DB6FD5 . C2 1400 retn 14
最终进入得是RegSetValueExA 其实还会继续进去RegSetValueExW。。。。
2.对于部分API有效得防护方法 使用汇编代码模拟
例如; GetVersion 其实可以自己写汇编代码实现
GetCurrentProcessId其实就是引用:
7C8111DA > 64:A1 18000000 mov eax,dword ptr fs:[18]
7C8111E0 8B48 30 mov ecx,dword ptr ds:[eax+30]
7C8111E3 8B81 B0000000 mov eax,dword ptr ds:[ecx+B0]
7C8111E9 0FB791 AC000000 movzx edx,word ptr ds:[ecx+AC]
7C8111F0 83F0 FE xor eax,FFFFFFFE
7C8111F3 C1E0 0E shl eax,0E
7C8111F6 0BC2 or eax,edx
7C8111F8 C1E0 08 shl eax,8
7C8111FB 0B81 A8000000 or eax,dword ptr ds:[ecx+A8]
7C811201 C1E0 08 shl eax,8
7C811204 0B81 A4000000 or eax,dword ptr ds:[ecx+A4]
7C81120A C3 retn
当然 还有个偶们最熟悉得 IsDebuggerPresent引用:
7C809920 > 64:A1 18000000 mov eax,dword ptr fs:[18]
7C809926 8B40 20 mov eax,dword ptr ds:[eax+20]
7C809929 C3 retn
其实代码就3句而已 HOHO引用:
7C813093 > 64:A1 18000000 mov eax,dword ptr fs:[18]
7C813099 8B40 30 mov eax,dword ptr ds:[eax+30]
7C81309C 0FB640 02 movzx eax,byte ptr ds:[eax+2]
7C8130A0 C3 retn
3.以上所说得方法都是属于治标不知本或者是有局限性得,真正得对付修改IAT地址得手段得方法是自己用GetProcAddress函数获取
注意了!使用GetProcAddress函数获取最好是自己写GetProcAddress函数去获取,而不是直接调用系统得GetProcAddress函数,什么原因呢?因为系统得GetProcAddress函数可是已经杯做过手脚得了
给出一段代码
lifeengines(netsowell)大侠得
二:在lifeengines大侠得帮助下,偶们就可以对付几乎所有得修改IAT地址得hook手段了,但是不要高兴得太早了,偶们还有一种人要对付,他们是Inline hook 修改前5个字节得人,上面得就是你自己取得了真实地址,但是开始代码杯改成了jmp xxxxxxxx,一样还是走到了hook函数里面去了,偶们必须在调用之前先恢复入口得5个字节引用:
DWORD GetFunctionAddress( HMODULE phModule,char* pProcName )
{
if (!phModule)
return 0;
PIMAGE_DOS_HEADER pimDH = (PIMAGE_DOS_HEADER)phModule;
PIMAGE_NT_HEADERS pimNH = (PIMAGE_NT_HEADERS)((char*)phModule+pimDH->e_lfanew);
PIMAGE_EXPORT_DIRECTORY pimED = (PIMAGE_EXPORT_DIRECTORY)((DWORD)phModule+pimNH->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
DWORD pExportSize = pimNH->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;
DWORD pResult = 0;
if ((DWORD)pProcName < 0x10000)
{
if ((DWORD)pProcName >= pimED->NumberOfFunctions+pimED->Base || (DWORD)pProcName < pimED->Base)
return 0;
pResult = (DWORD)phModule+((DWORD*)((DWORD)phModule+pimED->AddressOfFunctions))[(DWORD)pProcName-pimED->Base];
}else
{
DWORD* pAddressOfNames = (DWORD*)((DWORD)phModule+pimED->AddressOfNames);
for (int i=0;i<pimED->NumberOfNames;i++)
{
char* pExportName = (char*)(pAddressOfNames[i]+(DWORD)phModule);
if (strcmp(pProcName,pExportName) == 0)
{
WORD* pAddressOfNameOrdinals = (WORD*)((DWORD)phModule+pimED->AddressOfNameOrdinals);
pResult = (DWORD)phModule+((DWORD*)((DWORD)phModule+pimED->AddressOfFunctions))[pAddressOfNameOrdinals[i]];
break;
}
}
}
if (pResult != 0 && pResult >= (DWORD)pimED && pResult < (DWORD)pimED+pExportSize)
{
char* pDirectStr = (char*)pResult;
bool pstrok = false;
while (*pDirectStr)
{
if (*pDirectStr == '.')
{
pstrok = true;
break;
}
pDirectStr++;
}
if (!pstrok)
return 0;
char pdllname[MAX_PATH];
int pnamelen = pDirectStr-(char*)pResult;
if (pnamelen <= 0)
return 0;
memcpy(pdllname,(char*)pResult,pnamelen);
pdllname[pnamelen] = 0;
HMODULE phexmodule = GetModuleHandle(pdllname);
pResult = GetFunctionAddress(phexmodule,pDirectStr+1);
}
return pResult;
}
来自winroot大牛得 对付API-splicing的一种简单方法 [PSI_H]得文章中给出了一种方法
相关代码引用如下
请仔细看以上代码 以上代码实现了恢复函数入口10字节得功能,这样做,对付绝大多数得APIhook手段是完全奏效得,但是别忘记了,还有1位大侠得办法可以穿透他,HOOK API Lib by xIkUg引用:
首先脑子里想到的是,使用LoadLibrary/GetProcAddress函数来获取被拦截函数的原始代码,之后用它在内存里替换掉以前的代码,这 样就摘掉了对函数的HOOK。因为调用LoadLibrary将返回指向已加载模块的指针,所以必须将文件拷贝并加载此拷贝。下面的代码去除了对 ZwWriteVirtualMemory函数的拦截:
1. // 将NTDLL.DLL文件拷入TEMP文件夹
2.
3. char szTemp[MAX_PATH];
4.
5. GetTempPath(MAX_PATH, szTemp);
6.
7. strcat(szTemp, "ntdll2.dll");
8.
9. CopyFile("C:\\Windows\\System32\\ntdll.dll", szTemp, TRUE);
10.
11. // 取得指向原始函数的指针
12. HMODULE hMod = LoadLibrary(szTemp);
13. void* ptr_orig = GetProcAddress(hMod, "ZwWriteVirtualMemory");
14.
15. // 取得指向当前函数的指针
16. void* ptr_new = GetProcAddress (LoadLibrary("ntdll.dll"), "ZwWriteVirtualMemory");
17.
18. // 设置内存访问权限
19. DWORD dwOldProtect;
20. VirtualProtect(ptr_new, 10, PAGE_EXECUTE_READWRITE, &dwOldProtect);
21.
22. // 替换函数的前10个(为保险起见)字节
23. memcpy(ptr_new, ptr_orig, 10);
24.
25. FreeLibrary(hMod);
26. DeleteFile(szTemp);
尽管这里给出的摘除HOOK的方法完全奏效,但需要加载新的dll模块,这可能会引起防火墙的暴怒。我所认为的更为优雅的办法就是只需从文件中读取所需要的字节。下面这个函数的代码恢复了API的原始的起始部分。
1. bool RemoveFWHook(char* szDllPath, char* szFuncName) // szDllPath为DLL的完整路径 !
2. {
3. // 取得指向函数的指针
4. HMODULE lpBase = LoadLibrary(szDllPath);
5. LPVOID lpFunc = GetProcAddress(lpBase, szFuncName);
6. if(!lpFunc)
7. return false;
8. // 取得RVA
9. DWORD dwRVA = (DWORD)lpFunc-(DWORD)lpBase;
10.
11. // 将文件映射入内存
12. HANDLE hFile = CreateFile(szDllPath,GENERIC_READ, FILE_SHARE_READ,
13. NULL, OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL, NULL);
14. if(INVALID_HANDLE_VALUE == hFile)
15. return false;
16.
17. DWORD dwSize = GetFileSize(hFile, NULL);
18.
19. HANDLE hMapFile = CreateFileMapping(hFile, NULL, PAGE_READONLY|SEC_IMAGE, 0, dwSize, NULL);
20.
21. LPVOID lpBaseMap = MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, dwSize);
22.
23. // 指向当前函数的指针
24. LPVOID lpRealFunc = (LPVOID)((DWORD)lpBaseMap+dwRVA);
25.
26. // 修改访问权限并拷贝
27. DWORD dwOldProtect;
28. BOOL bRes=true;
29. if(VirtualProtect(lpFunc, 10, PAGE_EXECUTE_READWRITE, &dwOldProtect))
30. {
31. memcpy(lpFunc, lpRealFunc, 10);
32. }else{
33. bRes=false;
34. }
35.
36. UnmapViewOfFile(lpBaseMap);
37.
38. CloseHandle(hMapFile);
39. CloseHandle(hFile);
40.
41. return bRes;
42.
43. }
看代码 由于偶更加熟悉C代码一点。这里用得是海风月影得Hook api lib 0.2 for C
如果偶们强制恢复前面10字节会出错得,偶们得手段应该先使用GetOpCodeSize获得准确得字节大小予以恢复,不过偶选择1个比较偷懒得办法,直接恢复200个字节(对A系列得API基本有效)这种做法也对那些在函数返回ret处下断点得有效,引用:
BYTE BeforeStub[94] ={
0x58, // 0 pop eax
0xEB, 0x08, // 1 jmp short 0040100B
0x00, 0x00, 0x00, 0x00, // 3 dd 00000000
0x00, 0x00, 0x00, 0x00, // 7 dd 00000000
0xE8, 0x00, 0x00, 0x00, 0x00, // 11 call 00401010
0x59, // 16 pop ecx
0x81, 0xE9, 0x10, 0x10, 0x40, 0x00, // 17 sub ecx, 00401010
0x89, 0xA1, 0x03, 0x10, 0x40, 0x00, // 23 mov [ecx+401003], esp
0x89, 0x81, 0x07, 0x10, 0x40, 0x00, // 29 mov [ecx+401007], eax
0xE8, 0x36, 0x01, 0x00, 0x00, // 35 call HookProc 这里动态改变地址
0x8B, 0x44, 0x24, 0xFC, // 40 mov eax, [esp - 4]
0xE8, 0x00, 0x00, 0x00, 0x00, // 44 call 0040102D
0x59, // 49 pop ecx
0x89, 0x44, 0x24, 0xFC, // 50 mov [esp - 4], eax
0x81, 0xE9, 0x31, 0x10, 0x40, 0x00, // 54 sub ecx, 0040102D
0x8B, 0xA1, 0x03, 0x10, 0x40, 0x00, // 60 mov esp, [ecx+401003]
0x8B, 0x81, 0x07, 0x10, 0x40, 0x00, // 66 mov eax, [ecx+401007]
0x50, // 72 push eax
0x90, 0x90, 0x90, 0x90, // 73 保存入口处代码 16字节
0x90, 0x90, 0x90, 0x90,
0x90, 0x90, 0x90, 0x90,
0x90, 0x90, 0x90, 0x90,
0xE9, 0x18, 0x01, 0x00, 0x00 // 89 jmp HookedApi
};
参考文献:
西裤哥的 Hook api lib 0.2 for C by 海风月影 http://www.unpack.cn/viewthread.php?tid=11189
又一次写GetProcAddress函数 by lifeengines http://www.unpack.cn/thread-14189-1-1.html