在无法使用ESP定律时—EBP的妙用
程序员文章站
2022-03-29 16:01:35
在寄存器里面有很多寄存器虽然他们的功能和使用没有任何的区别,但是在长期的编程和使用中,在程序员习惯中已经默认的给每个寄存器赋上了特殊的含义,比如:EAX一般用来做返回值,ECX... 08-10-08...
在寄存器里面有很多寄存器虽然他们的功能和使用没有任何的区别,但是在长期的编程和使用中,在程序员习惯中已经默认的给每个寄存器赋上了特殊的含义,比如:eax一般用来做返回值,ecx用于记数等等。在win32的环境下ebp寄存器用与存放在进入call以后的esp的值,便于退出的时候回复esp的值,达到堆栈平衡的目的。
应用以前说过的一段话:
原程序的oep,通常是一开始以 push ebp 和mov ebp,esp这两句开始的,不用我多说大家也知道这两句的意思是以ebp代替esp,作为访问堆栈的指针。
为什么要这样呢?为什么几乎每个程序都是的开头能?因为如果我们写过c等函数的时候就应该清楚,程序的开始是以一个主函数main()为开始的,而函数在访问的过程中最重要的事情就是要确保堆栈的平衡,而在win32的环境下保持平衡的办法是这样的:
1.让ebp保存esp的值;
2.在结束的时候调用
mov esp,ebp
pop ebp
retn
或者是
leave
retn
两个形式是一个意思。
这样做的好处是不用考虑esp等于多少,push了多少次,要pop多少次了,因为我们知道ebp里面放的是开始时候的esp值。
2.推广的esp定律
在寻找oep的时候,往往下断hw esp-4不成功,除了壳代码将硬件断点删除了以外,很可能的情况就是因为壳代码在运行到oep的时候他的esp已经不再是在ep时候的esp(12ffc4)了,这样我们下断当然是不成功的。
那么如何找到在壳到达oep的时候的堆栈的值将是关键。
在这里我们应用的关键是
push ebp
mov ebp,esp----》关键是这句
我来解释一下,当程序到达oep的时候push ebp这句对于esp的值来说就是esp-4,然后是esp-4赋给了ebp,而做为保存esp值作用的ebp寄存器在这个“最上层的程序”中的值将始终不会改变。虽然他可能在进入子call里面以后会暂时的改变(用于子程序的堆栈平衡)但是在退出了以后依*pop ebp这一句将还原原来的ebp的值。
以这句做为突破口,就是说只要我们能断在“最上层的程序”中,就能通过观察ebp的值得到壳在jmp到oep的时候的esp的值了。
3.实战
来看看pespin1.1的壳,在pespin1.0的壳中,我们使用hw 12ffc0能很容易的找到stolen code的地方,但是到pespin1.1的时候,我们就不行了。用hw 12ffc0根本断不下来。
现在我们就使用这个推广的esp定律,载入程序后来到最后的一个异常
0040ed85 2bdb sub ebx,ebx //停在这里
0040ed87 64:8f03 pop dword ptr fs:[ebx]
0040ed8a 58 pop eax
0040ed8b 5d pop ebp
0040ed8c 2bff sub edi,edi
0040ed8e eb 01 jmp short pespin1_.0040ed91
0040ed90 c466 81 les esp,fword ptr ds:[esi-7f]
我用使用内存断点办法来到foep处
004010d3 0000 add byte ptr ds:[eax],al
004010d5 0000 add byte ptr ds:[eax],al
004010d7 0000 add byte ptr ds:[eax],al
004010d9 0000 add byte ptr ds:[eax],al
004010db 0000 add byte ptr ds:[eax],al
004010dd 0000 add byte ptr ds:[eax],al
004010df 75 1b jnz short pespin1_.004010fc //这里是foep
004010e1 56 push esi
004010e2 ff15 99f44000 call dword ptr ds:[40f499]
004010e8 8bf0 mov esi,eax
004010ea 8a00 mov al,byte ptr ds:[eax]
好了,这里就是“最上层的程序”的地方了,看看寄存器
eax 00141e22
ecx 0040c708 pespin1_.0040c708
edx 0040c708 pespin1_.0040c708
ebx 0040c708 pespin1_.0040c708
esp 0012f978
ebp 0012f9c0 //注意这里
esi 00141ee0
edi 0040e5cd pespin1_.0040e5cd
eip 004010df pespin1_.004010df
看到了吧,ebp=0012f9c0,我们来想象一下这个值是怎么得到的。
首先肯定是通过mov esp,ebp这一句,也就是说esp这时是0012f9c0的,然而上面还有一句push ebp也就是说esp在到达oep的时候应该是0012f9c4的。好了得到这个结论我们就能很快的找到stolen code的所在了。
重来停在最后的异常
0040ed85 2bdb sub ebx,ebx //停在这里
0040ed87 64:8f03 pop dword ptr fs:[ebx]
0040ed8a 58 pop eax
0040ed8b 5d pop ebp
0040ed8c 2bff sub edi,edi
0040ed8e eb 01 jmp short pespin1_.0040ed91
0040ed90 c466 81 les esp,fword ptr ds:[esi-7f]
然后下断hw 0012f9c0 ,f9运行,来到这里
0040d8fb 61 popad
0040d8fc 55 push ebp
0040d8fd eb 01 jmp short pespin1_.0040d900 //停在这里
0040d8ff 318b eceb01ac xor dword ptr ds:[ebx ac01ebec],ecx
0040d905 83ec 44 sub esp,44
0040d908 eb 01 jmp short pespin1_.0040d90b
0040d90a 72 56 jb short pespin1_.0040d962
0040d90c eb 01 jmp short pespin1_.0040d90f
0040d90e 95 xchg eax,ebp
0040d90f ff15 6cf34000 call dword ptr ds:[40f36c]
0040d915 eb 01 jmp short pespin1_.0040d918
于是就很快的找到了stolen code的所在了。
4.总结
上面的这个办法大概可以总结以下的步骤:
(1).直接或间接的断在“最上层的程序”的地方。
(2).得到“最上层的程序”的ebp的值。
(3).利用程序初始化的两个固定语句找到壳jmp到oep的堆栈值。这个办法有很大的局限性,因为只有vc和delphi程序使用这个初始化的开头。
但是找到“最上层的程序”的办法除了内存断点还有很多办法,例如对于vc来说使用 bp exitprocess也是一个很好的断点,可以直接得到ebp的数值。
5.后话
原来这个办法有很强的前提条件,不是一个很具普遍性的办法,我原来也不想单独的提出来,但是对于jney2兄弟的anti-esp定律来说这个办法却是一个解决之道。
当然还有更多的办法,在这里我只想说很多事情有矛就有盾,没有什么办法是一定没有漏洞的,只是希望这篇文章给大家阔宽思路,起到抛砖引玉的作用。
应用以前说过的一段话:
原程序的oep,通常是一开始以 push ebp 和mov ebp,esp这两句开始的,不用我多说大家也知道这两句的意思是以ebp代替esp,作为访问堆栈的指针。
为什么要这样呢?为什么几乎每个程序都是的开头能?因为如果我们写过c等函数的时候就应该清楚,程序的开始是以一个主函数main()为开始的,而函数在访问的过程中最重要的事情就是要确保堆栈的平衡,而在win32的环境下保持平衡的办法是这样的:
1.让ebp保存esp的值;
2.在结束的时候调用
mov esp,ebp
pop ebp
retn
或者是
leave
retn
两个形式是一个意思。
这样做的好处是不用考虑esp等于多少,push了多少次,要pop多少次了,因为我们知道ebp里面放的是开始时候的esp值。
2.推广的esp定律
在寻找oep的时候,往往下断hw esp-4不成功,除了壳代码将硬件断点删除了以外,很可能的情况就是因为壳代码在运行到oep的时候他的esp已经不再是在ep时候的esp(12ffc4)了,这样我们下断当然是不成功的。
那么如何找到在壳到达oep的时候的堆栈的值将是关键。
在这里我们应用的关键是
push ebp
mov ebp,esp----》关键是这句
我来解释一下,当程序到达oep的时候push ebp这句对于esp的值来说就是esp-4,然后是esp-4赋给了ebp,而做为保存esp值作用的ebp寄存器在这个“最上层的程序”中的值将始终不会改变。虽然他可能在进入子call里面以后会暂时的改变(用于子程序的堆栈平衡)但是在退出了以后依*pop ebp这一句将还原原来的ebp的值。
以这句做为突破口,就是说只要我们能断在“最上层的程序”中,就能通过观察ebp的值得到壳在jmp到oep的时候的esp的值了。
3.实战
来看看pespin1.1的壳,在pespin1.0的壳中,我们使用hw 12ffc0能很容易的找到stolen code的地方,但是到pespin1.1的时候,我们就不行了。用hw 12ffc0根本断不下来。
现在我们就使用这个推广的esp定律,载入程序后来到最后的一个异常
0040ed85 2bdb sub ebx,ebx //停在这里
0040ed87 64:8f03 pop dword ptr fs:[ebx]
0040ed8a 58 pop eax
0040ed8b 5d pop ebp
0040ed8c 2bff sub edi,edi
0040ed8e eb 01 jmp short pespin1_.0040ed91
0040ed90 c466 81 les esp,fword ptr ds:[esi-7f]
我用使用内存断点办法来到foep处
004010d3 0000 add byte ptr ds:[eax],al
004010d5 0000 add byte ptr ds:[eax],al
004010d7 0000 add byte ptr ds:[eax],al
004010d9 0000 add byte ptr ds:[eax],al
004010db 0000 add byte ptr ds:[eax],al
004010dd 0000 add byte ptr ds:[eax],al
004010df 75 1b jnz short pespin1_.004010fc //这里是foep
004010e1 56 push esi
004010e2 ff15 99f44000 call dword ptr ds:[40f499]
004010e8 8bf0 mov esi,eax
004010ea 8a00 mov al,byte ptr ds:[eax]
好了,这里就是“最上层的程序”的地方了,看看寄存器
eax 00141e22
ecx 0040c708 pespin1_.0040c708
edx 0040c708 pespin1_.0040c708
ebx 0040c708 pespin1_.0040c708
esp 0012f978
ebp 0012f9c0 //注意这里
esi 00141ee0
edi 0040e5cd pespin1_.0040e5cd
eip 004010df pespin1_.004010df
看到了吧,ebp=0012f9c0,我们来想象一下这个值是怎么得到的。
首先肯定是通过mov esp,ebp这一句,也就是说esp这时是0012f9c0的,然而上面还有一句push ebp也就是说esp在到达oep的时候应该是0012f9c4的。好了得到这个结论我们就能很快的找到stolen code的所在了。
重来停在最后的异常
0040ed85 2bdb sub ebx,ebx //停在这里
0040ed87 64:8f03 pop dword ptr fs:[ebx]
0040ed8a 58 pop eax
0040ed8b 5d pop ebp
0040ed8c 2bff sub edi,edi
0040ed8e eb 01 jmp short pespin1_.0040ed91
0040ed90 c466 81 les esp,fword ptr ds:[esi-7f]
然后下断hw 0012f9c0 ,f9运行,来到这里
0040d8fb 61 popad
0040d8fc 55 push ebp
0040d8fd eb 01 jmp short pespin1_.0040d900 //停在这里
0040d8ff 318b eceb01ac xor dword ptr ds:[ebx ac01ebec],ecx
0040d905 83ec 44 sub esp,44
0040d908 eb 01 jmp short pespin1_.0040d90b
0040d90a 72 56 jb short pespin1_.0040d962
0040d90c eb 01 jmp short pespin1_.0040d90f
0040d90e 95 xchg eax,ebp
0040d90f ff15 6cf34000 call dword ptr ds:[40f36c]
0040d915 eb 01 jmp short pespin1_.0040d918
于是就很快的找到了stolen code的所在了。
4.总结
上面的这个办法大概可以总结以下的步骤:
(1).直接或间接的断在“最上层的程序”的地方。
(2).得到“最上层的程序”的ebp的值。
(3).利用程序初始化的两个固定语句找到壳jmp到oep的堆栈值。这个办法有很大的局限性,因为只有vc和delphi程序使用这个初始化的开头。
但是找到“最上层的程序”的办法除了内存断点还有很多办法,例如对于vc来说使用 bp exitprocess也是一个很好的断点,可以直接得到ebp的数值。
5.后话
原来这个办法有很强的前提条件,不是一个很具普遍性的办法,我原来也不想单独的提出来,但是对于jney2兄弟的anti-esp定律来说这个办法却是一个解决之道。
当然还有更多的办法,在这里我只想说很多事情有矛就有盾,没有什么办法是一定没有漏洞的,只是希望这篇文章给大家阔宽思路,起到抛砖引玉的作用。