所谓 InlineHook
的本质就是添加或修改程序函数中的汇编指令(如 jmp、call
指令),以实现程序逻辑改变造成跳转,即 Shellcode
注入。当执行完目标函数后,在将程序 EIP
跳转后原代码处,以避免影响原程序的正常功能。
需要注意的是,当程序开始在操作系统中加载运行时,其不同段的属性已经被设置,如代码段为可读可执行但不可写,而数据段则只允许读写而不允许执行等。需要通过 API
函数 VirtualProtect
将目标代码段的属性设置为可读可写可执行,才能实现修改进程代码段,否则写命令将会被拒绝,从而引起读写错误异常。
测试代码如下:
#include <stdio.h>
#include <windows.h>
BYTE code[20]; //存储原硬编码
LPSTR lpszParam; //函数参数
DWORD dwRet; //指向函数返回地址
DWORD codeLength; //覆盖目标代码段数据长度
DWORD dwdstFuncAddr;
DWORD dwsrcFuncAddr;
struct Reg
{
DWORD EAX;
DWORD ECX;
DWORD EDX;
DWORD EBX;
DWORD ESP;
DWORD EBP;
DWORD ESI;
DWORD EDI;
DWORD EFL;
};
Reg reg;
VOID __declspec(naked)InlineHook()
{
__asm
{
pushad
pushfd
mov reg.EAX,eax
mov reg.ECX,ecx
mov reg.EDX,edx
mov reg.EBX,ebx
mov eax,[esp+0x10]
mov reg.ESP,eax
mov reg.EBP,ebp
mov reg.ESI,esi
mov reg.EDI,edi
mov eax,[esp]
mov reg.EFL,eax
mov eax,[esp+4*10]
mov lpszParam,eax
}
//打印寄存器的值
printf("eax: %x\n",reg.EAX);
printf("ecx: %x\n",reg.ECX);
printf("edx: %x\n",reg.EDX);
printf("ebx: %x\n",reg.EBX);
printf("esp: %x\n",reg.ESP);
printf("ebp: %x\n",reg.EBP);
printf("esi: %x\n",reg.ESI);
printf("edi: %x\n",reg.EDI);
printf("elf: %x\n",reg.EFL);
//打印函数参数
printf("函数参数: %s\n", lpszParam);
//恢复代码
memcpy((LPVOID)dwsrcFuncAddr, &code, codeLength);
__asm
{
popfd
popad
jmp dwRet
}
}
BOOL SetInLineHook(LPVOID lpdstFuncAddr, LPVOID lpsrcFuncAddr, DWORD codeLen)
{
//地址转换, 将jmp SayHello指令地址转化为真正的函数地址
dwdstFuncAddr = *(DWORD*)((DWORD)lpdstFuncAddr+1) + (DWORD)lpdstFuncAddr + 5;
dwsrcFuncAddr = *(DWORD*)((DWORD)lpsrcFuncAddr+1) + (DWORD)lpsrcFuncAddr + 5;
//将原代码的硬编码存储在code数组中
codeLength = codeLen;
memcpy(&code, (LPVOID)dwsrcFuncAddr, codeLength);
//返回地址
dwRet = dwsrcFuncAddr;
//计算jmp的硬编码
BYTE shellCode[5] = {
0xe9, 0x00, 0x00, 0x00, 0x00
};
DWORD jmpAddr = dwdstFuncAddr - (dwsrcFuncAddr + codeLength);
memcpy((LPVOID)((DWORD)&shellCode+1), &jmpAddr, 4);
//修改页面属性
DWORD flOldProtect;
DWORD ret = VirtualProtectEx(GetCurrentProcess(), (LPVOID)dwdstFuncAddr, codeLength, PAGE_EXECUTE_READWRITE, &flOldProtect);
if(!ret)
{
printf("页面属性设置失败!\n");
return FALSE;
}
//将shellCode写入代码中
memset((LPVOID)dwsrcFuncAddr, 0x90, codeLength);
memcpy((LPVOID)dwsrcFuncAddr, &shellCode, 5);
return TRUE;
}
VOID SayHello(LPSTR lpszName)
{
printf("\nhaha,%s!\n", lpszName);
}
int main()
{
//初始化code数组
memset(code, 0, 20);
//设置InLineHook
SetInLineHook(InlineHook, SayHello, 5);
SayHello("walker");
return 0;
}
效果图如下: