Posts

Showing posts with the label Windows編程

Windows內核安全之 線程調度

Image
線程一個比較重要的概念是, CPU中永遠調度的是線程, 而不是進程, 而進程的切換完全是源於是否同一個CR3寄存器, 詳見SwapContext的實現 以下先列出KTHREAD對象 內核中對線程優先級(priority) 分別使用0~31表示 實時(Real-time)類別: 16~31 動態(Dynamic)類別: 1~15 系統(System)類別: 0 線程在內核的執行體層(Executable Layer)中 使用以下6種優先級 實時(Realtime) 高(High) 普通之上(Above normal) 普通(normal) 普通之下(Below normal) 低(Low) 執行體層透過一個數組PspPriorityTable 轉換成真正的表示 以下是他們的對應關係 實時(Realtime) 24 高(High) 13 普通之上(Above normal) 10 普通(normal) 8 普通之下(Below normal) 6 低(Low) 4 進程的對象EPROCESS結構中 BasePriority 指定一個進程中所有線程的預設優先級 可以通過KeSetPriorityAndQuantumProcess 或 KeSetBasePriorityThread 函數設置優先級 KTHREAD中的Priority域記錄一個線程當前實際優先級, 他一般是在BasePriority基礎上提升到某個增量 Priority不會超過0~15 內核通過調用KiComputeNewPriority計算並調整非實時的線程優先級 我們可以使用NtSetInformationProcess為進程的基礎優先級進行微調, 所以一條線程在執行體中的值並非數組中指定的值 優先級的提升: ------------- 由於每個線程優先級都有對應的鏈表維護著相同優先級的線程 方便調度 所以優先級的提升, 一定是在加入鏈表之前 內核中不小函數的參數使用KPRIORITY Increment進行提升 如: KeInsertQueueApc KePulseEv...

Windows安全 之 內存映射文件(Section對像)MmCreateSection與MmMapViewOfSection 流程分析

Image
MmCreateSection 內存區(Section)內核對像有兩種,但都是基於分頁內存的: 一種是基於頁面文件的 一種是基於其他文件,以文件空間作為基礎的 如EXE在硬盤上的空間 MmCreateSection主要目的: 1. 填充CONTROL_AREA對像 2. 填充SEGMENT對像 3. 調用ObCreateObject創建Section內核對像 4. 返回Section內核對像 SEGMENT是真正描述映射區域的對像 , 內核函數MmCreateSection中有三種創建方式: 1. MiCreatePagingFileMap //以PageFile(頁面文件)為基礎的文件共享 2. MiCreateImageFileMap //以可執行映像作文件共享 3. MiCreateDataFileMap //以普通數據文件作為基 p.s. 後兩者雖表面內核文件對像, 從函數內部邏輯可發現, 如果映射同一個文件時, Section對像會共用同一個Segment對像 由於Section對像的創建完全是建基於SEGMENT對像, 因此探討SEGMENT對像才是正確方向 了解共享實現 SEGMENT(段對像,分配在分頁內存): 1. 分配的共享空間頁面總數量大小的MMPTE陣列緊隨著SEGMENT對像(用於建立映射視圖時) 2. 指向CONTROL_AREA指針(CONTROL_AREA, 分配在非分頁內存,即物理內存, 它亦會指向SEGMENT對像, 建立互指的關係) 3. 記錄對應節中的所有頁面的數量 4. 全部MMPTE最終指向節的起始地址 建數內部會再次創建真正的: CONTROL_AREA(控制區對象,分配在分頁內存) 1. 指向文件對像指針 2. 尾隨著多個SUBSECTION對像 (指PE中各個節的起始地址,使用Segment的MMPTE進行初始化->再讓VAD中會使用SubSection) 3. SUBSECTION形成鏈表,其中一個指針會指回CONTROL_DATA SubSection結構如下: typedef struct _SUBSECTION { PCONTROL_AREA ControlArea; PMMPTE SubsectionB...

軟件調試(12) - Win32堆,CRT堆簡介及堆溢出攻擊及系統檢測溢出方案

