棧溢出原理與實踐

本文基於win-xp-SP3,實際內存地址與環境有差異,需按實際調試結果爲準。

基礎概念

VA = Image Base + RVA

  • File Offset:文件偏移,PE文件中數據相對於文件開頭的偏移。
  • Image Base:裝載地址,PE裝入內存的地址。默認EXE基地址是0x00400000,DLL是0x10000000。
  • VA:Virtual Address,虛擬內存地址。PE中的指令被裝入內存後的地址。
  • RVA:Relative Virtual Address,相對虛擬地址。內存地址相對於映射基地址的偏移量。

PE文件是按磁盤數據標準存放,以0x200字節(512)爲基本單位組織。當數據節不足0x200字節時,不足的地方被0x00填充,超過0x200,下一個0x200塊將分配給這個節。
裝入內存後,按內存標準存放,並以0x1000字節(4096)爲單位組織。

節(section) 相對虛擬地址RVA 文件偏移量
.text 0x00001000 0x0400
.data 0x00009000 0x7400

這種差異引起的節基址差稱爲節偏移:
.text節偏移 = 0x1000 - 0x400 = 0xc00
.data節偏移 = 0x9000 - 0x7400 = 0x1C00

調試時基於內存地址,要想確認對應的文件偏移,公式如下:
文件偏移地址 = RVA - 節偏移
LordPE可以查看PE文件的節信息,確認節的虛擬地址、裝載地址、文件偏移,就可以確認其中一個內存指令對應的文件地址了。

棧幀介紹

下面以一段代碼示例來說明函數調用時的棧幀情況。

  • PUSH:爲棧增加一個元素
  • POP:從棧中取出一個元素
  • ESP:該寄存器指向棧幀的棧頂
  • EBP:該寄存器指向棧幀的底部
  • EIP:指令寄存器,指向下一條等待執行的指令地址。
int func_B(int arg_B1, int arg_B2)
{
    int var_B1, var_B2;
    var_B1=arg_B1+arg_B2;
    var_B2=arg_B1-arg_B2;
    return var_B1*var_B2;
}

int func_A(int arg_A1, int arg_A2)
{
    int var_A;
    var_A = func_B(arg_A1,arg_A2) + arg_A1 ;
    return var_A;
}

int main(int argc, char **argv, char **envp)
{
    int var_main;
    var_main=func_A(4,3);
    return var_main;
}

棧從高地址往低地址發展,堆從低地址往高地址發展。
VC6.0默認使用__stacall調用方式:參數入棧從右到左,函數返回時堆棧平衡操作在子函數中進行。
函數調用步驟:

  1. 參數入棧:將參數從右向左依次壓入棧中
  2. 返回地址入棧:將當前調用指令的下一條指令地址入棧,供函數返回時繼續執行。
  3. 跳轉:處理器從當前代碼區跳轉到被調用函數的入口
  4. 棧幀調整:保存當前棧幀狀態值,EBP入棧;切換到新棧幀,ESP值裝入EBP;給新棧分配空間,把ESP減去所需空間,擡高棧頂。

例如函數A調用函數B:

;A調用前,函數B有3個參數
push 參數 3 
push 參數 2
push 參數 1
call 函數B地址;call 指令將同時完成兩項工作: a)向棧中壓入當前指令的下一條在內存中的位置,即保存返回地址。
                                        ; b)跳轉到所調用函數的入口地址函數入口處
......
;函數B入口代碼
push ebp ;保存舊棧幀的底部
mov ebp, esp ;設置新棧幀的底部(棧幀切換)
sub esp, xxx ;設置新棧幀的頂部(擡高棧頂,爲新棧幀開闢空間)

函數返回步驟:

  1. 保存返回值:通常將函數的返回值保存在EAX中
  2. 彈出當前棧幀,恢復上一個棧幀:給ESP加上棧幀的大小,降低棧頂,回收棧空間;棧上保存的EBP值彈入EBP寄存器,恢復上一個棧幀;將返回地址彈給EIP寄存器。
