编写自己的缓冲区溢出利用程序

2008-03-08 10:35:42  作者
内容:本文主要讲解有关BufferOverflow的原理,以及结合实战范例介绍LinuxSolaris下的漏洞利用.
本文并不介绍如何编写shellcode.

要求:读者要有一点C和汇编语言基础.

目标:希望本文能够尽量做到通熟易懂,使得稍有计算机基础知识的朋友看后能够亲自动手写自己的EXPloit
假如你觉得自己对这些都懂了,就请不要再往下看了.


第一部份概述篇

1.Bufferoverflow是如何产生的?
所谓Bufferoverflow,中文译为缓冲区溢出.顾名思意,就是说所用的缓冲区太小了,以至装不下
那么多的东西,多出来的东西跑出来了.就好象是水缸装不了那么多的水,硬倒太多会溢出来一样;)
那么,在编程过程中为什么要用到buffer(缓冲区)呢?简单的回答就是做为数据处理的中转站.

2.UNIX下C语言函数调用的机制及缓冲区溢出的利用.
1)进程在内存中的影像.
我们假设现在有一个程序,它的函数调用顺序如下.
main(...)-%26gt;func_1(...)-%26gt;func_2(...)-%26gt;func_3(...)
即:主函数main调用函数func_1;函数func_1调用函数func_2;函数func_2调用函数func_3

当程序被操作系统调入内存运行,其相对应的进程在内存中的影像如下图所示.

(内存高址)
+--------------------------------------+
|......|...省略了一些我们不需要关心的区
+--------------------------------------+
|envstrings(环境变量字串)|\
+--------------------------------------+\
|argvstrings(命令行字串)|\
+--------------------------------------+\
|envpointers(环境变量指针)|SHELL的环境变量和命令行参数保存区
+--------------------------------------+/
|argvpointers(命令行参数指针)|/
+--------------------------------------+/
|argc(命令行参数个数)|/
+--------------------------------------+
|main函数的栈帧|\
+--------------------------------------+\
|func_1函数的栈帧|\
+--------------------------------------+\
|func_2函数的栈帧|\
+--------------------------------------+\
|func_3函数的栈帧|Stack(栈)
+......................................+/
||/
....../
||/
+......................................+/
|Heap(堆)|/
+--------------------------------------+
|Uninitialised(BSS)data|非初始化数据(BSS)区
+--------------------------------------+
|Initialiseddata|初始化数据区
+--------------------------------------+

|Text|文本区
+--------------------------------------+
(内存低址)

这里需要说明的是:
i)随着函数调用层数的增加,函数栈帧是一块块地向内存低地址方向延伸的.
随着进程中函数调用层数的减少,即各函数调用的返回,栈帧会一块块地
被遗弃而向内存的高址方向回缩.
各函数的栈帧大小随着函数的性质的不同而不等,由函数的局部变量的数目决定.
ii)进程对内存的动态申请是发生在Heap(堆)里的.也就是说,随着系统动态分
配给进程的内存数量的增加,Heap(堆)有可能向高址或低址延伸,依靠于不
同CPU的实现.但一般来说是向内存的高地址方向增长的.
iii)在BSS数据或者Stack(栈)的增长耗尽了系统分配给进程的自由内存的情况下,
进程将会被阻塞,重新被操作系统用更大的内存模块来调度运行.
(虽然和exploit没有关系,但是知道一下还是有好处的)
iv)函数的栈帧里包含了函数的参数(至于被调用函数的参数是放在调用函数的栈
帧还是被调用函数栈帧,则依靠于不同系统的实现),
它的局部变量以及恢复调用该函数的函数的栈帧(也就是前一个栈帧)所需要的
数据,其中包含了调用函数的下一条执行指令的地址.
v)非初始化数据(BSS)区用于存放程序的静态变量,这部分内存都是被初始化为零的.
初始化数据区用于存放可执行文件里的初始化数据.
这两个区统称为数据区.
vi)Text(文本区)是个只读区,任何尝试对该区的写操作会导致段违法出错.文本区
是被多个运行该可执行文件的进程所共享的.文本区存放了程序的代码.

2)函数的栈帧.
函数调用时所建立的栈帧包含了下面的信息:
i)函数的返回地址.返回地址是存放在调用函数的栈帧还是被调用函数的栈帧里,
取决于不同系统的实现.
ii)调用函数的栈帧信息,即栈顶和栈底.
iii)为函数的局部变量分配的空间
iv)为被调用函数的参数分配的空间--取决于不同系统的实现.

3)缓冲区溢出的利用.
从函数的栈帧结构可以看出:
由于函数的局部变量的内存分配是发生在栈帧里的,所以假如我们在某一个函数里定义
了缓冲区变量,则这个缓冲区变量所占用的内存空间是在该函数被调用时所建立的栈帧里.

由于对缓冲区的潜在操作(比如字串的复制)都是从内存低址到高址的,而内存中所保存
的函数调用返回地址往往就在该缓冲区的上方(高地址)--这是由于栈的特性决定的,这
就为复盖函数的返回地址提供了条件.当我们有机会用大于目标缓冲区大小的内容来向
缓冲区进行填充时,就有可以改写函数保存在函数栈帧中的返回地址,从而使程序的执
行流程随着我们的意图而转移.换句话来说,进程接受了我们的控制.我们可以让进程
改变原来的执行流程,去执行我们预备好的代码.

这是冯.诺曼计算机体系结构的缺陷.

下面是缓冲区溢出利用的示意图:
i)函数对字串缓冲区的操作,方向一般都是从内存低址向高址的.
如:strcpy(s,"AAA.....");

ss+1s+2s+3...
+---+---+---+--------+---+...+
(内存低址)|A|A|A|......|A|...|(内存高址)
+---+---+---+--------+---+...+

ii)函数返回地址的复盖

/|......|(内存高址)
/+--------------------+
调用函数栈帧|0x41414141|
\+--------------------+
\|0x41414141|调用函数的返回地址
\+--------------------+
/|......|
/+--------------------+s+8
/|0x41414141|
/+--------------------+s+4
被调用函数栈帧|0x41414141|
\+--------------------+s
\|0x41414141|
\+--------------------+
\|......|
+....................+
|......|(内存低址)

注:字符A的十六进制ASCII码值为0x41.

iii)从上图可以看出:假如我们用的是进程可以访问的某个地址而不是0x41414141

来改写调用函数的返回地址,而这个地址正好是我们预备好的代码的入口,那么
进程将会执行我们的代码.否则,假如用的是进程无法访问的段的地址,将会导
致进程崩馈--SegmentFaultCoredumped(段出错内核转储);假如该地址处有
无效的机器指令数据,将会导致非法指令(IlligalInstrUCtion)错误,等等.

