首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 組合語言

保護模式控制轉移詳解 (二):調用門、特權級變換、堆疊切換與返回機制

     編輯:組合語言

本文為保護模式教程系列第二部分,深入剖析任務內控制轉移機制:無特權級轉移 (JMP/CALL/RET)、調用門跨特權調用、堆疊自動切換與參數複製、帶立即數返回指令清理參數、別名技術,並附實例四 (ASM4.ASM) 完整演示 0↔1↔3 級特權轉換。

一、任務內無特權級轉移

  • JMP FAR Selector:Offset:CPL=DPL, RPL≤DPL,目標非一致碼段 → 直接載入 CS/EIP,快取更新,無堆疊變化
  • CALL FAR Selector:Offset:同級調用 → 當前 CS:EIP 壓入**當前堆疊**,載入目標 CS:EIP。16-bit 模式壓字,32-bit 壓雙字
  • RET FAR [imm]:從堆疊彈出 CS:EIP (及可選參數清理 imm 位元組),RPL=CPL=DPL → 同級返回

二、別名技術

同一記憶體區域用不同類型描述符描述,滿足不同操作需求。實例三中:

  • GDT 中 LDTable (系統段描述符, DT=0, TYPE=LDT) → LLDT 載入 LDTR
  • LDT 中 ToLDT (資料段描述符, DT=1, TYPE=RW) → 載入 DS/ES/FS/GS 以資料段方式存取 LDT 內容 (如讀取代碼段界限)

原因:系統段描述符**不能**載入資料段寄存器。別名技術突破此限制,常用於:不同 TYPE 訪問、不同 DPL 訪問、代碼段寫入 (以資料段別名寫入)。

三、任務內特權級變換核心規則

  • 四特權級 (0 最高, 3 最低),CPL 存 CS.RPL
  • 外→內 (提權):只能用 CALL 經調用門,JMP 不能提權
  • 內→外 (降權):只能用 RET FAR,JMP/CALL 不能降權
  • 一致碼段 (C=1) 例外:CPL≥DPL 可直接 CALL/JMP 進入,CPL 不變

四、調用門機制深度解析

調用門描述符 (8 bytes, DT=0, TYPE=12/14) 包含:目標代碼段選擇子 + 32-bit 偏移 = 48-bit 完整入口指標。DCount (0-31) 指定參數複製數量 (雙字)。

特權檢查順序 (CALL 經調用門)

  1. 檢查調用門描述符:P=1, max(CPL, RPL) ≤ Gate.DPL
  2. 取門內目標代碼段選擇子,**強制 RPL=0**
  3. 檢查目標代碼段描述符:P=1, 類型=代碼段
  4. 特權比較:
    • 目標 DPL = CPL → 同級轉移 (無堆疊切換)
    • 目標 DPL < CPL → **提權轉移** (觸發堆疊切換)
    • 目標 DPL > CPL → #GP (除一致碼段 CPL≥DPL)

提權時堆疊切換與參數複製流程

  1. 從 **TSS** 讀取目標特權級 SS:ESP (如 0 級堆疊),建立**空堆疊**
  2. 壓入**外層 SS:ESP** (用於返回恢復)
  3. 從外層堆疊複製 **DCount 個雙字參數** 到內層堆疊
  4. 壓入返回地址 (外層 CS:EIP)
  5. 載入目標 CS:EIP,CPL = 目標 DPL,SS:ESP = 內層堆疊頂

堆疊切換示意圖

**關鍵**:參數自動複製使內層程式無需感知堆疊切換,直接用 [EBP+8] 等訪問參數。

五、向外返回機制 (RET FAR imm)

  1. 彈出返回地址 (CS:EIP),檢查選擇子 RPL > CPL → 降權
  2. 跳過內層堆疊參數 (imm 未指定則不跳過)
  3. 彈出外層 SS:ESP,恢復外層堆疊
  4. 調整外層 ESP 跳過調用前壓入的參數 (imm 位元組)
  5. 檢查 DS/ES/FS/GS 指向段在外層是否可訪問,不可訪問則載入空選擇子 (防保護漏洞)
  6. 繼續外層執行