add esp, xxx ;降低棧頂,回收當前的棧幀
pop ebp;將上一個棧幀底部位置恢復到 ebp,
retn;這條指令有兩個功能: a)彈出當前棧頂元素,即彈出棧幀中的返回地址。至此,棧幀恢復工作完成。 
                        ;b)讓處理器跳轉到彈出的返回地址,恢復調用前的代碼區

下圖需要牢記心中,返回地址與漏洞利用有密切關係。

實踐:利用棧溢出漏洞,彈出MessageBox

使用如下代碼演示利用過程,從password.txt讀取內容拷貝到buffer[44]模擬棧溢出。
使用VC6.0編譯,默認編譯選項,編譯成debug版本。在password.txt中植入二進制機器碼,運行後彈出一個消息框顯示"hacktest"。
步驟:
(1)分析調試漏洞程序,獲得淹沒返回地址的偏移
(2)獲得buffer的起始地址,並將其寫入password.txt相應的偏移處,用來沖刷返回地址
(3)向password.txt寫入可執行的機器碼,用來調用API彈出一個消息框

#include <stdio.h>
#include <windows.h>
#define PASSWORD "1234567"
int verify_password (char *password)
{
    int authenticated;
    char buffer[44];
    authenticated=strcmp(password,PASSWORD);
    strcpy(buffer,password);//over flowed here! 
    return authenticated;
}
main()
{
    int valid_flag=0;
    char password[1024];
    FILE * fp;
    LoadLibrary("user32.dll");//prepare for messagebox
    if(!(fp=fopen("password.txt","rw+")))
    {
        exit(0);
    }
    fscanf(fp,"%s",password);
    valid_flag = verify_password(password);
    if(valid_flag)
    {
        printf("incorrect password!\n");
    }
    else
    {
        printf("Congratulation! You have passed the verification!\n");
    }
    fclose(fp);
}

1.分析調試漏洞程序,獲得淹沒返回地址的偏移
如果在 password.txt 中寫入恰好 44 個字符,那麼第 45 個隱藏的截斷符 null 將沖掉authenticated 低字節中的 1,從而突破密碼驗證的限制。
出於字節對齊、容易辨認的目的,我們把“ 4321”作爲一個輸入單元。buffer[44]共需要 11 個這樣的單元。
第 12 個輸入單元將 authenticated 覆蓋;第 13 個輸入單元將前棧幀 EBP 值覆蓋;第 14 個
輸入單元將返回地址覆蓋。

首先在password.txt寫入11組“4321”,使用OllyDbg加載,通過View-->source file定位到strcpy(buffer,password);下斷點,單步調試觀察棧內容。

EBP:0x0012FB20位置的上方是"4321"和authenticated ,下方是返回地址。buffer[44]的內存地址是0x0012FAF0。53~56個字符的ASCII碼值將寫入棧幀的返回地址,成爲函數返回後執行的指令地址。

也就是說將buffer[44]的起始地址0x0012FAF0寫入password.txt文件中的第53~56個字節,verify_password 函數返回時會跳轉到我們輸入的字串開始執行。

2.獲得buffer的起始地址,並將其寫入password.txt相應的偏移處,用來沖刷返回地址
調用MessageBoxA(NULL, "hacktest", "hacktest", NULL)即可彈出一個對話框,MessageBoxA在user32.dll中,使用Dependency Walker可確認其入口地址爲0x77D507EA。

調用MessageBoxA彙編指令對應機器碼如下:
首先在棧上創建"hacktest"字符串,然後壓入參數,最後CALL對應函數。
PUSH 74657374在內存中由低-->高:74 65 73 74
PUSH 0x44332211在內存中由低-->高:11 22 33 44


最後使用010 Editor編輯Hex碼:
首先將上述調用MessageBoxA的機器碼寫入,第53~56字節覆蓋返回地址爲buffer起始地址0x0012FAF0,其餘字節用nop 0x90填充。


3.向password.txt寫入可執行的機器碼,用來調用API彈出一個消息框
運行程序,成功彈出"hacktest"消息框。

參考

王清《0day安全:軟件漏洞分析技術》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章