Win32堆與CRT堆 ------------------------------------------------ 堆分配函數: malloc / HeapAlloc/ new 堆的基本運作: 內存管理器(Memory Manager)把一塊較大的內存空間,委托給堆管理器(Heap Manager)來管理,堆管理器將大塊內存分割成不同大小的很多個小塊來滿足應用程序的需要 而實現內存委托的一系列函數,稱為池管理器(Pool Manager) Windows在創建進程時,加載器函數執行進程用戶態初始化階段會調用RtlCreateHeap函數為新的進程創建第一個堆 稱為默認堆 默認堆: 由LdrpInitializeProcess所調用RtlCreateHeap建立 此時新進程中只有EXE模塊和NTDLL模塊,EXE模塊中的用戶代碼還未執行,創建好的堆句柄會存放在PEB中ProcessHeap字段, 與棧類似也有HeapSegmentReserve和HeapSegmentCommit兩個字段來決定默認堆的保留大小和提交大小 分配私有Win32堆: 用戶透過HeapCreate函數創建自己的堆 函數原型: HANDLE WINAPI HeapCreate( _In_ DWORD flOptions, _In_ SIZE_T dwInitialSize, _In_ SIZE_T dwMaximumSize ); 刪除則是調用: BOOL WINAPI HeapDestroy( _In_ HANDLE hHeap ); 堆列表: 每個進程的PEB結構以列表方式記錄當前進程的所有堆句柄: NumberOfHeap 是堆總數 ProcessHeaps 每個堆的句柄,它是一個數組 MaximumNumberOfHeaps 可分配堆總數 如果NumberOfHeap == MaximumNumberOfHeaps 堆管理器則增大MaximumNumberOfHeaps 的值並重新分配ProcessHeaps 分配堆空間: 使用HeapAlloc可以在指定堆中分配空間 函數原形: LPVOID WINAPI HeapAlloc( _In_ HANDLE hHeap, _In_ DWORD dwFlags,...

軟件調試(11) - 棧安全檢查,溢出及緩沖區溢出

用戶態及內核態棧 -------------------------------------------- TSS會記錄了不同優先級所使用的棧的基本信息 TSS+0x4 到 TSS+0x28的24字節是用來記錄棧的段信息和棧指針ss:esp 每個Win32線程都有兩個棧: 一是用戶態棧,記錄在_TEB->_NT_TIB 一是內核態棧,記錄在_KTHREAD 他們記著線程棧的詳細資料 用戶態棧- 1MB x86內核態棧-12KB x64內核態棧-24KB 考慮到gui線程在調用gdi服務時通常需要更大的棧段空間,所以在一個線程被轉為GUI線程後,內核態棧一般會被替換成一個更新的新棧 -------------------------------------------- 內核棧的創建: PspCreateThread是Windows內核態用於創建線程的一個重要內部函數 不論是PspCreateSystemThread或NtCreateThread 由PspCreateThread創建內核態棧,大小總是默認大小的內核態棧 但Windows線程在第一次調用Win32子系統服務時,將其轉變為GUI線程->轉化後系統的PsConvertToGuiThread函數會為該線程重新建立一個棧,然後使用KeSwitchKernelStack切換到新的棧,新的棧是可以改變大小的,稱為大內核態棧(Large Kernel Stack) 棧大小的最大值記錄在MmLargeStackSize 在一個線程被轉變為GUI線程 其KTHREAD結構的LargeStack會改為1 同時其Win32Thread字段會由0變為非0 棧段不會立即增大,在需要增大時調用MmGrowKernelStack函數來增長棧 ------------------------------------------------------- 用戶態棧的創建 初始線程: CreateProcess->NtCreateProcess->BaseCreateStack(調用NtCreateThread前)初始化用戶態棧 初始線程外: CreateThread / CreateRemoteThread->BaseCreateSt...

軟件調試(10) - 棧和函數調用

用戶態及內核態棧 -------------------------------------------- TSS會記錄了不同優先級所使用的棧的基本信息 TSS+0x4 到 TSS+0x28的24字節是用來記錄棧的段信息和棧指針ss:esp 每個Win32線程都有兩個棧: 一是用戶態棧,記錄在_TEB->_NT_TIB 一是內核態棧,記錄在_KTHREAD 他們記著線程棧的詳細資料 用戶態棧- 1MB x86內核態棧-12KB x64內核態棧-24KB 考慮到gui線程在調用gdi服務時通常需要更大的棧段空間,所以在一個線程被轉為GUI線程後,內核態棧一般會被替換成一個更新的新棧 -------------------------------------------- 內核棧的創建: PspCreateThread是Windows內核態用於創建線程的一個重要內部函數 不論是PspCreateSystemThread或NtCreateThread 由PspCreateThread創建內核態棧,大小總是默認大小的內核態棧 但Windows線程在第一次調用Win32子系統服務時,將其轉變為GUI線程->轉化後系統的PsConvertToGuiThread函數會為該線程重新建立一個棧,然後使用KeSwitchKernelStack切換到新的棧,新的棧是可以改變大小的,稱為大內核態棧(Large Kernel Stack) 棧大小的最大值記錄在MmLargeStackSize 在一個線程被轉變為GUI線程 其KTHREAD結構的LargeStack會改為1 同時其Win32Thread字段會由0變為非0 棧段不會立即增大,在需要增大時調用MmGrowKernelStack函數來增長棧 ------------------------------------------------------- 用戶態棧的創建 初始線程: CreateProcess->NtCreateProcess->BaseCreateStack(調用NtCreateThread前)初始化用戶態棧 初始線程外: CreateThread / CreateRemoteThread->BaseCreateSt...

軟件調試(9) - 深入探討Windows結構化異常(SEH)底層實現

r3/r0一樣, FS:[0] 保存著SEH的鏈表頭 應用層: TEB與TIB結構 TEB中的起始處是指向TIB的地址,TIB第一個字段位exceptionList即為鏈表表頭 內核層 KPCR與TIB結構 KPCR->TIB TIB的ExceptionList字段 ------------------------------------------------------- 該字段位於TIB的起始處 typedef struct _EXCEPTION_REGISTERATION_RECORD{ struct _EXCEPTION_REGISTERATION_RECORD *next; //0xFFFFFFFF代表最後一個節點 ULONG handler; //異常處理器的地址(需符合seh函數原形) } EXCEPTION_DISPOSITION SehHandler(_EXCEPTION_RECORD *exceptionRecord, //處理的異常 void * EstablisherFrame, //異常函數的EBP _CONTEXT *contextRecord, //線程上下文 void * DispatcherContext //分發函數 ) TIB自身不在棧中,而SEH鏈表則是存在棧中 登記異常函數 ------------------------------------------------------- push seh_handler //處理函數地址 棧空間: push FS:[0] //舊的鏈表節點處理器地址 FS:[0] move FS:[0],ESP //登記新結構 SEH_HANDLER ..... mov esp,dword ptr fs:[0] //保存下一個結構地址 pop dword ptr fs:[0] //將下一個結構的地址變成鏈表頭 用戶態版本RtlDispatchException ------------------------------------------------------- (內核中的異常分發:修改EIP到R3的KiUserExceptionDispatcher->)RtlD...

軟件調試(8) - 探討從應用層到內核層的中斷與異常管理

硬件異常 IDT 在idtr讀取idt表基址 ------------------------- 門描述符: 任務門 - 用於任務切換 里面包含用於選擇任務狀態段(tss)的段選擇子 中斷門 - 用於描述中斷處理例程入口 陷阱門 - 用於描述異常處理例程入口 任務門: 描述的是一個TSS段->CPU要做的是切換到這個TSS段所代表的線程,然後開始執行這個線程,TSS段是用來保存任務信息的一段內存區,其格式由CPU定義 Windows會為每個CPU創建3-4個tss 1: 處理NMI 2: 處理#DF(double fault) 異常中再異常 3: 處理機器檢查異常 軟件異常 -------------------------- EXCEPTION_RECORD 結構 typdef struct _EXCEPTION_RECORD{ DWORD ExceptionCode //異常代碼 DWORD ExceptionFlags //異常標志 struct _EXCEPTION_RECORD* ExceptionRecord; //相關的另一個異常 PVOID ExceptionAddress; //異常發生地址 DWORD NumberParameters; //參數數組元素 ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; //參數數組 }EXCEPTION_RECORD, *PEXCEPTION_RECORD; #define EXCEPTION_BREAKPOINT STATUS_BREAKPOINT #define EXCEPTION_SINGLE_STEP STATUS_SINGLE_STEP 登記CPU異常 --------------------------- KiTrapXX例程在完成針對本異常的動作後,會調用CommonDispatchException->KiDispatchException 並通過寄存器將如下信息傳送到函數 1. 異常代碼->EAX 2. 異常指令地址->EBX 3. 其他信息作為附帶參數(最多3個),EDX(參數1),ESI(參數2),...

軟件調試(7) - 調試體系之內核調試原理

內核調試器通過內核調試引擎 控制或調試內核 內核其他部份->內核調試引擎->內核調試器 調試器通過內核調試協議 訪問和控制目標系統 內核調試API: KdAPI - 類似遠程調用方式訪問內核 與系統內核的接口函數 與調試器通信函數 斷點管理 - 使用KdpBreakpointTable記錄斷點 內核調試api - 提供調試信息給內核調試引擎 系統內核控制函數 - KdEnterDebugger 與 KdExitDebugger 管理函數 - KdEnableDebugger 與KdDisableDebugger ETW支持函數 - event log... 驅動程序更新服務 - 本地內核調試支持(XP後) kdcom.dll 負責通信部份 收/發數據包 - KdSendPacket - KdReceivePacket 接收電源狀態變化 - KdD0Transition ,喚醒系統用 - KdD3Transition ,休眠時用 內核調試引擎初始化: BIOS->Harddisk引導程式->執行NTLDR.exe->CPU初始化->切換保護模式->啟用分頁機制->通過配置boot.ini得到windows系統的系統目錄並加載系統內核文(ntoskrnl.exe)先檢查他的導入表並加載依賴文(包括內核調試通信模塊KDCOM.dll)->讀取注册表的hive->加載其中定義的啟動類型的驅動程序->完成所有工作->找到內核文件的PE文件頭找到入口函數->kISystemStartup->進入初始化三部曲-> 1. 調用HalInitializeProcessor() 初始化CPU 2. 調用KdInitSystem 初始化內核調試引擎 3. 調用KiInitializeKenrel開始內核初始化 KiInitializeKenrel->KeInitializeThread -> ExpInitializeExecutive() ExpInitializeExecutive進行大量工作包括進程管理器初始化: 進程管理器初始化: - 定義進程/線程內核對象類型 - 建立記錄系統中所有進程的鏈...

軟件調試(6) - 深入探討調試體系啟動及附加調試

用戶 <=== UI線程 <=== 調試器 ===> 調試工作線程 ===> 被調試進程 (獲取調試對象) (設置調試對象) 調試器 ----------------------------------------------------------------- DbgSsReserved[0] => 被調試線程鏈表頭 DbgSsReserved[1] => 調試對象的句柄 //判斷是否句柄 !handle xxxxx 鏈表頭結構: typedef struct _DBGSS_THREAD_DATA { struct _DBGSS_THREAD_DATA *Next; HANDLE ThreadHandle; HANDLE ProcessHandle; DWORD ProcessID; DWORD ThreadID; BOOLEAN HandleMarked; //退出標志 }DBGSS_THREAD_DATA,*PDBGSS_THREAD_DATA; WaitForDebugEvent , ContinueDebugEvent 會維護DbgSsReserved[0]鏈表 WinXP=> Ntdll!DbgUiGetThreadDebugObject 和Ntdll!DbgUiSetThreadDebugObject 實際上就是讀寫鏈表 WaitForDebugEvent => 從teb獲取調試對象 DbgSsReserved[1] 由kernel32的調試API所設置, 如果Windbg不是調用他來調試 *而CreateProcess時R3層會把他設置為非0 但在CreateProcess後Windbg會調用DbgUiSetThreadDebugObject設置為0 被調試進程 和原本進程的差異 ------------------------------------------------------------------ 1. EPROCESS Debugport字段不為0, 內核空間判斷一個進程是否正在被調試的主要特征 2. PEB的BeingDebugged字段不為0, 用戶態判斷是否正在被調試的主要方法 3. 可...

軟件調試(5) - 深入探討Windows調試體系(調試信息傳遞)

參與者: 被調試進程 調試進程 調試子系統(內核) 調試子系統主要分為3部份: - 位於NTDLL中的支持函數 DbgUi - 位於內核文件中的支持函數 DbgSs - 調試子系統服務器 Dbg while(WaitForDebugEvent(&DbgEvt,INFINITE)) { //處理等待得到的事件 //處理後恢復調試目標繼續執行 ContinueDebugEvent(DbgEvt.dwProcessID, DbgEvt.dwThreadId,dwContinueStatus); } WaitForDebugEvent - 用於等待和接收調試事件,收到事件後,調試器會根據事件的類型(事件ID)來分發和處理, 在處理調試事件的過程中,被調試進程是處於掛起狀態,處理調試事件後,調試器用ContinueDebugEvent將處理結果回復給調試子系統,讓被調試程序繼續執行,調試器則再次調用WaitForDebugEvent 等下一個調試事件 調試子系統的內核部份 ------------------------------------ 收集調試事件後=>以一個消息結構發送給調試子系統 => 使其保存在調試子系統的調試消息隊列中 => 子系統與調試器靠一個內核對象來同步=>當有調試消息需要讀取=>調試子系統服務器會設置這個待器線程被喚起 內核中: DBGKM_APIMSG 調試API: DEBUG_EVENT 由於兩者結構不一, 需要轉化過程 子系統服務器會將自己使用的結構轉化為NTDLL使用的DEBUG_WAIT_STATE_CHANGE, NTDLL再將這個結構轉化為調試器使用的DEBUG_EVENT結構 內核中Dbgk收集例程將所有調試事件分為8類 -------------------------------------------------- typedef enum _DBGKM_APINUMBER { DbgkmExceptionApi = 0, // 异常 DbgkmCreateThreadApi = 1, // 创建线程 DbgkmCreateProcessApi = 2, // 创建进程 DbgkmExitThreadApi = 3...

軟件調試(4) - 探討內核進程對象與內核簡介

每個Win32進程擁有以下元素: - 一個全局唯一的PID - 一個可執行映象(IMAGE) - 一個或多個線程 - 一個EPROCESS位於內核空間 - 一個對象句柄表 用以記錄或索引該進程所創建/打開的內核對象 - 一個用於描述內存目錄表起始位址的基址,簡稱頁目錄基址(DirBase),當cpu切換時會將該地址加載到cr3 -> 一直索引到真實存在的物理地 - 一個位於用戶空間中的進程環境塊(peb) , 被內核空間映射到用戶空間 - 一個訪問權限令牌(access token) 用於表示進程用戶,安全組,優先級 !process 0 0 第一個為pid, 0代表所有進程,第二個0指定要顯示的進程屬性,0代表進程基本屬性 EPROCESS ------------------------------------ dt _EPROCESS 86a7d030 把目標地址用_EPROCESS結構解析 EPROCESS有兩個與調試有關的位 - DebugPort ; EPROCESS+0xBC - ExceptionPort ; EPROCESS+0xC0 PEB ------------------------------------- PEB是進程環境塊, 它包含了進程的大多數用戶態信息, 與EPROCESS結構是位於內核空間不同 PEB是在內核態建立後,映射到用戶空間, 因此,在一個系統中,多個進程的PEB地址可能是同一個值 因為PEB是生存在R3 .process 86a7d030 <<< 先指明進程eprocess位置 dt _PEB 7ffdf000 <<< 才能使用dt _PEB 查看進程peb SESSION ID -------------------------------------- 進程的SESSION ID 是指該進程所在的WINDOWS會話的ID號, 當有多個用戶同時登錄時,WINDOWS會為每個用戶建立一個會話, - 每個會話有自己的WorkStation和卓面 - 這樣可以工作在不同的會話中共用同一個windows系統 - 對於典型xp系統,當只有一個用戶登錄時,用戶啟動的程序和系統服務都在session 0 - 當切換到...

軟件調試(3) - 調試異常與斷點異常

保護模式下的INT 3 INT 3 對應的異常處理函數是內核函數: nt!KiTrap03 斷點命中 --------------------- 由於是R3程序調試 =>會因斷點指令由R3轉入R0 =>由內核函數分發(11章詳解) =>由於異常來自r3, 而產生異常的進程正被調試(debugport非0),所以內核例程會把這個異常通過調試子系統,以調試事件的形式分發給r3的調試器,例如(vc6),在通知調試器後, 內核等待回復=>收到回復後調試子系統的函數會層層返回,最後返回到異常處理例程 KiTrap03會把程序指針寄存器-1 => 因而調試器看到的仍然是int3的位置 看似未被執行=>但其實已經執行 1. 保持原有指令完整性 2. 由於斷點存在, 原本指令其實未被執行=>因應該指向原有指令位置(改為INT 3 的地址) 恢復執行 ----------------------- 當用戶結束分析希望恢復時 =>調試器通過調試api通知調試子系統(內核), 這會導致系統內核的異常分發函數返回到異常處理例程,然後異常處理例程通過IRET/IRETD指令觸發一個異常返回動作 =>使CPU恢復上下文,從發生異常的位置繼續執行 =>注意:EIP指向斷點的位置,最後恢復指令 =>CPU繼續執行 特別用途 ------------------------ 0xCC塊的作用: 為調試版本=>分配緩沖區=>因為緩沖區或堆棧溢出時,程序指針意外指向了這些區域便因為INT3 而馬上中斷到調試器 還有就是填充函數或代碼段未尾的空閑空間,也就是用來做內存對齊=>截獲內存溢出 斷點API ------------------------ R0=>DbgBreakPoint / DbgBreakPointWithStatus R3=>DebugBreak 調試寄存器 ------------------------ DR0~DR3 : 指定斷點地址, 是虛擬地址不是物理地址, 因為CPU是在虛擬地址被翻譯之前做斷點工作, 意味不能透過它對物理內存作斷點行為 DR7 : 24位被分成四組 分別與四個調試寄存器對應,用於設置...

軟件調試(2) - 異常中斷與中斷向量表(IDT)簡介

IA-32 中斷異常列表: No. 助記符 類型 描述 來源 0 #DE 錯誤 除零錯誤 div / idiv指令 1 #DB 錯誤/陷阱 調試異常 任何代碼或數據引用 2 中斷 NMI中斷 不可屏蔽的外部中斷 3 #BP 陷阱 斷點 INT3指令 4 #OF 陷阱 溢出 INT0指令 5 #BR 錯誤 數組出界 BOUND指令 6 #UD 錯誤 無效指令(沒定義) UD2指令 7 #NM 錯誤 數學協處理器不存在 浮點或WAIT/FWAIT指令 8 #DF 中止 雙重錯誤 任何可能產生異常的指令/任意中斷 9 #MF 錯誤 向協處理器傳送操作數, 浮點指令 時檢測到頁錯誤,或段不存 在. 10 #TS 錯誤 無效TSS(任務狀態段) 任務切換或訪問TSS 11 #NP 錯誤 段不存在 加載段寄存器或訪問系統段 12 #SS 錯誤 棧段錯誤 棧操作或加載SS寄存器 13 #GP 錯誤 通用保護(GP)異常,如果 任何內存引用和保護性檢測 一個操作違反了保護模式 下的約定,而且該情況不屬 於其他異常,則產生GP 14 #PF 錯誤 頁錯誤 任何內存引用 15 保留 16 #MF 錯誤 浮點錯誤 浮點或WAIT/FWAIT指令 17 #AC 錯誤 對齊檢查 對內存中數據的引用 18 #MC 中止 機器檢查 錯誤代碼和來源與型號有關 19 #XF 錯誤 SIMD浮點異常 SIMD浮點指令 20 保留 32-255 用戶自定義中斷 可屏蔽中斷 錯誤代碼 31 3 2 1 0 ----保留--------------- -TI- -IDT- -EXT- 位0: EXT 如果為1表示外部事件導致異常 位1: IDT 描述符位置,如果為1表示錯誤碼的段選擇子索引指向的是IDT表中的門描述符,如果0表示索引部份指向的是LDT/GDT的描述符 位2: TI(GDT/LDT, 當IDT位為0時有效), 0:GDT / 1:LDT IA-32中斷異常的優先級 1(最高級) 硬件重啟動和機器檢查異常 2 任務切換陷阱 3 外部硬件(例如芯片組)通過CPU引腳發給CPU的特別干預 #F...

軟件調試(1) - 斷點簡介

IA結構CPU 提供的調試支持: INT3 指令 - 斷點指令, 當CPU執行到該指令時便會產生斷點異常以便中斷9到調試器 -> 軟中斷 EFLAGS中的TF標志位: 陷阱標志位,當標志位為1: CPU每執行完一條指令就產生調試異常 -> 單步執行 調試寄存器: DR0~DR7 -> 用於設置硬件斷點和報告調試異常的細節 斷點異常(#BP): 當INT3 指令執行時,產生此異常, CPU將會轉到該異常的處理函數, 他會進一步分發到調試器 調試異常(#DB): 當除INT3 指令以外的調試發生時, 產生此異常 TSS的T標志位:任務陷阱標志,當切換到設置了T標志的任務時,CPU會產生調試異常,中斷到調試器 參考:Windows軟件調試

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), ...