帶立即數 RET (如 RET 8) 同時清理內外層參數,**必須**用於有堆疊傳參的子程序。否則外層堆疊指標錯誤,導致崩潰。

C 調用慣例 vs STDCALL:C 調用由呼叫者清理參數 (需外層手動 ADD ESP),STDCALL 由被呼叫者清理 (RET n),後者符合 x86 硬體機制,Win32 API 採用 STDCALL。

六、實例四:ASM4.ASM - 0/1/3 級特權轉換完整演示

任務結構:單任務,含 TSS、LDT、0/1/3 級堆疊、三代碼段 (演示 3 級、顯示子程序 1 級、過渡 0 級)、臨時 0 級 16-bit 代碼。

執行流程

  1. 實模式初始化 GDT/LDT/TSS
  2. 進入保護模式 (CPL=0),載入 TR/LDTR
  3. CALL 調用門 ToT32GateA (DPL=0) → 過渡代碼 T32Begin (0 級)
  4. 建立返回 3 級環境:壓入 3 級 SS:ESP + 入口點 DemoBegin
  5. RETF → **0→3 級降權**,切換到 3 級堆疊,進入 DemoBegin
  6. CALL 調用門 ToEchoGate (DPL=3) → 顯示子程序 EchoSub (1 級) → **3→1 級提權**,複製 0 參數
  7. EchoSub 從堆疊取呼叫者 CS.RPL (=3) 顯示 "CPL=3"
  8. RETF → **1→3 級降權**,返回演示代碼
  9. CALL 調用門 ToT32GateB (DPL=3) → 過渡代碼 T32End (0 級) → **3→0 級提權** (有去無回)
  10. JMP 臨時代碼 → 清理、返回實模式

關鍵代碼片段

; T32Begin: 建立 3 級返回環境並降權
T32Begin:
  mov ax, DemoStack0_Sel; mov ss, ax; mov esp, DemoStack0Len  ; 建 0 級堆疊
  push dword ptr DemoStack3_Sel; push DemoStack3Len           ; 外層 SS:ESP
  push dword ptr DemoCode_Sel; push offset DemoBegin          ; 返回 CS:EIP
  retf                                                      ; 0→3 級降權

; EchoSub: 顯示呼叫者 CPL (從堆疊取 CS 選擇子)
EchoSub:
  push ebp; mov ebp, esp
  mov ax, Echo_Sel1; mov ds, ax          ; 可讀代碼段別名
  mov ax, Video_Sel; mov es, ax
  mov edi, 1996; mov esi, offset Message; mov ah, 4eh
  ; ... 顯示 "CPL=" ...
  mov eax, [ebp+8]        ; 堆疊上方第一雙字 = 呼叫者 CS 選擇子
  and al, 3               ; 取 RPL (bit 0-1)
  add al, '0'; stosw      ; 顯示數字
  pop ebp; retf

; DemoBegin: 3 級代碼
DemoBegin:
  call32 ToEcho_Sel, 0    ; 3→1 級顯示 CPL
  call32 ToT32B_Sel, 0    ; 3→0 級進入過渡 (不返回)

總結

調用門是 x86 保護模式實現**受控提權**的核心機制:

  • 封裝入口點 (Selector:Offset),隱藏內部結構
  • DCount 自動複製參數,簡化跨特權調用
  • 硬體強制特權檢查 (Gate.DPL, Target.DPL, CPL, RPL)
  • 堆疊自動切換隔離特權域資料
  • RET FAR imm 硬體支援參數清理,匹配 STDCALL 慣例

實例四完整展示了 0↔1↔3 級雙向轉換、參數傳遞、別名技術、TSS 堆疊指標使用,是掌握 x86 保護機制的經典教材。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved