首页
学习
活动
专区
圈层
工具
发布
30 篇文章
1
《coredump问题原理探究》Linux x86版6.4节虚函数
2
《coredump问题原理探究》Linux x86版7.3节List对象
3
《coredump问题原理探究》Linux x86版4.4节函数的逆向之循环结构
4
《coredump问题原理探究》windows版8.5节堆布局空闲堆块遍历
5
《coredump问题原理探究》windows版9.1节栈溢出
6
《coredump问题原理探究》Linux x86版3.2节栈布局之函数桢
7
《coredump问题原理探究》Linux x86版3.4节栈布局之函数参数
8
《coredump问题原理探究》Linux x86版4.1节函数的逆向之序言
9
《coredump问题原理探究》Linux x86版4.2节函数的逆向之顺序结构
10
《coredump问题原理探究》Linux x86版4.3节函数的逆向之条件结构
11
《coredump问题原理探究》windows版3.4节coredump例子
12
《coredump问题原理探究》windows版第四章函数的逆向
13
《coredump问题原理探究》windows版5.1节基本数据类型
14
《coredump问题原理探究》windows版5.2节数组
15
《coredump问题原理探究》windows版5.3节结构体
16
《coredump问题原理探究》windows版5.4节联合体
17
《coredump问题原理探究》windows版6.1节无成员变量的类
18
《coredump问题原理探究》windows版6.2节有成员变量的类
19
《coredump问题原理探究》windows版6.3节虚函数
20
《coredump问题原理探究》windows版6.4节单继承
21
《coredump问题原理探究》windows版6.5节多继承
22
《coredump问题原理探究》windows版7.1节vector
23
《coredump问题原理探究》windows版7.2节list
24
《coredump问题原理探究》windows版7.3节map
25
《coredump问题原理探究》windows版7.4节set
26
《coredump问题原理探究》windows版7.5节iterator
27
《coredump问题原理探究》windows版7.6节string
28
《coredump问题原理探究》windows版8.1节堆布局背景
29
《coredump问题原理探究》windows版8.2节堆布局堆块结构
30
《coredump问题原理探究》windows版3.1节函数桢

《coredump问题原理探究》Linux x86版3.2节栈布局之函数桢

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://cloud.tencent.com/developer/article/1344464

看一个例子:

代码语言:javascript
复制
void FuncA()
{
}

void FuncB()
{
    FuncA();
}

int main()
{
    FuncB();
    return 0;
}

用下面命令编译出它发布版本:

代码语言:javascript
复制
[buckxu@xuzhina 1]$ g++  -o xuzhina_dump_c3_s1_relxuzhina_dump_c3_s1.cpp

在讨论它们的栈之前,先分析一下main,FuncB,FuncA这三个函数的汇编:

代码语言:javascript
复制
(gdb) disassemble FuncA
Dump of assembler code for function_Z5FuncAv:
  0x08048470 <+0>:    push   %ebp
  0x08048471 <+1>:    mov    %esp,%ebp
  0x08048473 <+3>:    pop    %ebp
  0x08048474 <+4>:    ret   
End of assembler dump.
代码语言:javascript
复制
(gdb) disassemble FuncB
Dump of assembler code for function_Z5FuncBv:
  0x08048475 <+0>:    push   %ebp
  0x08048476 <+1>:    mov    %esp,%ebp
  0x08048478 <+3>:    call   0x8048470 <_Z5FuncAv>
  0x0804847d <+8>:    pop    %ebp
  0x0804847e <+9>:    ret   
End of assembler dump.
代码语言:javascript
复制
(gdb) disassemble main
Dump of assembler code for function main:
  0x0804847f <+0>:    push   %ebp
  0x08048480 <+1>:    mov    %esp,%ebp
  0x08048482 <+3>:    call   0x8048475 <_Z5FuncBv>
  0x08048487 <+8>:    mov    $0x0,%eax
  0x0804848c <+13>:   pop    %ebp
  0x0804848d <+14>:   ret   
End of assembler dump.

从它们的汇编,都可以看到在这三个函数的开头,都有这样的指令:

代码语言:javascript
复制
push  %ebp
mov   %esp,%ebp

而在它们的结尾则有这样的指令:

代码语言:javascript
复制
pop   %ebp
ret   

在没有使用gcc的-fomit-frame-pointer选项来编译的函数一般都会有这样的开头和结尾。这几行指令可以看作是函数的开头和结尾的特征。像FuncA这样空叶子函数,一般就是由这两个特征拼起来的。