4)缓冲区在Heap(堆)区或BBS区的情况
i)假如缓冲区的内存空间是在函数里通过动态申请得到的(如:用malloc()函数申请),那
么在函数的栈帧中只是分配了存放指向Heap(堆)中相应申请到的内存空间的指针.这种
情况下,溢出是发生在(Heap)堆中的,想要复盖相应的函数返回地址,看来几乎是不可
能的.这种情况的利用可能性要看具体情形,但不是不可能的.
ii)假如缓冲区在函数中定义为静态(static),则缓冲区内存空间的位置在非初始化(BBS)区,
和在Heap(堆)中的情况差不多,利用是可能的.但还有一种特姝情况,就是可以利用它来
复盖函数指针,让进程后来调用相应的函数变成调用我们所指定的代码.


3.从缓冲区溢出的利用可以得到什么?
从上文我们看到,缓冲区溢出的利用可以使我们能够改写相关内存的内容及函数的返回地址,从而
改变代码的执行流程,让进程去执行我们预备好的代码.

但是,进程是以我们当前登录的用户身份来运行的.能够执行我们预备好的代码又怎样呢?我们还
是无法突破系统对当前用户的权限设置,无法干超越权限的事.

换句话来说,要想利用缓冲区溢出得到更高的权限,我们还得利用系统的一些特性.

对于UNIX来讲,有两个特性可以利用.
i)SUID及SGID程序
UNIX是答应其他用户可以以某个可执行文件的文件拥有者的用户ID或用户组ID的身份来执行该
文件的,这是通过设置该可执行文件的文件属性为SUID或SGID来实现的.
也就是说假如某个可执行文件被设了SUID或SGID,那么当系统中其他用户执行该文件时就相当
于以该文件属主的用户或用户组身份来执行该文件.
假如某个可执行文件的属主是root,而这个文件被设了SUID,那么假如该可执行文件存在可利
用的缓冲区溢出漏洞,我们就可以利用它来以root的身份执行我们预备好的代码.没有比让它
为我们产生一个具有超级用户root身份的SHELL更吸引人了,是不是?

ii)各种端口守护(服务)进程
UNIX中有不少守护(服务)进程是以root的身份运行的,假如这些程序存在可利用的缓冲区溢出,
那么我们就可以让它们以当前运行的用户身份--root去执行我们预备被好的代码.
由于守护进程已经以root的身份在运行,我们并不需要相对应的可执行文件为SUID或SGID属性.
又由于此类利用通常是从远程机器上向目标机器上的端口发送有恶意的数据造成的,所以叫做
"远程溢出"利用.

4.一个有问题的程序
以下例程纯属虚构,如有雷同,纯属巧合.

/*
*文件名:p.c
*编译:gcc-opp.c
*/

#include%26lt;stdio.h%26gt;

voidvulFunc(char*s)
{
charbuf[10];
strcpy(buf,s);
printf("String=%s\n",buf);
}

main(intargc,char*argv[])
{
if(argc==2)
{
vulFunc(argv[1]);
}
else
{
printf("Usage:%s%26lt;Astring%26gt;\n",argv[0]);
}

}

这个例程接受用户在命令行的字串输入,然后在标准输出(屏幕)上打印出来.我们可以看出在
vulFunc()这个函数里,定义了一个最多可以装十个字符的缓冲区buf.假如我们在命令行输入
小于等于十个字符的字串,则一切都很正常.但是,假如我们输入的字串长度大于十呢?情况
会怎样?缓冲区太小装不下了,所以溢出了?答案有待于具体分析一下才知道.

对于这个程序在不同操作系统下的分析和模拟攻击.请看第二部份基楚篇



第二部份基楚篇
5.Linuxx86平台
本文使用了如下Linux平台:
RedHatLinuxrelease6.2(Zoot)
Kernel2.2.14-12onani586

所使用的编译器及版本:
bash$gcc-v
Readingspecsfrom/usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/specs
gccversionegcs-2.91.6619990314/Linux(egcs-1.1.2release)

注重:不同版本的编译器编译相同代码所生成的机器指令可能不同.

1)例程p.c在Linuxx86平台下的剖析.
i)首先我们编译p.c并用gdb对相关函数进行反汇编
结果见如下清单:

bash$gcc-opp.c
bash$gdbp
GNUgdb19991004
Copyright1998FreeSoftwareFoundation,Inc.
GDBisfreesoftware,coveredbytheGNUGeneralPublicLicense,andyouare
welcometochangeitand/ordistributecopiesofitundercertainconditions.
Type"showcopying"toseetheconditions.
ThereisabsolutelynowarrantyforGDB.Type"showwarranty"fordetails.
ThisGDBwasconfiguredas"i386-redhat-linux"...
(gdb)disasmain
Dumpofassemblercodeforfunctionmain:
0x804842c%26lt;main%26gt;:push%ebp
0x804842d%26lt;main+1%26gt;:mov%esp,%ebp
0x804842f%26lt;main+3%26gt;:cmpl$0x2,0x8(%ebp)
0x8048433%26lt;main+7%26gt;:jne0x8048448%26lt;main+28%26gt;
0x8048435%26lt;main+9%26gt;:mov0xc(%ebp),%eax
0x8048438%26lt;main+12%26gt;:add$0x4,%eax
0x804843b%26lt;main+15%26gt;:mov(%eax),%edx
0x804843d%26lt;main+17%26gt;:push%edx
0x804843e%26lt;main+18%26gt;:call0x8048400%26lt;vulFunc%26gt;
0x8048443%26lt;main+23%26gt;:add$0x4,%esp
0x8048446%26lt;main+26%26gt;:jmp0x804845b%26lt;main+47%26gt;
0x8048448%26lt;main+28%26gt;:mov0xc(%ebp),%eax
0x804844b%26lt;main+31%26gt;:mov(%eax),%edx
0x804844d%26lt;main+33%26gt;:push%edx
0x804844e%26lt;main+34%26gt;:push$0x80484bb
0x8048453%26lt;main+39%26gt;:call0x8048330%26lt;printf%26gt;
0x8048458%26lt;main+44%26gt;:add$0x8,%esp
0x804845b%26lt;main+47%26gt;:leave
0x804845c%26lt;main+48%26gt;:ret
0x804845d%26lt;main+49%26gt;:nop
0x804845e%26lt;main+50%26gt;:nop
0x804845f%26lt;main+51%26gt;:nop
Endofassemblerdump.

