前段時間在寫《段錯誤造成的常見詭異宕機情況總結(中)》時,分析到 程序中數據寫超時有可能寫到this指針所在的地址里面,導致最終詭異的宕機。其實網絡攻防里常用的緩沖區溢出攻擊也是這個道理,除了使用戶程序甚至計算機掛掉外,還有可能執行攻擊者想執行的任何程序,這篇文章主要詳細剖析一下第二種攻擊的方法以及現在Linux(包括各種修改版本,例如Android)、Windows下常使用的防范措施。
話不多說,先貼一則小例子:
1 **
2 *\author peakflys
3 *\email peakflys@gmail.com
4 *\brief Buffer overflow attack
5 */
6 #include <iostream>
7 using namespace std;
8
9 void hack()
10 {
11 cout<<"hacked"<<endl;
12 }
13
14 void test()
15 {
16 char a[4];
17 int *ret = (int *)(a + 24);
18 *ret -= 0x48;
19 }
20
21 int main()
22 {
23 test();
24 return 0;
25 }
運行結果:
hacked
整個過程沒有顯示調用hack函數,而最終hack函數卻得以運行。下面給出三個函數的匯編代碼:
0000000000400814 <_Z4hackv>:
void hack()
{
400814: 55 push %rbp
400815: 48 89 e5 mov %rsp,%rbp
cout<<"hacked"<<endl;
400818: be b8 09 40 00 mov $0x4009b8,%esi
40081d: bf 80 0d 60 00 mov $0x600d80,%edi
400822: e8 c1 fe ff ff callq 4006e8 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
400827: be 08 07 40 00 mov $0x400708,%esi
40082c: 48 89 c7 mov %rax,%rdi
40082f: e8 c4 fe ff ff callq 4006f8 <_ZNSolsEPFRSoS_E@plt>
400834: c9 leaveq
400835: c3 retq
}
0000000000400836 <_Z4testv>:
void test()
{
400836: 55 push %rbp
400837: 48 89 e5 mov %rsp,%rbp
char a[4];
int *ret = (int *)(a + 24);
40083a: 48 8d 45 f0 lea -0x10(%rbp),%rax
40083e: 48 83 c0 18 add $0x18,%rax
400842: 48 89 45 f8 mov %rax,-0x8(%rbp)
*ret -= 0x48;
400846: 48 8b 45 f8 mov -0x8(%rbp),%rax
40084a: 8b 00 mov (%rax),%eax
40084c: 8d 50 b8 lea -0x48(%rax),%edx
40084f: 48 8b 45 f8 mov -0x8(%rbp),%rax
400853: 89 10 mov %edx,(%rax)
400855: c9 leaveq
400856: c3 retq
}
0000000000400857 <main>:
int main()
{
400857: 55 push %rbp
400858: 48 89 e5 mov %rsp,%rbp
test();
40085b: e8 d6 ff ff ff callq 400836 <_Z4testv>
return 0;
400860: b8 00 00 00 00 mov $0x0,%eax
400865: c9 leaveq
400866: c3 retq
}
在調用test函數后,gdb打印結果:
(gdb) p $rsp
$1 = (void *) 0x7fffffffe2a0
(gdb) p $rbp
$2 = (void *) 0x7fffffffe2a0
(gdb) p /x *(0x7fffffffe2a8)
$3 = 0x400860
在test函數堆棧前面(如上,堆棧地址:
0x7fffffffe2a8 )有函數返回的地址(即
0x400860 )。test函數調用時的內存模型大致為:
估計這時候大家都已經知道整個實現的詳細過程了,test函數中ret指針指向test函數返回地址0x7fffffffe2a8(注:這個很容易分析得到),然后通過函數偏移值0x48(注:這個根據特定平臺和特定編譯器來分析得出)來重定向返回值地址,即定向到hack函數,這段重定向代碼 也就是常說的shellcode。
緩沖區溢出攻擊在平時的網絡攻擊中能占到50%的比例,上面test函數可以輕易的讓計算機崩潰,也可以輕易的執行任何病毒或者木馬程序。
現在來分析一下防范措施。首先當然是作為程序員的我們需要注意的:寫數據時,特別警惕數據邊界,嚴防存在數據寫溢出的情況,類似于strcpy等函數不要使用或者小心使用。其次當然是通過其他方式的防范,上面我也提到了,shellcode中有兩個值是需要計算的,第一個是函數返回值地址,這個比較容易分析出來,因為編譯器是有固定的入棧壓棧規則的;第二個是兩個函數之間的偏移地址,函數地址在編譯階段就已經在代碼段固定下來了,偏移 只需要根據反匯編出的片段地址猜解出來(如果能完全反匯編出來,就不用猜了……)。這兩個值只要增加任何一個的計算難度,都可以有效減少這種漏洞的攻擊。目前Linux
(包括各種修改版本,例如Android) 、FreeBSD、Windows 等主流操作系統都已采用 ASLR(Address space layout randomization)技術(iOS系統自iOS 4.3以后也支持了ASLR技術 ),這種技術就是通過對堆、棧、共享庫映射等線性區布局的隨機化來達到增加猜解出函數偏移的目的。當然這種技術僅僅是減少而非杜絕緩沖區溢出攻擊。
世界上沒有完美的程序,只有暫時想不到的bug by peakflys