zoukankan      html  css  js  c++  java
  • 了解EBP指针

    在寄存器里面有很多寄存器虽然他们的功能和使用没有任何的区别,但是在长期的编程和使用中,在程序员习惯中已经默认的给每个寄存器赋上了特殊的含义,比如: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定律来说这个办法却是一个解决之道。 

    当然还有更多的办法,在这里我只想说很多事情有矛就有盾,没有什么办法是一定没有漏洞的,只是希望这篇文章给大家阔宽思路,起到抛砖引玉的作用。

    当调用函数时
    原EBP值已经被压栈(位于栈顶),而新的EBP又恰恰指向栈顶。
    此时EBP寄存器就已经处于一个非常重要的地位,该寄存器中存储着栈中的一个地址(原EBP入栈后的栈顶),
    从该地址为基准,向上(栈底方向)能获取返回地址、参数值,向下(栈顶方向)能获取函数局部变量值,
    而该地址处又存储着上一层函数调用时的EBP值!

    总结一个for循环的反汇编结构如下:

    mov <循环变量>,<初值>
    jmp 检查循环条件B
    A: (修改循环变量)
    ...
    ...

    B: cmp <循环变量>,<限制条件>
    jge 跳出循环
    (循环体)
    ...
    ...
    jmp 修改循环变量A

    ;if的模板如下

    ;cmp <各种条件>
    ;各种跳转跳到下一个分支

    ;else if模板

    ;jmp 跳出判断
    ;cmp <条件>
    ;各种跳转跳到下一个分支


    ;switch()的模板如下:

    ;cmp <条件1>
    ;je <case 分支1>
    ;cmp <条件2>
    ;je <case 分支2>
    ;...
    ;...

    ;jmp <default分支>




    Keep it simple!
    作者:N3verL4nd
    知识共享,欢迎转载。
  • 相关阅读:
    poj 2312 Battle City
    poj 2002 Squares
    poj 3641 Pseudoprime numbers
    poj 3580 SuperMemo
    poj 3281 Dining
    poj 3259 Wormholes
    poj 3080 Blue Jeans
    poj 3070 Fibonacci
    poj 2887 Big String
    poj 2631 Roads in the North
  • 原文地址:https://www.cnblogs.com/lgh1992314/p/5834872.html
Copyright © 2011-2022 走看看