(gdb)disasvulFunc
DumpofassemblercodeforfunctionvulFunc:
0x8048400%26lt;vulFunc%26gt;:push%ebp
0x8048401%26lt;vulFunc+1%26gt;:mov%esp,%ebp
0x8048403%26lt;vulFunc+3%26gt;:sub$0xc,%esp
0x8048406%26lt;vulFunc+6%26gt;:mov0x8(%ebp),%eax
0x8048409%26lt;vulFunc+9%26gt;:push%eax
0x804840a%26lt;vulFunc+10%26gt;:lea0xfffffff4(%ebp),%eax
0x804840d%26lt;vulFunc+13%26gt;:push%eax
0x804840e%26lt;vulFunc+14%26gt;:call0x8048340%26lt;strcpy%26gt;
0x8048413%26lt;vulFunc+19%26gt;:add$0x8,%esp
0x8048416%26lt;vulFunc+22%26gt;:lea0xfffffff4(%ebp),%eax
0x8048419%26lt;vulFunc+25%26gt;:push%eax
0x804841a%26lt;vulFunc+26%26gt;:push$0x80484b0
0x804841f%26lt;vulFunc+31%26gt;:call0x8048330%26lt;printf%26gt;
0x8048424%26lt;vulFunc+36%26gt;:add$0x8,%esp
0x8048427%26lt;vulFunc+39%26gt;:leave
0x8048428%26lt;vulFunc+40%26gt;:ret
0x8048429%26lt;vulFunc+41%26gt;:lea0x0(%esi),%esi
Endofassemblerdump.


这里我们只对所关心的main和vulFunc两个函数进行反汇编分析.

ii)进程的运行及其在内存中的情况分析
我们用gdb来跟踪看看进程是如何在内存中运行的.

首先把程序调入.
bash$gdbp
GNUgdb19991004
Copyright1998FreeSoftwareFoundation,Inc.
GDBisfreesoftware,coveredbytheGNUGeneralPublicLicense,andyouare
welcometochangeitand/ordistributecopiesofitundercertainconditions.
Type"showcopying"toseetheconditions.
ThereisabsolutelynowarrantyforGDB.Type"showwarranty"fordetails.
ThisGDBwasconfiguredas"i386-redhat-linux"...
(gdb)

把断点设到main的第一条可执行汇编指令上
(gdb)b*0x804842c
Breakpoint1at0x804842c

运行程序
(gdb)rAAAAAAAA
Startingprogram:/home/vcat/pAAAAAAAA

Breakpoint1,0x804842cinmain()

在断点处停下来了.
看一下这时各寄存器的值
(gdb)ireg
eax0x4010b3f81074836472
ecx0x804842c134513708
edx0x4010d0981074843800
ebx0x4010c1ec1074840044
esp0xbffff6bc-1073744196
ebp0xbffff6d8-1073744168
esi0x4000ae601073786464
edi0xbffff704-1073744124

eip0x804842c134513708
eflags0x246582
cs0x2335
ss0x2b43
ds0x2b43
es0x2b43
fs0x00
gs0x00
cwd0xffff037f-64641
swd0xffff0000-65536
twd0xffffffff-1
fip0x40034d701073958256
fcs0x35d002356426531
fopo0xbfffe400-1073748992
fos0xffff002b-65493

我们这里关心的是栈底(ebp),栈顶(esp)及指令寄存器(eip).
此时,ebp的值为0xbffff6d8,esp的值为0xbffff6bc,相差28个字节.
eip的值为0x804842c,正好是我们所设的断点.
(注:这里的值可能会随着程序运行在不同的系统环境而不同)

我们再看看当前栈帧里有什么内容?
(gdb)x/8x$esp
0xbffff6bc:0x400349cb0x000000020xbffff7040xbffff710
0xbffff6cc:0x400138680x000000020x080483500x00000000

也就是说,main函数刚被调用时进程在内存中的相关部份的影像是这样的:
(内存高址)
|......|
+--------+
|00000000|
0xbffff6d8+--------+%26lt;--ebp(调用main函数前的ebp)
|08048350|
+--------+
|00000002|
+--------+
|40013868|
+--------+
|bffff710|
+--------+
|bffff704|
+--------+
|00000002|
+--------+
|400349cb|
0xbffff6bc+--------+%26lt;--esp(调用main函数前的esp)
|......|
(内存低址)


我们看看接下来的指令做了些什么?
0x804842c%26lt;main%26gt;:push%ebp;esp的值等于esp-4(因为ebp是32位);
;把ebp的值放入esp所指的32位内存单
;元(注:这里保存栈底).
0x804842d%26lt;main+1%26gt;:mov%esp,%ebp;ebp的值等于esp的值(注:这里把原来
;的栈顶做为新的栈底).

运行这两条指令,然后看一下寄存器内容和栈的情况.
(gdb)si
0x804842dinmain()
(gdb)si
0x804842finmain()
(gdb)ireg
eax0x4010b3f81074836472
ecx0x804842c134513708
edx0x4010d0981074843800
ebx0x4010c1ec1074840044
esp0xbffff6b8-1073744200
ebp0xbffff6b8-1073744200
esi0x4000ae601073786464
edi0xbffff704-1073744124
eip0x804842f134513711
eflags0x346838
cs0x2335
ss0x2b43
ds0x2b43
es0x2b43
fs0x00
gs0x00
cwd0xffff037f-64641
swd0xffff0000-65536
twd0xffffffff-1
fip0x40034d701073958256
fcs0x35d002356426531
fopo0xbfffe400-1073748992

fos0xffff002b-65493
(gdb)x/9x$esp
0xbffff6b8:0xbffff6d80x400349cb0x000000020xbffff704
0xbffff6c8:0xbffff7100x400138680x000000020x08048350
0xbffff6d8:0x00000000

此时进程的相关影像为:
(内存高址)
|......|
+--------+
|00000000|
0xbffff6d8+--------+%26lt;--调用main函数前的ebp
|08048350|
+--------+
|00000002|
+--------+
|40013868|
+--------+
|bffff710|
+--------+
|bffff704|
+--------+
|00000002|
+--------+
|400349cb|
0xbffff6bc+--------+%26lt;--调用main函数前的esp
|bffff6d8|
0xbffff6b8+--------+%26lt;--ebp,esp
|......|
(内存低址)

接下来的两条指令:
0x804842f%26lt;main+3%26gt;:cmpl$0x2,0x8(%ebp);2和ebp+8所指向的内存(32位--4
;个字节)里面所放的内容比较.
0x8048433%26lt;main+7%26gt;:jne0x8048448%26lt;main+28%26gt;;假如不等则跳到0x08048448地址
;处继续执行,否则执行下条指令.
这里我们可以看到这是C语言语句
if(argc==2)
{
...
}
else
{
...
}
的等价汇编语句.内存地址ebp+8处存放的是argc的值.
(gdb)x/x$ebp+8
0xbffff6c0:0x00000002

