|
分享源码
界面截图: |
- |
是否带模块: |
- |
备注说明: |
- |
本帖最后由 雨过天晴 于 2023-2-21 19:48 编辑
上次说过后面会开源一份无痕HOOK,但是这个技术非常的麻烦,用易语言写出来很困难,所以不再对易语言进行编写,而以现有的可以使用的源码上对原理进行讲解
不要把硬断与这个方法混为一谈,两者虽然都是沾了异常处理的光,但是硬断只能四个还要依靠线程,十分不稳定.
Non_trace_jmpinfo Non_trace::Veh_Hook_strat(Base head, Base satrtaddr, Base HookAddr, DWORD size, Base NewMemory, PVOID proc)
{
参数1 为需要HOOK的模块基础地址 比如像0x400000
第二个参数是需要HOOK地址的0x1000字节对齐地址的地址 假如我们 HOOK的地址是 0x455555 那么开始地址必须是 0x455000 或者 0x454000 必须被0x1000整除
第三个参数是HOOK的绝对地址
第四个参数是从0x455000 或者 0x454000 开始的作用范围大小 (必须0x1000以上覆盖HOOK的绝对地址并且0x1000字节对齐) , 这个范围内的代码执行都会异常触发
第五个参数是拷贝出来的新内存 相当于回调构造 第五个参数是跳转的函数
Non_trace_hook.VehAdd(head, satrtaddr - head, satrtaddr, size, NewMemory); 把信息添加到数组结构
下面的代码是计算偏移然后对拷贝出来的对新内存地址的相对偏移进行HOOK
Base jmp = HookAddr- satrtaddr + NewMemory;
ApiHookinfo* b = InstallAPIhook(jmp);
Non_trace_jmpinfo a;
a.jmpback = b->originalbyte;//跳回的构造地址
BuildHookBG(jmp, proc);
/////--------------------------------
return a;
}
先介绍一下原理
使用AddVectoredExceptionHandler(1, Veh_Main);这个函数后我们的程序可以接收到来自本进程的各种异常,具体异常处理机制我不介绍,先说一下那些可以被我们利用,来实现这个无痕HOOK.
异常机制中有一个页面异常 EXCEPTION_ACCESS_VIOLATION
当程序发生异常执行或访问后会进入到我们的异常处理函数,提交缺页异常信息.
LONG Non_trace::veh_proc(EXCEPTION_POINTERS* ExceptionInfo)
{
if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
{ 处理代码.... }
}
}
这时候我们的程序,可以在异常处理中修改执行过程的 寄存器 之类的很多信息 当然包括了运行点.
当这个函数返回-1时候系统会不再调用其他的异常函数而直接跳到参数中地址的EIP(运行点)
如果我们想在不修改代码的情况下对程序进行HOOK,那么就可以利用页面异常,
我们需要使用 ZwProtectVirtualMemory 函数来是目标进程代码主动触发页面异常.
ZwProtectVirtualMemory 有5个参数, 我们只需要把NewProtect参数设置为1 这个地方的执行访问代码都会造成页面异常
ProcessHandle | 整数型 |
| | 打开的程序句柄 | BaseAddress | 长整数型 |
| | 开始地址 | RegionSize | 整数型 |
| | 地址范围 | NewProtect | 整数型 |
| | 新的保护设置 | OldProtect | 整数型 |
| | 旧的保护设置 |
非常注意的是,物理页面中最小的分页是0x1000字节 ,所以这个函数最小只能生效0x1000字节的内存区域 ,并且起始地址整除0x1000
意思便是说 当我们想无痕HOOK一个点,这个区域的0x1000字节的执行代码都会触发页面异常,然后跳到异常处理函数,所以我们需要一个逻辑与0xfff的操作来把地址换算成整除0x1000的页面起始地址.
当我们执行完Veh_Hook_strat函数后把跳转的信息添加到了数组, 我们还需要把那一块整除0x1000内存拷贝到新内存地址, 比如我们想 HOOK 0x455555 我们就需要拷贝 0x455000 - 0x456000 这个范围的内存到 Veh_Hook_strat函数的NewMemory的内存中,这样在执行异常处理的时候我们才能正确判断并且跳转
当然我们别忘了对NewMemory+555 这个地址进行InlineHOOK,这样才能使跳转到 NewMemory-NewMemory+0x1000 中的代码会执行这个InlineHOOK
完整的读写执行分离处理
LONG Non_trace::veh_proc(EXCEPTION_POINTERS* ExceptionInfo)//这个参数是系统传递的异常信息 包括寄存器跟出现异常的地址, 或者指令访问的地址
// 注意:易语言需要用到修改内存,不能像下面直接对数据类型进行直接的引用修改
{
if (ExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION)//判断是否页面异常
{
if ((Base)ExceptionInfo->ExceptionRecord->ExceptionAddress== ExceptionInfo->ExceptionRecord->ExceptionInformation[1])//代表执行
{
//如果是因为写入或者访问的指令引用到了页面异常区域那么ExceptionInfo->ExceptionRecord->ExceptionInformation[1] 的值会是被访问或者被写入的地址 如果ExceptionInfo->ExceptionRecord->ExceptionAddress相同于ExceptionInfo->ExceptionRecord->ExceptionInformation[1] 那么代表这个异常是执行异常,并不是访问异常
for (size_t i = 0; i < VEHHookinfo.size(); i++)
{ 通过对数组的循环我们判断到了异常执行地址处于我们刚刚备份的范围 这时候就要通过一个简单的算法计算出跳转到新内存区域的偏移
if (IsAddressInRange(VEHHookinfo.addr, VEHHookinfo.size, ExceptionInfo->ExceptionRecord->ExceptionInformation[1]))
{
int newp= abs((int)(VEHHookinfo.offset - (int)(ExceptionInfo->ContextRecord->Eip - VEHHookinfo.head))) + VEHHookinfo.Newaddr;
ExceptionInfo->ContextRecord->Eip = (DWORD)newp;最后把Eip改为指向新内存的区域
return -1;
}
}
}
else
{ //下面是处理对HOOK区域访问的代码,使用了单步处理,在没有crc自效验中可以剔除
for (size_t i = 0; i < VEHHookinfo.size(); i++)
{
if (IsAddressInRange(VEHHookinfo.addr, VEHHookinfo.size, ExceptionInfo->ExceptionRecord->ExceptionInformation[1]))
{
VEHHookinfo.visitAddr = ExceptionInfo->ExceptionRecord->ExceptionInformation[1];
CALL_ZwProtectVirtualMemory2((HANDLE)-1, (LPVOID)(VEHHookinfo.visitAddr & 0xFFFFF000), 0x1000, 64, &OldProtect);
ExceptionInfo->ContextRecord->EFlags |= 0x100;//Do a single step
return EXCEPTION_CONTINUE_EXECUTION;
}
}
}
}
if (ExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_SINGLE_STEP)
{
for (size_t i = 0; i < VEHHookinfo.size(); i++)
{
if (VEHHookinfo.visitAddr)
{
CALL_ZwProtectVirtualMemory2((HANDLE)-1, (LPVOID)(VEHHookinfo.visitAddr & 0xFFFFF000), 0x1000, 1, &OldProtect);
VEHHookinfo.visitAddr = 0;
return EXCEPTION_CONTINUE_EXECUTION;
}
}
}
return EXCEPTION_CONTINUE_SEARCH;
}
上述代码中其关键的只在第一个if中的代码,下面的是关于访问异常的处理,在一般情况下,是可以删除掉的. 但是在程序的代码中有crc在自效验的话,那就必须加上,让crc在访问这一片执行代码的时候,释放出可以访问的内存,但是有一种更先进的技术不需要单步异常也可以直接让访问指令获得值而不报错,我现在并没有掌握,因为我只见到一次,并不知道原理.
需要注意的是,拷贝内存结尾必须包含代码函数段的ret 这样在JMP了之后,执行代码才能返回到正常的执行段.
我一般直接把整个程序的.text段全部拷贝出来, 如此在无痕HOOK多处地址的时候 不需要一直重复的拷贝内存,而这样的执行效果更加高,因为程序被异常后几乎不会在走他自身的执行段,全部走我们自己申请的内存.当然如果你要这样拷贝 Veh_Hook_strat 这个函数中一些偏移你们要自己协调 ,我个人感觉这种HOOK用起来非常麻烦,而且效率也只能保持每秒执行触发一万次左右才能保证流畅性. 可能听原理起来没什么麻烦,但主要的还是每个异常触发的范围都会有0X1000字节 , 当固定下一个地址的设置,那么将能稳定运行,如果这个地址在同被HOOK程序的版本更新后变动,那么又需要重新计算起始地址.所以这个方法我只当做技术参考,并不提倡使用.
无限只是从原理方面来说!
|
评分
-
查看全部评分
|