找到存放字节码段首地址的栈地址。因为字节码段是乱序且繁多的,手动跟踪非常缓慢。之后注意到在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 里指定的值。
使用 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 里指定的值。
跟踪MessageBoxExA 引用的提示字符串时,发现多线程SMC。之后分析多线程时,无法掌控主线程的执行进度。Themida 在检测到虚拟机后,会用 MessageBoxExA 弹框,于是我开始跟踪“Sorry, this application cannot run under a Virtual Machine”字符串,发现该字符串是多次解密后产生的结果。之后我回溯该字符串的解密过程,但由于多线程之间频繁的相互切换,我无法持续单步跟踪主线程,导致当线程切换后,主线程已经执行到不可预估的地方了。
一是在真机和虚拟机中,“Sorry, this application cannot run under a Virtual Machine”字符串都会被解密。在虚拟机的分析中,我设定了一个前提,即解密该字符串意味着 Themida 发现了虚拟机。但真机的这一现象,瞬间颠覆了我的想法,看来准备工作没做充分。于是我开始在虚拟机和真机中交替分析程序,找到了两种环境下程序的分叉点,从该分叉点继续执行大概23000 条汇编指令,虚拟机会执行到 MessageBoxExA 并报告发现虚拟机,而真机则不会报告。
真机和虚拟机中都会解密“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 时,调试寄存器的值才会恢复原来的值。
跟踪MessageBoxExA 引用的提示字符串时,发现多线程SMC。之后分析多线程时,无法掌控主线程的执行进度。Themida 在检测到虚拟机后,会用 MessageBoxExA 弹框,于是我开始跟踪“Sorry, this application cannot run under a Virtual Machine”字符串,发现该字符串是多次解密后产生的结果。之后我回溯该字符串的解密过程,但由于多线程之间频繁的相互切换,我无法持续单步跟踪主线程,导致当线程切换后,主线程已经执行到不可预估的地方了。
一是在真机和虚拟机中,“Sorry, this application cannot run under a Virtual Machine”字符串都会被解密。在虚拟机的分析中,我设定了一个前提,即解密该字符串意味着 Themida 发现了虚拟机。但真机的这一现象,瞬间颠覆了我的想法,看来准备工作没做充分。于是我开始在虚拟机和真机中交替分析程序,找到了两种环境下程序的分叉点,从该分叉点继续执行大概23000 条汇编指令,虚拟机会执行到 MessageBoxExA 并报告发现虚拟机,而真机则不会报告。
真机和虚拟机中都会解密“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 时,调试寄存器的值才会恢复原来的值。
跟踪MessageBoxExA 引用的提示字符串时,发现多线程SMC。之后分析多线程时,无法掌控主线程的执行进度。Themida 在检测到虚拟机后,会用 MessageBoxExA 弹框,于是我开始跟踪“Sorry, this application cannot run under a Virtual Machine”字符串,发现该字符串是多次解密后产生的结果。之后我回溯该字符串的解密过程,但由于多线程之间频繁的相互切换,我无法持续单步跟踪主线程,导致当线程切换后,主线程已经执行到不可预估的地方了。
一是在真机和虚拟机中,“Sorry, this application cannot run under a Virtual Machine”字符串都会被解密。在虚拟机的分析中,我设定了一个前提,即解密该字符串意味着 Themida 发现了虚拟机。但真机的这一现象,瞬间颠覆了我的想法,看来准备工作没做充分。于是我开始在虚拟机和真机中交替分析程序,找到了两种环境下程序的分叉点,从该分叉点继续执行大概23000 条汇编指令,虚拟机会执行到 MessageBoxExA 并报告发现虚拟机,而真机则不会报告。
真机和虚拟机中都会解密“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 时,调试寄存器的值才会恢复原来的值。