Sound 发表于 2018-2-5 14:12

分析一个新型VM的CrackMe

本帖最后由 Sound 于 2018-2-5 14:22 编辑

0x00 前段时间实在是太忙了,没时间看这个cm,最近闲下来了, 准备开搞。
sd大佬来玩 cm 了 爆破就成 算法太简单 https://www.52pojie.cn/thread-682451-1-1.html

0x01 初步了解这个cm

要求玩家输入一串字符串, 如果错误则显示fail, 如果正确...应该就会显示其他信息, 作者没有给出成功后的截图, PE中也没有发现这类字符串...
拖进调试器, 发现这个cm的算法是通过虚拟机加密了的, 难怪作者说只要爆破就可以了, 废话不多,直接进入下一个步奏吧.

0x02 一个带虚拟机加密的cm

首先查虚拟机壳的相关信息, 发现毛都没看到, 但使用PE工具查看节表信息发现有个sp0101节, 直接反汇编这个节, 发现这里
就是那一处虚拟机..

      因为这个虚拟机应该是作者自写的, 所以我也决定手动来会会这个虚拟机..

      加密段进入虚拟机的代码:


虚拟机入口代码:



虚拟机的过程大概是 -> 保存现场 -> 设置esi edi vmesp -> 设置重定位字段 -> 进入第一个handler
      
      有兴趣的朋友可以自行跟踪了解, 这里就不详细述说了.

0x03 虚拟机自效验

      我想手写hook记录hanlder的日志信息, 发现只要hook就会出问题, 仔细操作发现只要在虚拟机任意位置下一个断点(int3)然后直接运行就会发生同样的错误,
      猜测虚拟机有自效验, 这就尴尬了, 想直接hook是不可能的了, 得先过这个自效验才行..
错误:

经过多次尝试发现, 仅仅只是修改虚拟机以及虚拟机的跳转表以及虚拟机内 jmp dword 的最后4字节会发生这个错误, 作者显然是不想别人动他的虚拟机了..
      
      不过作者没有效验整个PE文件, 这就给我们留了很大的空间发挥了, 如果作者这样做了, 那这个搞起来就很伤身体了..

0x04 六耳猕猴

现在先搞定它的自效验.
      
      步奏如下:
      1. 使用loadpe工具复制sp0101节.
      2. 使用loadpe工具添加复制的节到末尾.
      3. 删除重定位节. (因为虚拟机里面有很多地方都有重定位, 把这些重定位一并修复显然蛋疼, 还是直接删掉重定位爽~)
      4. 使用任意十六进制工具打开cm0.exe 查找加密段的最后的那个jmp, 请看图1, 将其跳转修改为跳转到我们复制的虚拟机入口处.
      
      *** 到这里之后, 我们先保存这个exe, 取个名字为: 修改至第一阶段.exe, 之后每次修改都从这个exe复制一份出来改名为:cm0.exe ***
      
      现在只是将虚拟机入口修改好了, 现在要修正跳转表, 否则跳转表依然指向的是之前的虚拟机位置, 跳转表有多大不清楚.. 只能去查特征,
发现几乎所有的handler最后一行代码都是 jmp dword ptr, 且X是虚拟机内唯一的特征, 那么就好办了, 写一段代码现查然后在
重新指向我们拷贝的虚拟机节中的跳转表(不能修改原始的, 因为原始的依然会被检测.). 前面说了X是虚拟机内唯一, 但是我们拷贝了一份, 因此
在2处位置会撞脸, 我们首先手动将我们拷贝那一处手动替换成新的跳转表地址吧, 然后在写代码查询这个就一定是唯一的了.

      手动替换跳转表地址, 新的跳转表VA为: 0x0047534a, 我使用的工具是HxD, 使用替换功能就可以了, 记得是从新虚拟机的位置开始哦, 如图:



