首页
社区
课程
招聘
[原创]浅谈VMP、safengine和Themida的反虚拟机
发表于: 2018-8-18 13:43 34918

[原创]浅谈VMP、safengine和Themida的反虚拟机

2018-8-18 13:43
34918
 浅谈VMP、safengine 和Themida 的反虚拟机 

       这里浅谈一下VMP、Safengine 和Themida 的反虚拟机,关于反调试只说下值得注意的地方。VMP 的反调试请参考 https://bbs.pediy.com/thread-226455.htm,Safengine 和 Themida 的反调试与 VMP 差不多。分析的 VMP 版本为 3.0.9,Themida 版本为 2.4.6.30,Safengine 不知道是什么版本了,程序是 32bit 。 
关于 64bit 程序,反调试和反虚拟机都更简单。其中反调试只包含 IsDebuggerPresent、 NtGlobalFlag、CheckRemoteDebuggerPresent。不过需注意的是程序在OEP 后,IsDebuggerPresent之前保存NtGlobalFlag标志,在IsDebuggerPresent之后同时检测BeingDebugged和NtGlobalFlag标志,最后调用 CheckRemoteDebuggerPresent。反虚拟机则只有注册表检测,没有 in 指令检测,因此只需修改注册表,就可在虚拟机运行。 

目录
1.反调试
       1.1. VMP 反调试
2.反虚拟机
       2.1. VMP 反虚拟机
       2.2. safengine 反虚拟机 
       2.3. Themida 反虚拟机

1 反调试
1.1 VMP 反调试 
       VMP 的某些版本有 tls 保护,该tls 用于检测调试器,具体方法不明。目前排除了 tls 对软件断点、硬件断点、SEH、NtGlobalFlag、BeingDebugged、Heap 结构的ForceFlags 和Flags 检测,包括堆中的调试记号(0xbababa,badf00d 等)。虽然不明检测方法,但要过掉还是挺简单的,直接跳过该tls。跳过的步骤是用调试器启动程序,在加载 ntdll 后,把 TLS 的DataDirectory清零,让系统读不到tls callback,当走到OEP 时,再还原。当然,如果程序没有文件完整性检测,可以直接修改文件中TLS 的DataDirectory 项。

2 反虚拟机 
2.1 VMP 反虚拟机 
       从结果来说,VMP 反虚拟机只使用了一个方法,特殊指令,该特殊指令是 cpuid。检测原理是赋eax 为1,执行 cpuid 后,如果ecx 的31st 位为 0,表示真机,否则为虚拟机。 
    从分析过程来说,主要分为 3 步。 一是分析虚拟机框架,二是逐步接近特殊指令,三是定位特殊指令,如图1。 

                                图1 VMP 分析过程

1)虚拟机框架:
        根据初步 VMP 的分析,该 VMP 无 dispatcher,全程是用 push edi,ret 来调整执行流程,其中的edi 由mov …,dword ptr [esi]转换而来。VMP 的单元步骤为一对,如代码段 1。

                                          代码段1VMP单元对
其中,值得注意的是字节码的表示,esi 和 edx 轮换表示字节码的当前获取地址,这些地址是一段一段的,且可以重复利用,如图2 所示。 

               图2 字节码执行流程
2)逐步接近特殊指令:
       找到存放字节码段首地址的栈地址。因为字节码段是乱序且繁多的,手动跟踪非常缓慢。之后注意到在ebp 的一个相对偏移处存有 esi 或edx 的字节码段首地址,且ebp 相对一段时间是固定不变的,因此可在这个相对偏移下写断点。果然,这个断点被触发了一千多次,由于堆栈的成长,ebp 会超过VMP 自己的虚拟栈,因此需要调整ebp,这时存放字节码段首地址的相对偏移会稍有变化,因此需要重复几次找这个“相对偏移”的步骤。最后越来越接近特殊指令。