我们来看看在调用vulFunc函数前的指令:
0x8048435%26lt;main+9%26gt;:mov0xc(%ebp),%eax;把内存地址ebp+12处的四个字节的
;内容放到eax里.
0x8048438%26lt;main+12%26gt;:add$0x4,%eax;eax等于eax+4.
0x804843b%26lt;main+15%26gt;:mov(%eax),%edx;把eax指向的四个内存字节单元里
;的内容赋给edx
0x804843d%26lt;main+17%26gt;:push%edx;esp等于esp-4,把edx的值放到esp
;所指的内存地址的四个字节单元里.

看看ebp+12处放的是什么?
(gdb)x/x$ebp+12
0xbffff6c4:0xbffff704
怀疑这里放的是指向argv[0]字串的地址的地址,看看是不是
(gdb)x/x0xbffff704
0xbffff704:0xbffff83e
(gdb)x/1s0xbffff83e
0xbffff83e:"/home/vcat/p"
果然是.那么$ebp+12的所指的四个字节的内容(argv[0]字串的地址)加上四,应该就是指向
argv[1]字串的地址了.
(gdb)x/x0xbffff704+4
0xbffff708:0xbffff856
(gdb)x/1s0xbffff856
0xbffff856:"AAAAAAAA"

可以看出,这四条指令是用来计算argv[1](即所输入的字串"AAAAAAAA"在内存中的起始地址),
然后把该地址压入栈中做为参数传给即将被调用的函数vulFunc的.

设个断点在0x804843e,让程序继续执行到调用vulFunc函数之前.
(gdb)b*0x804843e
Breakpoint2at0x804843e
(gdb)c
Continuing.

(gdb)ireg
eax0xbffff708-1073744120
ecx0x804842c134513708
edx0xbffff856-1073743786

ebx0x4010c1ec1074840044
esp0xbffff6b4-1073744204
ebp0xbffff6b8-1073744200
esi0x4000ae601073786464
edi0xbffff704-1073744124
eip0x804843e134513726
eflags0x282642
(以下省略)
...

(gdb)x/10x$esp
0xbffff6b4:0xbffff8560xbffff6d80x400349cb0x00000002
0xbffff6c4:0xbffff7040xbffff7100x400138680x00000002
0xbffff6d4:0x080483500x00000000

此时的进程在内存中的相关影像为:
(内存高址)
|......|
+--------+
|00000000|
0xbffff6d8+--------+%26lt;--调用main函数前的ebp
|08048350|
+--------+
|00000002|
+--------+
|40013868|
+--------+
|bffff710|
+--------+
|bffff704|argv的地址(即argv[0]的地址)
0xbffff6c4+--------+
|00000002|argc的值
0xbffff6c0+--------+
|400349cb|
0xbffff6bc+--------+%26lt;--调用main函数前的esp
|bffff6d8|调用main函数前的ebp
0xbffff6b8+--------+%26lt;--main函数的ebp
|bffff856|字符串"AAAAAAAA"在内存中的开始地址
0xbffff6b4+--------+%26lt;--main函数的esp
|......|
(内存低址)

单步执行
(gdb)si
0x8048400invulFunc()

好,现在进入vulFunc函数了.
(gdb)ireg
eax0xbffff708-1073744120
ecx0x804842c134513708
edx0xbffff856-1073743786
ebx0x4010c1ec1074840044
esp0xbffff6b0-1073744208
ebp0xbffff6b8-1073744200
esi0x4000ae601073786464
edi0xbffff704-1073744124
eip0x8048400134513664
eflags0x382898
(以下省略)
...

这时esp已经变为0xbffff6b0,和以前的值0xbffff6b4比较相差四个字节.
我们来看看到底压了什么东西入栈.
(gdb)x/11x$esp
0xbffff6b0:0x080484430xbffff8560xbffff6d80x400349cb
0xbffff6c0:0x000000020xbffff7040xbffff7100x40013868
0xbffff6d0:0x000000020x080483500x00000000

原来是main函数里调用vulFunc函数的指令的后续指令的地址--即vulFunc函数的返回地址.
这是我们的第一个焦点.

...
0x804843e%26lt;main+18%26gt;:call0x8048400%26lt;vulFunc%26gt;
0x8048443%26lt;main+23%26gt;:add$0x4,%esp
...

我们接着分析vulFunc函数.
0x8048400%26lt;vulFunc%26gt;:push%ebp
0x8048401%26lt;vulFunc+1%26gt;:mov%esp,%ebp
0x8048403%26lt;vulFunc+3%26gt;:sub$0xc,%esp;esp等于esp-12,栈帧大小增加12个字节.

前面两条指令的功能和main函数的一样,用来保存调用函数栈帧的栈底ebp和设置被调用函

数栈帧栈底.
即:保存调用函数的栈帧栈底,调用函数栈帧的栈顶变为被调用函数的栈底.可以看出当前
(被调用函数)的栈帧为空时,ebp和esp的值相等.


第三条指令在栈帧中分配了0xc(十二)个字节的内存空间,注重到里面的内容是垃圾.
(gdb)si
0x8048401invulFunc()
(gdb)si
0x8048403invulFunc()
(gdb)si
0x8048406invulFunc()

(gdb)x/15x$esp
0xbffff6a0:0x4000ae600xbffff7040xbffff6b80xbffff6b8
0xbffff6b0:0x080484430xbffff8560xbffff6d80x400349cb
0xbffff6c0:0x000000020xbffff7040xbffff7100x40013868
0xbffff6d0:0x000000020x080483500x00000000

此时进程在内存中相关的影像为:
(内存高址)
|......|
+--------+
|00000000|
0xbffff6d8+--------+%26lt;--调用main函数前的ebp
|08048350|
+--------+
|00000002|
+--------+
|40013868|
+--------+
|bffff710|
+--------+
|bffff704|argv的地址(即argv[0]的地址)
0xbffff6c4+--------+
|00000002|argc的值
0xbffff6c0+--------+
|400349cb|
0xbffff6bc+--------+%26lt;--调用main函数前的esp
|bffff6d8|调用main函数前的ebp
0xbffff6b8+--------+%26lt;--main函数的ebp
|bffff856|字符串"AAAAAAAA"在内存中的起始地址
0xbffff6b4+--------+
|08048443|vulFunc函数的返回地址
0xbffff6b0+--------+%26lt;--调用vulFunc函数前的esp
|bffff6b8|main函数的ebp
0xbffff6ac+--------+%26lt;--vulFunc函数的ebp
|bffff6b8|(垃圾)
0xbffff6a8+--------+
|bffff704|(垃圾)
0xbffff6a4+--------+
|4000ae60|(垃圾)
0xbffff6a0+--------+%26lt;--vulFunc的当前esp
|......|
(内存低址)

