重建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然后再合成。 没人看 自己顶下 谢谢,学习了
页:
[1]