Windows安全之 應用層(Ring3)的內存管理(三) 之 線程棧 Thread Stack

在前兩章提及過, 系統有時侯會在進程地址空間中,分配及調撥空間,(PEB,TEB), 在這裡線程棧也是一個例子

3.1 線程棧 

我們每創建一個線程, Windows也會為線程創建棧空間, 預設是1MB大小 透過修改Mircosoft C++ Compiler 的選項 /F 或 /STACK 選項, CreateThread 及_beginThreadex 都有一個參數是,可以用來指定一開始要調撥多少空間作為線程棧的用途

線程棧的存在有什麼意義呢? 主要就是供線程的所有函數使用, 隨著線程調用的函數樹愈深, 一直到棧底



線程棧初始時, 只會調撥分配到的首兩頁到內存(物理內存/ 虛擬內存 ) , 所有頁面也有PAGE_READWRITE屬性,  一開始調撥棧頂的0x080FF000這頁面 , 而他的下一頁面0x080FE00就會有PAGE_GUARD屬性, 此項技術使系統需要用到棧時才增大棧空間(調撥內存), 隨著函數一直調用, 參數不停的壓棧, 調用樹加深, 令指針接近棧底, 沒有及時返回, 就會棧位溢出(stack overflow)

假設:
調用樹非常深, 棧指針指到0x8003004, 而此時線程調用另一個函數 , 由於繼續壓入參數,  物理內存必需要調撥更多的物理存備器(虛擬或物理內存), 一如以往正常會調撥 0x08002000頁面 及 設置0x08001000保護屬性(PAGE_GUARD),   但是系統為0x08001000的頁面調撥時(棧底前的一頁), 系統對他進行特別處理, 變成以下樣子 ,



此時能看見到與一開始調撥的慣例很不同, 他的下一頁並沒有PAGE_GUARD屬性, 這是表明系統已經分配所有棧的空間給程序, 系統是"永遠不會調撥棧底頁面" , 具體原因如下: 當系統給0x08000000頁面調撥物理存儲器的時侯,它會執行一些額外的操作 -- 拋出棧位溢出異常(EXECEPTION_STACK_OVERFLOW異常) , 該異常代號 0x0C00000FD,通過使用結構化異常處理(SEH), 如果在異常後繼續使用棧, 那他會盡量用0x08001000頁面空間, 最終"試圖"訪問0x08000000 地址的頁面(該頁面並沒有調撥物理存儲器) , 系統會拋出訪問違規異常, 這異常發生後, 是因線程訪問棧時發生的話, 那線程麻煩十分大, 此時系統會回收控制權, 並將控制權提交給windows錯誤報告服務,(windows reporting services), 最終導致進程崩潰

另一個問題是, 當我們假設(0x08000000的下一頁)0x07FFF000的地方有機會是已經在進程中調撥了,  如果再調撥0x08000000, 那程序永遠不可能捕獲到棧位溢出

3.2 棧下溢,
如我們直接訪問棧頂外的空間如以下代碼
int test(){
        int a[10];
        a[10000] = 1;
    }

這種代碼可能會發生異常, 又可能不會, 因為如果訪問a[10000]的位置,  有可能棧外的位置, 也有可能是棧內的位置, 假如現在已調撥的只是棧頂位置, 那很順理成章-40000字節, 一定是棧外位置; 相反如果已經是接近棧底了 -40000 有可能是破壞棧空間 這是有兩種情況

P.S 在函數一開始, 編譯器會計算多少局部變量需要用到, 然後減去按需求減去esp, 騰出棧空間 供內部使用, 最後恢復棧空間 , 然後函數中一般使用ebp - 0x4 , -0x8 .... 的偏移 取局部變量, 所以-40000 是指以棧指針-10000個整數, 亦即-40000字節位置 

以下是兩種情況的圖解: 

Comments

Popular posts from this blog

Android Kernel Development - Kernel compilation and Hello World

How does Nested-Virtualization works?

Understanding ACPI and Device Tree