0
点赞
收藏
分享

微信扫一扫

逆向——C语言的汇编表示之堆栈图


C语言的汇编表示之堆栈图

逆向——C语言的汇编表示之堆栈图_逆向分析

 

写在前面:push一个数据到stack后,esp指向的是栈顶push的最新数据。

逆向——C语言的汇编表示之堆栈图_寄存器_02

 

 

发表于 2019-07-13

前天把C语言的汇编表示给看完了,但却没有怎么自己操作过,不过看懂了永远不能代表学会了,今天的话就从中挑选一个简单例子完整的再操作一遍,加深自己对它的理解!(之所以没怎么操作是因为VC6.0环境总是出问题!!!)

1. 在VC6.0创建一个文件

1
2
3
4
5
6
7
8
9
10
11
12
13

#include<stdio.h>
#include<stdlib>
#include<windows.h>
int plus(int x,int y)
{
    return x+y;
};
int main()
{
    int a=10;
    push(2,3);
    return 0;
}

 ==》代码修改为:


#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 单步步入

逆向——C语言的汇编表示之堆栈图_堆栈_03

总结:

1.C语言中传参方式 是堆栈传参 参数的调用从右到左依次push

2.函数名就是编译器给内存地址的别名

画一下 堆栈变化图

首先我们调出 Register(寄存器)窗口,与Memory窗口

1.Alt+5 Register(寄存器)窗口

逆向——C语言的汇编表示之堆栈图_寄存器_04


2.Alt+6 Memory 窗口

逆向——C语言的汇编表示之堆栈图_逆向分析_05

看栈顶 esp 地址为0012FF30

看栈底 ebp 地址为0012FF80

F10执行完push 3
ESP就会减4个字节 变成0012FF2c

逆向——C语言的汇编表示之堆栈图_逆向分析_06

同理 再 F10执行完 push 2后 猜测 ESP会变成 0012FF28

逆向——C语言的汇编表示之堆栈图_逆向分析_07

所以 堆栈图可以这么画

逆向——C语言的汇编表示之堆栈图_寄存器_08

逆向——C语言的汇编表示之堆栈图_逆向分析_09

然后下一步执行call指令 F11进去
这时

逆向——C语言的汇编表示之堆栈图_逆向分析_10


紧跟的是 02 03

逆向——C语言的汇编表示之堆栈图_寄存器_11

call (0040100a)指令本质

1.jmp 0040100a //即EIP为0040100A 下一次要执行的地址

2.把下一行地址压到堆栈去 /即栈顶
由2得3 esp的值减4 //因为下一行地址压到堆栈

所以 查看一下 栈顶0010FF24内容 就是call指令的下一行地址00 40 B7 83 (小端存储)

逆向——C语言的汇编表示之堆栈图_堆栈_12

此时观察 反汇编页面

发现它并没有直接 call到 函数部分 而是 这样的

逆向——C语言的汇编表示之堆栈图_寄存器_13

这时VC6 给我们生成的。
调用函数时VC6会给我们生成 一个jmp无条件跳转到 函数部分、

jmp 00401010实质:

mov eip,00401010//即 下一行执行 00401010指令

逆向——C语言的汇编表示之堆栈图_堆栈_14

所以 执行到这步,目前反汇编该执行 00401010了

观察下面框住的部分
此为编译器想要ebp寻址.
我们分析下

逆向——C语言的汇编表示之堆栈图_#include_15

push ebp //即 把 ebp的地址压入栈顶,同时esp-4

mov ebp,esp //即把esp的地址赋给ebp的地址,此时ebp就和esp在同一地址了
然后

sub esp,40h//目的是给 提升堆栈

堆栈一个地址占4个字节 40h的话就是40h/4=10h个 地址 换成10进制就是16个地址
即esp被向上提升了16个地址,
堆栈图如下:

逆向——C语言的汇编表示之堆栈图_寄存器_16

然后继续看反汇编

逆向——C语言的汇编表示之堆栈图_逆向分析_17

逆向——C语言的汇编表示之堆栈图_寄存器_18

这个比较简单了,连续把寄存器ebx,esi,edi的地址压入栈
目的是:保存原来的寄存器值 以便最后的恢复,因为你在过程中会发生变化
执行完堆栈图为

逆向——C语言的汇编表示之堆栈图_寄存器_19

而绿色的部分就是我们常提到的 缓冲区

为了程序使用完 正常运行 编译器会在没用到的缓冲区 填充CC即我们调试时的断点

缓冲区溢出 就是

当前的函数在执行的过程中需要内存,需要用内存 就提升堆栈 自己给自己分配内存,

对缓冲区做手脚,通过一些方式 如改变函数返回地址 来达到控制程序的目的;

下一个指令

逆向——C语言的汇编表示之堆栈图_寄存器_20

首先

lea edi,[ebp-40h] 的含义就是 把ebp现在的地址减40h 后的地址赋给edi

stos dword PTR [edi]的含义是将eax的值存储到[edi]指定的地址

rep指令:

按计数寄存器(ecx)指定的次数重复执行执行字符串指令

如 rep stosd(rep stos dword PTR [edi]的简写)

所以上面的几行指令就是给没用到的缓冲区填充 CC

所以 现在的堆栈结构图 是这个样子

逆向——C语言的汇编表示之堆栈图_寄存器_21

这是 我们该分析这步了

逆向——C语言的汇编表示之堆栈图_堆栈_22

看下图

红色1: ebp+4 地址里存放的是call指令的下一行地址

红色2(未画) ebp+8 地址里存放的是 第一个实参 2

红色2(未画) ebp+0C 地址里存放的是 第二个实参 3

逆向——C语言的汇编表示之堆栈图_堆栈_23

mov eax,dword PTR [ebp+8] 含义是 把esp+8 地址里的值 赋给eax
add eax,dword PTR [ebp+12] 含义是 把esp+8 地址里的值 加上eax 存在eax

此过程 堆栈以及缓冲区 没有改变只是 把 2+3的结果存在了eax中了

此时 看起来 我们似乎觉得 程序已经结束了

对的,我们希望的过程已经实现了,
但还要注意 堆栈平衡 原来是什么样子
记得用完后 把它恢复过来

继续分析

逆向——C语言的汇编表示之堆栈图_逆向分析_24


我们把edi esi ebx pop出去 ,同时esp 一共减去减12即0C

这是 esp地址又会变成0012FEE0

逆向——C语言的汇编表示之堆栈图_寄存器_25

F10 单步 验证下

逆向——C语言的汇编表示之堆栈图_#include_26

正确

还有一点点就要分析完毕了

逆向——C语言的汇编表示之堆栈图_逆向分析_27


mov esp,ebp //即把ebp的地址赋给esp

此时esp又和ebp在同一位置了

逆向——C语言的汇编表示之堆栈图_逆向分析_28


然后pop ebp//把0012FF80 pop给ebp地址

实质:把ebp pop最初的ebp地址 同时esp+4

堆栈图为:

逆向——C语言的汇编表示之堆栈图_寄存器_29

F10 验证下

逆向——C语言的汇编表示之堆栈图_寄存器_30

正确!
然后该执行 ret 了

ret的本质 是

mov eip ,esp

add esp,4

逆向——C语言的汇编表示之堆栈图_堆栈_31

所以 会 pop 0040B783给了EIP 同时esp+4

所以 堆栈图为

逆向——C语言的汇编表示之堆栈图_寄存器_32

原来的堆栈是黄色部分 现在的话 esp还没有恢复原位置
也就是堆栈还没有平衡

继续看汇编页面
add esp,8
执行后 esp将恢复到原来的地址 从0012FF28 到 0012FF30(原)

逆向——C语言的汇编表示之堆栈图_堆栈_33

有内平 (直接 return ?(?代表16进制数字))
和外平(我记录这个就是外平,return过后,再add esp,?)

此时:堆栈已经平衡
而 再堆栈平衡的状态下
我们完成了 2+3 并把值存在了EAX中
除此之外,我们在还留下了 大量的垃圾(框住的部分)危害是有的,具体的还暂时不是太清楚,继续深入吧!

逆向——C语言的汇编表示之堆栈图_逆向分析_34

上面有一个地方出了点小错,
call指令的下一行的地址为0040B788 一下为修正图(还请注意):

逆向——C语言的汇编表示之堆栈图_逆向分析_35


堆栈传参

当函数有很多参数的时候,不止8个,那我们使用通用寄存器去传参,明显不够用,所以我们需要使用堆栈帮助我们传递参数。

还是以加法举例,实际场景:

逆向——C语言的汇编表示之堆栈图_堆栈_36

如上图所示实现算术1+2,首先将1、2依次压入堆栈,CALL指令也会将其下一行指令地址压入堆栈,所以堆栈地址[ESP+8]为第一个压入的数据,堆栈地址[ESP+4]为第二个压入的数据。

堆栈平衡

我们知道当执行函数调用CALL指令的时候,会把CALL指令下一条指令的内存地址压入堆栈(ESP值减4);在函数内我们可以随意使用堆栈,比如PUSH指令压入堆栈,使用堆栈传参等等...

我们需要保证,在函数调用结束的时候(即执行RET指令之前,要把ESP栈顶指针的值修改为执行CALL指令压入堆栈或堆栈传参压入堆栈前的那个ESP栈顶指针的值),保证函数运行前与运行后ESP栈顶指针的值不变,这个我们称之为堆栈平衡

平衡堆栈有两个方法:

1.外平栈:使用ADD指令。

逆向——C语言的汇编表示之堆栈图_逆向分析_37

2.内平栈:使用RET指令,例如压入了2个32位(4字节)数据就可以写为RET 8。

逆向——C语言的汇编表示之堆栈图_逆向分析_38


 

 

举报

相关推荐

0 条评论