再看看下面的四条指令.
0x8048406%26lt;vulFunc+6%26gt;:mov0x8(%ebp),%eax;把ebp+8指向的内存单元(4字节)里
;的内容赋给eax.
从上图看出vulFunc函数栈帧的ebp+8四字节内存单元里放的是指向"AAAAAAAA"字符串的起始地址.

0x8048409%26lt;vulFunc+9%26gt;:push%eax;eax的值入栈.
把指向"AAAAAAAA"字符串的起始地址入栈.

0x804840a%26lt;vulFunc+10%26gt;:lea0xfffffff4(%ebp),%eax
哇!好吓人呀!这条指令是干什么的?让我们慢慢来分析一下.
这条指令是把ebp+0xfffffff4做为地址值赋给eax.
但是ebp的值加上0xfffffff4指向那里呀,这是我们要弄清楚的.
这里假如我们按正数来加,那是不行的.
实际上这个十六进制的0xfffffff4所表示的是负数,要知道它的值,让我们来算一下.

FFFFFFF4
+----+----+----+----+----+----+----+----+
|1111|1111|1111|1111|1111|1111|1111|0100|
+----+----+----+----+----+----+----+----+

取反

0000000B
+----+----+----+----+----+----+----+----+

|0000|0000|0000|0000|0000|0000|0000|1011|
+----+----+----+----+----+----+----+----+

加一

0000000C
+----+----+----+----+----+----+----+----+
|0000|0000|0000|0000|0000|0000|0000|1100|
+----+----+----+----+----+----+----+----+

也就是负的0xc.ebp+0xfffffff4,即ebp-0xc.

所以ebp+0xfffffff4,就是现在栈顶指向的那十二个字节的起始地址.

0x804840d%26lt;vulFunc+13%26gt;:push%eax
接着把得到的地址入栈.

让程序运行到调用strcpy函数之前看看
(gdb)b*0x804840e
Breakpoint3at0x804840e
(gdb)c
Continuing.

Breakpoint3,0x804840einvulFunc()
(gdb)x/17x$esp
0xbffff698:0xbffff6a00xbffff8560x4000ae600xbffff704
0xbffff6a8:0xbffff6b80xbffff6b80x080484430xbffff856
0xbffff6b8:0xbffff6d80x400349cb0x000000020xbffff704
0xbffff6c8:0xbffff7100x400138680x000000020x08048350
0xbffff6d8:0x00000000

这时进程在内存的相关影像为:
(内存高址)
|......|
+--------+
|00000000|
0xbffff6d8+--------+%26lt;--调用main函数前的ebp
|08048350|
+--------+
|00000002|
+--------+
|40013868|
+--------+
|bffff710|
+--------+
|bffff704|argv的地址(即argv[0]的地址)
0xbffff6c4+--------+
|00000002|argc的值
0xbffff6c0+--------+
|400349cb|
0xbffff6bc+--------+%26lt;--调用main函数前的esp
|bffff6d8|调用main函数前的ebp
0xbffff6b8+--------+%26lt;--main函数的ebp
|bffff856|字符串"AAAAAAAA"在内存中的起始地址
0xbffff6b4+--------+
|08048443|vulFunc函数的返回地址
0xbffff6b0+--------+%26lt;--调用vulFunc函数前的esp
|bffff6b8|main函数的ebp
0xbffff6ac+--------+%26lt;--vulFunc函数的ebp
|bffff6b8|(垃圾)
0xbffff6a8+--------+
|bffff704|(垃圾)
0xbffff6a4+--------+
|4000ae60|(垃圾)
0xbffff6a0+--------+
|bffff856|字符串"AAAAAAAA"在内存中的起始地址
0xbffff69c+--------+
|bffff6a0|vulFunc函数栈帧中分配的十二个字节起始地址
0xbffff698+--------+%26lt;--vulFunc的当前esp
|......|
(内存低址)

我们这里不关心strcpy函数具体运行,把断点设到调用它的后续指令.
(gdb)b*0x8048413
Breakpoint4at0x8048413
(gdb)c
Continuing.

Breakpoint4,0x8048413invulFunc()
(gdb)x/17x$esp
0xbffff698:0xbffff6a00xbffff8560x414141410x41414141
0xbffff6a8:0xbffff6000xbffff6b80x080484430xbffff856

0xbffff6b8:0xbffff6d80x400349cb0x000000020xbffff704
0xbffff6c8:0xbffff7100x400138680x000000020x08048350
0xbffff6d8:0x00000000

这时进程在内存中的相关影像为:
(内存高址)
|......|
+--------+
|00000000|
0xbffff6d8+--------+%26lt;--调用main函数前的ebp
|08048350|
+--------+
|00000002|
+--------+
|40013868|
+--------+
|bffff710|
+--------+
|bffff704|argv的地址(即argv[0]的地址)
0xbffff6c4+--------+
|00000002|argc的值
0xbffff6c0+--------+
|400349cb|
0xbffff6bc+--------+%26lt;--调用main函数前的esp
|bffff6d8|调用main函数前的ebp
0xbffff6b8+--------+%26lt;--main函数的ebp
|bffff856|字符串"AAAAAAAA"在内存中的起始地址
0xbffff6b4+--------+
|08048443|vulFunc函数的返回地址
0xbffff6b0+--------+%26lt;--调用vulFunc函数前的esp
|bffff6b8|main函数的ebp
0xbffff6ac+--------+%26lt;--vulFunc函数的ebp
|bffff600|
0xbffff6a8+--------+
|41414141|
0xbffff6a4+--------+
|41414141|
0xbffff6a0+--------+
|bffff856|字符串"AAAAAAAA"在内存中的起始地址
0xbffff69c+--------+
|bffff6a0|vulFunc函数栈帧中分配的十二个字节起始地址
0xbffff698+--------+%26lt;--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%26lt;vulFunc+19%26gt;:add$0x8,%esp;栈帧缩小8个字节--放弃了两个内存存储单元.

可以看到,在调用strcpy前,依次压了s和buf的地址入栈,现在这条指令是把这两个地址抛弃.

所以可以得出,Linuxx86系统在调用函数时(其实是编译器所生成的机器指令),所传给

被调用函数的参数是由调用函数从右到左依次入栈的.
如现在的strcpy(buf,s),首先是s先入栈,然后是buf.参数的出栈也由调用函数负责.

0x8048416%26lt;vulFunc+22%26gt;:lea0xfffffff4(%ebp),%eax
0x8048419%26lt;vulFunc+25%26gt;:push%eax
这两条指令和前面的一样,把argv[1](即"AAAAAAAA"字串)的起始地址入栈.

