zoukankan      html  css  js  c++  java
  • VMP加壳(三):VMP壳爆破实战-破解某编辑类软件

      这次爆破的是某编辑类软件,版本是32位绿色版本:V4.3.1

      

      1、OD打开后发现了VMP0段,这里下个内存访问断点:

      

      又来到这里了,非常明显的VMP入口特征:

      

      一大堆push指令又开始保存物理寄存器;同时让esi指向虚拟指令集;和上面一篇文章分析的混淆手法一模一样,个人猜测用的VMP也是3.5.0版本的;

     

     分配虚拟栈空间:

     、 

      这里就不再重复分析整个VMP过程了,感兴趣的小伙伴建议看看之前的VMP系列介绍;为了快速定位关键位置,来到注册地方,随便输入一个辨识度较高的注册码:

       

       同时记得继续在内存视图给VMP0段下断点后点击确认按钮,断到这里了,继续F7:

       

       单步F7走着,同时在栈中回溯,找到了自己输入的sn,这个地址就很重要了,果断取消VMP0段的断点,对这个地址下读的断点:

       

       这里也下访问断点;继续回溯栈,在栈下方凡是这个exe本身的调用都下断点:

       

       这里有messageBox,栈里面也有sn和“该注册码不正确!”的关键提示,说明这里已经走到了注册错误的分支,需要回溯栈,看看在哪调用了这个方法(这里的返回地址是0x5E01E9)

       这里的地址是0x3E6000,text段的基址是0x381000,偏移=0x3E6000-0x381000=0x65000,记住这个偏移,后续可能有用;

       

        继续F7,执行完messbaox弹窗后,居然回到了某个VM的入口:正常情况下,ret会回到call的下一行,但这里回到了push(或则说vm的入口),说明ret的的地址因该是人为故意加上的,也说明这段代码是关键代码,作者故意不想让逆向人员破解,真是此地无银三百两啊

       

       上面这个地方会不会是验证码检测的入口了?我没仔细分析,不过既然断到这里了,就有可能是,不管那么多了,先NOP这些代码试试,结果直接崩掉,说明不能这么粗暴,重新来!

         

         上面是爆破软件的传统思路:通过字符串找到各种关键提示(sn、注册不正确之类的)的内存,通过访问断点定位到关键代码,然后逐步往上回溯找到关键的JCC指令,改变JCC指令的跳转方向达到爆破的目的;由于被VMP加了很多混淆指令,直接这样简单粗暴找JCC难度不小,这条路暂时放弃,得换个思路和打法!

          2、既然3E6000是弹窗的,那么只要找出是哪个函数调用了这个函数距离JCC指令就更进一步了,上面就是这种思路。但ret后发现是VM的入口,并不是我们传统意义上的函数调用。既然动态分析行不通,那就静态查找试试;打开IDA,默认的base是401000,函数偏移是0x65000,绝对地址是466000,正好是目标函数:

      

        为了方便,可以把base改成exe运行的base,也就是0x380000,能在3E6000这里直接看到函数了:

      

         通过function calls能找到所有调用这个函数的函数:注意,这里的call用的是相对地址,不是绝对地址,所以直接用硬编码找是不行的

          

         一共有24个,如果挨个看代码难度非常大,只能继续接着动态调试:每个地方都下断点,然后继续操作注册的流程,满以为能找到调用点,结果一个都没断下来,说明注册失败弹窗的函数不是这么调用了,要么是jmp到这里,要么是call 寄存器这种间接调用

        

        3、静态分析也没找到关键的调用点,继续动态分析;VMP最大的特点就是混淆:明明一个简单的指令,非要用复杂的多行指令替代,那么这次就trace一下,看看从VM入口一直到弹窗,这中间究竟执行了哪些代码!

         (1)既然5E01E9是关键代码,那么先trace一下,看看都有哪些代码执行过!先在0x5E01E9下个断点,然后开启trace功能,接着上面输入sn验证的操作重新做一遍,run trace界面就能看到指令执行的记录了,如下(这里顺便吐槽一下log to file的功能,只能看到地址和寄存器的值,看不到执行的指令,WTF.......):

        

        接下来就是个体力活了:挨个找寄存器里面的值等于00000040的行(后续会解释原因),挨个下断点,比如下面这样:

        

        这里不得不吐槽一下OD的trace功能不好用:OD内部没法搜索关键词,保存到文件后又看不到执行的指令,很不方便,果断弃坑,换成x32dbg;trace的步骤:       

    •     在内存布局那找到VMP0段下个一次性的访问断点,然后操作注册的流程,正常情况下会断到VM入口。这时单步步进,进入VM
    •     在“跟踪”页面右键选择“启动追踪”,最后再在菜单栏选择“追踪->自动步进”,或则直接CTRL+F7

         此时x32dbg会自动开始单步步进,直到注册界面弹框;整个过程我花了一上午+中午,超过5小时,终于看到弹框;在跟踪界面查到一共执行了0x3F617=259607行代码;

       

         (2)这么多行代码,该怎么分析才能找到关键的JCC指令?在“跟踪”界面,右键选择搜索->常数,表达式这里输入00000040(后面会详细解释为啥是这个数),点击确定; 

        

        和0x40相关的指令有近100条,逐条筛选and指令(后续会详细解释为什么要重点查找and指令)

         

        可以看到除最后一条,所有的指令都是and eax,ecx; 第1、2条分别执行一次,第3条执行了30次;

       

        接下来就是纯体力活了:

    •     选中某条,右键 复制->索引;(虚拟机不好截图,用手机拍的,读者请多担待)
    •     回到跟踪窗口,ctrl+g后粘贴刚才复制的索引,定位到那行and代码;然后 右键->信息 查看寄存器的值

            

           对于eax=0x40、eflags=0x246或0xFFFFxxxxx开始的值,都下个断点(其实一共只有3个,也不用挨个检查,直接下断点也行);

          

           

         

         这三个and eax,ecx都有个共同点:之前都执行了not eax和not ecx,原理后续再介绍!3个断点下来后,继续操作注册的流程,3个断点都成功断下,然后挨个过滤,把执行完and eax,ecx后eax=0x0的选出来,人为把eax改成0x40或0x246;

         

        

       

       除此以外,由于影响eax的是ecx,所以把ecx=FFFFFDB9(或者是~276,因为刚好让ZF位=0,和eax与后也会让ZF=0)找出来,挨个下断点查看:

       

        关键的两个and指令夹杂在jmp中间,如果不是trace,根本找不到这些关键点:

       

       凡是eax不等于0x40的全都改成0x40

        

        

       终于,在改了好多次eax=0x40后,成功爆破!

         

        输入注册码购买之类的也没了!

         

          4、(1)VMP的万用门

          学过逻辑电路的朋友们都知道有一种门电路,叫与非门(俗称万用门),表示为: Nand(a,b) = ~a & ~b,就是两个数取反后再与;这是一个很普通的表达式,为啥要专门拿出来介绍?用名字就能看出来:万用门!汇编里面最基本的4种逻辑运算,都能用万用门表示,推导过程如下:

    • Not(a) = ~a = ~a & ~a = Nand(a,a)
    • Or(a,b) = a | b = ~(~a & ~b) = Nand(Nand(a,b),Nand(a,b))
    • And(a,b) = a & b = ~~a & ~~b = Nand(Nand(a,a),Nand(b,b))
    • Xor(a,b) = (~a & b) | (a & ~b) = (0 | (a & ~b)) | (0 | (b & ~a)) = (a & (~a | ~b)) | (b & (~a | ~b)) = (~a | ~b) & (a | b) = ~(a & b) | ~(~a & ~b) = Nand(And(a,b),Nand(a,b)) =Nand(Nand(Nand(a,a),Nand(b,b)),Nand(a,b))

          这里感觉就有点饶了: ~a表示a取反,用Nand(a,a)表示时是~a&~a,表达式里面又嵌套了取反,感觉有点像盗梦空间............

          再VMP 3.5.0版本中,大量使用了Nand运算来表示其他的各种逻辑,真实地隐藏了原本的各种逻辑运算,有效地加大了逆向分析的难度!所以上面

         (2)指令模拟     

         JCC跳转要依赖efalgs的标志位,而标志位又收到sub/cmp等指令的影响,如果逆向人员顺着sub/cmp等指令找JCC,会很容易暴露关键的JCC指令(我第一次就是用这种思路分析的),但找了很久都没找到关键的JCC指令,原因就是sub、cmp这种指令被混淆和模拟,请看下面的推导过程:

          cmp指令本质上是减法,只不过结果不会写回操作数,所以模拟减法就很重要了!下面是减法的模拟过程:
    •       -a = ~a+1  => ~a = -a -1
    •       ~(~a+b) = ~(-a-1+b) = -(-a-1+b)-1 = a-b  => a-b = Not(NotT(a)+b)
            a-b最终可以由Not(NotT(a)+b)来表示,而Not(a)又可以用Nand(a,a)来表示,这就导致了VMP中not eax; not ecx; and eax,ecx代码大量出现,在trace的时候某些代码甚至执行了上千次
     
         (3)eflags位模拟

           前面做的CMP、sub等指令,结果都会反馈到eflags的ZF位,不考虑其他位,当eflags=0x40时,ZF=1,JCC指令才会根据实际情况跳转, 所以要想办法改变eflags的ZF位。很不幸的是:x86汇编指并未提供可以直接修改ZF位的指令(只有STC、CTC、CMC、bt等少数指令可以修改CF位),这个该怎么修改ZF位了?

           VM的一大特点:寄存器都是虚拟的,存放在栈中,所以VM的eflags是可以随便改的!那么vm的eflags值是怎么计算得到的了? 计算方式如下:

    •  eflags = and( eflags1, 0x815) + and( eflags2, not(0x815)) ,其中eflag1和eflags2都是Nand(sn,sn)+随机数得到的,不过这两个数不重要,只要eflags的ZF=0就行;下面标红的这段就是前半段and(eflags1,0x815),eflags1原值0x286,保存在ecx;0x815在eax,and eax,ecx后把结果保存在eax=0x4,然后写入epb+4的位置(也即是虚拟eflags的位置)

             

    • zf   = and(0x40, eflags)    ZF取决于原eflags的值。具体到汇编代码层面,用的还是and eax, ecx;,所以上面要重点对这行代码下断点调试;eflags保存在ecx(应该是0x246或0xFFFFxxxx形式),0x40保存在eax,所以断点可以根据这两个条件筛选;执行完后的结果保存在eax,然后写回栈上面的VM_CONTEXT中的eflags位置,这样代码执行完后如果eax=0,要手动改成0x40

      参考:  1、https://www.52pojie.cn/thread-1304279-1-2.html  爆破vm代码关键点之某文本编辑辑xxxxEdit4.3.1(4480)的分析与爆破

                   2、https://bbs.pediy.com/thread-224732.htm 谈谈vmp的爆破

                   3、https://www.52pojie.cn/thread-1036956-1-1.html  VMP学习笔记之万用门

  • 相关阅读:
    Spring(九)Spring中的两种自动代理
    Spring(八)Spring错题总结
    Spring(七)Spring中的四种增强和顾问
    SourceTree使用git
    Idea集成git
    SpringMVC--AbstractController抽象类限定请求提交
    SpringMVC处理器配置方式
    SpringMVC静态资源无法访问解决方案
    SpringMVC--视图解析器
    HandlerMapping执行流程
  • 原文地址:https://www.cnblogs.com/theseventhson/p/14274653.html
Copyright © 2011-2022 走看看