堆栈基础(一)

本文稿费80软妹币

砸个广告:各位在网络安全方面有新创作的小伙伴,快将你们的心得砸过来吧~

文章以word形式发至邮箱:

minwei.wang@dbappsecurity.com.cn

有偿投稿,记得留下你的姓名联系方式哦~

-START-

新手入门pwn之栈溢出系列,先学习堆栈的基础,函数调用栈这些。

运行时栈

运行时栈(runtime stack)是有cpu内部硬件直接支持的,也是实现过程调用和过程返回机制的基本组成部分。在大多数时我们称运行时栈为:堆栈

这里的堆栈和数据结构里的栈抽象数据类型是不同的,堆栈即运行时栈在系统层上(由硬件直接实现) 处理子过程调用;堆栈抽象数据类型通常用于实现依赖后进先出操作的算法,一般使用高级语言如c++/java等编写。

栈方向

https://www.zhihu.com/question/36103513

https://www.cnblogs.com/xkfz007/archive/2012/06/22/2558935.html

栈方向跟体系结构有关系,x86是向下增长,x86硬件直接支持的栈确实是“向下增长”的,由高地址向低地址增长:push指令导致sp自减一个slot,pop指令导致sp自增一个slot。其它硬件有其它硬件的情况。arm没有固定,但一般操作系统会选择向下增长

在内存管理中,与栈对应是堆。对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方式是向下的,是向着内存地址减小的方向增长。在内存中,“堆”和“栈”共用全部的自由空间,只不过各自的起始地址和增长方向不同,它们之间并没有一个固定的界限,如果在运行时,“堆”和 “栈”增长到发生了相互覆盖时,称为“栈堆冲突”,系统肯定垮台。

三个寄存器

函数状态主要涉及三个寄存器

rsp/esp/sp:栈指针寄存器,其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶 rbp/ebp/bp:基址指针寄存器,其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部函数栈帧 rip/eip/ip:指令寄存器, 其内存放着一个指针,该指针永远指向下一条待执行的指令地址。

push / pop操作

运行时栈是有cpu直接管理的内存数组, 它使用连个寄存器,ss和esp(32是esp,16位是sp,64位是rsp), ss寄存器存放的段地址,esp是堆栈指针寄存器,指向最后压入到堆栈上的数据。我们很少直接操纵esp的值,而是由call,ret,push和pop指令间接修改的。

堆栈段的操作步骤——

压栈(入栈)push sth-> [esp]=sth,esp=esp-4

弹栈(出栈)pop sth-> sth=[esp],esp=esp+4

http://bestwing.me/2017/03/18/stack-overflow-one/

函数调用方式和栈帧

参考:https://www.zhihu.com/question/22444939

如下代码:

#include<stdio.h> void func_A(int arg_A1, int arg_A2);void func_B(int arg_B1, int arg_B2,int arg_B3);int main(int argc, char *argv[], char **envp){ printf("main start"); int arg_A1 = 1; int arg_A2 = 2; func_A(arg_A1, arg_A2); }void func_A(int arg_A1, int arg_A2){ int arg_A3 = 3; func_B(arg_A1,arg_A2,arg_A3); }void func_B(int arg_B1, int arg_B2,int arg_B3){ printf("%d,%d,%d\n",arg_B1,arg_B2,arg_B3); }

函数调用大致包括以下几个步骤:

参数入栈:将参数从右向左依次压入系统栈中 返回地址入栈:将当前代码区调用指令的下一条指令地址压入栈中,供函数返回时继续执行 代码区跳转:处理器从当前代码区跳转到被调用函数的入口处 栈帧调整:具体包括保存当前栈帧状态值,已备后面恢复本栈帧时使用(EBP入栈) 将当前栈帧切换到新栈帧。(将ESP值装入EBP,更新栈帧底部) 局部变量入栈,esp减小。 恢复的过程就是现弹出栈帧(pop ebp),这样就可以恢复出调用函数的栈帧了,此时栈顶会指向返回地址,再将返回地址弹出(pop eip),并保存到eip中,之后就会回到原来调用函数的下一条地址继续运行了。

栈帧的调整过程:

在main函数调用func_A的时候,首先在自己的栈帧中压入函数返回地址;

然后为func_A创建新栈帧并压入系统栈,在func_A调用func_B的时候,同样先在自己的栈帧中压入函数返回地址;

然后为func_B创建新栈帧并压入系统栈,在func_B返回时,func_B的栈帧被弹出系统栈,func_A栈帧中的返回地址被“露”在栈顶,此时处理器按照这个返回地址重新跳到func_A代码区中执行在func_A返回时,func_A的栈帧被弹出系统栈,main函数栈帧中的返回地址被“露”在栈顶,此时处理器按照这个返回地址跳到main函数代码区中执行在实际运行中;

main函数并不是第一个被调用的函数,程序被装入内存前还有一些其他操作,上图只是栈在函数调用过程中所起作用的示意图。

函数调用图如下:

栈帧调整图如下:

函数调用时栈内的数据从高地址到低地址分别是函数参数入栈(从右到左),返回地址入栈,ebp入栈,esp分配填充地址, 局部变量mov入栈。

函数调用的汇编代码为:

main函数

push ebp //保留旧栈帧