0x804841a%26lt;vulFunc+26%26gt;:push$0x80484b0
先看一下0x80484b0里面放的是什么,虽然很明显是即将调用的printf函数的第一个参数的地址.
(gdb)x/1s0x80484b0
0x80484b0%26lt;_IO_stdin_used+4%26gt;:"String=%s\n"
果然是.

下面的两条指令就是调用printf函数和抛弃在栈中的两个参数了.
0x804841f%26lt;vulFunc+31%26gt;:call0x8048330%26lt;printf%26gt;
0x8048424%26lt;vulFunc+36%26gt;:add$0x8,%esp

我们在0x08048427leave指令的前面设个断点并继续运行.
(gdb)b*0x8048427
Breakpoint5at0x8048427
(gdb)c
Continuing.
String=AAAAAAAA

Breakpoint5,0x8048427invulFunc()
屏幕输出了"String=AAAAAAAA".

这时栈帧的内容为:
(gdb)x/15x$esp
0xbffff6a0:0x414141410x414141410xbffff6000xbffff6b8
0xbffff6b0:0x080484430xbffff8560xbffff6d80x400349cb
0xbffff6c0:0x000000020xbffff7040xbffff7100x40013868
0xbffff6d0:0x000000020x080483500x00000000

进程在内存中的相关影像为:
(内存高址)
|......|
+--------+
|00000000|
0xbffff6d8+--------+%26lt;--调用main函数前的ebp
|08048350|
+--------+
|00000002|
+--------+
|40013868|
+--------+
|bffff710|
+--------+
|bffff704|argv的地址(即argv[0]的地址)
0xbffff6c4+--------+
|00000002|argc的值
0xbffff6c0+--------+
|400349cb|
0xbffff6bc+--------+%26lt;--调用main函数前的esp
|bffff6d8|调用main函数前的ebp
0xbffff6b8+--------+%26lt;--main函数的ebp
|bffff856|字符串"AAAAAAAA"在内存中的起始地址
0xbffff6b4+--------+
|08048443|vulFunc函数的返回地址
0xbffff6b0+--------+%26lt;--调用vulFunc函数前的esp
|bffff6b8|main函数的ebp
0xbffff6ac+--------+%26lt;--vulFunc函数的ebp
|bffff600|
0xbffff6a8+--------+
|41414141|
0xbffff6a4+--------+
|41414141|
0xbffff6a0+--------+%26lt;--vulFunc的当前esp
|bffff856|(垃圾)字符串"AAAAAAAA"在内存中的起始地址
0xbffff69c+--------+
|bffff6a0|(垃圾)vulFunc函数栈帧中分配的十二个字节起始地址
0xbffff698+--------+
|......|
(内存低址)

各寄存器的状况:
(gdb)ireg
eax0x1016
ecx0x4001024

edx0x4010a9801074833792
ebx0x4010c1ec1074840044
esp0xbffff6a0-1073744224
ebp0xbffff6ac-1073744212
esi0x4000ae601073786464
edi0xbffff704-1073744124
eip0x8048427134513703
eflags0x296662
(以下省略)
...

请注重:此时esp的内容为0xbffff6a0,ebp的内容为0xbffff6ac
单步运行leave指令,然后看一下寄存器的情况.
(gdb)si
0x8048428invulFunc()
(gdb)ireg
eax0x1016
ecx0x4001024
edx0x4010a9801074833792
ebx0x4010c1ec1074840044
esp0xbffff6b0-1073744208
ebp0xbffff6b8-1073744200
esi0x4000ae601073786464
edi0xbffff704-1073744124
eip0x8048428134513704
eflags0x396918
(以下省略)
...

此时的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:0x080484430xbffff8560xbffff6d80x400349cb
0xbffff6c0:0x000000020xbffff7040xbffff7100x40013868
0xbffff6d0:0x000000020x080483500x00000000

进程在内存中的相关影像:

(内存高址)
|......|
+--------+
|00000000|
0xbffff6d8+--------+%26lt;--调用main函数前的ebp
|08048350|
+--------+
|00000002|
+--------+
|40013868|
+--------+
|bffff710|
+--------+
|bffff704|argv的地址(即argv[0]的地址)
0xbffff6c4+--------+
|00000002|argc的值
0xbffff6c0+--------+
|400349cb|
0xbffff6bc+--------+%26lt;--调用main函数前的esp
|bffff6d8|调用main函数前的ebp
0xbffff6b8+--------+%26lt;--main函数的ebp
|bffff856|字符串"AAAAAAAA"在内存中的起始地址
0xbffff6b4+--------+
|08048443|vulFunc函数的返回地址
0xbffff6b0+--------+%26lt;--当前esp
|bffff6b8|(垃圾)main函数的ebp
0xbffff6ac+--------+
|bffff600|(垃圾)
0xbffff6a8+--------+
|41414141|(垃圾)
0xbffff6a4+--------+
|41414141|(垃圾)
0xbffff6a0+--------+
|bffff856|(垃圾)字符串"AAAAAAAA"在内存中的起始地址
0xbffff69c+--------+

|bffff6a0|(垃圾)vulFunc函数栈帧中分配的十二个字节起始地址
0xbffff698+--------+
|......|
(内存低址)

继续执行下条指令:ret
(gdb)si
0x8048443inmain()
(gdb)ireg
eax0x1016
ecx0x4001024
edx0x4010a9801074833792
ebx0x4010c1ec1074840044
esp0xbffff6b4-1073744204
ebp0xbffff6b8-1073744200
esi0x4000ae601073786464
edi0xbffff704-1073744124
eip0x8048443134513731
eflags0x396918
(以下省略)
...

可以看出,从栈中弹出0x8048443(vulFunc函数调用的返回地址)给了eip.
至此vulFunc函数调用完毕,返回到main函数继续执行.

值得注重的是:假如象上面所说的,我们输入的字串长度为二十个'A'--刚好复盖完0xbffff6b0
所指的单元,那么此时从栈中弹出给eip的内容将是0x41414141,而不是0x8048443,程序
将跳到0x41414141去执行那里的指令,由于0x41414141对于当前进程来说是不可访问的,
所以导致段出错(Segmentationfault),进程停止执行.

这是我们的第三个焦点.

假如我们能计算好位移(offset),用我们预备好的代码的入口地址来覆盖0xbffff6b0所
指的单元,那么从栈中弹出给eip的内容就是我们的代码的入口地址,程序将跳到我们的
代码去继续执行.

分析到这里,我们已经清楚了C语言函数调用的机制了.main函数的后续指令对于我们的
分析已无关紧要.但是为了保持文章的完整,我们继续再往下看看.

此时栈的情况:
(gdb)x/10x$esp
0xbffff6b4:0xbffff8560xbffff6d80x400349cb0x00000002
0xbffff6c4:0xbffff7040xbffff7100x400138680x00000002
0xbffff6d4:0x080483500x00000000

