大神论坛

找回密码
快速注册
查看: 576 | 回复: 1

[原创] 对vmp 3.8.1的反调试逆向分析与手动绕过 附模拟x64代码

主题

帖子

0

积分

初入江湖

UID
584
积分
0
精华
威望
0 点
违规
大神币
68 枚
注册时间
2023-09-16 15:07
发表于 2023-10-15 10:55
本帖最后由 huojingang 于 2023-10-15 10:55 编辑

环境:

vmp版本:3.8.1 professional 为了方便分析,只勾选反调试选项(user-mode+kernel-mode)
待分析样本: x32 编译环境vs2022
系统环境:windows 11 cpu:amd
用到的调试器 OllyDbg, windbg
本人水平有限,如果哪里有不对请指出 :)

初始化工作:
首先vmp会调用RtlAllocateHeap
然后通过peb取到ldr链来获取ntdll的模块基址(图中高亮行),接着使用二分查找通过解析导出表获取ZwQueryInformationProcess的地址,然后调用他

vmp会判断0xcc断点,不要在函数头下断,可以用硬件断点

参数0x1a(ProcessWow64Information)表示查询进程是否运行在wow64下(影响后续反调试流程,本文仅分析当前流程下vmp走的反调试流程)
接着通过遍历ntdll的资源信息来判断电脑系统版本

然后调用ZwOpenSectionZwMapViewOfSection copy 一份 ntdll,通过特征码0xb8获取函数的服务号
完成之后接着调用ZwUnmapViewOfSectionZwClose
在OD中观察程序刚加载时的esp的上方可以发现一些数据(标红即被修改的)

左边不难猜测是vmp用来进行直接系统调用的服务号,0xD  ZwSetInformationThread印象深刻...右边则有一些有趣的函数的地址
分析到这忽然想起前段时间vmp源码(据说是3.5版本?)泄露了部分,我对照着看了一下,目前的流程不能说是相似,简直一模一样

开始进行反调试:
紧接着,根据以下条件进行不同的反调试。
1、是否是wow64环境(前面有提到)
2、系统的版本(fs:[0x30]+0x0a4 OSMajorVersion   : Uint4B)下图高亮行
3、处理器类型(KUSER_SHARED_DATA + 0x26a NativeProcessorArchitecture : Uint2B)下图高亮行的下一行

如图,在当前环境下,vmp用“天堂之门”将代码切换到x64执行。然后我就干瞪眼了半天,OD没法调试x64的代码
与此同时我注意到切换到x64之后的代码似乎仍然是在虚拟机里执行的,所以我选择先对程序进行了一些黑盒测试
在我的系统是win11,程序运行在wow64的前提下,似乎只有NativeProcessorArchitecture为amd时vmp才会用“天堂之门”将代码切换至x64继续执行。尝试修改读取到的NativeProcessorArchitecture, 其余都是直接走x32的,我也不是很清楚具体原因
除此之外我在jmp far 和返回地址00C7CE2A处分别下硬件执行断点,然后F9运行几次,发现经过两次x32到x64切换之后,vmp似乎已经检测出调试器,使用NtRaiseHardError来弹窗提示然后退出。然后我通过观察堆栈,发现有一处红色变动的值0xFFFFFFFF,我尝试将其置0,发现流程继续下去到了第三个jmp far,之后再弹窗退出。此时很明显已经干预了一处反调试的结果。

因为od、x32dbg、x64dbg都没办法在切换成x64后调试,所以我就用unicorn模拟执行和windbg来继续分析x64代码(吐槽一下,切换x64之后还是在虚拟机里执行代码,好恶心啊)
可以发现在切换到x64代码后,vmp进行直接系统调用(rax=0x1c ZwSetInformationProcess)来disable InstrumentationCallback
参数 ProcessInformationClass = 0x28

然后我尝试对syscall下断,发现程序直接异常退出或者断不下来
这是有两个原因,第一个原因是vmp检测了0xCC断点
另一个原因是:vmp通过指令rdtsc的返回值,从多个不同但结果相同的路径中随机选择一个路径执行,尝试修改rdtsc返回值,会发现模拟到不同的路径。这也就是为什么实际执行和模拟的结果不同(可以通过patch rdtsc返回值让他实际走的流程跟模拟流程一致)
既然在切换到x64之后仍然是进行直接系统调用,并且他没有访问一些其他内存(或者做其他一些操作),而且我发现他前2次x32切换到x64都是走这个jmp far,我就有了个大胆的想法。它应该是有个类似的封装好的函数可以走x64系统调用,想象中的几句伪代码如下

