好友
阅读权限10
听众
最后登录1970-1-1
|
如有雷同,请管理删除次贴
高手略过 ,咱新手。切勿在此显摆技术 希望看了对大家有所帮助
在看的同时 请大家多给我点鼓励,混个脸熟
重建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:0053F04E 83C404 ADD ESP,04
001B:0053F051 FF6214 JMP [EDX+14]
001B:0053F054 C3 RET
001B:0053F055 0000 ADD [EAX],AL
001B:0053F057 0000 ADD [EAX],AL
001B:0053F059 0000 ADD [EAX],AL
001B:0053F05B 0000 ADD [EAX],AL
001B:0053F05D 0000 ADD [EAX],AL
001B:0053F05F 0000 ADD [EAX],AL
001B:0053F061 0000 ADD [EAX],AL
001B:0053F063 0000 ADD [EAX],AL
001B:0053F065 0000 ADD [EAX],AL
001B:0053F067 0000 ADD [EAX],AL
001B:0053F069 0000 ADD [EAX],AL
001B:0053F06B 0000 ADD [EAX],AL
001B:0053F06D 0000 ADD [EAX],AL
001B:0053F06F 0000 ADD [EAX],AL
001B:0053F071 0000 ADD [EAX],AL
001B:0053F073 0000 ADD [EAX],AL
001B:0053F075 0000 ADD [EAX],AL
001B:0053F077 0000 ADD [EAX],AL
001B:0053F079 0000 ADD [EAX],AL
001B:0053F07B FF15DCD15400 CALL [0054D1DC]
001B:0053F081 33D2 XOR EDX,EDX
001B:0053F083 8AD4 MOV DL,AH
001B:0053F085 891510AF8B08 MOV [088BAF10],EDX
以下只讨论如何恢复局部变量的初值。
执行到fake OEP时,查看堆栈内容,并注意到此时ebp的值为12FFC0(地址12FFC0处存放的是ebp寄存器的初值12FFF0,是由程序最开始的push ebp指令放进去的)这个值加上4就是栈底(即esp寄存器的初值,也就是程序刚被加载时esp的值)12FFC4。至于为什么,看第1点的说明就知道了。此时esp的值为12FF4C,为栈顶。栈顶和栈底的范围一确定,那么堆栈的范围也已经确定。下面要做的就是把对堆栈的初始化(即对堆栈中局部变量的初始化)用相应的mov dword ptr [ebp-xxxxxxxx], yyyyyyyy指令来完成即可。注意不要使用绝对地址,而要转换成ebp-xxxxxxxx这样的浮动地址(根据ebp的取值会浮动)。
:dd esp L 100
0023:0012FF4C 00000000 00000000 7FFDF000 0CE352F0 ............R..
0023:0012FF5C 33F7F161 00400000 0E24FAC5 0012FFA4 a..3..@...$.....
0023:0012FF6C 0CE10000 003D0000 0CE24138 0CE368F0 ......=.8A...h..
0023:0012FF7C 0CE3528C 00400000 088C0845 0CE3544A .R....@.E...JT..
0023:0012FF8C 00000000 00000170 0C977323 0CE354B6 ....p...#s...T..
0023:0012FF9C 0CE366B7 0CE354B7 00000000 0012FF4C .f...T......L...
0023:0012FFAC 0012FFE0 0012FFE0 00542B94 0054E948 .........+T.H.T.
0023:0012FFBC FFFFFFFF 0012FFF0 77E1F38C 00000000 ...........w....
0023:0012FFCC 00000000 7FFDF000 81F6E020 0012FFC8 ....... .......
0023:0012FFDC F6DC8C00 FFFFFFFF 77E40ABC 77E522E0 ...........w.".w
0023:0012FFEC 00000000 00000000 00000000 00401000 ..............@.
0023:0012FFFC 00000000 78746341 00000020 00000001 ....Actx .......
0023:0013000C 0000301C 000000DC 00000000 00000020 .0.......... ...
0023:0013001C 00000000 00000014 00000001 00000007 ................
0023:0013002C 00000034 0000016C 00000001 00000000 4...l...........
0023:0013003C 00000000 00000000 00000000 00000000 ................
堆栈中主要有三种值:
1、立即数常数
。
例如12FF4C处的值为立即数0, 可以用mov dword ptr [ebp – 74], 0来实现。
12FF54处的值为立即数7FFDF000, 用mov dword ptr [ebp – 6C], 7FFDF000。
当然,这里面可能有些是属于垃圾随机数,实际上是不需要赋初值的。可以通过单步跟踪壳在进入fake OEP之前的处理来过滤掉这些不需要重建的垃圾数,从而优化代码长度。
2、局部变量的地址。
由于局部变量在堆栈中,所以这些地址的值在栈顶地址和栈底地址之间,即0012XXXX这样的值。对于这样的值,要用ebp – xxxxxxxx这样的形式来赋值。
例如12FFA8(即ebp-18)处的值是地址12FF4C(即ebp-74),那么完成相应赋值功能的指令是:
lea eax, [ebp-74]
mov [ebp-18], eax
3、壳中的地址。
对于例子程序,即0CEXXXXX这样的值。无需处理。
这样,我们最终得到的重建的指令如下。这些指令可能在OEP处放不下,需要另找个空地放下,然后跳到fake OEP即可。
push ebp
mov ebp, esp
mov dword ptr [ebp – 74], 0
mov dword ptr [ebp – 6C], 7FFDF000
lea eax, [ebp-74]
mov [ebp-18], eax
。。。。。。。。(其它省略)
sub esp, 68 (esp指向正确的栈顶)
jmp 53F07B (跳到fake OEP)
下面通过跟踪壳在进入fake OEP之前的处理来印证并优化一下我们的重建代码(花指令,只需要看有注释的)。
:u eip l 20
001B:0CE34219 896C2404 MOV [ESP+04],EBP //即push ebp
001B:0CE3421D 6681352642E30C33F0 XOR WORD PTR [0CE34226],F033
001B:0CE34226 D8F1 FDIV ST,ST(1)
001B:0CE34228 0F8D6424048B JGE 97E76692
001B:0CE3422E EC IN AL,DX
001B:0CE3422F 6AFF PUSH FF
001B:0CE34231 6848E95400 PUSH 0054E948
001B:0CE34236 68942B5400 PUSH 00542B94
:u ce34229 l 20
001B:0CE34229 8D642404 LEA ESP,[ESP+04]
001B:0CE3422D 8BEC MOV EBP,ESP //建立栈帧
001B:0CE3422F 6AFF PUSH FF //立即数初始化
001B:0CE34231 6848E95400 PUSH 0054E948 //立即数初始化
001B:0CE34236 68942B5400 PUSH 00542B94 //立即数初始化
001B:0CE3423B 64A100000000 MOV EAX,FS:[00000000]
001B:0CE34241 EB02 JMP 0CE34245
001B:0CE34243 CD20 INT 20 VXDJmp 4F2D,0166
:u eip l 10
001B:0CE34260 89442404 MOV [ESP+04],EAX //初始化
001B:0CE34264 6681356D42E30C33F0 XOR WORD PTR [0CE3426D],F033
001B:0CE3426D D8F1 FDIV ST,ST(1)
001B:0CE3426F 0F8D64240464 JGE 70E766D9
:u eip l 10
001B:0CE3427B 83EC58 SUB ESP,58 //为局部变量预留空间
001B:0CE3427E EB02 JMP 0CE34282
001B:0CE34280 CD20 INT 20 VXDJmp 8C2D,0166
001B:0CE34286 42 INC EDX
001B:0CE34287 E30C JECXZ 0CE34295
001B:0CE34289 BE75F2A977 MOV ESI,77A9F275
:u eip l 10
001B:0CE3429D 895C2404 MOV [ESP+04],EBX //初始化
001B:0CE342A1 668135AA42E30C33F0 XOR WORD PTR [0CE342AA],F033
001B:0CE342AA D8F1 FDIV ST,ST(1)
001B:0CE342AC 0F8D642404EB JGE F7E76716
:u eip l 10
001B:0CE342D0 89742404 MOV [ESP+04],ESI //初始化
001B:0CE342D4 668135DD42E30C33F0 XOR WORD PTR [0CE342DD],F033
001B:0CE342DD D8F1 FDIV ST,ST(1)
001B:0CE342DF 0F8D642404EB JGE F7E76749
:u eip l 10
001B:0CE34303 897C2404 MOV [ESP+04],EDI //初始化
001B:0CE34307 6681351043E30C33F0 XOR WORD PTR [0CE34310],F033
001B:0CE34310 D8F1 FDIV ST,ST(1)
001B:0CE34312 0F8D64240489 JGE 95E7677C
:u eip l 10
001B:0CE34317 8965E8 MOV [EBP-18],ESP //初始化
001B:0CE3431A 2EEB01 JMP 0CE3431E
001B:0CE3431D F0F2EB01 LOCK REPNZ JMP0CE34322
001B:0CE34321 F2687BF05300 REPNZ PUSH 0053F07B
:u eip l 10
001B:0CE34322 687BF05300 PUSH 0053F07B //fake OEP
001B:0CE34327 681541E30C PUSH 0CE34115 //此处有段清零代码
001B:0CE3432C C3 RET //先去清零,再去fake OEP
通过分析上面的花指令单步跟踪,重建后得到的代码如下,运行正常。
push ebp
mov ebp, esp
push FF
push 54E948
push 542B94
lea eax, [ebp+20]
mov [ebp-10], eax
mov dword ptr [ebp-6C], 7FFDF000
xor eax, eax
mov [ebp-70], eax
mov [ebp-74], eax
leax eax, [ebp-74]
mov [ebp-18], eax
jmp 52F07B
由于可以“机械化”操作,实际上是可以写个工具自动或者半自动重建stolen bytes的。
BTW: aspr 1.23 RC4把虚拟地址弄得很大,full dump出来的文件有100多M,幸好rebuild一下就变小了,不然要每个section单独dump然后再合成。 |
|