3)定位特殊指令:
       通过程序检测到虚拟机的附近代码寻找特殊指令。虽然第二步的结果很接近特殊指令了,但在VMP 中,这个距离还是非常长的,因此需要另找方法。具体方法是定位程序检测到虚拟机的错误点,然后一步一步逼近,最后用 x32dbg 的 trace 功能扫描代码,找到特殊指令。其中,定位错误点根据“Sorry, this application cannot run under a Virtual Machine.”提示语句寻找,因为这段文字被存在栈中的一个固定位置,所以只需在此处下断点就能判断何时触发的错误点,然后向上回溯,最终找到特殊指令。

4)快速定位:
        以上是定位的过程,这里提供一个快速的定位方法。由于程序对 VM 化的代码不会再做加密,因此可以在vm 段直接内存搜索,找到cpuid,注意VMP 一般会用一次或两次该指令来检测。 

5)过掉cpuid:
一般执行cpuid 时,ecx 的31st 位为0,所以 cpuid 直接用两个nop 替换即可。

2.2 Safengine 反虚拟机 
       使用 in 指令和 RegQueryValueExA 检查 SystemManufacturer(在注册表里),in 指令的原理请参考https://bbs.pediy.com/thread-225735.htm。这里只说下如何定位 in 指令和过掉。in 指令在真机中会产生0xC0000096 异常,而在虚拟机中不会产生异常。因此定位 in 指令需要在真机的调试器中运行,但前提是你要知道它反虚拟机使用的是 in 指令,这个就需要各种尝试了(因为safengine 首先是反调试,然后再是反虚拟机,所以分析反调试时很轻松就能发现 in 指令)。 
       定位到后,就是过掉了。为了说明过掉方法的原理,这里需要再讲下 in 指令。in 在真机中产生异常,执行程序的SEH,在SEH 中会将eip 重新赋值。该过程就像一个跳转,在 in 指令的地址处有一条jmp 指令的感觉。而在虚拟机中,不会产生异常,那么就会进入检测到虚拟机的分支。 
       稍微了解了 in 指令后,现在说明两个过掉方法。一是换成 int3,二是重设 eip。关于一, in 和 int3 指令只占用一个字节,且 int3 也可产生异常,因此非常合适。但不排除程序的 SEH会检测该异常的 ExceptionCode,如果是这样,那就用第二种方法。关于二,调试程序,走到in 指令后(下硬件断点),手动给 eip 赋值,该值是 SEH 里指定的值。 
2.3 Themida 反虚拟机 
2.3.1 注册表检测 
       两次使用in 指令和RegQueryValueExA 检查注册表内容。其中注册表内容包括以下三点
      1. HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System\VideoBiosVersion(一般虚拟机中没有,不过没有该项也不会导致虚拟机被检测到) 
      2. HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System\ SystemBiosVersion 
      3. HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}\0000\DriverDesc

2.3.2 in 指令检测 
    两次in 指令的使用中ecx 分别为0x14 和0xA,0x14 是检查返回的eax,0xA 是检查返回的ebx。其中:
  • ecx 为0x14 时,VMware® Workstation 14 Pro 返回的 eax 为0x800,在真机中会跳到 SEH,将eax 赋值为0
  • ecx 为 0xA 时,虚拟机中的 ebx 为 'VMXh',在真机中进入 SEH,并在 SEH 中直接修改eip 跳转
2.3.3 in 指令定位过程 

        1)检测相关函数
        因为 Themida 没有用 PSAPI.dll、IPHLPAPI.dll、shlwapi.dll,所以能用来反虚拟机的函数比较有限,除了与注册表相关的函数,剩下的是检查固件信息的函数,不过设断点后都没有触发。