x64_code:

(可能是一些准备工作)

syscall

(将结果保存等等)

retf



function MakeX64DirectSystemCall (服务号,参数1,参数2,...,返回值):



push cs

push ret_addr

jmp far 0x33: x64_code

ret_addr:

nop

mov cx,ss

mov ss,cx

(不太清楚后续代码)

end



push &返回值

push 参数

push 服务号

call MakeX64DirectSystemCall

于是我尝试了几次,最后发现在数据窗口中跟随ebp,修改这里附近的值,模拟时发现调用号被修改成功,再试试参数是否能修改,发现也可以。这下可以操作的空间就很多啦。
只不过我还有一个疑惑,就是为什么x64返回到x32的时候,头3条汇编指令是

nop
mov cx,ss
mov ss,cx

虽然我知道有类似的方法可以反单步跟踪和反虚拟机,但是第四条指令是pushfd,这里第四条指令不是
现在我们不跟x64代码也知道他要做什么操作了
然后vmp通过直接系统调用(0x19 ZwQueryInformationProcess) 参数ProcessInformationClass= ProcessDebugPort= 0x7
查询调试端口号(在返回地址将结果0x0019fed8处将FFFFFFFF置0即可绕过)

(补充:注意下图标红的地方,从左到右依次是服务号,参数,末尾存放返回值)

然后到另一处jmp far 通过直接系统调用(0x19 ZwQueryInformationProcess) 参数ProcessInformationClass= ProcessDebugObjectHandle = 0x1E
查询调试对象句柄
但是这次结果是存放在另一个地方(r8=0x0019f72c)

将0x0019f72c处查询到的句柄置0,并且返回值(存放在堆栈上)置0xC0000353(STATUS_PORT_NOT_SET)即可绕过。(实际测试的时候改句柄发现没用,仅需返回值<0,甚至句柄有值都能过,感觉逻辑可能得改改?)

然后调用ZwSetInformationThread,参数0x11 ThreadHideFromDebugger,绕过方法:将0x11置0即可

--接下来是kernel-mode反调试--
在上面调用完ZwSetInformationThread之后,调用ZwQuerySystemInformation(SystemInformationClass==0x23)查询系统是否在调试模式下运行。
直接将存放结果的缓冲区置0即可

然后再调用两次该函数,查询内核模块信息(SystemInformationClass==SystemModuleInformation==0xB)
第一次是获取需要的缓冲区长度,第二次是获取真正的信息。直接在第一次将返回的长度置0即可
在此之后,vmp会对pe头、资源等数据进行读取校验。

在这之后再一次调用ZwQueryInformationProcess查询调试端口和ZwSetInformationThread进行反调试。绕过方法跟前面操作相似
然后反调试就成功绕过,程序就跑起来啦:)

小小的总结一下:
注意对api下断时不要在函数头下断,容易被检测
vmp反调试全程跑在虚拟机内,可以通过分析他的api调用以及访问的内存来推测相关逻辑
突发奇想的伪代码对x32->x64调用逻辑的帮助简化了分析流程,不必每次都模拟切换x64代码,让我顺利完成了此次分析
如果要自动绕过反调试的话,估计要写个驱动?或者如果要手动分析的话直接根据特征码对vmp段所有“天堂之门”全部下断,x64切换和返回的汇编特征还是很明显的(注意别对push cs或者返回时的nop下断,会检测0xcc)

注:若转载请注明大神论坛来源(本贴地址)与作者信息。


下方隐藏内容为本帖所有文件或源码下载链接:

游客你好,如果您要查看本帖隐藏链接需要登录才能查看, 请先登录

主题

帖子

120

积分

初入江湖

UID
154
积分
120
精华
威望
240 点
违规
大神币
63 枚
注册时间
2021-08-04 15:27
发表于 2023-10-16 13:38:58.0

1986750323

返回顶部