堆栈基础(一) linux篇

之前堆栈基础(一)里面主要是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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,475评论 4 372
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,744评论 2 307
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,101评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,732评论 0 221
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,141评论 3 297
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,049评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,188评论 2 320
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,965评论 0 213
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,716评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,867评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,341评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,663评论 3 263
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,376评论 3 244
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,200评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,990评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,179评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,979评论 2 279