?題
:?
【技術專題】軟件漏洞分析入門_2_初級棧溢出A_初識數組越界
作?者
:?
failwest
時?間
:?
2007
-
12
-
13
,
10
:
27
鏈?接
:?
http
:
//bbs.pediy.com/showthread.php?t=56479
2_
初級棧溢出_A
To?be?the?apostrophe?which?changed?“Impossible”?into?“I’m?possible”
——?failwest
今夜月明星稀
本想來點大道理申明下研究思路啥的,看到大家的熱情期待,稍微調整一下講課的順序。從今天開始,將用
3
~
4
次給大家做一下棧溢出的掃盲。
棧溢出的文章網上還是有不少的(其實優秀的也就兩三篇),原理也不難,讀過基本上就能夠明白是怎么回事。本次講解將主要集中在動手調試方面,更加著重實踐。
經過這
3
~
4
次的棧溢出掃盲,我們的目標是:
領會棧溢出攻擊的基本原理
能夠動手調試簡易的棧溢出漏洞程序,并能夠利用漏洞執行任意代碼(最簡易的shellcode)
最主要的目的其實是激發大家的學習興趣——寡人求學若干年,深知沒有興趣是決計沒有辦法學出名堂來的。
本節課的基本功要求是:會C語言就行(大概能編水仙花數的水平)
我會盡量用最最傻瓜的文字來闡述這些內存中的二進制概念。為了避免一開始涉及太多枯燥的基礎知識讓您失去了興趣,我并不提倡從匯編和寄存器開始,也不想用函數和棧開頭。我準備用一個自己設計的小例子開始講解,之后我會以這個例子為基礎,逐步加碼,讓它變得越來越像真實的漏洞攻擊。
您需要的就是每天晚上看一篇帖子,然后用十分鐘時間照貓畫虎的在編譯器里把例子跟著走一遍,堅持一個星期之后您就會發現世界真奇妙了。
不懂匯編不是拒絕這門迷人技術的理由——今天的課程就不涉及匯編——并且以后遇到會隨時講解滴
所以如果你懂C語言的話,不許不學,不許說學不會,也不許說難,哈哈
開場白多說了幾句,下面是正題。今天我們來一起研究一段暴簡單無比的C語言小程序,看看編程中如果不小心出現數組越界將會出現哪些問題,直到這個單元結束您能夠用這些數組越界漏洞控制遠程主機。
#include?
<
stdio
.
h
>
#define?
PASSWORD?
"1234567"
int?
verify_password?
(
char?
*
password
)
{
??
int?
authenticated
;
??
char?
buffer
[
8
];??
//?add?local?buff?to?be?overflowed
??
authenticated
=
strcmp
(
password
,
PASSWORD
);
??
strcpy
(
buffer
,
password
);??
//over?flowed?here!??
??
return?
authenticated
;
}
main
()
{
??
int?
valid_flag
=
0
;
??
char?
password
[
1024
];
??
while
(
1
)
??{
????
printf
(
"please?input?password:???????"
);
????
scanf
(
"%s"
,
password
);
????
valid_flag?
=?
verify_password
(
password
);
????
if
(
valid_flag
)
????{
??????
printf
(
"incorrect?password!\n\n"
);
????}
????
else
????
{
??????
printf
(
"Congratulation!?You?have?passed?the?verification!\n"
);
??????
break
;
????}
??}
}
對于這幾行亂簡單無比的程序,我還是稍作解釋。
程序運行后將提示輸入密碼
用戶輸入的密碼將被程序與宏定義中的“
1234567
”比較
密碼錯誤,提示驗證錯誤,并提示用戶重新輸入
密碼正確,提示正確,程序退出(真夠傻瓜的語言)
所謂的漏洞在于verify_password()函數中的strcpy
(
buffer
,
password
)
調用。
由于程序將把用戶輸入的字符串原封不動的復制到verify_password函數的局部數組
char?
buffer
[
8
]
中,但用戶的字符串可能大于
8
個字符。當用戶輸入大于
8
個字符的緩沖區尺寸時,緩沖區就會被撐暴——即所謂的緩沖區溢出漏洞。
緩沖區給撐暴了又怎么樣?大不了程序崩潰么,有什么了不起!
此話不然,如果只是導致程序崩潰就不用我在這里浪費大家時間了。根據緩沖區溢出發生的具體情況,巧妙的填充緩沖區不但可以避免崩潰,還能影響到程序的執行流程,甚至讓程序去執行緩沖區里的代碼。
今天我們先玩一個最簡單的。函數verify_password()里邊申請了兩個局部變量
int?
authenticated
;
char?
buffer
[
8
];?
當verify_password被調用時,系統會給它分配一片連續的內存空間,這兩個變量就分布在那里(實際上就叫函數棧幀,我們后面會詳細講解),如下圖
變量和變量緊緊的挨著。為什么緊挨著?當然不是他倆關系好,省空間啊,好傻瓜的問題,笑:)
用戶輸入的字符串將拷貝進buffer
[
8
]
,從示意圖中可以看到,如果我們輸入的字符超過
7
個(注意有串截斷符也算一個),那么超出的部分將破壞掉與它緊鄰著的authenticated變量的內容!
在復習一下程序,authenticated變量實際上是一個標志變量,其值將決定著程序進入錯誤重輸的流程(非
0
)還是密碼正確的流程(
0
)。
下面是比較有趣的部分:
當密碼不是宏定義的
1234567
時,字符串比較將返回
1
或
-
1
(這里只討論
1
,結尾的時候會談下
-
1
的情況)
由于intel是所謂的大頂機,其實就是內存中的數據按照
4
字節(DWORD)逆序存儲,所以authenticated為
1
時,內存中存的是
0x01000000
如果我們輸入包含
8
個字符的錯誤密碼,如“qqqqqqqq”,那么字符串截斷符
0x00
將寫入authenticated變量
這溢出數組的一個字節
0x00
將恰好把逆序存放的authenticated變量改為
0x00000000
。
函數返回,main函數中一看authenticated是
0
,就會歡天喜地的告訴你,oh?yeah?密碼正確!這樣,我們就用錯誤的密碼得到了正確密碼的運行效果
下面用
5
分鐘實驗一下這里的分析吧。將代碼用VC6
.0
編譯鏈接,生成可執行文件。注意,是VC6
.0
或者更早的編譯器,不是
7.0
,不是
8.0
,不是
.?
net,不是VS2003,不是VS2005。為什么,其實不是高級的編譯器不能搞,是比較難搞,它們有特殊的GS編譯選項,為了不給咱們掃盲班增加負擔,所以暫時飄過,用
6.0
!
??按照程序的設計思路,只有輸入了正確的密碼”
1234567
”之后才能通過驗證。程序運行情況如下:
?
????
??要是輸入幾十個字符的長串,應該會崩潰。多少個字符會崩潰?為什么?賣個關子,下節課慢慢講?,F在來個
8
個字符的密碼試下:?
?
注意為什么
01234567
不行?因為字符串大小的比較是按字典序來的,所以這個串小于“
1234567
”,authenticated的值是
-
1
,在內存里將按照補碼存負數,所以實際村的不是
0x01000000
而是
0xffffffff
。那么字符串截斷后符
0x00
淹沒后,變成
0x00ffffff
,還是非
0
,所以沒有進入正確分支。
總結一下,由于編程的粗心,有可能造成程序中出現緩沖區溢出的缺陷。
這種缺陷大多數情況下會導致崩潰,但是結合內存中的具體情況,如果精心構造緩沖區的話,是有可能讓程序作出設計人員根本意向不到的事情的
本節只是用一個字節淹沒了鄰接變量,導致了程序進入密碼正確的處理流程,使設計的驗證功能失效。
其實作為cracker,大家可能會說這有什么難的,我可以說出一堆方法做到這一點:
直接查看PE,找出宏定義中的密碼值,得到正確密碼
反匯編PE,找到爆破點
,
JZ?JNZ的或者TEST?EAX
,
EAX變XOR?EAX
,
EAX的在分支處改它一個字節
……
但是今天介紹的這種方法與crack的方法有一個非常重要的區別,非常非常重要~~
就是~~~我們是在程序允許的情況下,用合法的輸入數據(對于程序來說)得到了非法的執行效果(對于程序員來說)——這是hack與crack之間的一個重要區別,因為大多數情況下hack是沒有辦法直接修改PE的,他們只能通過影響輸入來影響程序的流程,這將使hack受到很多限制,從某種程度上講也更加困難。這個區別將在后面幾講中得到深化,并被我不斷強調。
好了,今天的掃盲課程暫時結束,作為棧溢出的開場白,希望這個自制的漏洞程序能夠給您帶來一點點幫助。
順便預告一下下一講的內容:
初級溢出B:將講述函數調用時怎樣和系統棧配合的,然后在本講的基礎上淹沒棧幀寄存器,直接改變程序流程
初級溢出C:手把手的教你寫一段超簡單的shellcode(可執行的機器代碼),并把這段代碼做為密碼輸入,最后引導程序跳去執行這段代碼
下次再見:)
到底要怎么樣才行?比它大就行了么?還是有什么特定的算法?望明示!
抱歉今天才有時間細看下
要怎樣才行?只要authenticated的值是0x00000000,“01234567”為什么不行,是因為小于“1234567”,所以strcmp比較結果(authenticated的值)被置為0xffffffff,再被截斷字符撐破一位字符,就變成0x00ffffff,結果不是0就提示失敗。