867427677 发表于 2010-2-6 12:50

重建stolen bytes的一般方法《转载看雪-blowfish》

如有雷同,请管理删除次贴
高手略过 ,咱新手。切勿在此显摆技术 希望看了对大家有所帮助
在看的同时 请大家多给我点鼓励,混个脸熟:victory:

重建stolen bytes的目的,是为了让局部变量、全局变量、寄存器的初值在执行到fake OEP时和未加壳前的保持一致,并非要精确还原这些stolen的指令,只要功能相同即可。当然如果能精确还原的话是最好了。以下所说的“重建”都是指功能上的重建。

  一般编译器生成的startup代码的最开始几条指令主要是进行如下操作:

1、 建立堆栈帧,也就是下面的两条指令。这两条指令是需要重建的,而且重建也很容易,因为是固定套路。

push ebp
mov ebp, esp

2、 在堆栈中保存寄存器的值。

保存寄存器对于普通的函数是有用的,但是对于startup函数来说是不需要的。普通函数在函数入口用类似push ecx这样的指令来保存寄存器的值到堆栈中,在返回之前则用pop ecx这样的指令来恢复寄存器的值;但是startup函数是不会返回的,因为在返回之前肯定主动调用了ExitProcess//TerminateProcess自己退出了,或者被其它进程中止了。所以我们不需要重建这些保存寄存器的指令,这些指令仅仅影响堆栈指针esp的值。

3、 初始化堆栈中的局部变量。

这些必须重建,否则某些局部变量的初值不对可能会导致程序运行出错。重建这些指令有固定的方法可循,后面单独讲。

4、 初始化全局变量。

一般的编译器生成的startup代码的最开始部分不对全局变量进行操作,所以这一份暂时可以忽略。

5、 初始化某些寄存器的值。