查询修正跳转表代码如下
start.      
      //
      // 打开cm0.exe
      
      CFMBuffer fp("cm0.exe");

      //
      // 移动至虚拟机机器码开始位置

      fp.FM_MOV_SET(0x6cc00);

      //
      // 获取虚拟机机器码 0x134a 为虚拟机机器码的总长度, 使用7534a(跳转表RVA) - 74000(新虚拟机节RVA) == 134a 但我不确定是不是, 不过使用134a貌似没有问题

      string vm_bc;
      char *vm_bytecode = new char;
      fp.FM_R_DATA(vm_bytecode, 0x134a);
      vm_bc.append(vm_bytecode, 0x134a);

      //
      // 确定 跳转表的个数

      DWORD Addr = 0;
      DWORD table_cnt = 0;
      for (;;)
      {
                char Number = { 0x4a, 0x53, 0x47, 0x00, 0x00 };
                Addr = vm_bc.find(Number, Addr);

                if (Addr == -1)
                {
                        break;
                }

                table_cnt++;
                Addr += 4;
      }

      printf("一共查询到跳转表数据:%d个.\n", table_cnt);
      
      //
      // 获取跳转表中的VA

      vector<DWORD> table_data;

      for (DWORD i = 0; i < table_cnt; ++i)
      {
                DWORD data = 0;
                data = fp.FM_R_UINT32();
                table_data.push_back(data);
      }

      fp.FM_OFF();
      fp.FM_ON_AB();
      fp.FM_MOV_SET(0x6cc00 + 0x134a);

      //
      // 根据table_cnt 替换跳转表

      for (DWORD i = 0; i < table_cnt; ++i)
      {
                DWORD data = table_data - 0x28000 + 0x74000;
                fp.FM_W_UINT32(data);
      }

      printf("一共重写跳转表数据:%d个.\n", table_cnt);

      fp.FM_OFF();
      
      查询修正跳转表代码如下 end.
      
      运行结果:


现在运行处理后的cm0.exe, 发现会GG, 查看问题发现代码异常还是跟之前图3一样.. 这是为啥, 仔细检查确定该修改的都修改了,
为什么还是出问题呢? 百思不得其解, 吃饭去了, 这里就告一段落.. - _ -

0x05 花世界

上面遇到挫折后猜测了很多可能, 但是由于虚拟机有大量花指令插在里面影响阅读, 所以决定去花然后看看有没有什么异常的地方..

前面说了手动干, 因此去花也决定手动写代码解决, 其实这个虚拟机使用的花并不多, 也就5 6 7朵吧, 还都是一些古老的花指令, 去起来也好办.
      
由于代码太长我就不发了, 截图发个效果吧.
      
      去花结果:




虚拟机去花后的效果:



现在虚拟机干净了, 就是很多nop好长, 不过至少可以正常阅读了, 花了一些时间快速扫了很久, 发现一处handler:
VA: 00474D40
      
      00474D40   push      eax
      00474D41   push      edx
      00474D42   push      ecx
      ... nop 无关紧要省略
      00474D51   push      eax
      00474D52   fldz                                                                // ------> 注意这个
      ... nop 无关紧要省略
      00474D62   mov         ecx,0xFF08AD38
      00474D67   not         ecx
      ... nop 无关紧要省略
      00474D79   wait
      00474D7A   fstenv                              // ------> 注意这个
      00474D7E   pop         ecx
      00474D7F   lea         esp,
      00474D83   push      ecx
      00474D84   lodsb
      00474D85   xor         al,0xD8
      00474D87   not         al
      00474D89   sub         al,0x76
      00474D8B   add         bl,al
      00474D8D   not         bl
      00474D8F   movzx       eax,al
      00474D92   jmp         dword ptr

这个handler是获取当前运行时的eip的, 我在原始的cm0.exe这个handler上下断点将栈顶数据改为一个很大的值, 然后去掉断点, 发现GG了, 又是那个错误..
经过很多次的实验, 我发现这个虚拟机居然还检测当前位置是否在合法的范围内, 应该就是用来防止拷贝虚拟机过自效验的, 现在知道了这个handler我们只需要
强行修改它, 在栈顶设置一个跟原始cm0.exe一模一样的值就可以了.

