吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 13530|回复: 67
收起左侧

[调试逆向] 逆向基础笔记七 堆栈图(重点)

  [复制链接]
lyl610abc 发表于 2021-3-1 00:02
本帖最后由 lyl610abc 于 2021-3-12 16:35 编辑

继续更新个人的学习笔记,
其它笔记传送门
逆向基础笔记一 进制篇
逆向基础笔记二 数据宽度和逻辑运算
逆向基础笔记三 通用寄存器和内存读写
逆向基础笔记四 堆栈篇
逆向基础笔记五 标志寄存器
逆向基础笔记六 汇编跳转和比较指令
逆向基础笔记八 反汇编分析C语言
逆向基础笔记九 C语言内联汇编和调用协定
逆向基础笔记十 汇编寻找C程序入口
逆向基础笔记十一 汇编C语言基本类型
逆向基础笔记十二 汇编 全局和局部 变量
逆向基础笔记十三 汇编C语言类型转换
逆向基础笔记十四 汇编嵌套if else
逆向基础笔记十五 汇编比较三种循环
逆向基础笔记十六 汇编一维数组
逆向基础笔记十七 汇编二维数组 位移 乘法
逆向基础笔记十八 汇编 结构体和内存对齐
逆向基础笔记十九 汇编switch比较if else
逆向基础笔记二十 汇编 指针(一)
逆向基础笔记二十一 汇编 指针(二)
逆向基础笔记二十二 汇编 指针(三)
逆向基础笔记二十三 汇编 指针(四)
逆向基础笔记二十四 汇编 指针(五) 系列完结
这篇笔记是前面笔记的一个阶段性应用,会用到不少前面笔记的知识点,也是逆向学习中极其重要的部分,因此篇幅较长,希望对大家能有所帮助

堆栈图

首先给定一段反汇编代码,分析该段代码的堆栈的变化情况,并绘制出堆栈图

函数调用

00401168  |.  6A 02         push 0x2
0040116A  |.  6A 01         push 0x1
0040116C  |.  E8 99FEFFFF   call HelloWor.0040100A
00401171  |.  83C4 08       add esp,0x8

CALL内部

00401040  /> \55            push ebp
00401041  |.  8BEC          mov ebp,esp
00401043  |.  83EC 40       sub esp,0x40
00401046  |.  53            push ebx
00401047  |.  56            push esi
00401048  |.  57            push edi
00401049  |.  8D7D C0       lea edi,dword ptr ss:[ebp-0x40]
0040104C  |.  B9 10000000   mov ecx,0x10
00401051  |.  B8 CCCCCCCC   mov eax,0xCCCCCCCC
00401056  |.  F3:AB         rep stos dword ptr es:[edi]
00401058  |.  8B45 08       mov eax,dword ptr ss:[ebp+0x8]
0040105B  |.  0345 0C       add eax,dword ptr ss:[ebp+0xC]
0040105E  |.  5F            pop edi                                  ;  HelloWor.00401171
0040105F  |.  5E            pop esi                                  ;  HelloWor.00401171
00401060  |.  5B            pop ebx                                  ;  HelloWor.00401171
00401061  |.  8BE5          mov esp,ebp
00401063  |.  5D            pop ebp                                  ;  HelloWor.00401171
00401064  \.  C3            retn

开始分析

分析流程较为冗长,可能会有些乏味,可以先看最后的流程总结,再来看分析的细节

我们现在开始逐语句分析堆栈的变化情况:

执行前

image-20210228194114427

寄存器状态

image-20210228194206740

堆栈内容

image-20210228194402844

初始堆栈图

我们观察堆栈的情况:

此时ESP:0012FF34        EBP:0012FF80

结合寄存器和堆栈内容绘出简易堆栈图

image-20210228194835221

执行中

压入参数
00401168  |.  6A 02         push 0x2                        

image-20210228195308258

image-20210228195225298

可以看到执行后ESP减少了4=0012FF30        并且0012FF30里的内容为2,这就是所谓的入栈操作

0040116A  |.  6A 01         push 0x1                        

image-20210228195558316

image-20210228195756412

