0x804842d
0x804842f
0x8048433
0x8048435
0x8048438
0x804843b
0x804843d
0x804843e
0x8048443
0x8048446
0x8048448
0x804844b
0x804844d
0x804844e
0x8048453
0x8048458
0x804845b
0x804845c
0x804845d
0x804845e
0x804845f
End of assembler dump.
(gdb) disas vulFunc
Dump of assembler code for function vulFunc:
0x8048400
0x8048401
0x8048403
0x8048406
0x8048409
0x804840a
0x804840d
0x804840e
0x8048413
0x8048416
0x8048419
0x804841a
0x804841f
0x8048424
0x8048427
0x8048428
0x8048429
End of assembler dump.
这里我们只对所关心的main和vulFunc两个函数进行反汇编分析.
ii) 进程的运行及其在内存中的情况分析
我们用gdb来跟踪看看进程是如何在内存中运行的.
首先把程序调入.
bash$ gdb p
GNU gdb 19991004
Copyright 1998 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type ""show copying"" to see the conditions.
There is absolutely no warranty for GDB. Type ""show warranty"" for details.
This GDB was configured as ""i386-redhat-linux""...
(gdb)
把断点设到main的第一条可执行汇编指令上
(gdb) b *0x804842c
Breakpoint 1 at 0x804842c
运行程序
(gdb) r AAAAAAAA
Starting program: /home/vcat/p AAAAAAAA
Breakpoint 1, 0x804842c in main ()
在断点处停下来了.
看一下这时各寄存器的值
(gdb) i reg
eax 0x4010b3f8 1074836472
ecx 0x804842c 134513708
edx 0x4010d098 1074843800
ebx 0x4010c1ec 1074840044
esp 0xbffff6bc -1073744196
ebp 0xbffff6d8 -1073744168
esi 0x4000ae60 1073786464
edi 0xbffff704 -1073744124
eip 0x804842c 134513708
eflags 0x246 582
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x0 0
cwd 0xffff037f -64641
swd 0xffff0000 -65536
twd 0xffffffff -1
fip 0x40034d70 1073958256
fcs 0x35d0023 56426531
fopo 0xbfffe400 -1073748992
fos 0xffff002b -65493
我们这里关心的是栈底(ebp), 栈顶(esp)及指令寄存器(eip).
此时, ebp的值为0xbffff6d8, esp的值为0xbffff6bc, 相差28个字节.
eip的值为0x804842c, 正好是我们所设的断点.
(注: 这里的值可能会随着程序运行在不同的系统环境而不同)
我们再看看当前栈帧里有什么内容?
(gdb) x/8x $esp
0xbffff6bc: 0x400349cb 0x00000002 0xbffff704 0xbffff710
0xbffff6cc: 0x40013868 0x00000002 0x08048350 0x00000000
也就是说, main函数刚被调用时进程在内存中的相关部份的影像是这样的:
(内存高址)
| ...... |
+--------+
|00000000|
0xbffff6d8 +--------+ <-- ebp (调用main函数前的ebp)
|08048350|
+--------+
|00000002|
+--------+
|40013868|
+--------+
|bffff710|
+--------+
|bffff704|
+--------+
|00000002|
+--------+
|400349cb|
0xbffff6bc +--------+ <-- esp (调用main函数前的esp)
| ...... |
(内存低址)
我们看看接下来的指令做了些什么?
0x804842c
; 把ebp的值放入esp所指的32位内存单
; 元(注: 这里保存栈底).
0x804842d
; 的栈顶做为新的栈底).
运行这两条指令, 然后看一下寄存器内容和栈的情况.
(gdb) si
0x804842d in main ()
(gdb) si
0x804842f in main ()
(gdb) i reg
eax 0x4010b3f8 1074836472
ecx 0x804842c 134513708
edx 0x4010d098 1074843800
ebx 0x4010c1ec 1074840044
esp 0xbffff6b8 -1073744200
ebp 0xbffff6b8 -1073744200
esi 0x4000ae60 1073786464
edi 0xbffff704 -1073744124
eip 0x804842f 134513711
eflags 0x346 838
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x0 0
cwd 0xffff037f -64641
swd 0xffff0000 -65536
twd 0xffffffff -1
fip 0x40034d70 1073958256
fcs 0x35d0023 56426531
fopo 0xbfffe400 -1073748992
fos 0xffff002b -65493
(gdb) x/9x $esp
0xbffff6b8: 0xbffff6d8 0x400349cb 0x00000002 0xbffff704
0xbffff6c8: 0xbffff710 0x40013868 0x00000002 0x08048350
0xbffff6d8: 0x00000000
此时进程的相关影像为:
(内存高址)
| ...... |
+--------+
|00000000|
0xbffff6d8 +--------+ <-- 调用main函数前的ebp
|08048350|
+--------+
|00000002|
+--------+
|40013868|
+--------+
|bffff710|
+--------+
|bffff704|
+--------+
|00000002|
+--------+
|400349cb|
0xbffff6bc +--------+ <-- 调用main函数前的esp
|bffff6d8|
0xbffff6b8 +--------+ <-- ebp, esp
| ...... |
(内存低址)
接下来的两条指令:
0x804842f
; 个字节)里面所放的内容比较.
0x8048433
; 处继续执行, 否则执行下条指令.
这里我们可以看到这是C语言语句
if(argc == 2)
{
...
}
else
{
...
}
的等价汇编语句. 内存地址ebp+8处存放的是argc的值.
(gdb) x/x $ebp+8
0xbffff6c0: 0x00000002
我们来看看在调用vulFunc函数前的指令:
0x8048435
; 内容放到eax里.
0x8048438
0x804843b
; 的内容赋给edx
0x804843d
; 所指的内存地址的四个字节单元里.
看看ebp+12处放的是什么?
(gdb) x/x $ebp+12
0xbffff6c4: 0xbffff704
怀疑这里放的是指向argv[0]字串的地址的地址, 看看是不是
(gdb) x/x 0xbffff704
0xbffff704: 0xbffff83e
(gdb) x/1s 0xbffff83e
0xbffff83e: ""/home/vcat/p""
果然是. 那么$ebp+12的所指的四个字节的内容(argv[0]字串的地址)加上四, 应该就是指向
argv[1]字串的地址了.
(gdb) x/x 0xbffff704+4
0xbffff708: 0xbffff856
(gdb) x/1s 0xbffff856
0xbffff856: ""AAAAAAAA""
可以看出, 这四条指令是用来计算argv[1](即所输入的字串""AAAAAAAA""在内存中的起始地址),
然后把该地址压入栈中做为参数传给即将被调用的函数vulFunc的.
设个断点在0x804843e, 让程序继续执行到调用vulFunc函数之前.
(gdb) b *0x804843e
Breakpoint 2 at 0x804843e
(gdb) c
Continuing.
(gdb) i reg
eax 0xbffff708 -1073744120
ecx 0x804842c 134513708
edx 0xbffff856 -1073743786
ebx 0x4010c1ec 1074840044
esp 0xbffff6b4 -1073744204
ebp 0xbffff6b8 -1073744200
esi 0x4000ae60 1073786464
edi 0xbffff704 -1073744124
eip 0x804843e 134513726
eflags 0x282 642
(以下省略)
...
(gdb) x/10x $esp
0xbffff6b4: 0xbffff856 0xbffff6d8 0x400349cb 0x00000002
0xbffff6c4: 0xbffff704 0xbffff710 0x40013868 0x00000002
0xbffff6d4: 0x08048350 0x00000000
此时的进程在内存中的相关影像为:
(内存高址)
| ...... |
+--------+
|00000000|
0xbffff6d8 +--------+ <-- 调用main函数前的ebp
|08048350|
+--------+
|00000002|
+--------+
|40013868|
+--------+
|bffff710|
+--------+
|bffff704| argv的地址(即argv[0]的地址)
0xbffff6c4 +--------+
|00000002| argc的值
0xbffff6c0 +--------+
|400349cb|
0xbffff6bc +--------+ <-- 调用main函数前的esp
|bffff6d8| 调用main函数前的ebp
0xbffff6b8 +--------+ <-- main函数的ebp
|bffff856| 字符串""AAAAAAAA""在内存中的开始地址
0xbffff6b4 +--------+ <-- main函数的esp
| ...... |
(内存低址)
单步执行
(gdb) si
0x8048400 in vulFunc ()
好, 现在进入vulFunc函数了.
(gdb) i reg
eax 0xbffff708 -1073744120
ecx 0x804842c 134513708
edx 0xbffff856 -1073743786
ebx 0x4010c1ec 1074840044
esp 0xbffff6b0 -1073744208
ebp 0xbffff6b8 -1073744200
esi 0x4000ae60 1073786464
edi 0xbffff704 -1073744124
eip 0x8048400 134513664
eflags 0x382 898
(以下省略)
...
这时esp已经变为0xbffff6b0, 和以前的值0xbffff6b4比较相差四个字节.
我们来看看到底压了什么东西入栈.
(gdb) x/11x $esp
0xbffff6b0: 0x08048443 0xbffff856 0xbffff6d8 0x400349cb
0xbffff6c0: 0x00000002 0xbffff704 0xbffff710 0x40013868
0xbffff6d0: 0x00000002 0x08048350 0x00000000
原来是main函数里调用vulFunc函数的指令的后续指令的地址--即vulFunc函数的返回地址.
这是我们的第一个焦点.
...
0x804843e
0x8048443
...
我们接着分析vulFunc函数.
0x8048400
0x8048401
0x8048403
前面两条指令的功能和main函数的一样, 用来保存调用函数栈帧的栈底ebp和设置被调用函
数栈帧栈底.
即: 保存调用函数的栈帧栈底, 调用函数栈帧的栈顶变为被调用函数的栈底. 可以看出当前
(被调用函数)的栈帧为空时, ebp和esp的值相等.
第三条指令在栈帧中分配了0xc(十二)个字节的内存空间, 注意到里面的内容是垃圾.
(gdb) si
0x8048401 in vulFunc ()
(gdb) si
0x8048403 in vulFunc ()
(gdb) si
0x8048406 in vulFunc ()
(gdb) x/15x $esp
0xbffff6a0: 0x4000ae60 0xbffff704 0xbffff6b8 0xbffff6b8
0xbffff6b0: 0x08048443 0xbffff856 0xbffff6d8 0x400349cb
0xbffff6c0: 0x00000002 0xbffff704 0xbffff710 0x40013868
0xbffff6d0: 0x00000002 0x08048350 0x00000000
此时进程在内存中相关的影像为:
(内存高址)
| ...... |
+--------+
|00000000|
0xbffff6d8 +--------+ <-- 调用main函数前的ebp
|08048350|
+--------+
|00000002|
+--------+
|40013868|
+--------+
|bffff710|
+--------+
|bffff704| argv的地址(即argv[0]的地址)
0xbffff6c4 +--------+
|00000002| argc的值
0xbffff6c0 +--------+
|400349cb|
0xbffff6bc +--------+ <-- 调用main函数前的esp
|bffff6d8| 调用main函数前的ebp
0xbffff6b8 +--------+ <-- main函数的ebp
|bffff856| 字符串""AAAAAAAA""在内存中的起始地址
0xbffff6b4 +--------+
|08048443| vulFunc函数的返回地址
0xbffff6b0 +--------+ <-- 调用vulFunc函数前的esp
|bffff6b8| main函数的ebp
0xbffff6ac +--------+ <-- vulFunc函数的ebp
|bffff6b8| (垃圾)
0xbffff6a8 +--------+
|bffff704| (垃圾)
0xbffff6a4 +--------+
|4000ae60| (垃圾)
0xbffff6a0 +--------+ <-- vulFunc的当前esp
| ...... |
(内存低址)
再看看下面的四条指令.
0x8048406
; 的内容赋给eax.
从上图看出vulFunc函数栈帧的ebp+8四字节内存单元里放的是指向""AAAAAAAA""字符串的起始地址.
0x8048409
把指向""AAAAAAAA""字符串的起始地址入栈.
0x804840a
哇! 好吓人呀! 这条指令是干什么的? 让我们慢慢来分析一下.
这条指令是把ebp+0xfffffff4做为地址值赋给eax.
但是ebp的值加上0xfffffff4指向那里呀, 这是我们要弄清楚的.
这里如果我们按正数来加, 那是不行的.
实际上这个十六进制的0xfffffff4所表示的是负数, 要知道它的值, 让我们来算一下.
F F F F F F F 4
+----+----+----+----+----+----+----+----+
|1111|1111|1111|1111|1111|1111|1111|0100|
+----+----+----+----+----+----+----+----+
取反
0 0 0 0 0 0 0 B
+----+----+----+----+----+----+----+----+
|0000|0000|0000|0000|0000|0000|0000|1011|
+----+----+----+----+----+----+----+----+
加一
0 0 0 0 0 0 0 C
+----+----+----+----+----+----+----+----+
|0000|0000|0000|0000|0000|0000|0000|1100|
+----+----+----+----+----+----+----+----+
也就是负的0xc. ebp+0xfffffff4, 即ebp-0xc.
所以ebp+0xfffffff4, 就是现在栈顶指向的那十二个字节的起始地址.
0x804840d
接着把得到的地址入栈.
让程序运行到调用strcpy函数之前看看
(gdb) b *0x804840e
Breakpoint 3 at 0x804840e
(gdb) c
Continuing.
Breakpoint 3, 0x804840e in vulFunc ()
(gdb) x/17x $esp
0xbffff698: 0xbffff6a0 0xbffff856 0x4000ae60 0xbffff704
0xbffff6a8: 0xbffff6b8 0xbffff6b8 0x08048443 0xbffff856
0xbffff6b8: 0xbffff6d8 0x400349cb 0x00000002 0xbffff704
0xbffff6c8: 0xbffff710 0x40013868 0x00000002 0x08048350
0xbffff6d8: 0x00000000
这时进程在内存的相关影像为:
(内存高址)
| ...... |
+--------+
|00000000|
0xbffff6d8 +--------+ <-- 调用main函数前的ebp
|08048350|
+--------+
|00000002|
+--------+
|40013868|
+--------+
|bffff710|
+--------+
|bffff704| argv的地址(即argv[0]的地址)
0xbffff6c4 +--------+
|00000002| argc的值
0xbffff6c0 +--------+
|400349cb|
0xbffff6bc +--------+ <-- 调用main函数前的esp
|bffff6d8| 调用main函数前的ebp
0xbffff6b8 +--------+ <-- main函数的ebp
|bffff856| 字符串""AAAAAAAA""在内存中的起始地址
0xbffff6b4 +--------+
|08048443| vulFunc函数的返回地址
0xbffff6b0 +--------+ <-- 调用vulFunc函数前的esp
|bffff6b8| main函数的ebp
0xbffff6ac +--------+ <-- vulFunc函数的ebp
|bffff6b8| (垃圾)
0xbffff6a8 +--------+
|bffff704| (垃圾)
0xbffff6a4 +--------+
|4000ae60| (垃圾)
0xbffff6a0 +--------+
|bffff856| 字符串""AAAAAAAA""在内存中的起始地址
0xbffff69c +--------+
|bffff6a0| vulFunc函数栈帧中分配的十二个字节起始地址
0xbffff698 +--------+ <-- vulFunc的当前esp
| ...... |
(内存低址)
我们这里不关心strcpy函数具体运行, 把断点设到调用它的后续指令.
(gdb) b *0x8048413
Breakpoint 4 at 0x8048413
(gdb) c
Continuing.
Breakpoint 4, 0x8048413 in vulFunc ()
(gdb) x/17x $esp
0xbffff698: 0xbffff6a0 0xbffff856 0x41414141 0x41414141
0xbffff6a8: 0xbffff600 0xbffff6b8 0x08048443 0xbffff856
0xbffff6b8: 0xbffff6d8 0x400349cb 0x00000002 0xbffff704
0xbffff6c8: 0xbffff710 0x40013868 0x00000002 0x08048350
0xbffff6d8: 0x00000000
这时进程在内存中的相关影像为:
(内存高址)
| ...... |
+--------+
|00000000|
0xbffff6d8 +--------+ <-- 调用main函数前的ebp
|08048350|
+--------+
|00000002|
+--------+
|40013868|
+--------+
|bffff710|
+--------+
|bffff704| argv的地址(即argv[0]的地址)
0xbffff6c4 +--------+
|00000002| argc的值
0xbffff6c0 +--------+
|400349cb|
0xbffff6bc +--------+ <-- 调用main函数前的esp
|bffff6d8| 调用main函数前的ebp
0xbffff6b8 +--------+ <-- main函数的ebp
|bffff856| 字符串""AAAAAAAA""在内存中的起始地址
0xbffff6b4 +--------+
|08048443| vulFunc函数的返回地址
0xbffff6b0 +--------+ <-- 调用vulFunc函数前的esp
|bffff6b8| main函数的ebp
0xbffff6ac +--------+ <-- vulFunc函数的ebp
|bffff600|
0xbffff6a8 +--------+
|41414141|
0xbffff6a4 +--------+
|41414141|
0xbffff6a0 +--------+
|bffff856| 字符串""AAAAAAAA""在内存中的起始地址
0xbffff69c +--------+
|bffff6a0| vulFunc函数栈帧中分配的十二个字节起始地址
0xbffff698 +--------+ <-- vulFunc的当前esp
| ...... |
(内存低址)
我们注意到在vulFunc函数栈帧中所分配的那十二个字节, 从传递给strcpy函数的起始
地址处被我们所输入的八个''A''(十六进制0x41)填充了.
这是我们的第二个焦点.
同时也注意到, 内存地址0xbffff6a8所指向的四个字节的内容由原来的垃圾数据0xbffff6b8
变成了bffff600.
低字节的00应该就是字符串""AAAAAAAA""的零结尾字节.
所以得出结论: vulFunc函数栈帧中分配的那十二个字节是给局部变量buf(缓冲区)的.
这里会奇怪: 程序中buf缓冲区只定义了十个字节的大小, 为什么为它分配了十二个字
节? 原因是: 内存的分配是以四字节为单位的.所以十个字节(4+4+2)要用三个内存分
配单元, 3*4=12.
如果我们在命令行提供的字串长度为十(多两个字符, 刚好是程序中定义的缓冲区的大
小), 那么内存地址0xbffff6a8所指向的四个字节的内容将是bf004141; 如果增加到十
一个, 内存地址0xbffff6a8所指向的四个字节的内容为00414141, 刚好填满栈帧中分配
给buf的内存空间. 可以看出, 在命令行中提供的字串长度小于12, 程序是不会出错的.
现在让我们看看字串长度等于十二的情况, 这时0xbffff6a8所指向的四个字节的内存单
元已被41414141填满.0xbffff6ac所指向的四个字节的内存单元的低字节被00所填, 其内
容变为bffff600, 从上面的影像图可知: 这个内存单元里保存的是调用函数的ebp. 也就
是说, 当字串长度大于或等于十二时, 调用函数的ebp被复盖.
从进程的影像图可以看出, 要想全面复盖vulFunc函数的返回地址, 则字节串的长度至少
要二十(12+8)个字节.
我们继续分析后面的指令:
0x8048413
可以看到, 在调用strcpy前, 依次压了s和buf的地址入栈, 现在这条指令是把这两个地址抛弃.
所以可以得出, Linux x86系统在调用函数时(其实是编译器所生成的机器指令), 所传给
被调用函数的参数是由调用函数从右到左依次入栈的.
如现在的strcpy(buf, s), 首先是s先入栈, 然后是buf. 参数的出栈也由调用函数负责.
0x8048416
0x8048419
这两条指令和前面的一样, 把argv[1](即""AAAAAAAA""字串)的起始地址入栈.
0x804841a
先看一下0x80484b0里面放的是什么, 虽然很明显是即将调用的printf函数的第一个参数的地址.
(gdb) x/1s 0x80484b0
0x80484b0 <_IO_stdin_used+4>: ""String=%s ""
果然是.
下面的两条指令就是调用printf函数和抛弃在栈中的两个参数了.
0x804841f
0x8048424
我们在0x08048427 leave 指令的前面设个断点并继续运行.
(gdb) b *0x8048427
Breakpoint 5 at 0x8048427
(gdb) c
Continuing.
String=AAAAAAAA
Breakpoint 5, 0x8048427 in vulFunc ()
屏幕输出了""String=AAAAAAAA"".
这时栈帧的内容为:
(gdb) x/15x $esp
0xbffff6a0: 0x41414141 0x41414141 0xbffff600 0xbffff6b8
0xbffff6b0: 0x08048443 0xbffff856 0xbffff6d8 0x400349cb
0xbffff6c0: 0x00000002 0xbffff704 0xbffff710 0x40013868
0xbffff6d0: 0x00000002 0x08048350 0x00000000
进程在内存中的相关影像为:
(内存高址)
| ...... |
+--------+
|00000000|
0xbffff6d8 +--------+ <-- 调用main函数前的ebp
|08048350|
+--------+
|00000002|
+--------+
|40013868|
+--------+
|bffff710|
+--------+
|bffff704| argv的地址(即argv[0]的地址)
0xbffff6c4 +--------+
|00000002| argc的值
0xbffff6c0 +--------+
|400349cb|
0xbffff6bc +--------+ <-- 调用main函数前的esp
|bffff6d8| 调用main函数前的ebp
0xbffff6b8 +--------+ <-- main函数的ebp
|bffff856| 字符串""AAAAAAAA""在内存中的起始地址
0xbffff6b4 +--------+
|08048443| vulFunc函数的返回地址
0xbffff6b0 +--------+ <-- 调用vulFunc函数前的esp
|bffff6b8| main函数的ebp
0xbffff6ac +--------+ <-- vulFunc函数的ebp
|bffff600|
0xbffff6a8 +--------+
|41414141|
0xbffff6a4 +--------+
|41414141|
0xbffff6a0 +--------+ <-- vulFunc的当前esp
|bffff856| (垃圾) 字符串""AAAAAAAA""在内存中的起始地址
0xbffff69c +--------+
|bffff6a0| (垃圾) vulFunc函数栈帧中分配的十二个字节起始地址
0xbffff698 +--------+
| ...... |
(内存低址)
各寄存器的状况:
(gdb) i reg
eax 0x10 16
ecx 0x400 1024
edx 0x4010a980 1074833792
ebx 0x4010c1ec 1074840044
esp 0xbffff6a0 -1073744224
ebp 0xbffff6ac -1073744212
esi 0x4000ae60 1073786464
edi 0xbffff704 -1073744124
eip 0x8048427 134513703
eflags 0x296 662
(以下省略)
...
请注意: 此时esp的内容为0xbffff6a0, ebp的内容为0xbffff6ac
单步运行leave指令, 然后看一下寄存器的情况.
(gdb) si
0x8048428 in vulFunc ()
(gdb) i reg
eax 0x10 16
ecx 0x400 1024
edx 0x4010a980 1074833792
ebx 0x4010c1ec 1074840044
esp 0xbffff6b0 -1073744208
ebp 0xbffff6b8 -1073744200
esi 0x4000ae60 1073786464
edi 0xbffff704 -1073744124
eip 0x8048428 134513704
eflags 0x396 918
(以下省略)
...
此时的esp的内容为0xbffff6b0, 即执行leave指令前的ebp内容0xbffff6ac+4;
ebp的内容为0xbffff6b8, 这个值从那来的呢? 看一下此时进程在内存中的影像, 正好是
vulFunc函数的ebp指向的内存的内容, 而随着这个值的出栈, esp的值正好为0xbffff6b0.
由此可见, leave指令其实等价于
mov %ebp,%esp
pop %ebp
这两条指令, 正好和刚进入被调用函数时
push %ebp
mov %esp,%ebp
这两条指令的功能相反.
也就是说leave指令抛弃了被调用函数的栈帧, 恢复了调用函数的栈帧.
此时栈中相关的内容:
(gdb) x/11x $esp
0xbffff6b0: 0x08048443 0xbffff856 0xbffff6d8 0x400349cb
0xbffff6c0: 0x00000002 0xbffff704 0xbffff710 0x40013868
0xbffff6d0: 0x00000002 0x08048350 0x00000000
进程在内存中的相关影像:
(内存高址)
| ...... |
+--------+
|00000000|
0xbffff6d8 +--------+ <-- 调用main函数前的ebp
|08048350|
+--------+
|00000002|
+--------+
|40013868|
+--------+
|bffff710|
+--------+
|bffff704| argv的地址(即argv[0]的地址)
0xbffff6c4 +--------+
|00000002| argc的值
0xbffff6c0 +--------+
|400349cb|
0xbffff6bc +--------+ <-- 调用main函数前的esp
|bffff6d8| 调用main函数前的ebp
0xbffff6b8 +--------+ <-- main函数的ebp
|bffff856| 字符串""AAAAAAAA""在内存中的起始地址
0xbffff6b4 +--------+
|08048443| vulFunc函数的返回地址
0xbffff6b0 +--------+ <-- 当前esp
|bffff6b8| (垃圾) main函数的ebp
0xbffff6ac +--------+
|bffff600| (垃圾)
0xbffff6a8 +--------+
|41414141| (垃圾)
0xbffff6a4 +--------+
|41414141| (垃圾)
0xbffff6a0 +--------+
|bffff856| (垃圾) 字符串""AAAAAAAA""在内存中的起始地址
0xbffff69c +--------+
|bffff6a0| (垃圾) vulFunc函数栈帧中分配的十二个字节起始地址
0xbffff698 +--------+
| ...... |
(内存低址)
继续执行下条指令: ret
(gdb) si
0x8048443 in main ()
(gdb) i reg
eax 0x10 16
ecx 0x400 1024
edx 0x4010a980 1074833792
ebx 0x4010c1ec 1074840044
esp 0xbffff6b4 -1073744204
ebp 0xbffff6b8 -1073744200
esi 0x4000ae60 1073786464
edi 0xbffff704 -1073744124
eip 0x8048443 134513731
eflags 0x396 918
(以下省略)
...
可以看出, 从栈中弹出0x8048443(vulFunc函数调用的返回地址)给了eip.
至此vulFunc函数调用完毕, 返回到main函数继续执行.
值得注意的是: 如果象上面所说的, 我们输入的字串长度为二十个''A''--刚好复盖完0xbffff6b0
所指的单元, 那么此时从栈中弹出给eip的内容将是0x41414141, 而不是0x8048443, 程序
将跳到0x41414141去执行那里的指令, 由于0x41414141对于当前进程来说是不可访问的,
所以导致段出错(Segmentation fault), 进程停止执行.
这是我们的第三个焦点.
如果我们能计算好位移(offset), 用我们准备好的代码的入口地址来覆盖0xbffff6b0所
指的单元, 那么从栈中弹出给eip的内容就是我们的代码的入口地址, 程序将跳到我们的
代码去继续执行.
分析到这里, 我们已经清楚了C语言函数调用的机制了. main函数的后续指令对于我们的
分析已无关紧要. 但是为了保持文章的完整, 我们继续再往下看看.
此时栈的情况:
(gdb) x/10x $esp
0xbffff6b4: 0xbffff856 0xbffff6d8 0x400349cb 0x00000002
0xbffff6c4: 0xbffff704 0xbffff710 0x40013868 0x00000002
0xbffff6d4: 0x08048350 0x00000000
进程在内存中的相关影像:
(内存高址)
| ...... |
+--------+
|00000000|
0xbffff6d8 +--------+ <-- 调用main函数前的ebp
|08048350|
+--------+
|00000002|
+--------+
|40013868|
+--------+
|bffff710|
+--------+
|bffff704| argv的地址(即argv[0]的地址)
0xbffff6c4 +--------+
|00000002| argc的值
0xbffff6c0 +--------+
|400349cb|
0xbffff6bc +--------+ <-- 调用main函数前的esp
|bffff6d8| 调用main函数前的ebp
0xbffff6b8 +--------+ <-- main函数的ebp
|bffff856| 字符串""AAAAAAAA""在内存中的起始地址
0xbffff6b4 +--------+ <-- 当前esp
|08048443| (垃圾) vulFunc函数的返回地址
0xbffff6b0 +--------+
|bffff6b8| (垃圾) main函数的ebp
0xbffff6ac +--------+
|bffff600| (垃圾)
0xbffff6a8 +--------+
|41414141| (垃圾)
0xbffff6a4 +--------+
|41414141| (垃圾)
0xbffff6a0 +--------+
|bffff856| (垃圾) 字符串""AAAAAAAA""在内存中的起始地址
0xbffff69c +--------+
|bffff6a0| (垃圾) vulFunc函数栈帧中分配的十二个字节起始地址
0xbffff698 +--------+
| ...... |
(内存低址)
再看看后续的指令做了些什么?
0x8048443
0x8048446
0x8048448
; 入口(即argc!=2的情况)
; 把ebp+0xc所指向的内存单元的
; 内容赋给eax, 从上面的分析我
; 们知道里面放的是argv的地址
0x804844b
; 的内容赋给edx, 我们知道argv
; 是个数组, argv的值就是argv[0]
0x804844d
; argv[0]其实是个地址值.
0x804844e
; 以上为调用printf函数准备参数.
0x8048453
0x8048458
0x804845b
0x804845c
估计0x80484bb指向的是printf函数的format字串, 看看是不是?
(gdb) x/1s 0x80484bb
0x80484bb <_IO_stdin_used+15>: ""Usage: %s ""
果然是. 那从0x8048448到0x8048458这段指令就是C语言
printf(""Usage: %s "", argv[0]);
的等价汇编语句了.
我们把断点设到0x804845b, 再继续执行.
(gdb) b *0x804845b
Breakpoint 6 at 0x804845b
(gdb) c
Continuing.
Breakpoint 6, 0x804845b in main ()
下一条指令是leave, 应该是恢复调用函数的函数的栈帧.
单步执行一下, 看看寄存器及栈的情况.
(gdb) si
0x804845c in main ()
(gdb) i reg
eax 0x10 16
ecx 0x400 1024
edx 0x4010a980 1074833792
ebx 0x4010c1ec 1074840044
esp 0xbffff6bc -1073744196
ebp 0xbffff6d8 -1073744168
esi 0x4000ae60 1073786464
edi 0xbffff704 -1073744124
eip 0x804845c 134513756
eflags 0x386 902
(以下省略)
...
(gdb) x/8x $esp
0xbffff6bc: 0x400349cb 0x00000002 0xbffff704 0xbffff710
0xbffff6cc: 0x40013868 0x00000002 0x08048350 0x00000000
下一条指令是ret, 我们知道栈顶放的是main函数的返回地址(0x400349cb).
此时进程在内存中的相关影像:
(内存高址)
| ...... |
+--------+
|00000000|
0xbffff6d8 +--------+ <-- 调用main函数前的ebp
|08048350|
+--------+
|00000002|
+--------+
|40013868|
+--------+
|bffff710|
+--------+
|bffff704| argv的地址(即argv[0]的地址)
0xbffff6c4 +--------+
|00000002| argc的值
0xbffff6c0 +--------+
|400349cb| main函数的返回地址
0xbffff6bc +--------+ <-- 当前esp
|bffff6d8| (垃圾) 调用main函数前的ebp
0xbffff6b8 +--------+
|bffff856| (垃圾) 字符串""AAAAAAAA""在内存中的起始地址
0xbffff6b4 +--------+
|08048443| (垃圾) vulFunc函数的返回地址
0xbffff6b0 +--------+
|bffff6b8| (垃圾) main函数的ebp
0xbffff6ac +--------+
|bffff600| (垃圾)
0xbffff6a8 +--------+
|41414141| (垃圾)
0xbffff6a4 +--------+
|41414141| (垃圾)
0xbffff6a0 +--------+
|bffff856| (垃圾) 字符串""AAAAAAAA""在内存中的起始地址
0xbffff69c +--------+
|bffff6a0| (垃圾) vulFunc函数栈帧中分配的十二个字节起始地址
0xbffff698 +--------+
| ...... |
(内存低址)
再单步执行, 返回到调用main函数的函数
(gdb) si
0x400349cb in __libc_start_main (main=0x804842c
fini=0x804848c <_fini>, rtld_fini=0x4000ae60 <_dl_fini>, stack_end=0xbffff6fc)
at ../sysdeps/generic/libc-start.c:92
92 ../sysdeps/generic/libc-start.c: No such file or directory.
原来是 __libc_start_main 函数调用了我们的main函数, 看来和概述里说的有些出入,
但这对于我们来讲不是很重要. 如果想看源码, 请到../sysdeps/generic/libc-start.c
文件中找.
(gdb) x/16x $esp
0xbffff6c0: 0x00000002 0xbffff704 0xbffff710 0x40013868
0xbffff6d0: 0x00000002 0x08048350 0x00000000 0x08048371
0xbffff6e0: 0x0804842c 0x00000002 0xbffff704 0x080482c0
0xbffff6f0: 0x0804848c 0x4000ae60 0xbffff6fc 0x40013e90
从上面可以看到, stack_end=0xbffff6fc, 也就是说我们的进程的栈底地址为0xbffff6fc,
在调用__libc_start_main函数前依次推了如下七个参数入栈:
0xbffff6fc -> 进程的栈底
0x4000ae60 -> _dl_fini函数的人口地址.
0x0804848c -> _fini函数的入口地址
0x080482c0 -> _init函数的入口地址
0xbffff704 -> argv命令行参数地址的地址
0x00000002 -> argc命令行参数个数值
0x0804842c -> 我们的main函数入口
从上面的分析可推出, 在内存地址0xbffff6dc的内容0x08048371就是__libc_start_main函数
的返回地址了.
我们来看看是什么函数调用了__libc_start_main.
(gdb) disas 0x08048371
Dump of assembler code for function _start:
0x8048350 <_start>: xor %ebp,%ebp
0x8048352 <_start+2>: pop %esi
0x8048353 <_start+3>: mov %esp,%ecx
0x8048355 <_start+5>: and $0xfffffff8,%esp
0x8048358 <_start+8>: push %eax
0x8048359 <_start+9>: push %esp
0x804835a <_start+10>: push %edx
0x804835b <_start+11>: push $0x804848c
0x8048360 <_start+16>: push $0x80482c0
0x8048365 <_start+21>: push %ecx
0x8048366 <_start+22>: push %esi
0x8048367 <_start+23>: push $0x804842c
0x804836c <_start+28>: call 0x8048320 <__libc_start_main>
0x8048371 <_start+33>: hlt
0x8048372 <_start+34>: nop
0x8048373 <_start+35>: nop
(省略以下的nop)
End of assembler dump.
原来是_start函数调用了__libc_start_main函数.
至于_start函数调用__libc_start_main函数后, 接是如何调用_init函数和_dl_runtime_resove
函数来调用共享库函数和我们的main函数然后退出的, 已经远远脱离了本文的主题, 这里不再继
续介绍.
(gdb) x/1024x 0xbffff6f0
0xbffff6f0: 0x0804848c 0x4000ae60 0xbffff6fc 0x40013e90
0xbffff700: 0x00000002 0xbffff83e 0xbffff856 0x00000000
0xbffff710: 0xbffff85f 0xbffff8