如果有的话,需要对这些指令进行重建而且重建比较简单。就是执行到fake OEP时记下各个寄存器的值,例如如果此时esi的值为xxxxxx,就可以用mov esi, xxxxxxxx这样的指令来完成相应的寄存器初始化功能。注意:一般只有EAX、EBX、ECX、EDX、ESI、EDI这几个寄存器可能有初值要求(仅仅是可能)。

  目前所见到的具有stolen bytes特性的壳似乎都只是对startup代码中第一个call之前的指令进行了隐藏处理。上述5部分中,对局部变量初值的重建稍微复杂一些,但是可以通过分析堆栈来采用比较“机械”的方法进行处理,也是有固定的套路。下面用一个例子来说明。

  如下是一个用ASProtect v1.23 RC4加壳的程序,我们找到其OEP为53F055,fake OEP为53F07B,OEP和fake OEP之间的38个字节被壳清零了(fake OEP处的call是call GetVersion)。不要指望能在内存中找到这些字节的原版。

  001B:0053F04E83C404            ADD       ESP,04
  001B:0053F051FF6214            JMP      
  001B:0053F054C3                  RET
  001B:0053F0550000                ADD       ,AL
  001B:0053F0570000                ADD       ,AL
  001B:0053F0590000                ADD       ,AL
  001B:0053F05B0000                ADD       ,AL
  001B:0053F05D0000                ADD       ,AL
  001B:0053F05F0000                ADD       ,AL
  001B:0053F0610000                ADD       ,AL
  001B:0053F0630000                ADD       ,AL
  001B:0053F0650000                ADD       ,AL
  001B:0053F0670000                ADD       ,AL
  001B:0053F0690000                ADD       ,AL
  001B:0053F06B0000                ADD       ,AL
  001B:0053F06D0000                ADD       ,AL
  001B:0053F06F0000                ADD       ,AL
  001B:0053F0710000                ADD       ,AL
  001B:0053F0730000                ADD       ,AL
  001B:0053F0750000                ADD       ,AL
  001B:0053F0770000                ADD       ,AL
  001B:0053F0790000                ADD       ,AL
  001B:0053F07BFF15DCD15400      CALL      
  001B:0053F08133D2                XOR       EDX,EDX
  001B:0053F0838AD4                MOV       DL,AH
  001B:0053F085891510AF8B08      MOV       ,EDX

  以下只讨论如何恢复局部变量的初值。

  执行到fake OEP时,查看堆栈内容,并注意到此时ebp的值为12FFC0(地址12FFC0处存放的是ebp寄存器的初值12FFF0,是由程序最开始的push ebp指令放进去的)这个值加上4就是栈底(即esp寄存器的初值,也就是程序刚被加载时esp的值)12FFC4。至于为什么,看第1点的说明就知道了。此时esp的值为12FF4C,为栈顶。栈顶和栈底的范围一确定,那么堆栈的范围也已经确定。下面要做的就是把对堆栈的初始化(即对堆栈中局部变量的初始化)用相应的mov dword ptr , yyyyyyyy指令来完成即可。注意不要使用绝对地址,而要转换成ebp-xxxxxxxx这样的浮动地址(根据ebp的取值会浮动)。

  :dd esp L 100
  0023:0012FF4C 00000000000000007FFDF0000CE352F0      ............R..
  0023:0012FF5C 33F7F161004000000E24FAC50012FFA4      a..3..@...$.....
  0023:0012FF6C 0CE10000003D00000CE241380CE368F0      ......=.8A...h..
  0023:0012FF7C 0CE3528C00400000088C08450CE3544A      .R....@.E...JT..
  0023:0012FF8C 00000000000001700C9773230CE354B6      ....p...#s...T..
  0023:0012FF9C 0CE366B70CE354B7000000000012FF4C      .f...T......L...
  0023:0012FFAC 0012FFE00012FFE000542B940054E948      .........+T.H.T.
  0023:0012FFBC FFFFFFFF0012FFF077E1F38C00000000      ...........w....
  0023:0012FFCC 000000007FFDF00081F6E0200012FFC8      ....... .......
  0023:0012FFDC F6DC8C00FFFFFFFF77E40ABC77E522E0      ...........w.".w
  0023:0012FFEC 00000000000000000000000000401000      ..............@.
  0023:0012FFFC 00000000787463410000002000000001      ....Actx .......
  0023:0013000C 0000301C000000DC0000000000000020      .0.......... ...
  0023:0013001C 00000000000000140000000100000007      ................
  0023:0013002C 000000340000016C0000000100000000      4...l...........
  0023:0013003C 00000000000000000000000000000000      ................
  
  堆栈中主要有三种值:

  1、立即数常数

  例如12FF4C处的值为立即数0, 可以用mov dword ptr , 0来实现。
  12FF54处的值为立即数7FFDF000, 用mov dword ptr , 7FFDF000。
  当然,这里面可能有些是属于垃圾随机数,实际上是不需要赋初值的。可以通过单步跟踪壳在进入fake OEP之前的处理来过滤掉这些不需要重建的垃圾数,从而优化代码长度。

  2、局部变量的地址。

  由于局部变量在堆栈中,所以这些地址的值在栈顶地址和栈底地址之间,即0012XXXX这样的值。对于这样的值,要用ebp – xxxxxxxx这样的形式来赋值。
  例如12FFA8(即ebp-18)处的值是地址12FF4C(即ebp-74),那么完成相应赋值功能的指令是:
   lea eax,
   mov , eax

  3、壳中的地址。

  对于例子程序,即0CEXXXXX这样的值。无需处理。

  这样,我们最终得到的重建的指令如下。这些指令可能在OEP处放不下,需要另找个空地放下,然后跳到fake OEP即可。

  push ebp
  mov ebp, esp
  mov dword ptr , 0
  mov dword ptr , 7FFDF000
  lea eax,
  mov , eax
  。。。。。。。。(其它省略)
  sub esp, 68    (esp指向正确的栈顶)
  jmp 53F07B   (跳到fake OEP)
  
  下面通过跟踪壳在进入fake OEP之前的处理来印证并优化一下我们的重建代码(花指令,只需要看有注释的)。

  :u eip l 20
  001B:0CE34219896C2404            MOV       ,EBP       //即push ebp
  001B:0CE3421D6681352642E30C33F0XOR       WORD PTR ,F033
  001B:0CE34226D8F1                FDIV      ST,ST(1)
  001B:0CE342280F8D6424048B      JGE      97E76692
  001B:0CE3422EEC                  IN      AL,DX
  001B:0CE3422F6AFF                PUSH      FF
  001B:0CE342316848E95400          PUSH      0054E948
  001B:0CE3423668942B5400          PUSH      00542B94
  :u ce34229 l 20
  001B:0CE342298D642404            LEA       ESP,
  001B:0CE3422D8BEC                MOV       EBP,ESP         //建立栈帧
  001B:0CE3422F6AFF                PUSH      FF                //立即数初始化
  001B:0CE342316848E95400          PUSH      0054E948          //立即数初始化
  001B:0CE3423668942B5400          PUSH      00542B94          //立即数初始化
  001B:0CE3423B64A100000000      MOV       EAX,FS:
  001B:0CE34241EB02                JMP      0CE34245
  001B:0CE34243CD20                INT       20 VXDJmp 4F2D,0166
  :u eip l 10
  001B:0CE3426089442404            MOV       ,EAX       //初始化
  001B:0CE342646681356D42E30C33F0XOR       WORD PTR ,F033
  001B:0CE3426DD8F1                FDIV      ST,ST(1)
  001B:0CE3426F0F8D64240464      JGE      70E766D9
  :u eip l 10
  001B:0CE3427B83EC58            SUB       ESP,58         //为局部变量预留空间
  001B:0CE3427EEB02                JMP      0CE34282
  001B:0CE34280CD20                INT       20 VXDJmp 8C2D,0166
  001B:0CE3428642                  INC       EDX
  001B:0CE34287E30C                JECXZ    0CE34295
  001B:0CE34289BE75F2A977          MOV       ESI,77A9F275
  :u eip l 10
  001B:0CE3429D895C2404            MOV       ,EBX   //初始化
  001B:0CE342A1668135AA42E30C33F0XOR       WORD PTR ,F033
  001B:0CE342AAD8F1                FDIV      ST,ST(1)
  001B:0CE342AC0F8D642404EB      JGE      F7E76716
  :u eip l 10
  001B:0CE342D089742404            MOV       ,ESI      //初始化
  001B:0CE342D4668135DD42E30C33F0XOR       WORD PTR ,F033
  001B:0CE342DDD8F1                FDIV      ST,ST(1)
  001B:0CE342DF0F8D642404EB      JGE      F7E76749
  :u eip l 10
  001B:0CE34303897C2404            MOV       ,EDI       //初始化
  001B:0CE343076681351043E30C33F0XOR       WORD PTR ,F033
  001B:0CE34310D8F1                FDIV      ST,ST(1)
  001B:0CE343120F8D64240489      JGE      95E7677C
  :u eip l 10
  001B:0CE343178965E8            MOV       ,ESP       //初始化
  001B:0CE3431A2EEB01            JMP      0CE3431E
  001B:0CE3431DF0F2EB01            LOCK REPNZ JMP0CE34322
  001B:0CE34321F2687BF05300      REPNZ PUSH 0053F07B
  :u eip l 10
  001B:0CE34322687BF05300          PUSH      0053F07B         //fake OEP
  001B:0CE34327681541E30C          PUSH      0CE34115         //此处有段清零代码
  001B:0CE3432CC3                  RET                        //先去清零,再去fake OEP
  
  通过分析上面的花指令单步跟踪,重建后得到的代码如下,运行正常。

               push    ebp
               mov   ebp, esp
               push    FF
               push    54E948
               push    542B94
               lea   eax,
               mov   , eax
               mov   dword ptr , 7FFDF000
               xor   eax, eax
               mov   , eax
      mov   , eax
               leax    eax,
               mov   , eax
               jmp      52F07B

   由于可以“机械化”操作,实际上是可以写个工具自动或者半自动重建stolen bytes的。
   BTW: aspr 1.23 RC4把虚拟地址弄得很大,full dump出来的文件有100多M,幸好rebuild一下就变小了,不然要每个section单独dump然后再合成。

867427677 发表于 2010-2-6 13:17

没人看 自己顶下

avzhongjiezhe 发表于 2010-2-6 15:47

谢谢,学习了
页: [1]
查看完整版本: 重建stolen bytes的一般方法《转载看雪-blowfish》