可以看到执行后ESP又减少了4=0012FF2C ,并且0012FF2C里的内容为1

上面的两条push语句是将两个立即数 2和1压入到堆栈中,我们可以画出对应的堆栈图:

image-20210228200135180

CALL指令
0040116C  |.  E8 99FEFFFF   call HelloWor.0040100A

F7单步步入

image-20210228210527980

image-20210228210546996

image-20210228210615310

可以看到CALL之后跳转到了0040100A,并且esp又减少了4=0012FF28

而且我们可以注意到此时堆栈中0012FF28存放的内容是:00401171正好是我们call指令的下一行指令的地址

0040116C  |.  E8 99FEFFFF   call HelloWor.0040100A
00401171  |.  83C4 08       add esp,0x8

所以应证了前面所学的call指令会将要返回的地址压入栈中来保存现场

此时的堆栈图为

image-20210228211140508

接着我们就跳转到了call的内部

CALL内部指令
初始化堆栈

image-20210228211439223

00401040  /> \55            push ebp

ESP变化

image-20210228211546005

堆栈内容

image-20210228211605725

EBP被压入到堆栈中,此时堆栈图为

image-20210228211708008


接着执行

00401041  |.  8BEC          mov ebp,esp

ebp赋值为esp,此时堆栈图为

image-20210228212308060


接着执行

00401043  |.  83EC 40       sub esp,0x40

image-20210228212435133

image-20210228212508587

将esp的值减去0x40=64,我们这里的相差的数据宽度为4即16,64/4=16,因此堆栈图里多了16格(蓝色部分),这种操作常被叫做提升堆栈,此时堆栈图为:

image-20210228212917496

我们可以发现提升完堆栈以后,堆栈的数据有些★意义不明★,这是因为堆栈中存放的是临时的数据,可能是之前使用时没有清理的垃圾数据


接着执行

00401046  |.  53            push ebx
00401047  |.  56            push esi
00401048  |.  57            push edi

将三个通用寄存器压入堆栈,用于保护现场,注意CALL之前和CALL之后,其前后环境要一致,这就是所谓的堆栈平衡

image-20210228213353827

堆栈内容

image-20210228213410658

根据此时的堆栈内容绘制堆栈图

image-20210228221004379


接着执行

00401049  |.  8D7D C0       lea edi,dword ptr ss:[ebp-0x40]

image-20210228220848768

将ebp-40所指向的内存地址赋给edi

前面我们执行了sub esp,0x40 所以这里其实就是将那时esp的地址传给了edi(就是push ebx esi edi)之前的的esp

此时堆栈图并发生没有变化

接着看下一行

0040104C  |.  B9 10000000   mov ecx,0x10
00401051  |.  B8 CCCCCCCC   mov eax,0xCCCCCCCC

分别给ecx和eax赋值,堆栈图依旧没有发生变化

image-20210228222347833

image-20210228222402431

image-20210228223255706


接着看下一行

00401056  |.  F3:AB         rep stos dword ptr es:[edi]

这条语句用到了我们前面所学的逆向基础笔记五 标志寄存器中的内容(如有疑惑可前往查看)

rep的作用是,重复执行 stos dword ptr es:[edi],每次执行都会使ecx-1,直到ecx为0再执行下一条语句

前面赋值ecx为0x10=16,正好对应我们堆栈图中蓝色的格子数,所以将会执行16次

stos dword ptr es:[edi]则是将eax的值赋值给edi所指向的内存地址里的值,并且每执行一次edi都会增加4(D标志位为0所以是增加)

结合前面edi==esp,这里其实是将我们提升堆栈的那部分内存区域初始化

此时的堆栈内容为

image-20210228223550564

很明显地看到原本的垃圾数据被我们初始化为了CCCCCCCC

堆栈图也变成了

image-20210228223651626


实际执行内容

接着看下面的代码

00401058  |.  8B45 08       mov eax,dword ptr ss:[ebp+0x8]
0040105B  |.  0345 0C       add eax,dword ptr ss:[ebp+0xC]

image-20210228224415383

image-20210228224436248

