(续)
by 黑猫(virtualcat@hotmai.com)
值得注意的是: 如果象上面所说的, 我们输入的字串长度为二十个'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 : add $0x4,%esp ; 抛弃栈中为被调用函数准备的参数.
0x8048446 : jmp 0x804845b ; 跳转到0x804845b继续执行
0x8048448 : mov 0xc(%ebp),%eax ; 0x8048433 jne的条件判断跳转
; 入口(即argc!=2的情况)
; 把ebp+0xc所指向的内存单元的
; 内容赋给eax, 从上面的分析我
; 们知道里面放的是argv的地址
0x804844b : mov (%eax),%edx ; 把eax指向的地址的内存单元里
; 的内容赋给edx, 我们知道argv
; 是个数组, argv的值就是argv[0]
0x804844d : push %edx ; 把argv[0]入栈. 注意这里的
; argv[0]其实是个地址值.
0x804844e : push $0x80484bb ; 把常数0x80484bb入栈
; 以上为调用printf函数准备参数.
0x8048453 : call 0x8048330 ; 调用printf函数
0x8048458 : add $0x8,%esp ; 抛弃为调用printf函数准备的参数
0x804845b : leave ; 恢复调用main函数的函数的栈帧
0x804845c : ret ; 返回到调用main函数的函数
估计0x80484bb指向的是printf函数的format字串, 看看是不是?
(gdb) x/1s 0x80484bb
0x80484bb <_IO_stdin_used+15>: "Usage: %s \n"
果然是. 那从0x8048448到0x8048458这段指令就是C语言
printf("Usage: %s \n", 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 , argc=2, argv=0xbffff704, init=0x80482c0 <_init>,
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函数, 看来和概述里说的有些出入,
但这对于我们来讲不是很重要. 如果想看[url=http://www.pccode.net].net" class="wordstyle"源码, 请到../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 0xbffff881 0xbffff88f 0xbffff89e
0xbffff720: 0xbffff8c4 0xbffff8d2 0xbffff900 0xbffff91a
0xbffff730: 0xbffff932 0xbffff94d 0xbffff9a8 0xbffff9df
0xbffff740: 0xbffffaf3 0xbffffb06 0xbffffb11 0xbffffb31
0xbffff750: 0xbffffb5a 0xbffffb68 0xbffffc72 0xbffffc7e
0xbffff760: 0xbffffc8f 0xbffffca4 0xbffffcb4 0xbffffcbf
0xbffff770: 0xbffffcd7 0xbffffcf5 0xbffffd0e 0xbffffd19
0xbffff780: 0xbffffd23 0xbffffd6c 0xbffffd79 0xbffffda0
0xbffff790: 0xbffffdb2 0xbffffdc1 0xbffffde6 0xbffffe08
0xbffff7a0: 0xbffffe10 0xbfffffd3 0x00000000 0x00000003
0xbffff7b0: 0x08048034 0x00000004 0x00000020 0x00000005
0xbffff7c0: 0x00000006 0x00000006 0x00001000 0x00000007
0xbffff7d0: 0x40000000 0x00000008 0x00000000 0x00000009
0xbffff7e0: 0x08048350 0x0000000b 0x000001f5 0x0000000c
0xbffff7f0: 0x000001f5 0x0000000d 0x00000004 0x0000000e
0xbffff800: 0x00000004 0x00000010 0x008001bf 0x0000000f
0xbffff810: 0xbffff839 0x00000000 0x00000000 0x00000000
0xbffff820: 0x00000000 0x00000000 0x00000000 0x00000000
0xbffff830: 0x00000000 0x00000000 0x38356900 0x682f0036
0xbffff840: 0x2f656d6f 0x65776f74 0x74742f72 0x2f737775
0xbffff850: 0x2f6c6469 0x41410070 0x41414141 0x4c004141
0xbffff860: 0x4f535345 0x3d4e4550 0x73752f7c 0x69622f72
...
(省略)
...
0xbfffffd0: 0x54003a35 0x75413d5a 0x61727473 0x2f61696c
0xbfffffe0: 0x0057534e 0x6d6f682f 0x6f742f65 0x2f726577
0xbffffff0: 0x77757474 0x64692f73 0x00702f6c 0x00000000
0xc0000000: Cannot access memory at address 0xc0000000
我们知道内存单元0xbffff704放的是指argv[0]的地址, 那么0xbffff708放的就是argv[1]
的地址了. 0xbffff700里放的是argc的值.
那么0xbffff710里放的是什么呢? 看样子象是指向字符串的地址, 让我们来看看.
(gdb) x/1s 0xbffff85f
0xbffff85f: "LESSOPEN=|/usr/bin/lesspipe.sh %s"
(gdb)
0xbffff881: "HISTSIZE=1000"
...
再看看最后一个.
(gdb) x/1s 0xbfffffd3
0xbfffffd3: "TZ=Australia/NSW"
0xc0000000以后的地址空间已不是进程能合法访问的了.
原来都是些SHELL的环境变量字符串.
这一片东西是从内存地址0xbffff839开始的, 让我们再看看.
(gdb) x/1s 0xbffff839
0xbffff839: "i586"
(gdb)
0xbffff83e: "/home/vcat/p" ===> 细心的朋友会发现这里已被俺改掉了,
让俺保留一点私隐吧 ;)
(gdb)
0xbffff856: "AAAAAAAA"
(gdb)
0xbffff85f: "LESSOPEN=|/usr/bin/lesspipe.sh %s"
...
我们得出结论: 0xbffff700放的是argc的值; 0xbffff704放的是argv[0]的地址,
0xbffff708放的是argv[1]的地址; 0xbffff710--0xbffffa4放的是指向各个环境变量
字符串起始地址的指针; 从内存地址0xbffff839开始依次存放的是: 系统平台信息字
串; 命令行字串; 环境变量字串.
至于0xbffff7a8--0xbffff838里放的是什么, 还有待研究. 由于对本文不是至关重要,
暂时放一下.
分析到这, 我们来组合一下进程在内存的影像:
(内存高址)
| ...... | ...省略了一些我们不需要关心的区
+--------+
|00000000|\
0xbffffffc +--------+ \
| ...... | \
\
| ...... | \
0xbffff844 +--------+ 系统平台信息串(如:"i586")和命令行参数及环境变量字串
|2f656d6f| /
0xbffff840 +--------+ /
|682f0036| /
0xbffff83c +--------+ /
|38356900|/ --> 从内存地址0xbffff839开始, 0x69353836="i586"
0xbffff838 +--------+
| ...... |\
里面放