美文网首页
堆栈基础(一) 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