根据堆栈图我们可以很清晰地看出

[ebp+0x8]正是我们call外部push的参数:1

[ebp+0xc]正是我们call外部push的参数:2

这里是将eax赋值为1,然后再给eax+2,最终结果eax=3


还原现场并返回

此时堆栈图依旧没有发生变化,接着看下面的语句

0040105E  |.  5F            pop edi                                  ;  HelloWor.00401171
0040105F  |.  5E            pop esi                                  ;  HelloWor.00401171
00401060  |.  5B            pop ebx                                  ;  HelloWor.00401171

出栈,还原现场,堆栈图

image-20210228224826624


下一条

00401061  |.  8BE5          mov esp,ebp

image-20210228225841386

还原esp,前面mov ebp,esp对应也要还原

此时堆栈图为:

image-20210228225604814


继续看下一条指令

00401063  |.  5D            pop ebp                                  ;  HelloWor.00401171

image-20210228225904289

将ebp出栈,恢复现场,此时的堆栈图为

<img src="https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/image-20210228225933363.png" alt="image-20210228225933363"  />


最后一句

00401064  \.  C3            retn

此时栈顶为

image-20210228230325578

返回,相当于于pop eip

执行后

image-20210228230612505

执行后的堆栈图为

image-20210228231716869

执行返回后

此时返回到了

image-20210228231812185

也就是之前call的下一句指令

00401171  |.  83C4 08       add esp,0x8

image-20210228231947017

此时的堆栈图

image-20210228232141747

我们可以发现此时的ESP和EBP又变回到了原本执行前的状态,(寄存器也一样),这就是所谓的堆栈平衡

总结

通过上面的分析,我们可以得出这段代码所处理的大致流程

可分为三个部分:压入参数、调用CALL、CALL返回后

压入参数

压入参数部分十分简单,就是将调用CALL所需的参数压入堆栈,方便CALL内部执行时调用

这里对应的语句为

00401168  |.  6A 02         push 0x2
0040116A  |.  6A 01         push 0x1

即这个CALL得到的参数为2和1

调用CALL

调用CALL又可以分为六个部分:

  1. 提升堆栈
  2. 保护现场
  3. 初始化提升的堆栈
  4. 执行实际内容
  5. 恢复现场
  6. 返回

提升堆栈

对应语句为

00401040  /> \55            push ebp
00401041  |.  8BEC          mov ebp,esp
00401043  |.  83EC 40       sub esp,0x40

将堆栈提升了0x40

保护现场

对应语句为

00401046  |.  53            push ebx
00401047  |.  56            push esi
00401048  |.  57            push edi

将ebx、esi、edi三个通用寄存器保存到堆栈中,前面的push ebp其实也属于保护现场

初始化提升的堆栈

00401049  |.  8D7D C0       lea edi,dword ptr ss:[ebp-0x40]
0040104C  |.  B9 10000000   mov ecx,0x10
00401051  |.  B8 CCCCCCCC   mov eax,0xCCCCCCCC
00401056  |.  F3:AB         rep stos dword ptr es:[edi]

这里将我们提升的堆栈中的内容全部初始化为CCCCCCCC

为什么是初始化为CC?防止缓冲溢出

CC的硬编码对应的指令为int 3,即断点

这么做有什么好处呢?当程序执行超过缓冲区时,遇到int 3就会自动停下来

执行实际的内容

对应语句为

00401058  |.  8B45 08       mov eax,dword ptr ss:[ebp+0x8]
0040105B  |.  0345 0C       add eax,dword ptr ss:[ebp+0xC]

就是将前面压入的参数2和1进行相加得到3

恢复现场

对应语句为

0040105E  |.  5F            pop edi                                  ;  HelloWor.00401171
0040105F  |.  5E            pop esi                                  ;  HelloWor.00401171
00401060  |.  5B            pop ebx                                  ;  HelloWor.00401171
00401061  |.  8BE5          mov esp,ebp
00401063  |.  5D            pop ebp                                  ;  HelloWor.00401171

与前面保护现场相对应

返回

对应语句为

00401064  \.  C3            retn