ba e1 KERNELBASE!GetSystemFirmwareTable 
ba e1 KERNELBASE!EnumSystemFirmwareTables
        之后我用APIMonitor 监控Themida 对API 的调用,除了注册表相关函数,确实没有其他用于检测虚拟机的函数了。因此我猜测剩下的虚拟机检测方法不是用的函数,而是特殊指令。

        2)搜索cpuid 指令
        让程序运行,在Themida 的可执行段搜索cpuid 指令,在可能的指令处下硬件断点,结果没有命中。因为Themida 有SMC,所以在代码解密之后搜索cpuid,尝试下断点,但仍然没有命中。

        3)跟踪错误提示字符串并尝试分析多线程
        跟踪MessageBoxExA 引用的提示字符串时,发现多线程SMC。之后分析多线程时,无法掌控主线程的执行进度。Themida 在检测到虚拟机后,会用 MessageBoxExA 弹框,于是我开始跟踪“Sorry, this application cannot run under a Virtual Machine”字符串,发现该字符串是多次解密后产生的结果。之后我回溯该字符串的解密过程,但由于多线程之间频繁的相互切换,我无法持续单步跟踪主线程,导致当线程切换后,主线程已经执行到不可预估的地方了。

关于跟踪主线程,还有两点需要解释: 
       第一、无法保持一直跟踪主线程,因为主线程执行到不同代码片段时会进入死循环,等待其他的某个线程修改对应的死循环代码,因此仅仅是挂起除主线程的所有进程是无法继续分析的,必须在某一点唤醒其他解密线程。 
       第二、线程切换时不能预估主线程的执行进度。当主线程需要执行待解密的代码时,会首先标记一个事件句柄(即告诉其他线程,你们可以执行了),然后调用Sleep(0),释放掉当前分配给自己的执行时间,以让其他线程被调度。而其他线程有部分是调用 Sleep,正准备被唤醒,有部分是用WaitForSingleObject 等待某事件句柄,该事件句柄正是主线程标记的事件句柄。在这个过程中,如果对主线程 Sleep(0)的下一条汇编指令下断点,结果就是断点会被命中,但往往被命中的线程不是主线程,而是同样调用 Sleep、等待被唤醒的其他线程。因此,这里不应该在主线程将被唤醒的汇编代码处下断点,即使断点被命中,也很可能是其他线程被命中。因为除主线程,还有大概 25 个线程等待被唤醒,其中调用 Sleep 等待被唤醒的数量可能有 7、8个。
    
       4)尝试分析虚拟机框架
       分析虚拟机框架,尝试定位提示字符串产生的过程,找到反虚拟机的特殊指令。因为无法精确跟踪主线程,所以我想分析 Themida 的虚拟机框架,进一步分析细节,找到提示字符串的解密以及产生过程。在看雪搜索相关帖子后,发现分析成本很大,无法快速达到定位特殊指令的目的,因此需要另找思路。

       5)尝试根据虚拟机配置来定位特殊指令
       Themida 的虚拟机检测可以通过设置 VMware 选项过掉(不过我尝试没成功),比如:
disable_acceleration = "TRUE" 
monitor_control.restrict_backdoor = "TRUE"
      于是我想弄懂这两句的原理,以此定位特殊指令。通过一段时间的搜索,发现这些语句可能与虚拟机解释执行指令有关,不会与某条特定的汇编指令产生关联(因为找不到官方文档的说明,无法确定该猜想)。因此通过虚拟机配置寻找特殊指令大概是不可行的。

      6)与真机对比并锁定关键条件跳转 
      对比真机和虚拟机的执行情况,找到关键代码段;对比这两份代码段,发现判断虚拟机的标志。单独分析虚拟机的情况无法找到特殊指令,那在真机中执行相同步骤,以此来寻找异同点,以下为两个关键点。 
    一是在真机和虚拟机中,“Sorry, this application cannot run under a Virtual Machine”字符串都会被解密。在虚拟机的分析中,我设定了一个前提,即解密该字符串意味着 Themida 发现了虚拟机。但真机的这一现象,瞬间颠覆了我的想法,看来准备工作没做充分。于是我开始在虚拟机和真机中交替分析程序,找到了两种环境下程序的分叉点,从该分叉点继续执行大概23000 条汇编指令,虚拟机会执行到 MessageBoxExA 并报告发现虚拟机,而真机则不会报告。 
    二是对比代码段,发现关键条件跳转。使用 x32dbg 的 trace 功能,把两种环境下从分叉点执行的 23000 行代码 dump 下来,然后用 BCompare 进行文本比较,发现大概在 1500 行后,程序判断eflag 的ZF 标志,ZF 为1 对应真机,ZF 为 0 对应虚拟机。从eflag 的比较处开始回溯,发现一句关键的代码 