在x86里,ebp存放着函数桢指针,而esp则指向当前栈顶位置,而eip则是要执行的下一条指令地址。

所以,函数开头的两条指令的含义如下

代码语言:javascript
复制
push  %ebp             // esp = esp – 4,把ebp的值放入到esp指向的地址
mov   %esp,%ebp        // 把esp的值放到ebp里。即ebp = esp

函数结尾两条指令的含义如下

代码语言:javascript
复制
pop   %ebp                      // 把esp指向地址的内容放到ebp, esp = esp+4
ret                             //把ebp指向地址下一个单元的内容放到eip,esp = esp + 4

下面验证一下上面的内容。在main函数的开头指令地址0x0804847f打断点

代码语言:javascript
复制
(gdb) tbreak *0x0804847f
Temporary breakpoint 1 at 0x804847f

逐步地看一下是不是

代码语言:javascript
复制
(gdb) r
Starting program:/home/buckxu/work/3/1/xuzhina_dump_c3_s1_rel
 
Temporary breakpoint 1, 0x0804847f in main()
(gdb) i r ebp esp
ebp            0x0      0x0
esp            0xbffff4dc       0xbffff4dc
(gdb) x /4x $esp
0xbffff4dc:     0x4a8bf635      0x00000001      0xbffff574      0xbffff57c
(gdb) ni
0x08048480 in main ()
(gdb) i r ebp esp
ebp            0x0      0x0
esp            0xbffff4d8       0xbffff4d8
(gdb) x /4x $esp
0xbffff4d8:     0x00000000      0x4a8bf635      0x00000001      0xbffff574

果然,在运行了

代码语言:javascript
复制
push  %ebp

之后,esp的值由0xbffff4dc变为0xbffff4d8,而它所指向的单元刚好是ebp的值0。这一操作实质是把旧的函数桢指针保存到栈里。

再继续执行下去

代码语言:javascript
复制
(gdb) ni
0x08048482 in main ()
(gdb) i r ebp esp
ebp            0xbffff4d8       0xbffff4d8
esp            0xbffff4d8       0xbffff4d8
(gdb) x /4x $esp
0xbffff4d8:     0x00000000      0x4a8bf635      0x00000001      0xbffff574

可见

代码语言:javascript
复制
mov   %esp,%ebp

确实是把esp的值赋给了ebp,这一操作实质是设置函数桢指针,新的函数桢指针所指向的地址刚好放着旧的函数桢指针。

那为什么要设置函数桢呢?只是考察了main函数,并不一定能够找到规律,继续执行FuncB,FuncA,看一下能不能找到规律。

代码语言:javascript
复制
(gdb) si
0x08048475 in FuncB() ()
(gdb) i r esp ebp
esp            0xbffff4d4       0xbffff4d4
ebp            0xbffff4d8       0xbffff4d8
(gdb) x /4x $esp
0xbffff4d4:     0x08048487      0x00000000      0x4a8bf635      0x00000001

只是进入了FuncB,没有进行任何操作,esp的值却比ebp要小4个字节,变为0xbffff4d4,而0xbffff4d4地址却放入一个值0x08048487,正好是

代码语言:javascript
复制
  0x08048482 <+3>:    call   0x8048475 <_Z5FuncBv>

的下一条指令的地址:

代码语言:javascript
复制
   0x08048487 <+8>:     mov   $0x0,%eax

这是由于main函数使用call指令调用FuncB时,call指令把0x08048487压入栈的。这个地址实际上就是main函数的返回地址。

继续执行

代码语言:javascript
复制
(gdb) ni
0x08048476 in FuncB() ()
(gdb) ni         
0x08048478 in FuncB() ()
(gdb) i r ebp esp
ebp            0xbffff4d0       0xbffff4d0
esp            0xbffff4d0       0xbffff4d0
(gdb) i r eip
eip            0x8048478        0x8048478 <FuncB()+3>
(gdb) x /4x $esp
0xbffff4d0:     0xbffff4d8      0x08048487      0x00000000      0x4a8bf635
(gdb) x /4x 0xbffff4d8
0xbffff4d8:     0x00000000      0x4a8bf635      0x00000001      0xbffff574

从上面可以看到,函数桢指针可能是一种单链表关系,它的表头指针由ebp来放置。用C/C++语言方式表示,则是

代码语言:javascript
复制
struct  FramePointer
{
       struct  FramePointer* next;
};