CALL返回后

对应语句为

00401171  |.  83C4 08       add esp,0x8

作用为平衡堆栈

逆推C语言代码

根据我们前面的分析,我们不难发现这其实就是个简单的加法函数

int add(int x,int y){
    x=x+y;        //这里的x和y分别对应压入的参数
    return x;        //对应RETN 默认采用eax作为返回值的传递载体
}

事后感言

一个小小的加法函数其对应的汇编代码却不少,而其中的关键代码只有两句

00401058  |.  8B45 08       mov eax,dword ptr ss:[ebp+0x8]
0040105B  |.  0345 0C       add eax,dword ptr ss:[ebp+0xC]

其它的大部分代码主要都是为保护现场和恢复现场所服务

编译器编译出的Debug和Release版本对应的汇编代码会有所差异,但只要掌握了核心思想,万变不离其宗

本笔记可能会有谬误之处,欢迎大家指出,一起探讨,共同提升

免费评分

参与人数 22吾爱币 +25 热心值 +21 收起 理由
weakdog + 1 + 1 谢谢@Thanks!
skyward + 1 + 1 谢谢@Thanks!
QRQ + 1 + 1 热心回复!
erh + 1 + 1 谢谢@Thanks!
SacXismo + 1 + 1 谢谢@Thanks!
wap6575 + 1 + 1 我很赞同!
kwdwkiss + 1 + 1 学习了!
pnccm + 2 + 1 不给个赞都觉得对不起你了
国际豆哥 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
寒枫雨雪 + 1 + 1 谢谢@Thanks!
小卍卒 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
墨石不菲 + 1 + 1 谢谢@Thanks!
递啥嘛 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
blywq + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
wofei666 + 1 + 1 谢谢@Thanks!
异度猪仔 + 1 + 1 用心讨论,共获提升!
lazy3801 + 1 + 1 谢谢@Thanks!
ljj_1025 + 1 + 1 我很赞同!
Henny + 1 用心讨论,共获提升!
weizhuqiang + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
朱朱你堕落了 + 3 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
为之奈何? + 1 + 1 我很赞同!

查看全部评分

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

 楼主| lyl610abc 发表于 2021-3-3 17:15
ScorpionY 发表于 2021-3-3 17:00
厉害,为啥最后逆向回来返回值为void的呢

感谢指正,当初这里没注意看,一般默认情况是采用eax作为返回值进行传递的,这里的返回值应该为int
bester 发表于 2021-3-1 09:24
讲到堆栈 需要将一下 ret 立即数、 leave 、还有add esp 立即数、 这几个点,涉及堆栈这些必不可少,当初学习的时候真的走了很多弯路,根本不明白 为什么有的地方是ret 8 有的地方是add esp,8
kwdwkiss 发表于 2021-3-6 22:50
call内部使用到的寄存器,eax,ecx,edi。保存现场push的寄存器ebx,esi,edi。
问:ebx,esi没用到为什么保存?ecx用到了为什么没保存?
eax作为函数返回结果,应该不用保存。

免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
lyl610abc + 1 + 1 感谢您的宝贵建议,我们会努力争取做得更好!

查看全部评分

zzy1912 发表于 2021-3-1 12:37
学习了学习了学习了
bigdawn 发表于 2021-3-1 00:10
好东西,要重新上学学习一下了
PrincessSnow 发表于 2021-3-1 00:17
谢谢大佬虽然不是特别懂但还是保存起来以后可以学习
AsuraSong 发表于 2021-3-1 00:17
谢谢楼主分享,十分详细的说明
366199558 发表于 2021-3-1 00:21
好东西啊,感谢分享
OAK1900 发表于 2021-3-1 00:34
感谢大佬分享!
zhangyangblue 发表于 2021-3-1 05:48
谢谢分享!!
weizhuqiang 发表于 2021-3-1 06:48

谢谢楼主分享,十分详细的说明
promrise 发表于 2021-3-1 07:50
学习下,谢谢
chenyuan911 发表于 2021-3-1 08:20
学习了,谢谢
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

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

GMT+8, 2024-12-22 00:50

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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