• [原理分析]Linux下的栈溢出案例分析-GDB调试操练[1]


    摘要:
     本文主要演示linux平台下的栈溢出,首先依据理论对演示样例代码进行溢出攻击;结果是溢出攻击成立,可是与设想的有区别。然后採用GDB调试工具对发生的意外。进行深入的分析。

    測试的平台:
    1.  ubuntu 9;   gcc 4.4.1;   Gdb 7.0-ubuntu
    2.  ubuntu系统安装在virtual box 3.2.8虚拟机上。

    演示样例代码例如以下:

    #include<string.h>
    void overflow(char* arg)
    {
    	char buf[12];
    	strcpy(buf, arg);
    }
    
    int main(int argc, char *argv[])
    {
    	if(argc > 1)
    		overflow(argv[1]);
    	return 0;
    }

    假设依照一般的方式编译:
    gcc –o stackoverflow stackoverflow.c
    linux系统可以探測到程序中的stack  overflow,从而终止程序,例如以下图所看到的:

    那有没有办法让系统不探測到stack overflow。此处能够在编译时,禁用堆栈保护,详细命令例如以下:
    gcc –fno-stack-protector –o stackoverflow stackoverflow.c
    然后採用gdb调试stackoverflow,

    这里的输出跟设想的存在非常大的区别,由于设想中的函数栈例如以下:

    前面的12个字节填充buf,然后接下来的4个字节填充ebp,最后的4个字节填充RET地址。那么照理说,这里的eip应该是0x65656565,那为什么此处是0x61616161,刚好是aaaa的值呢?

    依据单步调试的结果。发现eip变为0x61616161是在main函数退出后达到的,依照设想应该是在overflow退出时。eip变为0x65656565。

    为什么overflow退出后还能回到main函数?可能的原因:输入的字符串没有覆盖掉ret地址,可是字符串却意外地将main的返回地址给覆盖掉了?

    但就算是覆盖,为什么覆盖的值没有採用e的值,而是採用的a的值?要知道a是在字符串的起始处?这点的确让人奇怪。

    1. 我们还是採用一步步调试的方式来观察问题所在。先看下gcc编译后的反汇编代码:
    使用到的命令:
    set disassembly-flavor intel  //将汇编设定为intel风格;
    disassemble main  //反汇编main函数。

    Main函数:

    1. push ebp
    2. mvo ebp, esp
    3. and esp, 0xfffffff0
    4. sub esp, 0x10
    5. cmp DWORD PTR [ebp+0x8], 0x1
    6. jle  0x804841d <main+31>
    7. mov eax, DWORD PTR [ebp+0xc]
    8. add eax, 0x4
    9. mov eax, DWORD PTR [eax]
    10. mov DWORD PTR [esp], eax
    11. call 0x80483e4 <overflow>
    12. mov eax, 0x0
    13. leave
    14. ret

    然后再来看下。overflow的反汇编代码,命令:disassemble overflow
    Overflow函数:

    1. push ebp
    2. mov ebp, esp
    3. sub esp, 0x28
    4. mov  eax, DWORD PTR[ebp+0x8]
    5. mov  DWORD PTR[esp+0x4], eax
    6. lea  eax, [ebp-0x14]
    7. mov  DWORD  PTR [esp], eax
    8. call  0x804831c <strcpy@plt>
    9. leave
    10. ret


    我们单步调试上述的指令,关注当中esp值的变化。

    总图例如以下,后面是对当中每一步的分析:

    在完毕main.1后,命令p $esp后,esp的值变为:Esp = 0xbffff438
    Main.3后。esp的值变为0xbffff430,预计是用于对齐;
    Main.4后,esp的值为0xbffff420;
    Main.7-10,这里主要将argv[]的arg[1]字符串的首地址取出来。而且将其放置在esp中,此时esp的值为0xbffff420;

    运行overflow.1后。esp的值变为:0xbffff41c,当中存放着main的下一条语句的地址。
    通过命令:x $esp能够看到overflow返回的地址:
    0xbffff41c   0x0804841d
    Overflow.1运行后,esp的值变为0xbffff418,用于存放ebp的值;
    Overflow.3运行后,esp的值变为0xbffff3f0,
    Overflow.5-7运行后,将字符串的地址放置在3f0+0x4地址处,然后再将暂时字符串也即buf的首地址[ebp-0x14]放置在3f0地址处(当前的esp指向处);

    运行完overflow.8后,我们查看buf起始处后,发现的确完毕了内容的赋值,命令例如以下:
    X 地址。

    运行完leave指令后,发现esp的值变为:0xbffff41c。刚好指向的返回main的地址;然后再运行ret指令后。发现esp变为0xbffff420。然后,程序跳回到main函数;

    然后再运行main函数中的leave指令,依照设想中leave指令运行后。esp的值应该变为43c,指向main的返回地址;可是实际我们运行后,esp的值变为0xbffff404,当中的内容刚好是0x61616161。也就是aaaa的值;这里我们測试时使用的參数的头四个字符刚好是aaaa;

    到这里为止,就明确整个问题的症结:
     Main函数中调用leave指令时。esp的值并没有调整到位。本来应该指向43c(前面的地址忽略)的,此处却指向的404?
     那么为什么会产生这种状况?照理说这是编译器该干的事。为什么此处编译器没有尽责呢?

    奇怪的是:
     不发生栈溢出时,也即我们输入的字符串长度不超过12时,main中的leave指令运行后,程序能够正常的返回到43c的位置,顺利退出。
     那这里的问题就非常奇怪了:栈溢出是发生在overflow中。程序能够从overflow中顺利返回到main中,然后main的leave指令就不正常了。假设overflow中没有栈溢出,程序也顺利返回到main函数,然后main的leave指令能够正常工作。

    上述的问题的症结在于搞清楚leave指令本身;猜想其可能会依赖某些寄存器,或者依赖特定的存储单元来达到恢复目的。

    首先看看能不能stepi进入leave;答案是leave是单条指令。不是一个处理函数;
    所以我们试试能不能从寄存器上发现点什么。发现事实上对比vistual studio中的操作,
    Leave指令应该等效为:
     Mov  esp, ebp
     Pop  ebp

    之前关注的都是esp,那依照上面的等效的话,接下来应该关注ebp寄存器的变化。

    所以,接下来要做的1. 首先验证上述的的等效成立,2. 要盯着ebp在运行过程中的变化。

    对于问题1的验证,我们偷个懒,直接baidu之,发

  • 相关阅读:
    IoC模式
    开发流程与模式
    YbSoftwareFactory
    简单的FTP文件安全识别方案
    C# 通过探测邮件服务器进行Email地址有效性检验
    YbRapidSolution for WinForm 插件生成项目总体架构介绍
    lucene.net已经从孵化器毕业
    XSql 源码开放
    TCP 套接字函数和入门级TCP编程
    C#中泛型学习笔记
  • 原文地址:https://www.cnblogs.com/wgwyanfs/p/7214683.html
Copyright © 2020-2023  润新知