一、 什么是 inline hook
inline hook 就是在运行的流程中插入跳转指令(call/jmp)来抢夺程序运行流程的一个方法。
好了那么问题就来了:
1. 插入怎么样的跳转指令
一般为 相对JMP(0xE9) + 4字节地址,方便计算
公式:源地址 + 相对偏移 = 目的地址
公式:目的地址 - 源地址 = 相对偏移
2. 被跳转指令覆盖的原始指令应该被保存起来,找适当的时机再执行
3. 需要对应的 jmpIn 和 jmpOut 跳转。
4. inline hook 的一般流程:
1. 指令或者指令之和大于等于5,因为我们要塞进去5个字节(0xE9 + Address)。只要你处理跳转指令覆盖的指令,理论上可以 inline hook 函数中的任意位置。
2. 判断被 inline hook 位置的指令可以使用反汇编引擎判断指令长度。
3. 很多系统调用的开头都是,能很方便的 inline hook
代码:
1. 把Add函数的前5个字节搬到了 hookProxy的前5个字节。
2. 然后再 Add函数的前5个字节填充跳转到 hookProxy 的跳转指令
3. 在hookProxy的末尾5个字节填充跳转回 Add函数 + 5 的跳转指令
4. 原本 Add 函数的结构是3,被 hook 之后会变成 5
inline hook 就是在运行的流程中插入跳转指令(call/jmp)来抢夺程序运行流程的一个方法。
好了那么问题就来了:
1. 插入怎么样的跳转指令
一般为 相对JMP(0xE9) + 4字节地址,方便计算
公式:源地址 + 相对偏移 = 目的地址
公式:目的地址 - 源地址 = 相对偏移
2. 被跳转指令覆盖的原始指令应该被保存起来,找适当的时机再执行
3. 需要对应的 jmpIn 和 jmpOut 跳转。
4. inline hook 的一般流程:
源程序流 -> jmpIn -> 保存寄存器 -> 具体处理 -> 恢复寄存器 -> jmpOut -> 源程序流被跳转指令覆盖的代码可以在 jmpIn之后 或者 jmpOut之前
1. 指令或者指令之和大于等于5,因为我们要塞进去5个字节(0xE9 + Address)。只要你处理跳转指令覆盖的指令,理论上可以 inline hook 函数中的任意位置。
2. 判断被 inline hook 位置的指令可以使用反汇编引擎判断指令长度。
3. 很多系统调用的开头都是,能很方便的 inline hook
代码:
mov edi, edi
push ebp
mov ebp, esp
三、 可以写 hookProxy 函数,保存和恢复寄存器,统一调用逻辑。
完整代码逻辑:
//.H
#pragma once
__declspec(naked) int Add(int a, int b)
{
_asm
{
mov edi, edi
push ebp
mov ebp, esp
sub esp, 8
mov eax, [ebp+0x08]
mov [ebp-0x08], eax
mov eax, [ebp+0xc]
mov [ebp-0x04], eax
mov eax, [ebp-0x08]
add eax, [ebp-0x04]
mov esp, ebp
pop ebp
ret
}
}
__declspec(naked) void HookProxy()
{
// moved bytes
_asm
{
nop
nop
nop
nop
nop
}
// save registers
//_asm
//{
// pushad
// pushfd
//}
__asm
{
add [esp+0x08], 1
add [esp+0x0c], 1
}
// restore registers
//_asm
//{
// popad
// popfd
//}
// use to jump source func back code
_asm
{
nop
nop
nop
nop
nop
}
}
void InlineHook()
{
// move old bytes
memcpy((BYTE*)HookProxy, (BYTE*)Add, 5);
// rewrite Add the first of 5 bytes to jmp xxxx
BYTE jmpInBuffer[5];
jmpInBuffer[0] = 0xe9;
DWORD* pJmpInOffset = (DWORD*)&jmpInBuffer[1];
*pJmpInOffset = (DWORD)HookProxy - ((DWORD)Add + 5);
memcpy(Add, jmpInBuffer, 5);
// rewrite HookProc the end 0f 5 bytes to jmp xxxx
BYTE jmpOutBuffer[5];
jmpOutBuffer[0] = 0xe9;
DWORD* pJmpOutOffset = (DWORD*)&jmpOutBuffer[1];
BYTE* pJmpBackCode = &((BYTE*)HookProxy)[0xF]; // the pos that the end of 5 bytes
*pJmpOutOffset = ((DWORD)Add + 5) - ((DWORD)pJmpBackCode + 5);
memcpy(pJmpBackCode, jmpOutBuffer, 5);
}
void UnInlineHook()
{
// write back Add the first of 5 bytes
BYTE restoreBuffer[5] = { 0 };
memcpy(restoreBuffer, HookProxy, 5);
memcpy(Add, restoreBuffer, 5);
}
//.CPP
DWORD dwNewProtect = PAGE_EXECUTE_READWRITE;
DWORD oldProtect1;
DWORD oldProtect2;
VirtualProtect((LPVOID)Add, 4096, dwNewProtect, &oldProtect1);
VirtualProtect((LPVOID)HookProxy, 4096, dwNewProtect, &oldProtect2);
InlineHook();
printf("After Hook Result = %d \n", Add(1, 2));
UnInlineHook();
printf("After UnHook Result = %d \n", Add(1, 2));
VirtualProtect((LPVOID)Add, 4096, oldProtect1, &dwNewProtect);
VirtualProtect((LPVOID)HookProxy, 4096, oldProtect2, &dwNewProtect);
四、解析例子
1. 把Add函数的前5个字节搬到了 hookProxy的前5个字节。
2. 然后再 Add函数的前5个字节填充跳转到 hookProxy 的跳转指令
3. 在hookProxy的末尾5个字节填充跳转回 Add函数 + 5 的跳转指令
4. 原本 Add 函数的结构是3,被 hook 之后会变成 5
栗子2:
//.H
#pragma pack(1)
typedef struct _JMPCODE
{
BYTE jmp;
DWORD addr;
}JMPCODE, *PJMPCODE;
typedef int (WINAPI *MessageBox_type) (
__in_opt HWND hWnd,
__in_opt LPCSTR lpText,
__in_opt LPCSTR lpCaption,
__in UINT uType);
MessageBox_type RealMessageBox = NULL;
_declspec(naked) int WINAPI MyMessageBox(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType)
{
_asm
{
push ebp
mov ebp, esp
}
printf("hwnd:%p lpText:%s lpCaption:%s,uType:%X\n", hWnd, lpText, lpCaption, uType);
_asm
{
mov ebx, RealMessageBox
add ebx, 5
jmp ebx
}
}
void HookMessageBox()
{
RealMessageBox = MessageBoxA;
JMPCODE jumpCode;
jumpCode.jmp = 0xe9;
jumpCode.addr = (DWORD)MyMessageBox - ((DWORD)RealMessageBox + 5);
DWORD dwNewProtect = PAGE_EXECUTE_READWRITE;
MEMORY_BASIC_INFORMATION mbi;
VirtualQuery(MessageBoxA, &mbi, sizeof(MEMORY_BASIC_INFORMATION));
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &mbi.Protect);
WriteProcessMemory(GetCurrentProcess(), MessageBoxA, &jumpCode, sizeof(jumpCode), NULL);
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, &dwNewProtect);
}
void UnHookMessageBox()
{
// 也可以在Hook的时候保存MessageboxA的前5个字节
BYTE backCode[5] = { 0 };
backCode[0] = 0x8B; // mov edi, edi
backCode[1] = 0xFF;
backCode[2] = 0x55; // push ebp
backCode[3] = 0x8B; // mov ebp, esp
backCode[4] = 0xEC;
DWORD dwNewProtect = PAGE_EXECUTE_READWRITE;
MEMORY_BASIC_INFORMATION mbi;
VirtualQuery(MessageBoxA, &mbi, sizeof(MEMORY_BASIC_INFORMATION));
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &mbi.Protect);
WriteProcessMemory(GetCurrentProcess(), MessageBoxA, backCode, sizeof(backCode) / sizeof(backCode[0]), NULL);
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, &dwNewProtect);
}
//.CPP
HookMessageBox();
::MessageBoxA(NULL, "HELLO", "WORLD", MB_OK);
UnHookMessageBox();
::MessageBoxA(NULL, "HELLO2", "WORLD2", MB_OK);
执行步骤:
1.把MessageBoxA的前5个字节改写为:jmp xxx(地址是MyMessageBoxA),跳转的指令格式:jmp 相对地址
2.在MessageBoxA中,重新执行原来的汇编指令:(mov edi, edi可忽略)
push ebp
mov ebp, esp
然后执行自己的逻辑
最后再跳转到MessageBoxA中第5个字节的位置继续执行,这里的跳转的指令格式:jmp 16位寄存器
参考:
http://bbs.pediy.com/showthread.php?p=1337589