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 时,调试寄存器的值才会恢复原来的值。
- 分析主线程时,由于多线程之间会经常跳转,因此当跟踪主线程的关键代码时,要将除主线程的所有线程挂起。