美文网首页
堆栈基础(一) linux篇

堆栈基础(一) linux篇

作者: rivir | 来源:发表于2018-06-08 10:37 被阅读109次

    之前堆栈基础(一)里面主要是window下面的堆栈,然而ctf里面大都是linux系统,因此还是很有必要分析一下Linux下的堆栈调用情况.

    下面全篇都将以如下demo为例进行编译调试:

    #include<stdio.h>
    int sum(int i,int j){
        int sum;
        sum = i+j;
        return sum;
    }
    int main(){
        int i,j,k;
        scanf("%d%d",&i,&j);
        k = sum(i,j);
        printf("%d\n",k);
    }
    

    linux下32位和64位是有区别的,我们先来看一下32位的

    ubuntu下面编译32位的程序需要先安装依赖,安装命令为:

    $ sudo apt-get install build-essential module-assistant  
    $ sudo apt-get install gcc-multilib g++-multilib  
    
    $ gcc -m32 -g -o pwn2_32 pwn2.c
    

    gcc 添加-g参数是为了产生调试信息,方便后面gdb调试的时候可以看源码

    我们来看一看objdump -d 命令反汇编出来的汇编指令.

    0000056d <sum>:
     56d:   55                      push   %ebp//保存旧栈帧
     56e:   89 e5                   mov    %esp,%ebp//设置sum的栈帧ebp
     570:   83 ec 10                sub    $0x10,%esp//分配sum0x10h的堆栈大小
     573:   e8 86 00 00 00          call   5fe <__x86.get_pc_thunk.ax>
     578:   05 88 1a 00 00          add    $0x1a88,%eax
     57d:   8b 55 08                mov    0x8(%ebp),%edx
     580:   8b 45 0c                mov    0xc(%ebp),%eax
     583:   01 d0                   add    %edx,%eax
     585:   89 45 fc                mov    %eax,-0x4(%ebp)
     588:   8b 45 fc                mov    -0x4(%ebp),%eax
     58b:   c9                      leave  
     58c:   c3                      ret    
    
    0000058d <main>:
     58d:   8d 4c 24 04             lea    0x4(%esp),%ecx
     591:   83 e4 f0                and    $0xfffffff0,%esp
     594:   ff 71 fc                pushl  -0x4(%ecx)
     597:   55                      push   %ebp
     598:   89 e5                   mov    %esp,%ebp
     59a:   53                      push   %ebx
     59b:   51                      push   %ecx
     59c:   83 ec 10                sub    $0x10,%esp
     59f:   e8 cc fe ff ff          call   470 <__x86.get_pc_thunk.bx>
     5a4:   81 c3 5c 1a 00 00       add    $0x1a5c,%ebx
     5aa:   83 ec 04                sub    $0x4,%esp
     5ad:   8d 45 ec                lea    -0x14(%ebp),%eax
     5b0:   50                      push   %eax
     5b1:   8d 45 f0                lea    -0x10(%ebp),%eax
     5b4:   50                      push   %eax
     5b5:   8d 83 90 e6 ff ff       lea    -0x1970(%ebx),%eax
     5bb:   50                      push   %eax
     5bc:   e8 4f fe ff ff          call   410 <__isoc99_scanf@plt>
     5c1:   83 c4 10                add    $0x10,%esp
     5c4:   8b 55 ec                mov    -0x14(%ebp),%edx
     5c7:   8b 45 f0                mov    -0x10(%ebp),%eax
     5ca:   83 ec 08                sub    $0x8,%esp
     5cd:   52                      push   %edx
     5ce:   50                      push   %eax
     5cf:   e8 99 ff ff ff          call   56d <sum>
     5d4:   83 c4 10                add    $0x10,%esp
     5d7:   89 45 f4                mov    %eax,-0xc(%ebp)
     5da:   83 ec 08                sub    $0x8,%esp
     5dd:   ff 75 f4                pushl  -0xc(%ebp)
     5e0:   8d 83 95 e6 ff ff       lea    -0x196b(%ebx),%eax
     5e6:   50                      push   %eax
     5e7:   e8 04 fe ff ff          call   3f0 <printf@plt>
     5ec:   83 c4 10                add    $0x10,%esp
     5ef:   b8 00 00 00 00          mov    $0x0,%eax
     5f4:   8d 65 f8                lea    -0x8(%ebp),%esp
     5f7:   59                      pop    %ecx
     5f8:   5b                      pop    %ebx
     5f9:   5d                      pop    %ebp
     5fa:   8d 61 fc                lea    -0x4(%ecx),%esp
     5fd:   c3                      ret    
    

    main函数前面有一段很有意思;

    0000058d <main>:
     58d:   8d 4c 24 04             lea    0x4(%esp),%ecx
     591:   83 e4 f0                and    $0xfffffff0,%esp
     594:   ff 71 fc                pushl  -0x4(%ecx)
    

    粘上在http://stackoverflow.com/questions/4228261/understanding-the-purpose-of-some-assembly-statements 里面的回答:

    This code makes sure that the stack is aligned to 16 bytes. After this operation esp will be less than or equal to what it was before this operation, so the stack may grow, which protects anything that might already be on the stack. This is sometimes done in main just in case the function is called with an unaligned stack, which can cause things to be really slow (16 byte is a cache line width on x86, I think, though 4 byte alignment is what is really important here). If main has a unaligned stack the rest of the program will too.

    main函数调用sum函数的入栈情况:

     5c4:   8b 55 ec                mov    -0x14(%ebp),%edx
     5c7:   8b 45 f0                mov    -0x10(%ebp),%eax
     5ca:   83 ec 08                sub    $0x8,%esp
     5cd:   52                      push   %edx
     5ce:   50                      push   %eax
     5cf:   e8 99 ff ff ff          call   56d <sum>
    

    和window下的入栈方式差不多,现实mov方式局部变量入栈,然后是push 参数入栈。

    image.png

    我们再来分析一下sum函数,比较简单

    0000056d <sum>:
     56d:   55                      push   %ebp//保存旧栈帧
     56e:   89 e5                   mov    %esp,%ebp//设置sum的栈帧ebp
     570:   83 ec 10                sub    $0x10,%esp//分配sum0x10h的堆栈大小
     573:   e8 86 00 00 00          call   5fe <__x86.get_pc_thunk.ax>
     578:   05 88 1a 00 00          add    $0x1a88,%eax
     57d:   8b 55 08                mov    0x8(%ebp),%edx
     580:   8b 45 0c                mov    0xc(%ebp),%eax
     583:   01 d0                   add    %edx,%eax
     585:   89 45 fc                mov    %eax,-0x4(%ebp)
     588:   8b 45 fc                mov    -0x4(%ebp),%eax
     58b:   c9                      leave  
     58c:   c3                      ret  
    

    我们看看一下主要的指令

    mov 0x8(%ebp),%edx
    mov 0xc(%ebp),%eax
    add %edx,%eax
    mov %eax,-0x4(%ebp)
    

    我们知道ebp的高八个字节是返回地址,0x8(%ebp)就代表最后入栈的参数(2),,0xc(%ebp)就是最先入栈的参数(3),就是i

    然后相加后再赋值给-0x4(%ebp) 就是局部参数sum入栈的过程了。

    image.png

    64位elf

    00000000004005d6 <_Z3sumii>:
      4005d6:   55                      push   %rbp
      4005d7:   48 89 e5                mov    %rsp,%rbp
      4005da:   89 7d ec                mov    %edi,-0x14(%rbp)
      4005dd:   89 75 e8                mov    %esi,-0x18(%rbp)
      4005e0:   8b 55 ec                mov    -0x14(%rbp),%edx
      4005e3:   8b 45 e8                mov    -0x18(%rbp),%eax
      4005e6:   01 d0                   add    %edx,%eax
      4005e8:   89 45 fc                mov    %eax,-0x4(%rbp)
      4005eb:   8b 45 fc                mov    -0x4(%rbp),%eax
      4005ee:   5d                      pop    %rbp
      4005ef:   c3                      retq   
    
    00000000004005f0 <main>:
      4005f0:   55                      push   %rbp
      4005f1:   48 89 e5                mov    %rsp,%rbp
      4005f4:   48 83 ec 10             sub    $0x10,%rsp
      4005f8:   48 8d 55 f4             lea    -0xc(%rbp),%rdx
      4005fc:   48 8d 45 f8             lea    -0x8(%rbp),%rax
      400600:   48 89 c6                mov    %rax,%rsi
      400603:   bf c4 06 40 00          mov    $0x4006c4,%edi
      400608:   b8 00 00 00 00          mov    $0x0,%eax
      40060d:   e8 ae fe ff ff          callq  4004c0 <scanf@plt>
      400612:   8b 55 f4                mov    -0xc(%rbp),%edx
      400615:   8b 45 f8                mov    -0x8(%rbp),%eax
      400618:   89 d6                   mov    %edx,%esi
      40061a:   89 c7                   mov    %eax,%edi
      40061c:   e8 b5 ff ff ff          callq  4005d6 <_Z3sumii>
      400621:   89 45 fc                mov    %eax,-0x4(%rbp)
      400624:   8b 45 fc                mov    -0x4(%rbp),%eax
      400627:   89 c6                   mov    %eax,%esi
      400629:   bf c9 06 40 00          mov    $0x4006c9,%edi
      40062e:   b8 00 00 00 00          mov    $0x0,%eax
      400633:   e8 78 fe ff ff          callq  4004b0 <printf@plt>
      400638:   b8 00 00 00 00          mov    $0x0,%eax
      40063d:   c9                      leaveq 
      40063e:   c3                      retq   
      40063f:   90                      nop
    

    64位和32位很直观的不同就是sum的函数名改变了,另外,main函数更短了,寄存器名都变成rbp,rsp了

    我们简单的看一下main函数

      4005f0:   55                      push   %rbp
      4005f1:   48 89 e5                mov    %rsp,%rbp
      4005f4:   48 83 ec 10             sub    $0x10,%rsp
      4005f8:   48 8d 55 f4             lea    -0xc(%rbp),%rdx
      4005fc:   48 8d 45 f8             lea    -0x8(%rbp),%rax
      400600:   48 89 c6                mov    %rax,%rsi
      400603:   bf c4 06 40 00          mov    $0x4006c4,%edi
    

    64位在main函数这里并没有像32位一样有栈位数检测的指令,直接就开始栈帧调整

    就下来两个lea指令有点奇怪,而且还用到了两个很少见的rsi和edi,接下来我们会介绍一下这两个寄存器有什么作用

    我们继续来看sum函数的调用指令

      400612:   8b 55 f4                mov    -0xc(%rbp),%edx //取出参数2
      400615:   8b 45 f8                mov    -0x8(%rbp),%eax//取出参数1
      400618:   89 d6                   mov    %edx,%esi//右边第一个参数入栈
      40061a:   89 c7                   mov    %eax,%edi//右边第二个参数入栈
      40061c:   e8 b5 ff ff ff          callq  4005d6 <_Z3sumii>//调用sum函数
    

    这里发现sum的两个参数i,j入栈的方式是采用edi,esi来入栈的,而edi,esi通过lea传地址指令大概是在栈顶的位置。

    image.png

    我们来看一下sum函数的栈帧。

    image.png

    linux 64位和window以及linux 32位的有很大差别,在调试的时候要特别注意区别。

    64位中调用的sum函数并不会抬高栈顶,只在main函数中有抬高栈顶的操作,可能是为了节约堆栈空间?

    64位的堆栈参考: https://zhuanlan.zhihu.com/p/27339191

    相关文章

      网友评论

          本文标题:堆栈基础(一) linux篇

          本文链接:https://www.haomeiwen.com/subject/dovosftx.html