进程在内存中的相关影像:

(内存高址)
|......|
+--------+
|00000000|
0xbffff6d8+--------+%26lt;--调用main函数前的ebp
|08048350|
+--------+
|00000002|
+--------+
|40013868|
+--------+
|bffff710|
+--------+
|bffff704|argv的地址(即argv[0]的地址)
0xbffff6c4+--------+
|00000002|argc的值
0xbffff6c0+--------+
|400349cb|
0xbffff6bc+--------+%26lt;--调用main函数前的esp
|bffff6d8|调用main函数前的ebp
0xbffff6b8+--------+%26lt;--main函数的ebp
|bffff856|字符串"AAAAAAAA"在内存中的起始地址
0xbffff6b4+--------+%26lt;--当前esp
|08048443|(垃圾)vulFunc函数的返回地址
0xbffff6b0+--------+
|bffff6b8|(垃圾)main函数的ebp
0xbffff6ac+--------+
|bffff600|(垃圾)
0xbffff6a8+--------+
|41414141|(垃圾)
0xbffff6a4+--------+
|41414141|(垃圾)
0xbffff6a0+--------+
|bffff856|(垃圾)字符串"AAAAAAAA"在内存中的起始地址
0xbffff69c+--------+
|bffff6a0|(垃圾)vulFunc函数栈帧中分配的十二个字节起始地址
0xbffff698+--------+

|......|
(内存低址)


再看看后续的指令做了些什么?
0x8048443%26lt;main+23%26gt;:add$0x4,%esp;抛弃栈中为被调用函数预备的参数.
0x8048446%26lt;main+26%26gt;:jmp0x804845b%26lt;main+47%26gt;;跳转到0x804845b继续执行
0x8048448%26lt;main+28%26gt;:mov0xc(%ebp),%eax;0x8048433jne的条件判定跳转
;入口(即argc!=2的情况)
;把ebp+0xc所指向的内存单元的
;内容赋给eax,从上面的分析我
;们知道里面放的是argv的地址
0x804844b%26lt;main+31%26gt;:mov(%eax),%edx;把eax指向的地址的内存单元里
;的内容赋给edx,我们知道argv
;是个数组,argv的值就是argv[0]
0x804844d%26lt;main+33%26gt;:push%edx;把argv[0]入栈.注重这里的
;argv[0]其实是个地址值.
0x804844e%26lt;main+34%26gt;:push$0x80484bb;把常数0x80484bb入栈
;以上为调用printf函数预备参数.
0x8048453%26lt;main+39%26gt;:call0x8048330%26lt;printf%26gt;;调用printf函数
0x8048458%26lt;main+44%26gt;:add$0x8,%esp;抛弃为调用printf函数预备的参数
0x804845b%26lt;main+47%26gt;:leave;恢复调用main函数的函数的栈帧
0x804845c%26lt;main+48%26gt;:ret;返回到调用main函数的函数

估计0x80484bb指向的是printf函数的format字串,看看是不是?
(gdb)x/1s0x80484bb
0x80484bb%26lt;_IO_stdin_used+15%26gt;:"Usage:%s%26lt;Astring%26gt;\n"
果然是.那从0x8048448到0x8048458这段指令就是C语言
printf("Usage:%s%26lt;Astring%26gt;\n",argv[0]);
的等价汇编语句了.

我们把断点设到0x804845b,再继续执行.
(gdb)b*0x804845b
Breakpoint6at0x804845b
(gdb)c
Continuing.

Breakpoint6,0x804845binmain()

下一条指令是leave,应该是恢复调用函数的函数的栈帧.
单步执行一下,看看寄存器及栈的情况.
(gdb)si
0x804845cinmain()
(gdb)ireg
eax0x1016
ecx0x4001024
edx0x4010a9801074833792
ebx0x4010c1ec1074840044
esp0xbffff6bc-1073744196
ebp0xbffff6d8-1073744168
esi0x4000ae601073786464
edi0xbffff704-1073744124
eip0x804845c134513756
eflags0x386902
(以下省略)
...

(gdb)x/8x$esp
0xbffff6bc:0x400349cb0x000000020xbffff7040xbffff710
0xbffff6cc:0x400138680x000000020x080483500x00000000

下一条指令是ret,我们知道栈顶放的是main函数的返回地址(0x400349cb).

此时进程在内存中的相关影像:

(内存高址)
|......|
+--------+
|00000000|
0xbffff6d8+--------+%26lt;--调用main函数前的ebp

|08048350|
+--------+
|00000002|
+--------+
|40013868|
+--------+
|bffff710|
+--------+
|bffff704|argv的地址(即argv[0]的地址)
0xbffff6c4+--------+
|00000002|argc的值
0xbffff6c0+--------+
|400349cb|main函数的返回地址
0xbffff6bc+--------+%26lt;--当前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
0x400349cbin__libc_start_main(main=0x804842c%26lt;main%26gt;,argc=2,argv=0xbffff704,init=0x80482c0%26lt;_init%26gt;,
fini=0x804848c%26lt;_fini%26gt;,rtld_fini=0x4000ae60%26lt;_dl_fini%26gt;,stack_end=0xbffff6fc)
at../sysdeps/generic/libc-start.c:92
92../sysdeps/generic/libc-start.c:NosuchfileorDirectory.

原来是__libc_start_main函数调用了我们的main函数,看来和概述里说的有些出入,
但这对于我们来讲不是很重要.假如想看源码,请到../sysdeps/generic/libc-start.c
文件中找.
(gdb)x/16x$esp
0xbffff6c0:0x000000020xbffff7040xbffff7100x40013868
0xbffff6d0:0x000000020x080483500x000000000x08048371
0xbffff6e0:0x0804842c0x000000020xbffff7040x080482c0
0xbffff6f0:0x0804848c0x4000ae600xbffff6fc0x40013e90

从上面可以看到,stack_end=0xbffff6fc,也就是说我们的进程的栈底地址为0xbffff6fc,
在调用__libc_start_main函数前依次推了如下七个参数入栈:
0xbffff6fc-%26gt;进程的栈底
0x4000ae60-%26gt;_dl_fini函数的入口地址.
0x0804848c-%26gt;_fini函数的入口地址
0x080482c0-%26gt;_init函数的入口地址
0xbffff704-%26gt;argv命令行参数地址的地址
0x00000002-%26gt;argc命令行参数个数值
0x0804842c-%26gt;我们的main函数入口

从上面的分析可推出,在内存地址0xbffff6dc的内容0x08048371就是__libc_start_main函数
的返回地址了.
我们来看看是什么函数调用了__libc_start_main.
(gdb)disas0x08048371
Dumpofassemblercodeforfunction_start:

