此时我们可以看到,[EBP]为保存的ebp的值,现在对我们没有用处,函数返回前用于恢复EBP的值,[EBP+4]是recv函数的CALL XXXX后面指令的地址(也就是第六个字节的地址),我们可以通过将此值减去5来得到recv的入口地址,这样在我们所有hook的api函数的列表中进行检索,就可以匹配出用户调用的是哪一个API函数,从而为后面恢复和再次改变该API的入口5字节做准备,因为调用任何我们需要HOOK的API程序都会进入到这个无返回值无参数的函数,所以通过这种方法找到当前HOOK的是哪一个API,从而可以区分不同的API进行特殊的处理。[EBP+8]保存的是用户调用recv后的返回地址,由于我们执行完替换函数后,应该返回到这个地址,而不应该返回到recv的第6个字节处执行,所以我们还是需要保存下这个值,以便在我们用ret返回前把它压栈从而使程序返回到用户调用recv的下一条指令处继续运行。
我们先定义如下函数:
void CommonFunc(void);
我们现在实现它,请注意参考上面堆栈表格。
void CommonFunc(void)
{
DWORD pdwCall; // recv入口地址
DWORD dwRtAddr; // 我们的函数真正要返回的地址
DWORD* pdwParam; // 第一个参数的地址
DWORD dwParamCount; // 参数个数
DWORD dwParamSize; // 所有参数所占用的大小应该=4* dwParamCount
DWORD dwRt; // 返回值
_asm
{
lea EAX,[EBP+4] // recv入口处第6个字节的地址
mov [pdwCall],EAX
mov EAX,[EBP+8] // 用户调用recv(即call XXXX)后面一条指令的地址
mov [dwRtAddr],EAX
lea EAX, [EBP+12] // 第一个参数的地址!
mov [pdwParam],EAX
}
(*pdwCall) -= 5; // 获得recv入口地址
HOOKINFO *hi = findHookInfo(pdwCall); // 通过原始API的入口地址获得此API的相关信息
memcpy(pdwCall,hi->OrgApi5bytes,5); // 恢复被调用API的前5个字节,使下面的代码可以正常调用
// 下面准备进入用户针对此API的替换函数,现准备参数
dwParamCount = hi->ParamCount; // 得到本API的参数个数
dwParamSize = 4*dwParamCount; // 计算参数所占用大小
DWORD pdwESP;
_asm
{
sub esp,[dwParamSize] // 将栈增加,可以容纳参数
mov [pdwESP],esp // 保存当前栈的地址
}
memcpy(pdwESP,pdwParam,dwParamSize);//将用户传递的参数拷贝到栈中
hi->myAPIFunc(); // 调用用户针对此API的替换函数
_asm
{
mov [dwRt],eax // 保存返回值
}
// 如果是CreateProcess,那么继续hook它
pPi = (PROCESS_INFORMATION*)pdwParam[9];
if(strcmpi(pai->szOrgApiName,"CreateProcessA") != 0 || strcmpi(pai->szOrgApiName,"CreateProcessW") != 0)
{
InjectDll(pPi->dwProcessId,m_szDllPathName);
}
// 下面再次修改原始API的前5个字节
memcpy(pdwCall,JMPCODE,5); // #define JMPCODE 0xE8
DWORD* pdwapi = pdwCall[1];
pdwapi[0] = (DWORD) CommonFunc – (DWORD)pdCall – 5; // CommonFunc函数地址的偏移
// 下面准备返回的操作
_asm
{
add esp,[dwParamSize] // 清理我们为了调用真正的替换函数而分配的堆栈里的参数
// 下面弹出所有保存的寄存器值(按照入栈的逆顺序)
pop EDI // 恢复EDI
pop ESI // 恢复ESI
pop EBX // 恢复EBX
// 我们没有改动过EBP的值,所以EBP指向堆栈中OldEBP的位置
mov ESP,EBP
pop EBP // 恢复EBP
// 由于堆栈中还剩下参数和两个返回地址(我们真正要返回的地址和原始API中的第6个字节的地址),所以我们把这些数据也清除出堆栈
add ESP,8 // 清除两个返回地址
mov ECX,[dwParamSize] // 获得参数的大小
add ESP,ECX // 清除参数
mov EAX,[dwRt] // 设置返回值
// 由于调用ret返回时,程序先从堆栈中取出返回地址,所以我们把要真正返回的地址压入堆栈中
mov EDX,[dwRtAddr] // 设置返回地址
push EDX
ret // 返回
}
} 最后要注意得一点是,如果要执行得API函数是CreateProcess,那么应该把它新开启得进程也HOOK掉。以上我们了解了通用替换函数的原理,那么让我们深入的讨论CHookApi类,并且实现它。
|