吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2527|回复: 8
收起左侧

[其他转载] 记一次"摸鱼"期间学习的堆栈平衡

[复制链接]
Anekys 发表于 2021-6-2 10:26
本帖最后由 Anekys 于 2021-6-2 10:30 编辑

话不多说先上代码:
代码.png


接下来就是话要多说的环节了



堆栈是一块儿连续的内存区域,通常情况下,由栈顶向栈底压入内容
可以将堆栈抽象的理解为一个放在地上的袋子
在没有东西的时候,袋子是瘪的,袋子的上面与下面是紧挨着的
但是每次压栈都会向袋子里放入一些东西,
袋子里面装了东西就鼓起来了,袋子变高了,也就是栈顶也就向上走了,但是栈底还是在原来的地方
从袋子里面把东西拿出来,袋子也就变瘪了,栈顶也就向下走了




push指令就是向袋子(堆栈)里面放东西,放的东西就是push后面的内容,
也就是说每次push时,会向堆栈压入一个双字,然后栈顶会自动-4

为什么是-4而不是+4呢,因为堆栈是高地址在下,低地址在上,通俗来讲堆栈的内存地址编号数字大的下面,数字小的在上面
至于为什么是4呢,因为"16/4=4"


pop指令刚好与push相反,是从袋子(堆栈)里面拿东西,每次加四


Call指令是用来调用程序段中的子程序的,使用Call的时候回自动记录下当前指令的下一条指令的地址,并push到堆栈中
retn指令学过编程的可以将其理解为return指令,不过这个retn可不是会返回什么结果给其他程序调用,在汇编中,retn命令的作用是返回到调用Call指令时所记录的call指令的下一条指令的位置.


mov 指令和 add指令百度就有很多成熟的讲解,我就不再多做赘述了.


概念讲的差不多了,下面来解析代码:

图中的代码,主要目的是带参数call地址为00598310的"子函数"
提供参数的方法简单粗暴,就是把参数压入堆栈中.
两次push(传参)以后,进入子函数程序段,第一步就push了ebp,
为什么要push ebp呢


注意了,这里就又蹦出来两个概念,我们可以很明显的从代码中看到这个子程序的开头和结尾是相互对应的
push ebp以后把esp的值给了ebp,在子程序retn之前呢又pop了ebp,其实这个pop ebp就是为了将原本的ebp值还回去

这么做的原因就是因为堆栈自身只是一块内存空间,它的结构过于简单,简单到需要人去手动将堆栈划分成某一个程序或者某一个子函数的堆栈空间
esp寄存器存放的的是堆栈的栈顶地址,ebp则是存放的栈底地址,这就印证了为什么ebp叫基址指针寄存器,你知道了最下面的地址是什么,想要找上面的地址,只要用基址向上加就可以了


言归正传,将栈顶的给栈底,就形成了如图所示的情况
堆栈空间.png

这样,原有的下方堆栈空间就一定程度上得到了保护,整个堆栈也就大致的划分成程序堆栈段,以及被esp赋值后的ebp所形成的新的"函数堆栈段"(我自己瞎起的名字)

现在命令继续向下执行,完成这个call的功能,将两个"参数"相加,最后得出的结果赋给eax
首先将第一个参数放到eax中,这里的dword实际上是double word也就是双字的缩写

prt则是临时的类型转换符  学过C语言的,你可以把他同以下代码进行比较来相互理解
[C] 纯文本查看 复制代码
int a;
char b='A';
a=int(b);
这里的a就是eax而b就是那个需要被ptr的内容,int()则充当了ptr
这样是不是好理解多了

ss代表的是堆栈段 (汇编基础知识,段寄存器都有啥)
[]是间接寻址的意思(这里我记忆模糊了,好像是叫间接寻址)
[]中的内容就是基址加上偏移地址(ebp是基址0xc是偏移地址),如果你比较细心的话,就会发现mov和add两条指令一个是0xc一个是0x8也就是说这两个地址相差4  实际上堆栈中每两个地址之间都是相差4的,在进入call之前是连续push两次,所以两个"参数"在堆栈中也是挨着存放的/

这两行主要功能代码结束后,就是前面提到的相互对应的地方了


在完成子程序的功能以后需要将ebp复原,也就是把分割出来的函数堆栈段还给程序堆栈段,我自我感觉这个堆栈有点儿套娃,刚开机什么都不干的时候,就是系统堆栈段,运行程序后会划分出来程序的堆栈段,然后程序运行子函数又划分函数堆栈段


在将函数的堆栈段还回去以后,函数就要执行最后一条指令retn到之前call指令的下一条指令
而为什么retn后面要加上一个0x8呢,这里本文的重点就来了,这就是为了堆栈平衡

在一开始要"传参"给call的时候,我们的做法是将参数push进堆栈,现在需要传参的call已经完成了它的功能,这两个参数已经没有用了,如果此时直接retn的话,这两个值还会留存于堆栈中,白白占用着空间


我们知道,堆栈的空间并不是无限大的,这只是一个需要传两个参数的call,如果每个call都这样的话,程序运行时的可用空间就会越来越小直到没有,最后程序崩溃.(整不好可能不止程序崩溃)


这时候在retn后面加上0x8的作用就展现出来了,加上以后,代表栈顶的esp就会自动加上0x8,这样就将之前两次push留下的空间给空了出来.而在retn后面加上要"释放的堆栈空间的大小"的这种方法,就叫做堆栈平衡里的内平衡.


如果不想在retn后面加上要释放的空间的话,也可以在call后面的指令手动给esp加4而这种在子程序外去平衡堆栈的方法也叫做外平衡

有的人用od试验后可能发现,就算堆栈平衡后esp已经调正确了,但是之前push的两个值却还在堆栈里面
其实这里已经没有什么太大的必要担心了,再下一次push或者call的时候,会自动将垃圾值覆盖掉

写到这里,正文内容已经差不多了,不得不吐槽一下汇编确实是挺抽象的
还是那句话,开贴只是为了记录下所学内容,大佬笑笑就好,
但是万一有人恰好在学这里,然后恰好看到这里的一些内容或者概念想明白了呢
当然如果我的上述内容有问题还请指正

免费评分

参与人数 1吾爱币 +5 热心值 +1 收起 理由
苏紫方璇 + 5 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

Rt1Text 发表于 2022-4-12 18:42
点赞师傅的学习
hiodis 发表于 2022-1-4 09:43
slzslz 发表于 2022-1-2 22:54
 楼主| Anekys 发表于 2021-6-2 13:46

我也感觉文字功底有欠缺,想表达的东西很多,但是到嘴边却不知道怎么说了
还是有待练习啊
wxz561032370 发表于 2021-6-2 12:04
简单。。不易懂
有、 发表于 2021-6-2 11:39
膜拜,学到了;
吃兔子的肉 发表于 2021-6-2 11:23
膜拜大佬,学习了
龍謹 发表于 2021-6-2 10:45
谢谢大佬的教程,太清晰明了了
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-5-9 17:23

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表