cmp [addr], 0
        addr 指向的地址处保存了一个标志,该标志是用来存储某个结果的。如果是虚拟机,该标志为1,如果是真机,则为 0。之后继续回溯,找到了该标志是在哪片代码被设置的。这片代码只是在设置标志,无法得知为何会设置该标志。由于Themida 的跳转很多,不清楚程序是从哪跳转到这片代码的,于是再使用 x32dbg 的trace 功能,发现在程序调用GetNativeSystemInfo和 GetVersion 后不久(大概 200,300 行),就跳到了这片代码,这时已经看到了 in 指令,即成功定位到in 指令。

       7)两次in 指令检测
      第一次检测:


           第二次检测:
            最后,in 指令的快速定位和 Safengine 一样,在真机中跟踪异常,直到发现 in eax, dx 产生的异常。之前我在真机中没有定位到in 指令的原因是 0xC0000096 的异常太多了,大多数是由Themida 的sti 指令产生的,于是当时我就偷懒没有挨个查看产生异常的指令。 
           再总结下分析Themida 反虚拟机时需要注意的几点:

  • 真机和虚拟机中都会解密“Sorry, this application cannot run under a Virtual Machine”这段文字,所以不能跟踪这段文字来找到 in 特殊指令。另外Themida 使用MessageBoxExA(不管程序是否使用Unicode)输出错误信息。
  • Themida 使用多线程 SMC,使得主线程边运行边执行被解密的代码,多线程之间通过事件对象同步。(解密代码一般由其他线程完成,不过主线程也会解密一部分代码)。从结果来说, in 指令是解密代码后才出现的,虽然程序运行后会解密出来,但因为 in eax,dx 只需一个字节,所以即使解密代码后,搜索时结果也会有很多无关信息,直接导致无法判定。
  • Themida 会使用很多 sti 指令,该指令会触发异常,且 ExceptionCode 和 in 指令的ExceptionCode 是一样的(因为 sti 指令较多,最初在真机中没有枚举所有的异常,所以没发现in 指令。当最后发现是 in 指令时,才在真机中证实了 in 指令的使用)。因为 SEH 的不同,sti的作用可能不一样,不过大多数都是将 eip 加 1。另外,要从 sti 跟踪到 SEH,比如断到ntdll!KiUserExceptionDispatcher , 需 要 下 软 件 断 点 , 不 能 下 硬 件 断 点 。 在 刚 进 入ntdll!KiUserExceptionDispatcher 时,调试寄存器的值为 0,等执行到程序的SEH 时,调试寄存器的值才会恢复原来的值。
  • 分析主线程时,由于多线程之间会经常跳转,因此当跟踪主线程的关键代码时,要将除主线程的所有线程挂起。

[课程]Android-CTF解题方法汇总!

最后于 2019-1-24 17:43 被admin编辑 ,原因:
上传的附件:
收藏
免费 7
支持
分享
最新回复 (27)
雪    币: 954
活跃值: (118)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
好文章,浅显易懂。
最后于 2018-8-18 15:51 被tdsss编辑 ,原因:
2018-8-18 15:48
0
雪    币: 1
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
赞一个
2018-8-18 16:01
0
雪    币: 8120
活跃值: (1515)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
4
不错不错啊,感谢分享…
2018-8-22 05:59
0
雪    币: 7068
活跃值: (3517)
能力值: ( LV12,RANK:340 )
在线值:
发帖
回帖
粉丝
5
感谢分享~~~
2018-8-22 08:21
0
雪    币: 24
活跃值: (90)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
懵懵懂懂 迷迷糊糊 感谢
2018-8-22 14:31
0
雪    币: 6124
活跃值: (4476)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
7
不错不错
2018-9-7 03:01
0
雪    币: 3272
活跃值: (4409)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
不错 
2018-9-7 05:14
0
雪    币: 7971
活跃值: (3650)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
9
写的太好了
2018-9-7 07:14
0
雪    币: 10726
活跃值: (2730)
能力值: ( LV5,RANK:71 )
在线值:
发帖
回帖
粉丝
10
膜拜啊,牛逼啊大侠。
2018-9-7 10:23
0
雪    币: 2391
活跃值: (309)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
11
mark
2018-9-10 09:22
0
雪    币: 1470
活跃值: (74)
能力值: ( LV5,RANK:75 )
在线值:
发帖
回帖
粉丝
12
怎么分析出来的tql?
2018-10-6 19:27
0
雪    币: 7752
活跃值: (2144)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
写的很详细,我看完了,不错。
2018-10-6 23:50
1
雪    币: 4830
活跃值: (1370)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
感谢
2018-10-24 08:55
0
雪    币: 3121
活跃值: (1609)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
总结这种东西,不是给小白看的,它像是一种备忘录,关于历史研究的内容。
而大佬 明知 继而浅显。
如果有篇文章 可以细细的铺展开来 想来 新手和大牛 均可“开花”
2018-10-24 10:07
0
雪    币: 51
活跃值: (133)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
好文章,但是我更想知道笔者是怎么想到的呢
2019-3-12 14:36
0
雪    币: 4811
活跃值: (4461)
能力值: ( LV10,RANK:171 )
在线值:
发帖
回帖
粉丝
17
云清-sky 好文章,但是我更想知道笔者是怎么想到的呢
vmp这类虚拟机保护会使用自己的一套汇编解析逻辑来处理程序,这使得代码量异常庞大,分析成本提高,为了能定位到反虚拟机指令,则需要巧取。在大草原你想要找到遗失的戒指时,肯定会从自己旅游过的地方开始找起吧。另外,由于分析涉及到SEH这类概念,所以深入理解SEH,多线程等技术原理是分析的基础也是关键
2019-8-25 11:49
0
雪    币: 310
活跃值: (250)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
mark
2019-8-25 19:03
0
雪    币: 51
活跃值: (133)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
谢谢
2019-9-21 13:47
0
雪    币: 144
活跃值: (335)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
支持一个
2019-9-21 14:11
0
雪    币: 259
活跃值: (283)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
21
学习
2019-9-23 00:57
0
雪    币: 377
活跃值: (5996)
能力值: ( LV4,RANK:55 )
在线值:
发帖
回帖
粉丝
22

楼主写的31位是不是写错了,是32位,个人实测,在三个虚拟机,一个真机里,31位都是1,32位真机为0,虚拟机全为1

2020-4-6 15:55
0
雪    币: 377
活跃值: (5996)
能力值: ( LV4,RANK:55 )
在线值:
发帖
回帖
粉丝
23
vmp快速定位那个方法不可靠,vm后的代码确实没再加密,但几百个cpuid一个个下断?,别说我还真这么试过,tmd一个都没断下来
2020-4-9 11:29
1
雪    币: 377
活跃值: (5996)
能力值: ( LV4,RANK:55 )
在线值:
发帖
回帖
粉丝
24
定位方法个人觉得也不靠谱,我在那堆栈那下断了,也断在了那个语句那,但回溯是不可能的,vm后的代码有大量的无序跳转jmp,不像普通程序那样的call有来的地址,可以回溯,这个你根本不知道是那跳过来的,(如果有大神知道特殊的回溯方法也欢迎指出)
2020-4-9 22:27
1
雪    币: 377
活跃值: (5996)
能力值: ( LV4,RANK:55 )
在线值:
发帖
回帖
粉丝
25
@coneco 我说的那个32位楼主改一下吧,毕竟也是个精华,如果是我说的不对,也欢迎指正
2020-4-9 22:34
1
游客
登录 | 注册 方可回帖
返回
//