它是不是一种链表关系呢?继续执行到FuncA。

代码语言:javascript
复制
(gdb) tbreak FuncA
Temporary breakpoint 2 at 0x8048473
(gdb) c
Continuing.
 
Temporary breakpoint 2, 0x08048473 inFuncA() ()
(gdb) i r ebp esp
ebp            0xbffff4c8       0xbffff4c8
esp            0xbffff4c8       0xbffff4c8
(gdb) x /4x $ebp
0xbffff4c8:     0xbffff4d0      0x0804847d      0xbffff4d8      0x08048487
(gdb) x /4x 0xbffff4d0
0xbffff4d0:     0xbffff4d8      0x08048487      0x00000000      0x4a8bf635
(gdb) x /4x 0xbffff4d8
0xbffff4d8:     0x00000000      0x4a8bf635      0x00000001      0xbffff574

可见函数桢指针确实如上面structFramePointer,是由ebp为表头。而且考察每个在栈里的函数桢指针的下一个单元内容和eip的值,会发现这样的情况:

代码语言:javascript
复制
(gdb) i r eip
eip            0x8048473        0x8048473 <FuncA()+3>
(gdb) info symbol 0x0804847d
FuncB() + 8 in section .text of/home/buckxu/work/3/1/xuzhina_dump_c3_s1_rel
(gdb) info symbol 0x08048487
main + 8 in section .text of/home/buckxu/work/3/1/xuzhina_dump_c3_s1_rel

正好和栈的内容一致:

代码语言:javascript
复制
(gdb) bt
#0  0x08048473 in FuncA() ()
#1  0x0804847d in FuncB() ()
#2  0x08048487 in main ()

所以,函数桢指针的结构扩展如下:

代码语言:javascript
复制
struct FramePointer
{
       struct FramePointer* next;
       void*  ret;               //返回地址
};

用图形表示的话,如下:

gdb也是根据这个规律来解析栈,才能够显示正确的栈。那么不正确的栈是怎样的呢?

在了解这个问题之前,先考察一下函数结尾的特征指令

代码语言:javascript
复制
pop   %ebp                      // 把esp指向地址的内容放到ebp, esp = esp+4
ret                             //把ebp指向地址下一个单元的内容放到eip, esp = esp + 4

由于现在程序已经执行到FuncA,看一下执行完FuncA,ebp,eip,esp的值是不是真的这样变化:

代码语言:javascript
复制
(gdb) x /4x $ebp
0xbffff4c8:     0xbffff4d0     0x0804847d      0xbffff4d8      0x08048487
(gdb) i r eip esp ebp
eip            0x8048473        0x8048473 <FuncA()+3>
esp            0xbffff4c8       0xbffff4c8
ebp            0xbffff4c8       0xbffff4c8
(gdb) ni
0x08048474 in FuncA() ()
(gdb) ni
0x0804847d in FuncB() ()
(gdb) i r eip esp ebp
eip            0x804847d        0x804847d <FuncB()+8>
esp            0xbffff4d0       0xbffff4d0
ebp            0xbffff4d0       0xbffff4d0

果然ebp,esp,eip的变化在意料之中。

现在来仔细考察一下函数结尾的特征指令,pop %ebp是把栈顶的内容放入ebp。如果栈顶的内容被修改了,指向一个非法的位置,结果会怎样?

在这里,重新跑一下程序,在FuncA退出前,修改一下栈的内容。

代码语言:javascript
复制
(gdb) tbreak FuncA
Temporary breakpoint 1 at 0x8048473
(gdb) r
Starting program:/home/buckxu/work/3/1/xuzhina_dump_c3_s1_rel
 
Temporary breakpoint 1, 0x08048473 inFuncA() ()
 (gdb) bt
#0  0x08048473 in FuncA() ()
#1  0x0804847d in FuncB() ()
#2  0x08048487 in main ()
(gdb) i r ebp eip esp
ebp            0xbffff4c8       0xbffff4c8
eip            0x8048473        0x8048473 <FuncA()+3>
esp            0xbffff4c8       0xbffff4c8
(gdb) x /4x $esp
0xbffff4c8:     0xbffff4d0      0x0804847d      0xbffff4d8      0x08048487
(gdb) set *0xbffff4c8 =0x10
(gdb) ni
0x08048474 in FuncA() ()
(gdb) i r ebp eip esp
ebp            0x10     0x10
eip            0x8048474        0x8048474 <FuncA()+4>
esp            0xbffff4cc       0xbffff4cc
(gdb) ni
Cannot access memory ataddress 0x14
(gdb) bt
#0  0x0804847d in FuncB() ()
Cannot access memory ataddress 0x14
(gdb) c
Continuing.
[Inferior 1 (process 1178) exited normally]

可以看到,gdb只能解析出FuncB函数桢,而无法解析出桢main函数桢,只因为FuncB的返回地址没有被修改。

再看一下函数结尾的第二条指令,ret是把esp所指向的内容放到eip里。假如esp所指向的内容是非法,栈又会变成怎样?

在重新考察一下这个程序,但这次是修改esp+4那个单元的内容。

代码语言:javascript
复制
(gdb) tbreak FuncA
Temporary breakpoint 1 at 0x8048473
(gdb) r
Starting program: /home/buckxu/work/3/1/xuzhina_dump_c3_s1_rel
 
Temporary breakpoint 1, 0x08048473 inFuncA() ()
(gdb) bt
#0  0x08048473 in FuncA() ()
#1  0x0804847d in FuncB() ()
#2  0x08048487 in main ()
(gdb) i r eip esp ebp
eip            0x8048473        0x8048473 <FuncA()+3>
esp            0xbffff4c8       0xbffff4c8
ebp            0xbffff4c8       0xbffff4c8
(gdb) x /4x $esp
0xbffff4c8:     0xbffff4d0      0x0804847d      0xbffff4d8      0x08048487
(gdb) set *0xbffff4cc= 0x10
(gdb) ni
0x08048474 in FuncA() ()
(gdb) ni
0x00000010 in ?? ()
(gdb) bt
#0  0x00000010 in ?? ()
#1  0xbffff4d8 in ?? ()
#2  0x08048487 in main ()
(gdb) c
Continuing.
 
Program received signalSIGSEGV, Segmentation fault.
0x00000010 in ?? ()
(gdb) bt
#0  0x00000010 in ?? ()
#1  0xbffff4d8 in ?? ()
#2  0x08048487 in main ()

可以看到,出现了“??”的栈,这也是因为函数的返回地址被修改的原因。那么,存放在栈上的函数桢指针和返回地址都被修改了,栈又会变成怎样?

代码语言:javascript
复制
(gdb) tbreak FuncA
Temporary breakpoint 1 at 0x8048473
(gdb) r
Starting program:/home/buckxu/work/3/1/xuzhina_dump_c3_s1_rel
 
Temporary breakpoint 1, 0x08048473 inFuncA() ()
(gdb) bt
#0  0x08048473 in FuncA() ()
#1  0x0804847d in FuncB() ()
#2  0x08048487 in main ()
(gdb) i r eip esp ebp
eip            0x8048473        0x8048473 <FuncA()+3>
esp            0xbffff4c8       0xbffff4c8
ebp            0xbffff4c8       0xbffff4c8
(gdb) x /4x $esp
0xbffff4c8:     0xbffff4d0      0x0804847d      0xbffff4d8      0x08048487
(gdb) set *0xbffff4c8= 0x10
(gdb) set *0xbffff4cc= 0x20
(gdb) ni
0x08048474 in FuncA() ()
(gdb) ni
0x00000020 in ?? ()
(gdb) i r eip esp ebp           
eip            0x20     0x20
esp            0xbffff4d0       0xbffff4d0
ebp            0x10     0x10
(gdb) x /4x $esp           
0xbffff4d0:     0xbffff4d8      0x08048487      0x00000000      0x4a8bf635
(gdb) bt
#0  0x00000020 in ?? ()
#1  0xbffff4d8 in ?? ()
Backtrace stopped:previous frame inner to this frame (corrupt stack?)
(gdb) c
Continuing.
 
Program received signalSIGSEGV, Segmentation fault.
0x00000020 in ?? ()
(gdb) bt
#0  0x00000020 in ?? ()
#1  0xbffff4d8 in ?? ()
Backtrace stopped: previous frame inner tothis frame (corrupt stack?)

可以看到,这正好是前言看到那种的栈。现在可以知道,之后会出现“??“的栈,是因为存在栈上的函数桢指针和返回地址被修改了。在实际开发过程中,往往会由于拷贝内存导致这种情况。这种情况叫做栈溢出。

在这一章的最后一节“coredump例子“会显示怎样恢复部分正常的栈。而为什么内存拷贝之类的操作会导致栈溢出,原因会放在第5章里讲述。

下一篇
举报
领券