0x8048350%26lt;_start%26gt;:xor%ebp,%ebp
0x8048352%26lt;_start+2%26gt;:pop%esi
0x8048353%26lt;_start+3%26gt;:mov%esp,%ecx
0x8048355%26lt;_start+5%26gt;:and$0xfffffff8,%esp
0x8048358%26lt;_start+8%26gt;:push%eax
0x8048359%26lt;_start+9%26gt;:push%esp
0x804835a%26lt;_start+10%26gt;:push%edx
0x804835b%26lt;_start+11%26gt;:push$0x804848c
0x8048360%26lt;_start+16%26gt;:push$0x80482c0
0x8048365%26lt;_start+21%26gt;:push%ecx
0x8048366%26lt;_start+22%26gt;:push%esi
0x8048367%26lt;_start+23%26gt;:push$0x804842c
0x804836c%26lt;_start+28%26gt;:call0x8048320%26lt;__libc_start_main%26gt;
0x8048371%26lt;_start+33%26gt;:hlt
0x8048372%26lt;_start+34%26gt;:nop
0x8048373%26lt;_start+35%26gt;:nop
(省略以下的nop)
Endofassemblerdump.

原来是_start函数调用了__libc_start_main函数.
至于_start函数调用__libc_start_main函数后,接是如何调用_init函数和_dl_runtime_resove
函数来调用共享库函数和我们的main函数然后退出的,已经远远脱离了本文的主题,这里不再继
续介绍.

(gdb)x/1024x0xbffff6f0
0xbffff6f0:0x0804848c0x4000ae600xbffff6fc0x40013e90
0xbffff700:0x000000020xbffff83e0xbffff8560x00000000
0xbffff710:0xbffff85f0xbffff8810xbffff88f0xbffff89e
0xbffff720:0xbffff8c40xbffff8d20xbffff9000xbffff91a
0xbffff730:0xbffff9320xbffff94d0xbffff9a80xbffff9df
0xbffff740:0xbffffaf30xbffffb060xbffffb110xbffffb31
0xbffff750:0xbffffb5a0xbffffb680xbffffc720xbffffc7e
0xbffff760:0xbffffc8f0xbffffca40xbffffcb40xbffffcbf
0xbffff770:0xbffffcd70xbffffcf50xbffffd0e0xbffffd19
0xbffff780:0xbffffd230xbffffd6c0xbffffd790xbffffda0
0xbffff790:0xbffffdb20xbffffdc10xbffffde60xbffffe08
0xbffff7a0:0xbffffe100xbfffffd30x000000000x00000003
0xbffff7b0:0x080480340x000000040x000000200x00000005
0xbffff7c0:0x000000060x000000060x000010000x00000007
0xbffff7d0:0x400000000x000000080x000000000x00000009
0xbffff7e0:0x080483500x0000000b0x000001f50x0000000c
0xbffff7f0:0x000001f50x0000000d0x000000040x0000000e
0xbffff800:0x000000040x000000100x008001bf0x0000000f
0xbffff810:0xbffff8390x000000000x000000000x00000000
0xbffff820:0x000000000x000000000x000000000x00000000

0xbffff830:0x000000000x000000000x383569000x682f0036
0xbffff840:0x2f656d6f0x65776f740x74742f720x2f737775
0xbffff850:0x2f6c64690x414100700x414141410x4c004141
0xbffff860:0x4f5353450x3d4e45500x73752f7c0x69622f72
...
(省略)
...
0xbfffffd0:0x54003a350x75413d5a0x617274730x2f61696c
0xbfffffe0:0x0057534e0x6d6f682f0x6f742f650x2f726577
0xbffffff0:0x777574740x64692f730x00702f6c0x00000000
0xc0000000:CannotAccessmemoryataddress0xc0000000

我们知道内存单元0xbffff704放的是指argv[0]的地址,那么0xbffff708放的就是argv[1]
的地址了.0xbffff700里放的是argc的值.

那么0xbffff710里放的是什么呢?看样子象是指向字符串的地址,让我们来看看.
(gdb)x/1s0xbffff85f
0xbffff85f:"LESSOPEN=|/usr/bin/lesspipe.sh%s"
(gdb)
0xbffff881:"HISTSIZE=1000"
...

再看看最后一个.
(gdb)x/1s0xbfffffd3
0xbfffffd3:"TZ=Australia/NSW"
0xc0000000以后的地址空间已不是进程能合法访问的了.

原来都是些SHELL的环境变量字符串.

这一片东西是从内存地址0xbffff839开始的,让我们再看看.
(gdb)x/1s0xbffff839
0xbffff839:"i586"
(gdb)
0xbffff83e:"/home/vcat/p"===%26gt;细心的朋友会发现这里已被俺改掉了,
让俺保留一点私隐吧;)
(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|/--%26gt;从内存地址0xbffff839开始,0x69353836="i586"
0xbffff838+--------+
|......|\
里面放的是什么?还有待研究
|......|/
0xbffff7a8+--------+
|bfffffd3|\
0xbffff7a4+--------+\
|......|\
......环境变量指针
|......|/

0xbffff714+--------+/
|bffff85f|/
0xbffff710+--------+
|00000000|
0xbffff70c+--------+
|bffff856|argv[1]的地址
0xbffff708+--------+
|bffff83e|argv[0]的地址
0xbffff704+--------+
|00000002|argc的值
0xbffff700+--------+
|40013e90|????(和_dl_starting_up函数有关)
0xbffff6fc+--------+%26lt;--进程的栈底
|bffff6fc|stack_end(进程的栈底)
0xbffff6f8+--------+
|4000ae60|_dl_fini函数入口地址
0xbffff6f4+--------+
|0804848c|_fini函数入口地址
0xbffff6f0+--------+
|080482c0|_init函数入口地址
0xbffff6ec+--------+
|bffff704|argv地址的地址
0xbffff6e8+--------+
|00000002|argc的值
0xbffff6e4+--------+
|0804842c|main函数的入口地址
0xbffff6e0+--------+
|08048371|__libc_start_main的返回地址(指令hlt),正常情况不会返回到这.
0xbffff6dc+--------+
|00000000|
0xbffff6d8+--------+%26lt;--调用main函数前的ebp
|08048350|_start函数的入口地址
+--------+
|00000002|argc的值
+--------+
|40013868|????
+--------+
|bffff710|环境变量指针的地址
+--------+
|bffff704|argv地址的地址(即argv[0]的地址)
0xbffff6c4+--------+
|00000002|argc的值
0xbffff6c0+--------+
|400349cb|main函数的返回地址
0xbffff6bc+--------+%26lt;--当前esp

相关文章