下面把HP的vm称为hvm,本文只是简单的研究了hploader.sys的vm,简单的讲述hvm的一些原理,不能帮您分析或bypass hprotect, HP的保护,完全不懂~
这里HPLoader.sys(顾名思义,是个pe loader~)的版本大概是八九月份(那时候有空看的)~不知现在是什么个情况…
1. HVM执行原理
下面直接从Hploader.sys的代码开始分析:
进入Hvm之前:
.HProt:004377EA push offset unk_44C855 // push一个参数
.HProt:004377EF jmp InterVm
进入Hvm的初始化操作
.HProt:0044C82F InterVm:
.HProt:0044C82F pusha
.HProt:0044C830 pushf
.HProt:0044C831 mov esi, [esp+24h] ; esi = unk_44c855
.HProt:0044C835 mov ebp, esp
.HProt:0044C837 sub esp, 180h
.HProt:0044C83D mov edi, esp
.HProt:0044C83F cld
.HProt:0044C840 mov ebx, 400000h ; HPLoader module base, ‘Mz’
Hvm的初始化操作完成后,栈框架如下(后边介绍运行虚拟代码时候的栈):
他的esi指向了unk_44C855,也就是进入vm前push进去的参数,其实esi指向的是虚拟代码数据。
Hvm初始化完成后,接着就是指令模拟执行过程(VmCodeDispatch)了:
.HProt:0044C845 VmCodeDispatch: ; ebx = 400000
.HProt:0044C845 lodsb ; 从esi上取一个字节(其实是指令号)
.HProt:0044C846 movzx eax, al
.HProt:0044C849 mov eax, [ebx+eax*4+4C653h] ; ebx=400000,从44c653中取值
.HProt:0044C850 lea eax, [ebx+eax] ; 计算出地址
.HProt:0044C853 jmp eax ; 跳过去执行
还原成为代码便是goto 400000h + *(PDWORD)(*(PBYTE)esi + 400000h + 4c653h)
4c653h的数据如下:
.HProt:0044C653 dd 4BD79h, 4BD89h, 4BDA1h, 4BDB2h, 4BDB8h, 4BDC5h, 4BDD8h
.HProt:0044C653 dd 4BDEBh, 4BDFBh, 4BE20h, 4BE30h, 4BE40h, 4BE51h, 4BE62h
……………………
根据上边数据可以计算出,当esi指向的第一个字节为0时
400000h + *(PDWORD)(*(PBYTE)esi + 400000h + 4c653h) = 44bd79。查看44bd79代码如下:
.HProt:0044BD79 push 0
.HProt:0044BD7B pop eax
.HProt:0044BD7C lodsb // 取参数
.HProt:0044BD7D mov ecx, [edi+eax*4]
.HProt:0044BD80 lodsb
.HProt:0044BD81 shl byte ptr [edi+eax*4], cl
.HProt:0044BD84 jmp VmCodeDispatch // 执行完上边的指令后又跳回去了
由此可以看出,0044BD79其实是一条指令模拟(前辈们所称的handle),而4c653h是一张存储模拟指令地址的表(HandleAddressTable),esi其实是作为虚拟代码的指令指针,每次从esi取出1个字节,根据这个字节计算出该跳到哪个handle去执行(执行的时候也从esi中取参数)。执行完之后,又跳回VmCodeDispatch继续取下一个指令。
上面0044BD79这个handle可以写成:
vm_shl byte ptr [edi + arg2 * 4], byte ptr [edi + arg1 * 4]
Hvm总共有0x77个handle,具体见 Hvm指令模拟表.txt。好像有两条乘除法指令模拟错了.
对照着指令模拟表,翻译一小段指令再接着看会舒服一点. DriverEntry_mResult_smp.txt是翻译的一小段vm指令,不好看懂.
2. HVM的特点
下边是一些hvm的特点.
(1)hvm执行虚拟指令时的栈框架及栈框架调整:
从DriverEntry_mResult_smp.txt 可以看到,虚拟指令执行的首要操作便是九个vm_pop,这九个vm_pop将hvm初始化时压入栈中的九个cpu寄存器值一次性的pop到[edi + 4* xx]的缓冲区中,xx值是随机的,每次都不一样。完成这些操作后,hvm栈框架变成了右边的样子。
上图的栈框架会有一个问题,由于栈指针(由ebp模拟),当遇到VM_PUSH指令的时候,上边的栈框架会被打乱(edi – ebp的值会变小,如果为0的话就没有缓冲区空间了),所以每当hvm执行完一条vm_push或者更改ebp的指令后,都会调用一条调整框架的指令(vm_reframe)来调整栈框架(抬高edi并拷贝栈内容,如果栈内容太多的话,效率影响可能就比较大了),对于vm_pop指令,则不需要调整,因为它只会使缓冲区空间变大,不会影响指令运行。
(2)hvm寄存器:
Hvm是依靠栈运行的,在解析虚拟指令的时候它使用esi作为指令指针。每一条指令的执行,都从esi中取出第一个字节作为执行的指令号,然后根据各个指令具体的功能从esi中取出不同数量的参数。
从栈框架图可以看出,Hvm使用edi作为缓冲区框架指针(0x77个handle的代码也能看出这点);使用ebp模拟栈指针,下面是vm_pop指令的代码:
Vm_pop:
xor eax, eax
lodsb
push dword ptr [ebp+0]
add ebp, 4
pop dword ptr [edi+eax*4]
Jmp VmCodeDispatch
将其简化之后得到如下代码:
Mov dword ptr [edi + arg1 * 4], dword ptr [ebp] // 先把[ebp]内容赋给[edi + arg1 * 4]
Add ebp, 4 // 然后再自加4
可以看到,Vm_pop就是把ebp来模拟esp使用。
Hvm不会直接使用CPU寄存器,而是把寄存器放在栈中(参看上图右边的栈框架图),运行虚拟指令的时候修改栈中的值,当退出Hvm的时候,再从栈中相应位置取值”还”给寄存器,Hvm就是这样模拟cpu寄存器的。
(3)hvm跳转和退出
hvm中只有一条vm_leave指令是影响eip,能实现跳转(退出hvm和调用函数)。下面是Hvm跳转时的相关hvm指令:
2f vm_mov dword ptr [edi + 06 * 4], 0004c90e
3d vm_mov dword ptr [edi + 15 * 4], ebx // ebx = 400000
44 vm_add dword ptr [edi + 06 * 4], dword ptr [edi + 15 * 4]
1c vm_push dword ptr [edi + 06 * 4] // push 44c90e
43 vm_reframe // 调整VM栈框架,每个push后都要调整栈框架
2f vm_mov dword ptr [edi + 13 * 4], 00037485
3d vm_mov dword ptr [edi + 07 * 4], ebx
44 vm_add dword ptr [edi + 13 * 4], dword ptr [edi + 07 * 4]
1c vm_push dword ptr [edi + 13 * 4] // push 0×437485
43 vm_reframe
1c vm_push dword ptr [vm_eflag]
43 vm_reframe
1c vm_push dword ptr [vm_eax]
43 vm_reframe
1c vm_push dword ptr [vm_ecx]
43 vm_reframe
1c vm_push dword ptr [vm_edx]
43 vm_reframe
1c vm_push dword ptr [vm_ebx]
43 vm_reframe
1c vm_push dword ptr [vm_esp]
43 vm_reframe
1c vm_push dword ptr [vm_ebp]
43 vm_reframe
1c vm_push dword ptr [vm_esi]
43 vm_reframe
1c vm_push dword ptr [vm_edi]
43 vm_reframe
5b vm_leave // mov esp, ebp
// popad
// popfd
// retn
上面的vm_push dword ptr [vm_eflag]到vm_push dword ptr [vm_edi]对应后边的popfd和popad,执行到retn的时候,栈中第一个值是0×437485,也就是跳转目标地址,第二个值是0x44c90e,对于函数调用这个值是跳转之后返回的地址,也就是执行完0x437485之后该返回到哪里去;但如果跳转的地址是新的hvm入口,那第二个值就不一定是返回地址了,也可能是参数。
(4)不定时变化VmCodeDispatch地址和指令号
Hvm在运行的时候,会改变虚拟指令的号码,所以初始态的vm_pop指令号为0x66,运行一会儿后,可能vm_pop的指令号就变成0x09了。VmCodeDispatch的地址也会变。但进入hvm后,VmCodeDispatch地址和虚拟指令号码都不会改变,只有当离开hvm(vm_leave)之后才有可能改变VmCodeDispatch地址和虚拟指令号码,所以这对调试不会有太多影响。
3. HVM的几个分析方法
hvm有很多垃圾指令,很难看懂。。。光是看完一个DriverEntry都不太可能。只能找一些关键代码来看。下边是我的一些调试方法:
(1)断VmCodeDispatch中的movzx eax, al来查看hvm指令流和它的参数。从第一部分可以知道,执行到这个指令的时候,al的值为指令号,esi指向参数,虽然参数个数不确定,但是hvm最多就5个参数,所以只要db esi l5即可了。能看到指令流的话还是有可能对调试有些帮助的。
(2)断vm_leave中的retn指令。因为hvm只有这么一个跳转指令,所以调用函数离开hvm都需要它来实现,断下它的话,然后dd esp l2,能找到hvm将跳到哪里去,返回地址是多少。通过这,基本上能看出hvm函数调用的大概过程,用来定位关键代码比较方便。
(3)Windbg的pt(执行到retn)指令。原理同2类似,因为只有vm_leave这条虚拟指令含有retn,所以用pt指令能快速走到这条指令,迷失在hvm执行过程中的时候用这条指令能快速退出来,但需要指定线程,但只有用户态支持指定线程。双机调试的话,只要从handle00开始搜索字节码3c,还是很容易搜到vm_leave指令的。
下边示例如何接近HPloader.sys调用KdDisableDebugger的相关虚拟代码:
kd> lmvm hploader
start end module name
ee11d000 ee16e000 HPLoader (no symbols)
kd> u eip l3
HPLoader+0x4c849:
ee169849 8b848353c60400 mov eax,dword ptr [ebx+eax*4+4C653h]
ee169850 8d0403 lea eax,[ebx+eax]
ee169853 ffe0 jmp eax
kd> dd ebx+5b*4+4c653 l1
ee1697bf 0004c444
kd> u 4c444+ebx l4
HPLoader+0x4c444:
ee169444 8be5 mov esp,ebp
ee169446 61 popad
ee169447 9d popfd
ee169448 c3 ret
kd> bp ee169448 “j 1 ‘dd esp l2;gc’;””
kd> bl
0 e 80582373 0001 (0001) nt!IopLoadDriver+0×669
1 e 804f8876 0001 (0001) nt!KdDisableDebugger
2 e ee169448 0001 (0001) HPLoader+0x4c448 “j 1 ‘dd esp l2;gc’;””
kd> g
f7a48c5c ee154485 ee16990e
f7a48c58 ee154743 ee1699d9
f7a48c44 ee154485 ee16d126
f7a48c54 ee15474c 86271f38
f7a48c50 80537ff8 ee169b71
……………….几十个……………..
f7a48c50 ee16a349 86271f38
f7a48c4c ee154539 ee16a349
f7a48c50 ee16a349 86271f38
f7a48c4c ee154539 ee16a349
f7a48c50 ee16a349 86271f38
f7a48c4c ee154539 ee16a349
f7a48c50 ee16a349 86271f38
f7a48c54 8054c950 ee16a864
f7a48c50 ee125c30 ee16a911
Breakpoint 1 hit
nt!KdDisableDebugger:
804f8876 8bff mov edi,edi
可以看到,最接近KdDisableDebugger的跳转地址是ee125c30,下次只要断在ee125c30,那么KdDisableDebugger相关vm代码就在附近了。然后可以结合虚拟指令表来尝试还原部分代码,也可以继续跟踪调试,这能很大程度地降低分析复杂度。
原版看 http://www.krnldbg.com/?p=69
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课