方法: 从新虚拟机的位置开始搜索特征码, 找到这个handler, 然后自行发挥修改即可, 我的修改如下:

代码:

UCHAR bc[] = {
                0x50, 0x52, 0x51, 0x90, 0x90, 0x90, 0x68, 0x58, 0x8C, 0x42, 0x00, 0x90, 0x90, 0x90, 0x90, 0x90,
                0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
                0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
                0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x59, 0x8D,
                0x64, 0x24, 0x0C, 0x51, 0xAC, 0x34, 0xD8, 0xF6, 0xD0, 0x2C, 0x76, 0x02, 0xD8, 0xF6, 0xD3, 0x0F,
                0xB6, 0xC0, 0xFF, 0x24, 0x85, 0x4A, 0x53, 0x47, 0x00
      };

      CFMBuffer fp("cm0.exe");
      fp.FM_OFF();
      fp.FM_ON_AB();

      //
      // 移动至虚拟机机器码开始位置 与 handler 的开始位置

      fp.FM_MOV_SET(0x6cc00 + 0xD40);
      fp.FM_W_DATA(bc, sizeof(bc));
      fp.FM_OFF();

      printf("修改handler_geteip代码已经完成.\n");

效果图:



然后在运行一次cm0.exe, 哈哈哈哈哈哈啊, 完美运行, OK鸟, 虚拟机自效验算是完整突破(因为我们修改了一个handler, 还是可以正常运行), 接下来就可以回到最前面, hook~~

0x06 糟糕的体力活 hook

这里hook我使用了最简单的方法, inline hook, 一步一步的hook这个虚拟机使用的handler, 然后打印出日志信息, 有了详细的日志信息, 基本上在notepad++上通过查询一些关键数据

就可以突破了。

下面是我hook的一些handler:



由于hook的细节有一些繁琐, 这里就不说了, 总而言之就是记录每个handler的作用, 以及当时的参数 数据等信息, 我录下来的日志是这样的:



现在有了这些日志, 但是完整的日志显然太大了, 这非常不利于我们分析, 于是我又想了一个办法, 过滤掉cm0.exe输入之前的所有记录, 于是我加了个
Sleep(6000), 运行后6秒重新创建一个日志文件, 将输入以及之后的handler保存到另外一个单独的文件中, 这样我们就可以直接分析我们关心的handler逻辑了。

      虽然几句话就说完了hook, 但实际动手去搞真的很蛋疼, 需要一点点耐心.

0x07 大海捞针之抽象定位

很久以前, 我这样分析一个VM, 都会将表达式整出来, 这样做其实是相当消耗时间跟耐心的, 但这次我没有这样做, 而是从爆破出发, 所以我只关心了一个指令: VMSetPC.
      
      从所有handler找到操作esi寄存器的handler, 这个十有八九就是VMSetPC指令, 然后hook, 打印出所有我们关心的信息, 然后看日志:



我们看到有一行日志是: VMSetPC key:, h:, pc:, 当前pc:, 跳转pc:.


      key是跳转过去后修正的key
      h是跳转过去后下一个指令的handler
      pc是字节码内的RVA
      当前pc是指执行这个VMSetPC时esi的值
      跳转的pc是指esi被修改后的值

有了这些数据, 一切都好办了, 现在唯一要解决的问题就是怎么爆破, 因为我们输入的数据是胡乱输入的, 因此那个跳转pc, 一定是跳到提示错误的地方去了,
所以我们要找到如果它不是要跳到那, 那究竟是要跳到哪...

      这个需要分析这个jcc的vm指令, 向上找日志, 搜索关键字0x0004684a, 向上找看看这个0x0004684a是怎么被计算出来的.

      向上找到第一个0x004684a, 如图:



其中日志: VMNulNor32 , , 结果:.

      是一个nor操作, 那我们向上跟踪这3个值, 可以发现是5个nor得出的, 很显然, 这是一个xor, 也就是说, 0x0004684a是一个数被解密出来的, 这个数是第一个nor里面的:
      
      VMNulNor32 , , 结果:. 加上后面的4个nor, 其实就是 xor 0xfc1b5f95, 0xfc1f37df, 知道了加密方法, 那我们看看哪个是被加密的数值,
