逆向——C语言的汇编表示之堆栈图
C语言的汇编表示之堆栈图
写在前面:push一个数据到stack后,esp指向的是栈顶push的最新数据。
前天把C语言的汇编表示给看完了,但却没有怎么自己操作过,不过看懂了永远不能代表学会了,今天的话就从中挑选一个简单例子完整的再操作一遍,加深自己对它的理解!(之所以没怎么操作是因为VC6.0环境总是出问题!!!)
1. 在VC6.0创建一个文件
1
|
|
123456789101112#include<stdio.h>
#include<windows.h>
int
plus(
int
x,
int
y)
{
return
x + y;
};
int
main()
{
int
a = 10;
plus(2, 3);
return
0;
}
Cral+s 保存
F7 bluid(构建exe)
查看反汇编:
F10 单步步过
F11 单步步入
总结:
1.C语言中传参方式 是堆栈传参 参数的调用从右到左依次push
2.函数名就是编译器给内存地址的别名
画一下 堆栈变化图
首先我们调出 Register(寄存器)窗口,与Memory窗口
1.Alt+5 Register(寄存器)窗口
2.Alt+6 Memory 窗口
看栈顶 esp 地址为0012FF30
看栈底 ebp 地址为0012FF80
F10执行完push 3
ESP就会减4个字节 变成0012FF2c
同理 再 F10执行完 push 2后 猜测 ESP会变成 0012FF28
所以 堆栈图可以这么画
然后下一步执行call指令 F11进去
这时
紧跟的是 02 03
call (0040100a)指令本质
1.jmp 0040100a //即EIP为0040100A 下一次要执行的地址
2.把下一行地址压到堆栈去 /即栈顶
由2得3 esp的值减4 //因为下一行地址压到堆栈
所以 查看一下 栈顶0010FF24内容 就是call指令的下一行地址00 40 B7 83 (小端存储)
此时观察 反汇编页面
这时VC6 给我们生成的。
调用函数时VC6会给我们生成 一个jmp无条件跳转到 函数部分、
jmp 00401010实质:
mov eip,00401010//即 下一行执行 00401010指令
所以 执行到这步,目前反汇编该执行 00401010了
push ebp //即 把 ebp的地址压入栈顶,同时esp-4
mov ebp,esp //即把esp的地址赋给ebp的地址,此时ebp就和esp在同一地址了
然后
sub esp,40h//目的是给 提升堆栈
堆栈一个地址占4个字节 40h的话就是40h/4=10h个 地址 换成10进制就是16个地址
即esp被向上提升了16个地址,
堆栈图如下:
这个比较简单了,连续把寄存器ebx,esi,edi的地址压入栈
目的是:保存原来的寄存器值 以便最后的恢复,因为你在过程中会发生变化
执行完堆栈图为
而绿色的部分就是我们常提到的 缓冲区
为了程序使用完 正常运行 编译器会在没用到的缓冲区 填充CC即我们调试时的断点
缓冲区溢出 就是
当前的函数在执行的过程中需要内存,需要用内存 就提升堆栈 自己给自己分配内存,
对缓冲区做手脚,通过一些方式 如改变函数返回地址 来达到控制程序的目的;
首先
lea edi,[ebp-40h] 的含义就是 把ebp现在的地址减40h 后的地址赋给edi
stos dword PTR [edi]的含义是将eax的值存储到[edi]指定的地址
rep指令:
按计数寄存器(ecx)指定的次数重复执行执行字符串指令
如 rep stosd(rep stos dword PTR [edi]的简写)
所以上面的几行指令就是给没用到的缓冲区填充 CC
看下图
红色1: ebp+4 地址里存放的是call指令的下一行地址
红色2(未画) ebp+8 地址里存放的是 第一个实参 2
红色2(未画) ebp+0C 地址里存放的是 第二个实参 3
mov eax,dword PTR [ebp+8] 含义是 把esp+8 地址里的值 赋给eax
add eax,dword PTR [ebp+12] 含义是 把esp+8 地址里的值 加上eax 存在eax
此过程 堆栈以及缓冲区 没有改变只是 把 2+3的结果存在了eax中了
此时 看起来 我们似乎觉得 程序已经结束了
对的,我们希望的过程已经实现了,
但还要注意 堆栈平衡 原来是什么样子
记得用完后 把它恢复过来
继续分析
我们把edi esi ebx pop出去 ,同时esp 一共减去减12即0C
这是 esp地址又会变成0012FEE0
F10 单步 验证下
正确
还有一点点就要分析完毕了
mov esp,ebp //即把ebp的地址赋给esp
此时esp又和ebp在同一位置了
然后pop ebp//把0012FF80 pop给ebp地址
实质:把ebp pop最初的ebp地址 同时esp+4
ret的本质 是
mov eip ,esp
add esp,4
所以 会 pop 0040B783给了EIP 同时esp+4
原来的堆栈是黄色部分 现在的话 esp还没有恢复原位置
也就是堆栈还没有平衡
继续看汇编页面
add esp,8
执行后 esp将恢复到原来的地址 从0012FF28 到 0012FF30(原)
有内平 (直接 return ?(?代表16进制数字))
和外平(我记录这个就是外平,return过后,再add esp,?)
此时:堆栈已经平衡
而 再堆栈平衡的状态下
我们完成了 2+3 并把值存在了EAX中
除此之外,我们在还留下了 大量的垃圾(框住的部分)危害是有的,具体的还暂时不是太清楚,继续深入吧!
上面有一个地方出了点小错,
call指令的下一行的地址为0040B788 一下为修正图(还请注意):
堆栈传参
当函数有很多参数的时候,不止8个,那我们使用通用寄存器去传参,明显不够用,所以我们需要使用堆栈帮助我们传递参数。
还是以加法举例,实际场景:
如上图所示实现算术1+2,首先将1、2依次压入堆栈,CALL指令也会将其下一行指令地址压入堆栈,所以堆栈地址[ESP+8]为第一个压入的数据,堆栈地址[ESP+4]为第二个压入的数据。
堆栈平衡
我们知道当执行函数调用CALL指令的时候,会把CALL指令下一条指令的内存地址压入堆栈(ESP值减4);在函数内我们可以随意使用堆栈,比如PUSH指令压入堆栈,使用堆栈传参等等...
我们需要保证,在函数调用结束的时候(即执行RET指令之前,要把ESP栈顶指针的值修改为执行CALL指令压入堆栈或堆栈传参压入堆栈前的那个ESP栈顶指针的值),保证函数运行前与运行后ESP栈顶指针的值不变,这个我们称之为堆栈平衡。
平衡堆栈有两个方法:
1.外平栈:使用ADD指令。
2.内平栈:使用RET指令,例如压入了2个32位(4字节)数据就可以写为RET 8。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· 2 本地部署DeepSeek模型构建本地知识库+联网搜索详细步骤
2022-03-23 通过wmic 无文件攻击的例子
2018-03-23 机器学习中,有哪些特征选择的工程方法?
2018-03-23 python利用决策树进行特征选择
2018-03-23 机器学习 不均衡数据的处理方法
2018-03-23 python dns server开源列表 TODO
2017-03-23 lucene Index Store TermVector 说明
2017-03-23 Lucene in action 笔记 term vector——针对特定field建立的词频向量空间,不存!不会!影响搜索,其作用是告诉我们搜索结果是“如何”匹配的,用以提供高亮、计算相似度,在VSM模型中评分计算