注:我实验的环境:win7 x64
经验证XP会有稍微区别,主要是我本次实验HOOK的API, 从XP到WIN7微软有了些许改变。
----------------------------------------------------------------------------------------------------------------------------------------
最近在看驱动教程时,从前人的HOOK得到启发,才有了本篇文章,在此甚是感激。
文章估计有些长,当是我的学习笔记了,适合有一定HOOK基础的盆友,
我写这篇文章的目的就是为了提高HOOK的效率。
本次我以HOOK GetProcAddress为例。
例子VS2010工程HookGetProcAddressTest.rar下载地址:
http://download.csdn.net/detail/friendan/8454859
--------------------------------------------------------------------------------------------------------------------------------
问题的提出:
以前我们HOOK时,第一步就是定义我们自己假的API了,如:
FARPROC WINAPI MyGetProcAddress(
__in HMODULE hModule,
__in LPCSTR lpProcName
);
在我们假的API中,一般做法如下:
FARPROC WINAPI MyGetProcAddress(__in HMODULE hModule, __in LPCSTR lpProcName)
{
// 1.记录参数
// 2.修改参数
// 3.修改返回值
// 4.恢复HOOK,调用原函数,然后再HOOK
// ...
}
问题就出在第4步了,如果是多线程的环境,在恢复HOOK时,我们可能会漏HOOK
-----------------------------------------------------------------------------------------------------------------------------------------------------
问题的解决:
为了不恢复HOOK,也能调用原API,我加了一个中转函数,该函数没有参数,也没有返回值,
没有参数和没有返回值是为了保持堆栈的平衡,该函数声明如下:
void GetProcAddressProxy(void);
实现如下:
__declspec(naked) void GetProcAddressProxy(void)
{
// 先执行原来的代码,再跳到原地址+5处执行
__asm
{
// 原函数入口代码
mov edi,edi
push ebp
mov ebp,esp
// 调用我们的过滤函数
push [ebp+0xC] // 参数lpProcName
push [ebp+0x8] // 参数hModule
call MyGetProcAddress
// 跳到原函数首地址+5处执行,因为其前面的5字节已经被我们修改了
mov eax, g_iOldGetProcAddress
add eax, 5
jmp eax
}
}
可以看出在中转函数中调用了我们的过滤函数即假API了,本次实验,我的假API主要功能为替换参数和替换返回值,
主要代码如下:
FARPROC WINAPI MyGetProcAddress(__in HMODULE hModule, __in LPCSTR lpProcName)
{
printf("MyGetProcAddress: hModule=0x%X, lpProcName=%s \n", hModule, lpProcName);
// 如果是Sleep,我们将其替换成不存在的一个函数
if (strcmp(lpProcName, "Sleep") == 0)
{
DisabeMemoryProtect((int)lpProcName, strlen(lpProcName));
strcpy((char*)lpProcName, "Slee8"); // 函数名不能比原来的长哈
EnableMemoryProtect((int)lpProcName, strlen(lpProcName));
}
// 如果是要过滤的函数
if (strcmp(lpProcName, "LoadLibraryW") == 0)
{
// 获取KernelBa.GetProcAddress入口地址
// 7564122F - FF25 280B6475 jmp dword ptr ds:[<&API-MS-Win-Core-LibraryLoader-L1-1-0.GetProcAd>; KernelBa.GetProcAddress
// win7 64
if (*(byte*)(g_iOldGetProcAddress+6) == 0xEB)
{
g_iGetProcAddressHookAddr = g_iOldGetProcAddress + 15;
g_iGetProcAddressHookAddr = *(int*)g_iGetProcAddressHookAddr;
g_iGetProcAddressHookAddr = *(int*)g_iGetProcAddressHookAddr;
}
// xp
if (*(byte*)(g_iOldGetProcAddress+6) == 0x51)
{
g_iGetProcAddressHookAddr = g_iOldGetProcAddress;
}
// 从函数入口寻找特征码,找到要替换的起始地址
/*76121E83 8B45 0C mov eax,dword ptr ss:[ebp+0xC] ;获取到的函数地址
76121E86 5F pop edi
76121E87 5B pop ebx
76121E88 C9 leave
76121E89 > C2 0800 retn 0x8 ; 返回用户层
76121E8C CC int3
76121E8D CC int3
76121E8E CC int3
76121E8F CC int3
76121E90 CC int3*/
byte bSpecialCode[9] = {0x8B, 0x45, 0x0C, 0x5F, 0x5B, 0xC9, 0xC2, 0x08, 0x00};
g_iGetProcAddressHookAddr = FindSpecialCode(g_iGetProcAddressHookAddr, bSpecialCode, sizeof(bSpecialCode));
if (g_iGetProcAddressHookAddr <= 0)
{
return 0;
}
//ShowMemoryData(g_iGetProcAddressHookAddr, 14);
// 旧函数的尾部代码
byte bOldCode[14] = {0x8B, 0x45, 0x0C, 0x5F, 0x5B, 0xC9, 0xC2, 0x08, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC};
// 替换原来的0x8B, 0x45, 0x0C,即替换mov eax,dword ptr ss:[ebp+0xC]
// 76071242 B8 78563412 mov eax,0x12345678
byte bNewCode[11] = {0xB8, 0x90, 0x90, 0x90, 0x90, 0x5F, 0x5B, 0xC9, 0xC2, 0x08, 0x00};
DisabeMemoryProtect(g_iGetProcAddressHookAddr, sizeof(bNewCode));
memcpy((void*)g_iGetProcAddressHookAddr, bNewCode, sizeof(bNewCode));
*(int*)(g_iGetProcAddressHookAddr + 1) = (int)LoadLibraryI; // 将4个0x90替换成我们的函数地址
g_bIsModifyGetProcAddressHookAddr = true;
EnableMemoryProtect(g_iGetProcAddressHookAddr, sizeof(bNewCode));
// ShowMemoryData(g_iGetProcAddressHookAddr, 14);
}
else
{
// 恢复被我们修改过的指令
// 旧函数的尾部代码
byte bOldCode[14] = {0x8B, 0x45, 0x0C, 0x5F, 0x5B, 0xC9, 0xC2, 0x08, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC};
if (g_bIsModifyGetProcAddressHookAddr)
{
DisabeMemoryProtect(g_iGetProcAddressHookAddr, sizeof(bOldCode));
memcpy((void*)(g_iGetProcAddressHookAddr), bOldCode, sizeof(bOldCode));
g_bIsModifyGetProcAddressHookAddr = false;
EnableMemoryProtect(g_iGetProcAddressHookAddr, sizeof(bOldCode));
}
}
return 0;
}
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
在HOOK时,我们让原API跳到我们的中转函数即可,我HOOK的实现代码如下:
void HookGetProcAddress(bool bIsHook)
{
// 为方便OD调试,加入的特征码
__asm
{
mov eax, eax
mov ebx, ebx
mov ecx, ecx
}
// HOOK GetProcAddress
if (bIsHook)
{
// 取GetProcAddress地址
g_iOldGetProcAddress = (int)GetProcAddress(LoadLibrary(_T("Kernel32.dll")), "GetProcAddress");
if (g_iOldGetProcAddress <= 0)
{
return;
}
// 修改函数前5个字节,使其跳到我们的函数GetProcAddressProxy
DisabeMemoryProtect(g_iOldGetProcAddress, 5);
int iJmpAddr = (int)GetProcAddressProxy - g_iOldGetProcAddress - 5;
*(byte *)g_iOldGetProcAddress = 0xE9; // jmp
*(int *)(g_iOldGetProcAddress + 1) = iJmpAddr;
EnableMemoryProtect(g_iOldGetProcAddress, 5);
}
// UNHOOK GetProcAddress
if (!bIsHook && g_iOldGetProcAddress > 0)
{
// 恢复函数前5个字节
// 8B FF 55 8B EC
byte bBeginCode[5] = {0x8B,0xFF, 0x55, 0x8B, 0xEC};
DisabeMemoryProtect(g_iOldGetProcAddress, sizeof(bBeginCode));
memcpy((void*)g_iOldGetProcAddress, bBeginCode, sizeof(bBeginCode));
EnableMemoryProtect(g_iOldGetProcAddress, sizeof(bBeginCode));
// 恢复被我们修改过的指令
// 旧函数的尾部代码
byte bOldCode[14] = {0x8B, 0x45, 0x0C, 0x5F, 0x5B, 0xC9, 0xC2, 0x08, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC};
if (g_bIsModifyGetProcAddressHookAddr)
{
DisabeMemoryProtect(g_iGetProcAddressHookAddr, sizeof(bOldCode));
memcpy((void*)(g_iGetProcAddressHookAddr), bOldCode, sizeof(bOldCode));
g_bIsModifyGetProcAddressHookAddr = false;
EnableMemoryProtect(g_iGetProcAddressHookAddr, sizeof(bOldCode));
}
}
}
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
效果截图如下:
-------------------------------------------------------------------------------------------------------------------------------------------------
帖下我在WIN7 X64使用OD得出GetProcAddress的反汇编代码:
// bp Kernel32.GetProcAddress
75641222 > 8BFF mov edi,edi ; GetProcAddresss入口
75641224 55 push ebp
75641225 8BEC mov ebp,esp
75641227 5D pop ebp
75641228 EB 05 jmp X<jmp.&API-MS-Win-Core-LibraryLoader-L1-1-0.GetProcAddress>
7564122A 90 nop
7564122B 90 nop
7564122C 90 nop
7564122D 90 nop
7564122E 90 nop
7564122F - FF25 280B6475 jmp dword ptr ds:[<&API-MS-Win-Core-LibraryLoader-L1-1-0.GetProcAd>; KernelBa.GetProcAddress
75641235 90 nop
75641236 90 nop
76121E15 > 8BFF mov edi,edi ; KernelBa.GetProcAddress入口地址
76121E17 55 push ebp
76121E18 8BEC mov ebp,esp
76121E1A 51 push ecx
76121E1B 51 push ecx
76121E1C 53 push ebx
76121E1D 57 push edi
76121E1E 8B7D 0C mov edi,dword ptr ss:[ebp+0xC]
76121E21 BB FFFF0000 mov ebx,0xFFFF
76121E26 3BFB cmp edi,ebx
76121E28 76 17 jbe XKernelBa.76121E41
76121E2A 57 push edi
76121E2B 8D45 F8 lea eax,dword ptr ss:[ebp-0x8]
76121E2E 50 push eax
76121E2F FF15 AC121176 call dword ptr ds:[<&ntdll.RtlInitString>] ; ntdll_1a.RtlInitString
76121E35 8D45 0C lea eax,dword ptr ss:[ebp+0xC]
76121E38 50 push eax
76121E39 6A 00 push 0x0
76121E3B 8D45 F8 lea eax,dword ptr ss:[ebp-0x8]
76121E3E 50 push eax
76121E3F EB 07 jmp XKernelBa.76121E48
76121E41 8D45 0C lea eax,dword ptr ss:[ebp+0xC]
76121E44 50 push eax
76121E45 57 push edi
76121E46 6A 00 push 0x0
76121E48 6A 00 push 0x0
76121E4A FF75 08 push dword ptr ss:[ebp+0x8]
76121E4D E8 C05E0200 call KernelBa.76147D12
76121E52 50 push eax
76121E53 FF15 A8121176 call dword ptr ds:[<&ntdll.LdrGetProcedureAddress>] ; ntdll_1a.LdrGetProcedureAddress
76121E59 85C0 test eax,eax
76121E5B 7D 0A jge XKernelBa.76121E67
76121E5D 50 push eax
76121E5E E8 1F590200 call KernelBa.76147782
76121E63 33C0 xor eax,eax
76121E65 EB 1F jmp XKernelBa.76121E86
76121E67 6A 00 push 0x0
76121E69 FF75 08 push dword ptr ss:[ebp+0x8]
76121E6C E8 A15E0200 call KernelBa.76147D12
76121E71 3945 0C cmp dword ptr ss:[ebp+0xC],eax
76121E74 75 0D jnz XKernelBa.76121E83
76121E76 3BDF cmp ebx,edi
76121E78 1BC0 sbb eax,eax
76121E7A F7D8 neg eax
76121E7C 05 380100C0 add eax,0xC0000138
76121E81 ^ EB DA jmp XKernelBa.76121E5D
76121E83 8B45 0C mov eax,dword ptr ss:[ebp+0xC];获取到的函数地址
76121E86 5F pop edi
76121E87 5B pop ebx
76121E88 C9 leave
76121E89 > C2 0800 retn 0x8 ; 返回用户层
76121E8C CC int3
76121E8D CC int3
76121E8E CC int3
76121E8F CC int3
76121E90 CC int3
---------------------------------------------------------------------------------------------------------------------------------------------
帖下我在XP使用OD得出GetProcAddress的反汇编代码:
7C80AE40 > 8BFF MOV EDI, EDI ; kernel32.7C800000
7C80AE42 55 PUSH EBP
7C80AE43 8BEC MOV EBP, ESP
7C80AE45 51 PUSH ECX
7C80AE46 51 PUSH ECX
7C80AE47 53 PUSH EBX
7C80AE48 57 PUSH EDI
7C80AE49 8B7D 0C MOV EDI, DWORD PTR SS:[EBP+0xC]
7C80AE4C BB FFFF0000 MOV EBX, 0xFFFF
7C80AE51 3BFB CMP EDI, EBX
7C80AE53 ^ 0F86 D1F2FFFF JBE kernel32.7C80A12A
7C80AE59 57 PUSH EDI
7C80AE5A 8D45 F8 LEA EAX, DWORD PTR SS:[EBP-0x8]
7C80AE5D 50 PUSH EAX
7C80AE5E FF15 9412807C CALL NEAR DWORD PTR DS:[<&ntdll.RtlIn>; ntdll.RtlInitString
7C80AE64 8D45 0C LEA EAX, DWORD PTR SS:[EBP+0xC]
7C80AE67 50 PUSH EAX
7C80AE68 6A 00 PUSH 0x0
7C80AE6A 8D45 F8 LEA EAX, DWORD PTR SS:[EBP-0x8]
7C80AE6D 50 PUSH EAX
7C80AE6E 6A 00 PUSH 0x0
7C80AE70 FF75 08 PUSH DWORD PTR SS:[EBP+0x8]
7C80AE73 E8 1CEBFFFF CALL kernel32.7C809994
7C80AE78 50 PUSH EAX
7C80AE79 E8 B7FFFFFF CALL <JMP.&ntdll.LdrGetProcedureAddre>
7C80AE7E 85C0 TEST EAX, EAX
7C80AE80 0F8C F6840000 JL kernel32.7C81337C
7C80AE86 6A 00 PUSH 0x0
7C80AE88 FF75 08 PUSH DWORD PTR SS:[EBP+0x8]
7C80AE8B E8 04EBFFFF CALL kernel32.7C809994
7C80AE90 3945 0C CMP DWORD PTR SS:[EBP+0xC], EAX
7C80AE93 0F84 B65F0300 JE kernel32.7C840E4F
7C80AE99 8B45 0C MOV EAX, DWORD PTR SS:[EBP+0xC]
7C80AE9C 5F POP EDI
7C80AE9D 5B POP EBX
7C80AE9E C9 LEAVE
7C80AE9F C2 0800 RETN 0x8 ; 函数之后的数据和WIN7不一样
7C80AEA2 837D 10 00 CMP DWORD PTR SS:[EBP+0x10], 0x0
7C80AEA6 ^ 0F85 07E5FFFF JNZ kernel32.7C8093B3
7C80AEAC 33FF XOR EDI, EDI
7C80AEAE ^ E9 07E5FFFF JMP kernel32.7C8093BA