继续向上查询这两个数, 直到找不到为止, 就一定是第一次出现的地方.

      找到如下日志:



日志内容:
      
      VMPushImm32 .
      VMPushImm32 .
      VMPushImm32 .
      VMPushImm32 .
      VMPushImm32 .
      VMPushImm32 .
      
      里面包含了数据 0xfc1f37df, 那么就好办了, 它上面的那个一定会是跳到正确路径的RVA了, 我们拿出它跟 0xfc1b5f95 xor 得到RVA 0x0004353B,
那么不用犹豫了, 上面的其他数据也是跟着配套的, 请回顾:

      VMSetPC key:, h:, pc:, 当前pc:, 跳转pc:.
      那我们这个VMSetPC要强制修正为:
      VMSetPC key:, h:, pc: (为什么只要改这3个? 因为只有这3个是参数, 后面2个是影响后的数据, 不能修改.)
      
      那么我们回到hook的地方, 自己写段代码强行将数据修改为我上面的指出的数据即可.
      
      然后在尝试录制一次日志, 你会发现, 嘿嘿, 现在有2个VMSetPC了, 这表示我们之前的修改是完全正确的, 因此, 我们只要使用这个方法, 一直修改下去,
就可以爆破这个cm0.exe了, 为了节省篇幅, 我直接截图我这边的修改吧.



好了, 当这些补丁全部分析完打上去之后, 爆破就成功了, 效果:



0x08 总结
      
      1 制造六耳猕猴 过掉自效验
      2 多想想有可能导致自效验依然生效的原因
      3 利用日志的关键字搜索 快速定位核心信息
      4 要有耐心
      
题外话:
这个cm0.exe里面使用的虚拟机, 只是作者拿出来娱乐的虚拟机,
真正有强度的虚拟机跟这个虚拟机的架构完全是不一样的, 无法使用相同的方法过自效验, 也没有跳转表, 想找handler非常不容易, 总之架构已经完全不同,
以后你们遇见sp0101的虚拟机壳, 很可能就是作者的SProtect壳了.    这些是之后我向作者请教才了解的,


Aug.LuKai 发表于 2018-2-5 23:15

栋呀 发表于 2018-2-5 21:32
最近在破解一个vmp壳的软件    搞得好头疼 或许我不该碰vmp这个东西

我遇到的vmp壳,反虚拟机,反调试{:1_911:}{:1_911:}{:1_911:}

wo4mt 发表于 2018-2-7 16:46

cunzhe0410 发表于 2018-2-7 16:02
斑竹。吃J开挂被封后,该怎么处理电脑才能玩下一个号,而导致不会再被封啊。

重装系统 233 :lol

张桓 发表于 2018-2-5 14:31

大佬刚才下载了你发的主播吃鸡专用 小白不会用,卡密怎么输入啊。谢谢大佬了

材鸟 发表于 2018-2-5 14:39

看了一半,然后就没有看下去的欲望了{:301_1007:}

索马里的海贼 发表于 2018-2-5 14:56

大佬终究是大佬,全程看不懂。

Webrobot 发表于 2018-2-5 15:31

大佬还是大佬。

东吴周郎 发表于 2018-2-5 16:13

学习了,牛逼

小道观 发表于 2018-2-5 17:33

非常有看下去的兴趣,但是看完了依然懵逼。

quenna 发表于 2018-2-5 17:46

看到sound这个名字 想起来以前某游戏的恶心的随机crc和内存检查代码就藏在sound.dll 里

lies2014 发表于 2018-2-5 19:55

大佬的文章必须顶贴加分

苏子陌 发表于 2018-2-5 20:06

版主用的什么破解工具
页: [1] 2 3 4 5 6 7 8 9 10
查看完整版本: 分析一个新型VM的CrackMe