-
-
[学习分享] InlineHook NtReadVirtualMemory实现过程
-
发表于: 18小时前 170
-
前言
昨天做了ssdthook搞到挺晚最终还是出现一些bug导致程序无法正常打开, 于是今天修改思路使用inlineHook实现, inlineHook相比ssdthook是更加实用的技术, 也能做到更加隐蔽, 比ssdthook上限高了很多, 用起来也舒服.
大体思路为: 在NtReadVirtualMemory中的call nt!MiReadWriteVirtualMemory下手, 将其跳转到自己的方法, 实现自己的功能后在原封不动的jmp到MiReadWriteVirtualMemory中, 提前透露说下今天的inlinehook相比于ssdthook非常顺利, 直接开始
第一步 获取NtReadVirtualMemory和MiReadWriteVirtualMemory地址
和ssdthook做法一模一样. 直接上代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | //获取NtReadVirtualMemory函数UINT_PTR GetNtReadVirtualMemory(){ //获取内核基址 UINT_PTR NtoskrnlBase = GetNtoskrnlBase(); //取NtReadVirtualMemory函数 UINT_PTR NtReadVirtualMemory = NtoskrnlBase + 0x622A80; return NtReadVirtualMemory;}//获取MiReadWriteVirtualMemory函数UINT_PTR GetMiReadWriteVirtualMemory(){ //获取内核基址 UINT_PTR NtoskrnlBase = GetNtoskrnlBase(); //取MiReadWriteVirtualMemory函数 UINT_PTR MiReadWriteVirtualMemory = NtoskrnlBase + 0x622AB0; return MiReadWriteVirtualMemory;} //===============调度方法=================//赋值MiReadWriteVirtualMemorySysMiReadWriteVirtualMemory = GetMiReadWriteVirtualMemory();if (!SysMiReadWriteVirtualMemory){ PZY_PRINT("获取MiReadWriteVirtualMemory地址失败"); return FALSE;}//获取NtReadVirtualMemory地址SysNtReadVirtualMemory = GetNtReadVirtualMemory();if (!SysNtReadVirtualMemory){ PZY_PRINT("获取NtReadVirtualMemory地址失败"); return FALSE;} |
第二步: inlineHOOK初始化过程
NtReadVirtualMemory原汇编代码:
1 2 3 4 5 6 7 8 | nt!NtReadVirtualMemory:fffff803`65ce2a80 4883ec38 sub rsp,38hfffff803`65ce2a84 488b442460 mov rax,qword ptr [rsp+60h]fffff803`65ce2a89 c744242810000000 mov dword ptr [rsp+28h],10hfffff803`65ce2a91 4889442420 mov qword ptr [rsp+20h],raxfffff803`65ce2a96 e815000000 call nt!MiReadWriteVirtualMemory (fffff803`65ce2ab0)fffff803`65ce2a9b 4883c438 add rsp,38hfffff803`65ce2a9f c3 ret |
可以计算得出, 从开头到call nt!MiReadWriteVirtualMemory距离16字节
1 2 | // call 指令偏移g_CallAddress = (PUCHAR)SysNtReadVirtualMemory + 0x16; |
保存原始字节以便还原
1 2 | // 保存原始 5 字节RtlCopyMemory(g_OriginalBytes, g_CallAddress, 5); |
计算新的偏移
1 2 | // 计算新的相对偏移LONG64 diff = (LONG64)MyNtReadVirtualMemory - ((LONG64)g_CallAddress + 5); |
插入指令是32位的 加上判断防止出错
1 2 3 | //判断是否超出范围if (diff > 0x7fffffffLL || diff < -0x80000000LL) return STATUS_NOT_SUPPORTED; // 超出 rel32 范围 |
制作新偏移
1 2 3 4 5 | LONG disp32 = (LONG)diff;UCHAR patch[5];patch[0] = 0xE8; // call*(PLONG)&patch[1] = disp32; // 写入新偏移 |
开关保护写入偏移
1 2 3 4 5 6 7 | //关闭写保护DisableWP();RtlCopyMemory(g_CallAddress, patch, 5);//开启写保护EnableWP(); |
第三步: 构造自己的MyNtReadVirtualMemory
MyNtReadVirtualMemory也是在ssdt写的基础上修改的, 相比之前添加了保存通用寄存器这步, 以便确保寄存器的正确性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | OPTION CASEMAP:NONEEXTERN SysNtReadVirtualMemory:PROCEXTERN SysMiReadWriteVirtualMemory:PROCEXTERN HookNtReadVirtualMemoryXx:PROCPUBLIC MyNtReadVirtualMemory.code MyNtReadVirtualMemory PROC ;自己的代码 ; -------- 保存通用寄存器 -------- push rcx push rdx push r8 push r9 push r10 push r11 sub rsp, 28h call HookNtReadVirtualMemoryXx add rsp, 28h ; -------- 还原寄存器(逆序)-------- pop r11 pop r10 pop r9 pop r8 pop rdx pop rcx ;调用原MiReadWriteVirtualMemory jmp qword ptr [SysMiReadWriteVirtualMemory] MyNtReadVirtualMemory ENDPEND |
HookNtReadVirtualMemoryXx这个方法就是自己的算法, 框架写好后算法都可以放上加, 可以记一下默认NtReadVirtualMemory的参数, 直接用形参取即可
1 2 3 4 5 6 7 8 | //原系统函数NtReadVirtualMemorytypedef NTSTATUS(NTAPI* NtReadVirtualMemory)( HANDLE ProcessHandle, PVOID BaseAddress, PVOID Buffer, SIZE_T BufferSize, PSIZE_T NumberOfBytesRead ); |
大概是这样, 但好像有些版本是六个参数, 我就取了五个参数, 因为栈值都是8字节一个参数所以并不存在对齐问题, 还是很舒服的
其实我就上了个日志的功能, 大致就是取了第一个参数就是句柄, 根据句柄获取eprocess, 然后继续获取pid和程序名称, 其实推荐用pid和handle获取就好, 用文件名获取会消耗一些内存
第五步: 效果展示及总结
依旧是拿ce做演示, 可以正常获取并且顺利打开了程序没有昨天的内存访问失败错误

当打开进程列表也能看到ce在访存的记录, 一切正常

后续要添加过滤或者监控都非常方便, 只需要在r3传入pid, 就可以在r0做相应处理
最后就是对于根据句柄获取eprocess的ObReferenceObjectByHandle函数有个坑, 就是我们默认都是要进程句柄, 所以传入的参数都是PsProcessType 相当于一个过滤, 如果不是进程句柄则返回失败, 失败了切记要直接返回, 之前我就没注意这个点, 即便是返回失败了还继续用空的eprocess获取pid等后续操作, 换来的自然就是疯狂蓝屏 然后又花了时间排查..
但是总的来说 inlinehook确实非常好用 简单效果强, 并且微软也确实没有对NtReadVirtualMemory的字节码做扫描, 毕竟对NtReadVirtualMemory访问频率高的函数定时扫描那对性能损耗非常高.
对于对句柄降权方式的保护无非就是看谁hook的函数更加底层, 其实也可以直接通过映射物理地址等方式直接重塑NtReadVirtualMemory进行访存, 但是对于CE OD这些工具就不是很友好, 要在r3层进行拦截分流到自己的接口, 并且并不止NtReadVirtualMemory一个函数, 通常保护软件会拦截所有与内存 句柄 等相关的函数, 如果全部重塑实在不是好的办法...
[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!