Hot-Patch Hook
基于全局“热补丁钩子”方式,实现一个窗口控制程序可以实现计算器calc.exe的进程隐藏。
文章目录
实验环境
VMware Workstation Pro 16.2.4 + Windows 7(32位)+ Microsoft Visual Studio 2010 Express,环境配置参考《VM安装windows7 32》
基本原理
-
使用全局钩取是指隐藏进程时需要钩取当前系统中运行的所有进程的ZwQuerySystemInformation()API,并且对后面将要启动运行的所有进程也做相同的钩取操作(当然操作是自动进行的),因此需要在stealth1.dll的基础上对创建新进程的API——CreateProcessA()和CreateProcessW()进行钩取。
-
“热补丁钩子”相比于基于API修改进行钩取的方式方式,其工作原理以及特征如下:
- 二次跳转:采用修改5个字节代码的方式,当每当程序内部调用CreateProcess时,自定义钩取函数NewCreateProcess都会反复执行脱钩/挂钩操作,不仅会造成整体性能低下,在多线程环境下还会产生运行时错误。使用热补丁技术修改7个字节代码,比修改5个字节代码更加稳定。以Kernel32.CreateProcessA()为例。API代码以2个字节的MOV EDI,EDI指令开始,上方还有5个nop指令,一共7个字节没有意义的指令,这就是为了方便打热补丁用的。将API起始代码前的5个字节修改为FAR JMP指令,然后将API起始代码处的2个字节修改为SHORT JMP指令跳转到前面FAR JMP处,这就是所谓的“二次跳转”。
- 不需要在钩取函数内部进行“脱钩”/挂钩操作:使用“热补丁”技术钩取API时,不需要在钩取函数内部进行“脱钩”/挂钩操作。在5字节代码修改技术中“脱钩”/挂钩是为了“调用原函数”,而使用“热补丁”技术钩取API时,在API代码遭到修改的状态下也能正常调用原API。这是因为,从API角度看只是修改了其起始代码的MOVEDI,EDI指令(无意义的2个字节),从【API起始地址 + 2】地址开始,仍然能正常调用原API,且执行的动作也完全一样。以Kernel32.CreateProcessA()为例,从图2所示的原API起始地址(7C80236B)开始执行,与【API起始地址 + 2】的地址(7C80236B)开始执行,结果完全一样。这样钩取函数在获取原API时直接可以使用【API起始地址 + 2】,这样也能正常调用原API,去除了“脱钩”/挂钩操作,在多线程环境下使API钩取变得稳定。
-
从上图(源自《逆向工程核心原理》)中的7C802366、7C80236B地址可以看到,虽然都是JMP指令,但指令形态不同。7C802366地址处的指令形式为E9 XXXXXXXX,大小为5个字节,被称为FAR JMP,用来实现远程跳转(可以跳转到进程内存用户区域中的任意位置);而7C80236B地址处的指令形式为EB YY,大小为2个字节,被称为SHORT JMP,它只能以当前EIP为基准,在-128~127范围内跳转。IA-32指令中有些相同指令拥有不同指令形态。
-
值得注意的是,“热补丁”API钩取技术也不是万能的,使用时目标API必须满足它的适用条件(NOP*5指令+MOVEDI,EDI指令),但是有些API却不能满足这些条件,比如下图的kernel32.GetStartInfoA() API就不可以使用“热补丁”API钩取技术进行钩取(图源自《逆向工程核心原理》)。
以及ntdll.dll提供的API(图源自《逆向工程核心原理》)。
- 并非所有API都能使用“热补丁”API钩取技术,所以使用前先确认要钩取的API是否支持它。若不支持,则要使用前面介绍过的5字节代码修改技术。
- Ntdll.dll中提供的API代码都较短,钩取这些API时有一种非常好的方法,使用这种方法时先将原API备份到用户内存区域,然后使用5字节代码修改技术修改原API的起始部分。在用户钩取函数内部调用原API时,只需调用备份的API即可,这样实现的API钩取既简单又稳定。由于Ntdll.dll API代码较短,且代码内部地址无依赖性,所以它们非常适合用该技术钩取API。
———《逆向工程核心原理》
-
与进程隐藏相关的其他原理详见《API钩取技术研究(二)—— Byte-Patch Hook》
实验过程
编写HideProc.cpp实现dll注入
为实现方便,本文的HideProc.cpp与《API钩取技术研究(二)—— Byte-Patch Hook》代码相似,只不过已经将要隐藏的进程calc.exe硬编码在dll中,实现dll注入就可以实现进程隐藏。所以,本文的HideProc.cpp删去了利用Filemapping技术实现进程名在exe和dll中共享的部分。
编写stealth2.dll实现Hot-Patch Hook
在stealth2.dll中进行全局热补丁钩子从而实现进程隐藏的操作,与《API钩取技术研究(二)—— Byte-Patch Hook》的stealth1.dll相比,新增加了hook_by_hotpatch()实现热补丁钩取、unhook_by_hotpatch()实现热补丁脱钩、钩取函数NewCreateProcessA()、钩取函数NewCreateProcessW()(ntdll.ZwQuerySystemInformation() API仍采用修改五个字节代码的方式钩取)这些部分的代码;并把要隐藏的进程硬性编码为计算器calc.exe进程(因此HideProc.cpp中只修改主函数,接收三个参数的输入命令)。
Ⅰ. 新增hook_by_hotpatch()实现热补丁钩取
将位于API起始地址的MOV EDI,EDI指令修改为EB YY(SHORT JMP指令),用于跳转至被修改的七个字节中的第一个字节。其中YY = 要跳转的地址(被修改的七个字节中的第一个字节) - 当前指令地址(被修改的七个字节中的第六个字节) - 当前指令长度2 = -7,-7的十六进制表示为0xF9。
API起始地址上方的5个NOP指令修改为E9 XXXXXXXX(FAR JMP指令),其中XXXXXXXX = 要跳转的地址(自定义钩取函数的起始地址pfnNew)- 当前指令地址(pFunc)-当前指令长度5 = (DWORD)pfnNew - ((DWORD)pFunc - 5) - 5 = (DWORD)pfnNew - (DWORD)pFunc。
Ⅱ. 新增unhook_by_hotpatch()实现热补丁脱钩
热补丁脱钩实际上就是恢复先前修改的七个字节为5个NOP指令(0x90)和1个MOV EDI EDI(0x8BFF)指令。
Ⅲ. stealth2.dll —— 增添钩取函数NewCreateProcessA()和NewCreateProcessW()
相对于stealth1.dll增添钩取函数NewCreateProcessA()和NewCreateProcessW(),两个函数代码相似。下面以NewCreateProcessA为例。与修改五字节钩取函数不同的是,NewCreateProcessA()中不再需要调用unhook_by_code和hook_by_code函数,而是添加了计算pFunc的语句,用来跳过API起始位置处的JMP YY指令,使得能够达到与调用原API一样的效果。
Ⅳ. stealth2.dll —— DllMain主函数
stealth2.dll在stealth1.dll新增加了hook_by_hotpatch()实现热补丁钩取、unhook_by_hotpatch()实现热补丁脱钩、钩取函数NewCreateProcessA()、钩取函数NewCreateProcessW(),ntdll.ZwQuerySystemInformation() API仍采用修改五个字节代码的方式钩取,因为其API起始部分不符合热补丁钩取的要求。
实现演示
Hot-Patch-Hook演示
参考:
-
《系统的快照》,https://learn.microsoft.com/zh-cn/windows/win32/toolhelp/snapshots-of-the-system?redirectedfrom=MSDN
-
《DLL注入-创建远程线程》,https://blog.kn0sky.com/?p=23
-
《Hook-两种Inline Hook》,https://blog.kn0sky.com/?p=34
-
《逆向工程核心原理》