Posts

Showing posts from 2015

Windows安全之 應用層(Ring3)的)內存管理(四) 堆(Heap)

Image
4. Windows 最小單位的內存管理 - 堆 進程的默認堆是1MB, 透過修改鏈接器的/HEAP 屬性可以自定義大小 相比之前兩章, 堆是用來管理鏈表和樹的最好內存結構, 堆的優點就是可以不顧分配粒度, 和頁面邊界的事情, 但缺點就是分配及釋放比較慢一點, 而且也沒法再對物理存備器調撥進行直接控制。 4.1 創建額外的堆  HANDLE WINAPI HeapCreate( _In_ DWORD  flOptions, _In_ SIZE_T dwInitialSize, _In_ SIZE_T dwMaximumSize  ); 使用創建 HeapCreate 額外的堆 使用額外的堆 可使線程開銷減低, 而且把儲存對象分類, 不然如果把鏈表及二叉樹的對象放在同一個堆中, 會發現鏈表的節點有可能會令二叉樹的節點被破壞,  如下圖 創建額外的堆的原因 假設NODE1 有代碼會覆蓋它的下8個字節的地方, 這樣就會使NODE1及BRAJNCH2,3 被破壞 所以我們是需要創建額外的堆來保護我們的組件, 不應把所有類別都在放同一個堆中 使用堆, 能夠快速釋放, 例如我們將一個二叉樹的所有節點 進行釋放, 需要遍歷所有節點進行釋放, 但現在 我們使用堆存放著所有節點, 我們只需要把這個堆釋放, 例如Windows的資源管理器使用二叉樹來遍歷系統所有進程, 那如果刷新一次, 他就需要重新遍歷一次系統 看進程是否已經存在, 但使用堆就可以直接清除, 最後重新遍歷, 不用再比對什麼 4.2 從堆中分配內存塊 使用以下函數 LPVOID WINAPI HeapAlloc( _In_ HANDLE hHeap, //指定哪一塊堆中建立內存塊 _In_ DWORD  dwFlags, _In_ SIZE_T dwBytes ); 使用創建HeapAlloc在堆中分配一塊內存,  如果成功返回內存塊地址, 若較大的內存分配應直接使用VirtualAlloc , 而不使用堆函數 LPVOID WINAPI HeapReAlloc( _In_ HANDLE hHeap, _In_ DWORD  dwFlags, _In_ LPVOID lpMem, _In_ SIZE_T dwByte

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

Image
在前兩章提及過, 系統有時侯會在進程地址空間中,分配及調撥空間,(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安全之 應用層(Ring3)的內存管理(二) 內存映射文件

Image
2.1 內存映射文件 映射到內存的可執行文件與DLL 載入.exe到地址空間 1. 使用CreateProcess 所指定可執行文件的位置, 如果無法找到.exe 則返回FALSE 2. 如果找到, 系統會為進程創建一個進程內核對象 3. 系統為進程創建4GB私有地址空間 4. 在地址空間裡, 系統預訂(Reserve)一塊足夠大的空間來容納.EXE文件,待預訂的具體位置在x86是0x00400000, 在x64 是動態加載, 5. 系統會為地址空間區域進行標示, 表明該區域 的後備存儲器是在.EXE文件自身, 而非頁交換文件 載入.dll 到地址空間, 使用LoadLibrary函數 載入DLL 1. 系統會預訂一塊足夠大的地址空間區域來容納.dll, 待預訂的地址在.dll中指明, 預設為0x00100000 , 如已加載 , 加載到別的地方時, .DLL內部會進行重定位, 一開始都假設區域的加載地址是0x00100000 , 那以這個地址作為基址, 進行偏移就錯了, 所以需要重新獲取dll 加載地址進行重定位(Relocation), 在我PE文件章中已提及過 2. 系統會對地址空間進行標示, 標示是後備存儲器是來自.DLL文件(硬盤上) ,如果因無法分配dll文件大小使無法加載到原本指定位置(0x00100000), 而系統需要進行重定位, 系統會額外標示有一部份是映射到頁交換文件 2.2 內存不會共享靜態數據 如果一個應用程序已在運行, 我們再打開一個新進程時, 系統會創建4GB內存地址空間及新的進程及線程對象,假設第二個進程現在開始, 這時系統只不過把應用程序的代碼與數據的虛擬內存頁面,映射到第二個實例空間中 如下圖 P.S 這裡的圖為什麼說是虛擬內存, 沒有物理內存, 因為所有虛擬地址空間跟物理內存本來就存在映射關係,所以在內存中他們地址也是一樣的,不用特別複製, 虛擬地址映射到虛擬內存是因為當頁面沒有用時被交換到硬盤(虛擬內存) 是會隨機變動的, 頁面就更新到新進程到。 2.3 頁面寫時複製 但這裡存第另一個問題, 當一個進程修改了頁面內容, 就改變了本來進程初始化的樣貌, 甚至令其他同時運行的程序崩潰. 為解決這個問題, 在寫入頁面時 系統會首先攔截這項嘗試, 把要修改的頁面, 複製到

Windows安全之 應用層(Ring3)的內存管理(一) 進程的虛擬地址空間 與 虛擬內存(頁交換文件), 及物理內存的關係

Image
Windows應用層,有三種內存管理 1. 進程的虛擬地址空間 2. 內存映射文件(Memory-Mapping File) 3. 堆(Heap) 本章節先剖析 WIN32進程的虛擬地址空間 一個進程的虛擬地址空間為 2^系統位數, 如32位就2^32 = 4GB , 代表每個進程擁有4GB的虛擬地址空間 但這4GB是"虛擬的" 不是實質存在, 在我的另一篇文章中寫了WINDOWS是怎麼把虛擬地址跟物理掛勾起來 實現每人也有4gb的虛擬地址空間 而這裡是說我們如何使用?? 進程的虛擬地址空間 在Windows中4GB空間被劃分成以下區域 就32位Windows而言 1.1 0x00000000 - 0x0000FFFF 是空指針分區 以下代碼 int *ptr = (int*)malloc(sizeof(int)); *ptr = 5; 這個代表沒有檢測到分配內存空間不成功時侯的情況 , windows系統,針對這種情況 , 劃分這區域, 由於ptr分配失敗的時侯, ptr = NULL , 亦即0x00000000 程序就會拋出異常 1.2 0x00010000 - 0x7FFEFFFF 是用戶模式分區 1.2.1 分配虛擬地址空間 , 粒度與頁面大小限制 用戶模式分區,在32位系統中有2GB空間 當程序被系統加載時, 大部份的虛擬內存都是還沒跟物理內存掛勾起來的,或說閒置(free)的,我們必須調用VirtualAlloc函數分配區域,分配的動作稱為預訂(Reserving) 當程序預訂地址時,會確保區域的起始地址正好是分配粒度的整倍數, 大部份CPU 分配粒度也是64kb 也就是說, 分配一個區域時系統會分配的地址是64KB的整數倍 而區域的大小為頁面大小4KB 有時侯系統會以應用程序的名義來預訂區域, 例如系統會分配一塊空間用來存放系統環境塊(Process Environment Block(PEB)), PEB是一個完全由系統創建的內存塊, 當系統創建進程時, 同時都會替他在他的虛擬內存中,預訂一塊區域,存放PEB, 管理所有進程信息 系統同時會創建線程環境塊(Thread Environemnt Block(TEB))來協助管理所有線程, 系統會在創

Windows安全之 內存管理 虛擬地址, 邏輯地址 ,物理內存, 頁表與頁目錄

Image
以32位Windows 為例 物理內存頁, 每0x1000 為一頁 , 頁號(page number)為首地址, 內容為頁面偏移(page offset) , 由於內存最小單位為頁(4KB) 由 0x0000 0x1000 0x2000 0x3000 .... 0xFFFFF 每次加上一千 最大數為而往後數需要符合頁面內存大小(4KB) 的限制, 最大的頁幀/頁面號為0xFFFFF 最大內存號最大值可以去到0xFFFFF (總數為 : 2^20) 因此帶出以下概念 頁目錄(Page Directory,PD), 管理所有頁目錄項(Page Directory Entry,PDE) , 管理所有物理內存頁的信息 就需要一個大小為2^20的連續內存空間, 沒有可能分配一個1MB的數組,或說是連續空間來處理, 這太不現實也不夠效率, 於是開發OS的大神想到頁目錄(Page Directory)與頁表(Page Table)機制, 目錄內每一項也是4byte, 高20bit存放 頁表(Page Table) 首地址, 低12bit為0 (頁目錄指向1024個不同頁表) 而 頁表(Page Table), 頁表中每一個內容 為4byte , 一共有1024項 , 即大小為4KB , 高20bit為物理頁的物理地址, 低12bit為物理表面的設置 (如讀,寫,保護等) (頁表指向1024個不同物理內存頁) 意思就是 頁目錄有1024個指針,指向1024個頁表(Page Table), 然後每1個頁表,指向1024個地址全是物理頁的物理地址, 透過物理頁的首地址加上虛擬地址低12位的偏移, 最後容易地算出物理地址 那頁目錄*頁表就是1024*1024 剛好是 2^20 = 1MB 是32位操作系統的內存頁總數 對於x86系統, 頁目錄的物理地址存放在CR3寄存器 ----------------------------------------------- 轉換地址過程: 地址結構 1. 首先找到頁目錄所在的物理頁 2. 按照虛擬地址中的 32-22bit(位) 找到 PDE 地址 3. 通過頁目錄項的首 20bit(位) 找到了頁表地址 4. 通過頁表中使用 虛擬地址最低12位(21-12),

Windows安全 之 向量化異常處理機制(Vectored Exception Handling, VEH) 之Hook

Image
本文需讀者具備基礎的匯編知識,調用約定知識, 以及基本逆向概念 提到Windows異常處理機制, 可分兩大類, 一類是向量化異常處理(VEH), 一類是結構化異常處理(Structured Exception Handling, SEH), 由於在Windows中, VEH 是除雙機調試及調試器外, 最優先處理的函數, 因此本文先集中解釋VEH 很多人編程時都不會使用異常機制, 因為當正常運行時, 異常根本沒有作出任何用途。 但當異常出現在我們預計之內, 異常處理函數就變得十分強大。 本文分兩大部份 1. 硬件斷點 2. VEH的使用與分析 1. 硬件斷點 這個在網上很多文章講得十分詳盡, 我這裡輕輕帶過。 每個線程中, 都有自己一套的CPU寄存器, 稱為上下文(CONTEXT) 結構如下: typedef struct _CONTEXT { // // The flags values within this flag control the contents of // a CONTEXT record. // // If the context record is used as an input parameter, then // for each portion of the context record controlled by a flag // whose value is set, it is assumed that that portion of the // context record contains valid context. If the context record // is being used to modify a threads context, then only that // portion of the threads context will be modified. // // If the context record is used as an IN OUT parameter to capture // the context of a thread, then only those portions of the thread's // context

函數調用 push ebp 詳解

//假設函數a中調用 函數b - 0x123456 // 函數a push ebp mov ebp, esp ...................... push eax push ebx push ecx call 0x123456 <--- 1. 函數調用前 使用ebp訪問參數, push EIP , jmp xxxxxxxxx <----- 6. ebp 正常使用 ...................... mov esp, ebp pop ebp //函數b - 0x123456 push ebp <------- 2. 保存上層用於訪問參數的ebp mov ebp, esp <-----3. 設定新的ebp 訪問此函數的參數, 此時esp 指向ebp地址 ..................... mov esp, ebp <----- 4. 把指向ebp在找中的地址 給棧指針 pop ebp <------ 5.把ebp 恢復成調用者ebp ret // 返回 , 返回時ebp 正常 保存ebp 1. Esp 用作分配棧空間 不能用作訪問參數 2. Ebp 用作訪問參數,意味每次調用函數ebp 也會被修改, 因為調用函數參數會壓棧,再次使用來訪問參數(不停調用 不停改變) 3. 因此 每次調用 也保存ebp 是為了調用者 返回後 仍讓調用者繼續訪問自己的參數 push ebp 也便於找函數返回地址

Windows安全之PE結構(七)之重定位表(Relocation Table)結構分析

Image
什麼是重定位呢.. 是把我們一些地址,重新定位的意思。 我們都知道每個程序在windows系統中 都有自己4GB的虛擬空間, 可是如果在WIN系統只有.EXE這樣一種PE文件的話。真是沒有重定位的重要性存在,可是事實有動態鏈接庫.DLL檔 , DLL檔本身自己是沒有一個虛擬空間,而他是需要被加載到不同的.EXE的私有空間中, 當加載後 有什麼基址是需要重定位呢? 就是DLL中的一些牽涉到直接尋址的一些指令 就需要進行重定位處理 直接尋址: 簡單意義就是在匯編代碼中 看到有 [XXXXXXX] 的語句 基本上都需要重定位 間接尋址: 這是額外知識, 匯編語言可以把一個地址存入eax 然後透過eax去尋址,這個eax就不需要重定位了 那重定位 究竟需要什麼呢?? 首先一定是需要重定位的地方 然後就是重定位的結果 如下圖 dec dword ptr [100030000] push dword ptr [10003000] mov eax, dword ptr[10003000] 這三個地址是需要進行重定位的,因為這是反匯編.dll檔後的靜態地址,加載後這個地址就不一樣了 假如加載後 dll在exe檔中的虛擬基地址是0x20000000 那0x20000000h - 0x10000000h = 0x10000000 這裡0x20000000是假設加載dll模塊到exe的虛擬空間後,他的基址是0x20000000 他如 預設DLL的基址 相差了10000000H 那我們就需要把dll中所有的直接尋址 後移10000000H 以上面的例子就是 dec dword ptr [200030000] push dword ptr [20003000] mov eax, dword ptr[20003000] 可是我們怎麼知道重定位表有多少個內容需要重定位呢?? 整個.reloc區塊就如下圖 IMAGE_BASE_RELOCATION STRUCT VirtualAddress DWORD ? ;重定位表的rva SizeOfBlock DWORD ? ;重定位表的大小 TypeOffset WORD ? ;每一項佔一個字(兩個byte) IMAGE_BASE

Windows安全之PE結構(六)之導出表(IDT)結構分析

Image
在這裡來說 要清一下DLL檔(dynamic link library)的基礎認識, 每一個windows擁有4GB的虛擬內存空間, 可是我們發現 有好多好多的函數 如MessageBox等 在一個win系統下 有無限個程序會調用到這個方法 但如果每一個程序也需要在內存中加載一次 不就重覆了嗎? 有一百個程序需要調用MessageBox, 在內存也只需要加載一次User.dll 所有程序在內存中找到他, 然後複製需要的函數到我們的程序裡. 省了很多內存.我們的微軟前輩們都很強大 lol.. 大部份的DLL文件也包含了導出表! (可不是指所有的DLL) 導出表(EXPORT TABLE) 中的主要成分是一個表格,內含函數名稱,輸出序數等,序數是指定DLL某個函數的16位數字,在指向的DLL中是獨一無二的,在此我們不提倡只用序數來索引函數的方法,這樣會給DLL文件帶來維護的問題,例如當DLL文件一旦升級或修改可能導致調用改DLL的程序無法加載到需要的函數 又是DataDirectory(數據目錄表)的第一個成員指向導出表 IMAGE_EXPORT_DIRECTORY STRUCT Characteristics DWORD ? ;未使用,總是定義為0 TimeDateStamp DWORD ? ;文件生成時間 MajorVersion WORD ? ;未使用,總是定義為0 MinorVersion WORD ? ;未使用,總是定義為0 Name DWORD ? ;模塊的真實名稱 Base DWORD ? ;基數,加上序數就是函數地址數組的索引值 NumberOfFunction DWORD ? ; 導出函數的總數量 NumberOfName DWORD ? ; 以名稱方式導出的函數的總數 AddressOfFunction DWORD ? ; 指向輸出函數地址的RVA AddressOfNames DWORD ? ; 指向輸出函數名稱的RVA AddressOfNameOrdinals DWORD ?; 指向輸出函數序號的RVA IMAGE_EXPORT_DIRECTORY ENDS 以下是count.dll 的解析 我只導出了一個函數 嘗試查找它的地址 用LordPE把它打開,

Windows安全之PE結構(五)之導入表(IAT)結構分析

Image
輸入表結構 回顧一下, 在PE文件頭的IMAGE_OPTIONAL_HEADER 結構中的DataDirectory(數據目錄表)的第二個成員就是指向輸入表的, 而輸入表是以一個IMAGE_IMPORT_DESCRIPTOR(簡稱IID)的數組開始. 每個PE文件鏈接進來的DLL文件都分別對應一個IID數組結構在這個IID數組中,並沒有指出有多少個項(就是沒有 IMAGE_IMPORT_DESCRIPTOR STRUCT UNION Characteristics DWORD ? OriginalFirstThunk DWORD ? ;一個指針 指向INT基址 (IMAGE_THUNK_DATA數組) ends TimeDataStamp DWORD ? ForwarderChain DWORD ? Name DWORD ? FirstThunk DWORD ? ;一個指針 指向IAT基址 (IMAGE_THUNK_DATA數組) IMAGE_IMPORT_DESCRIPTOR ENDS 以下 IMAGE_THUNK_DATA 這個結構十分重要 WINDOW能把靜態和動態的內存分配達至最小的變動因為UNION是共享內存的 最高位為1時 表示函數以序號方式導入,低31位被看作為一個函數序號 最高位為0時 表示函數以字符串類型的函數名方式導入,這時雙字的值是一個RVA,指向一個IMAGE_IMPORT_BY_NAME的結構中。 IMAGE_THUNK_DATA STRUCT union u1 ForwarderString DWORD ? ;指向一個轉向者字符串的相對地址(RVA) Function DWORD ? ;被輸入的函數的內存地址 Ordinal DWORD ? ;被輸入的API函數值 AddressOfData DWORD ? ;指向IMAGE_IMPORT_BY_NAME ends IMAGE_THUNK_DATA ENDS OriginalFirstThunk 和 FirstThunk (IAT/INT)在硬盤靜態時都是指向同一個地方 就是IMAGE_IMPORT_BY_NAME IMAGE_IMPORT_BY_NAME STRUCT Hint WORD ? ;表示函數的序號 Name BYTE ? ;Name 字段定義了