mov ebp,esp //分配新的栈帧 sub esp,4C //设置填充空间大小为4C push ebx //寄存器入栈 push esi push edi lea edi,dword ptr ss:[ebp-4C] 将栈顶指针地址给edi mov ecx,13 //计数器设置为13 mov eax,CCCCCCCC //填充数据 rep stosd dword ptr es:[edi],eax //重复填充13次0xccccccc到[ebp-4C]~[ebp]到这个空间 mov dword ptr ss:[ebp-4],pwn3.42201C //pwn3.c:6, [ebp-4]:"test programmer", 42201C:"test programmer" //局部变量入栈(并不是push的) mov dword ptr ss:[ebp-8],1 //pwn3.c:7 局部变量入栈 mov dword ptr ss:[ebp-C],2 //pwn3.c:8 局部变量入栈 mov eax,dword ptr ss:[ebp-C] //pwn3.c:9 push eax //右边第一个参数2入栈 mov ecx,dword ptr ss:[ebp-8] push ecx //右边第二个参数1入栈 call <pwn3.ILT+5(_func_A)> //函数调用

参数入栈的方式:取决于调用约定,一般情况下:

X86 从右向左入栈,X64 优先寄存器,参数过多时才入栈

main函数的栈帧:

ebp:0019ff40

我们看到函数调用后首先是函数参数入栈,函数调用后会发生什么呢?我们来看一看:

push ebp 保存main函数的栈帧ebp mov ebp,esp //设置funcA的栈帧ebp sub esp,44 //给fucA栈帧分配填充字节空间44 push ebx //寄存器入栈 push esi push edi lea edi,dword ptr ss:[ebp-44] mov ecx,11 mov eax,CCCCCCCC rep stosd dword ptr es:[edi],eax //填充同上 mov dword ptr ss:[ebp-4],3 //局部变量mov入栈 mov eax,dword ptr ss:[ebp-4] push eax //函数参数push入栈 mov ecx,dword ptr ss:[ebp+C] push ecx mov edx,dword ptr ss:[ebp+8] push edx edx:&"ALLUSERSPROFILE=C:\\ProgramData" call <pwn3.ILT+10(_func_B)> //调用fucB add esp,C pop edi pwn3.c:14 pop esi pop ebx add esp,44 cmp ebp,esp call <pwn3._chkesp> mov esp,ebp pop ebp ret

func_A的栈帧: ebp=0019FED8

func_B的汇编代码:

push ebp mov ebp,esp sub esp,40 push ebx push esi push edi lea edi,dword ptr ss:[ebp-40] mov ecx,10 mov eax,CCCCCCCC rep stosd dword ptr es:[edi],eax mov eax,dword ptr ss:[ebp+10] push eax //函数参数入栈 mov ecx,dword ptr ss:[ebp+C] push ecx mov edx,dword ptr ss:[ebp+8] push edx push pwn3.422030 422030:"%d,%d,%d\n" call <pwn3.printf> add esp,10 pop edi pop esi pop ebx add esp,40 cmp ebp,esp call <pwn3._chkesp> mov esp,ebp ret

将这段代码的所有汇编一步一步跟踪了解清楚了后,对堆栈算是大概了解了,下面就是入门栈溢出了,之后学到栈溢出再来更新。

-END-

原文发布于微信公众号 - 安恒网络空间安全讲武堂(cyberslab)

原文发表时间:2018-06-04

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏张善友的专栏

Silverlight + Model-View-ViewModel (MVVM)

     早在2005年,John Gossman写了一篇关于Model-View-ViewModel模式的博文,这种模式被他所在的微软的项目组用来创建Expr...

3038
来自专栏魂祭心

原 canvas绘制clock

4294
来自专栏ASP.NETCore

ASP.NET Core 整合Autofac和Castle实现自动AOP拦截

除了ASP.NETCore自带的IOC容器外,我们还可以使用其他成熟的DI框架,如Autofac,StructureMap等(笔者只用过Unity,Ninjec...

694
来自专栏张善友的专栏

LINQ via C# 系列文章

LINQ via C# Recently I am giving a series of talk on LINQ. the name “LINQ via C...

2675
来自专栏张善友的专栏

Miguel de Icaza 细说 Mix 07大会上的Silverlight和DLR

Mono之父Miguel de Icaza 详细报道微软Mix 07大会上的Silverlight和DLR ,上面还谈到了Mono and Silverligh...

2737
来自专栏Ceph对象存储方案

Luminous版本PG 分布调优

Luminous版本开始新增的balancer模块在PG分布优化方面效果非常明显,操作也非常简便,强烈推荐各位在集群上线之前进行这一操作,能够极大的提升整个集群...

3265
来自专栏我和未来有约会

Kit 3D 更新

Kit3D is a 3D graphics engine written for Microsoft Silverlight. Kit3D was inita...

2626
来自专栏菩提树下的杨过

Flash/Flex学习笔记(23):运动学原理

先写一个公用的小球类Ball: package{ import flash.display.Sprite; //小球 类 public class B...

25410
来自专栏我和未来有约会

Silverlight第三方控件专题

这里我收集整理了目前网上silverlight第三方控件的专题,若果有所遗漏请告知我一下。 名称 简介 截图 telerik 商 RadC...

4075
来自专栏转载gongluck的CSDN博客

cocos2dx 打灰机

#include "GamePlane.h" #include "PlaneSprite.h" #include "BulletNode.h" #include...

5676

扫码关注云+社区