SDM Volume 3 Figure 2-1. IA-32 System-Level Registers and Data Structures
Author: 堇姬Naup
圍繞這張圖來展開(SDM Volume 3中的Figure 2-1. IA-32 System-Level Registers and Data Structures)
privilege level
這邊簡單展開一下 privilege level (https://blog.csdn.net/Gyc8787/article/details/121879012)
- Current Privilege Level (CPL): CPL 儲存在目前執行的 CS 暫存器的選擇器中。它表示目前執行任務的特權等級(PL)
- Descriptor Privilege Level (DPL): 當前任務試圖存取的物件的 PL
- Requestor Privilege Level (RPL): 用於 segment 的訪問控制
- I/O Privilege Level(IOPL): 對 I/O 操作進行控制
IOPL
Segment Selector 和 Segment Register
這邊繼續展開 Segment Selector 和 Segment Register
分段暫存器是存放 segment selector 的,共有六個分段暫存器:CS、DS、ES、FS、GS、和 SS
https://www.csie.ntu.edu.tw/~wcchen/asm98/asm/proj/b85506061/chap2/segment.html
register | name | 用途 |
---|---|---|
CS(Code Segment) | 程式碼段暫存器 | 儲存程式碼段(.text 段)的基底位置,負責指令存取。 |
DS(Data Segment) | 資料段暫存器 | 儲存變數的預設位置(.data 段),負責資料存取。 |
ES(Extra Segment) | 額外段暫存器 | 用於字串操作(MOVS 、LODS 、STOS 等指令)。 |
SS(Stack Segment) | 堆疊段暫存器 | 儲存堆疊段的基底位置,與 ESP (堆疊指標)和 EBP (基底指標)相關。 |
FS(Extra Segment) | 額外段暫存器 | 額外的段暫存器,常用於特殊用途(如 Windows TEB、Linux TLS)。 |
GS(Extra Segment) | 額外段暫存器 | 額外的段暫存器,常用於特殊用途(如 Linux TLS)。 |
接著繼續看 segment selector 的樣子A segment selector provides the software that uses it with an index into the GDT or LDT (the offset of its associated segment descriptor), a global/local flag (determines whether the selector points to the GDT or the LDT), and access rights information
字段 | bits | 說明 |
---|---|---|
Index | 13 bits | 索引值,用於定位 GDT 或 LDT 中的段描述符。 |
TI(Table Indicator) | 1 bits | 表指示器,0 代表 GDT,1 代表 LDT。 |
RPL(Requested Privilege Level) | 2 bits | 請求特權級,範圍 0~3,表示請求者希望以哪個特權級訪問該段。 |
- CPU 從段暫存器(如 CS)讀取 Segment Selector。
- 根據 TI 位 確定是從 GDT 還是 LDT 讀取段描述符。
- 用 Index 查找對應的 Segment Descriptor。
- 根據 段描述符內的屬性(Base、Limit、DPL 等) 確定如何存取該段
Segment Descriptor
存放在 GDT、LDT 中,是描述一個 segment 的資料結構,每一個 segment descriptor 都是 8 bytes
接下來看 GDT、LDT
https://blog.csdn.net/lizongti/article/details/138668202
每個 Descriptor Table 可以存放 8192 個 Segment Descriptors
GDT、LDT 是 x86 架構用來描述 segment 權限的兩張表,用於定義 segment 屬性及權限
GDT 可以被所有的程式和工作(task)所使用,而一個程式可能會有自己的 LDT
型態是指
GDT (Global Description Table)
GDT(全域描述表)主要存放作業系統和各個任務共用的描述符,例如共用的數據段和代碼段描述符、各個任務的 TSS(任務狀態段)描述符 以及 LDT(區域描述表)描述符。
一個系統一定要有一張 GDT
(TSS,即任務狀態段,用於存放每個任務的私有運行狀態信息描述符。)
代碼段(Code Segment)
數據段(Data Segment)
堆疊段(Stack Segment)
TSS(Task State Segment,任務狀態段)
LDT 描述符(LDT Descriptor)
TSS
TSS 是儲存一個工作的狀態的系統 segment
TSS resource
GDTR
GDTR 就是 CPU 如何知道 GDT 在哪的 register
結構是 (2^16 = 65536 // 8 = GDTsize // SegmentDescriptorSize = 8192)
1 | 47(64 bits = 79) 15 0 |
PS: GDT 的基底位址存放在 GDTR 中,是一個線性位址,也就是說,GDT 不算是一個 segment
LDT (Local Description Table)
每個 task 可以有一張,LDT(區域描述表)主要存放各個 task 的私有描述符,例如該任務的 代碼段描述符 和 數據段描述符等
LDTR
LDTR 本質上是個 Segment Selector(可以參考上面的 segment selector 長相)
因為 LDT 是個 segment
Access GDT or LDT way
這邊舉例
假設要訪問一個 Code Segment
先去 CS 挖,這邊假設是 0x1c
0x1c 是一個 description selector,為 16 bits 拆開來看
0x1c = 0b11 1 00
IT = 1 為 LDT,00 是RPL,0b11 = 3 是 index(代表其位於 LDT 第3個 Description)
所以要去 access LDT,不過我們不知道 LDT 位子
接下來去看 LDTR,他是 0x8
0x8 = 0b 1 0 00
TI = 0 是 GDT,1 是 index (代表其位於 GDT 第1個 Description)
因此去 access GDT
GDT 不知道位子去看 GDTR
GDTR = 0xC00000 FFFF
0xC00000 代表 GDT 的 base address,之後找到第 1 個 description
第一個 description 是 0x800000 FFFF
0x800000 是 LDT base address
找到 LDT 上述 CS register 所述的第三個 descriptor
其值為 0x400000 00FF
0x400000 為 segment base address
這樣就成功找到要找的 code segment 了
這篇有詳細的計算方法及注意事項,懶人包可以看這個:
https://medium.com/@cousin3516/%E8%99%95%E7%90%86%E5%99%A8%E4%B8%AD%E7%9A%84%E8%A8%98%E6%86%B6%E9%AB%94%E5%88%86%E6%AE%B5-memory-segmentation-b28bc10b437
通過這個實際例子可以更了解 LDT 跟 GDT 在幹嘛
你問我知道這可以幹嘛,起源是因為當初在玩 windows kernel EoP 時,寫 shellcode 用了 token stealing 的方式,需要通過 gs register 來去找 EPROCESS,windbg 下 r gs看的時候我蠻矇的,因為噴出來的是 gs = 0x2b 之類的數值,通過上述的理解可以知道 0x2b 是怎麼轉換成一個線性 address 的
這也是為啥寫 shellcode 時不是用 gs + 0x188 而是 gs:[0x188]
總之超酷
整個 segment 相關的機制已經差不多了,接下來開始講這張圖 Interrput 的部分
Interrupt
首先先了解一個重要的概念 interrupt
翻成中文”中斷”,可以直觀的理解成去中斷一個正常的執行流程,這件事可以藉由 CPU 或 hardware 來產生
An interrupt is an event that alters the normal execution flow of a program and can be generated by hardware devices or even by the CPU itself.
More information about Privilege Level
根據上述,可以知道 segment 是有區分權限的,也就是熟知 ring 0 ~ ring 3,常用的只有 ring 0、ring 3(有特別需要,也可以使用權力介於 ring 0 和 ring 3 之間的 ring 1 和 ring 2)
可以理解,低權限的程式不能去執行高權限程式
但其實高權限也不能直接執行低權限程式,必須經由 call gate 來呼叫
這件事有例外情況,回顧一下 Privilege Level 有以下這些 CPL、DPL、RPL (這三個比較重要)
這邊更詳細的展開
- 目前特權等級(Current Privilege Level,CPL):目前執行的程式或工作的特權等級,存放在 CS 和 SS 的第 0 和第 1 位元,一般情形下,CPL 會和目前程式所在的 segment 的特權等級(即 DPL)相同。而在轉移控制權時(例如,用 JMP 或 CALL 命令跳到另一 segment 中),CPL 會改變為新的 segment 的 DPL
- 分段特權等級(Descriptor Privilege Level,DPL):一個 segment(或 gate)的特權等級,存放在 segment descriptor 中的 DPL 位元
一段程式在存取一個 segment 或 gate 會根據 CPL、DPL、RPL 來有不一樣的操作
- data segment 的 DPL 指出存取該 segment 所需要的最低特權等級,一段 segment descriptor 裡面的 DPL,我們去 access 這塊時,若為 2,代表 CPL 為 0、1、2 可以存取
- Call gate 和 TSS 的 DPL 指出存取該 gate 所需要的最低特權等級
- Non-conforming 程式的 segment
- 經由 call gate 呼叫程式 segment 時,其 DPL 指出呼叫者所容許的最高的特權等級,若一程式 segment 的 DPL 為 2,則只有 CPL 為 2 和 3 的程式才能透過 call gate 呼叫它
Interrupt Descriptor Table (IDT)
https://www.csie.ntu.edu.tw/~wcchen/asm98/asm/proj/b85506061/chap4/idt.html
被稱作中斷描述表,被存在 memory 中
一張表可以存 256 個,每個每個被稱作 entry,也是一條 interrupt vector,每條 16 bytes
CPU 需要知道這張表存在哪,所以有個暫存器 IDTR 用來存 IDT 相關資訊
前半段是 IDT 起始 address,後半段是 IDT 有多長 (找第 52 條 vector = IDTR.idt_base_address + 16 * 52)
https://stackoverflow.com/questions/67586556/understand-idtr-register
LIDT 指令可以把記憶體中的值載入到 IDTR 中,而用 SIDT 指令可以把 IDTR 中的位址存到記憶體中
Gate
根據 2.1.2 System Segments, Segment Descriptors, and Gates
所述
架構還定義了一組特殊的描述符,稱為門(Gate),包括:
- 呼叫門(Call Gate)
- 中斷門(Interrupt Gate)
- 陷阱門(Trap Gate)
- 任務門(Task Gate)
這些門提供了受保護的機制,允許存取與應用程式或一般程序不同權限等級(privilege level)的系統程序與處理程序
The architecture also defines a set of special descriptors called gates (call gates, interrupt gates, trap gates, and task gates). These provide protected gateways to system procedures and handlers that may operate at a different privilege level than application programs and most procedures.
這些 descriptor 被存在 IDT 中
一個重要的概念,可以描述整個 IDT 運作原理
CPU 在執行下一條指令之前,會先檢查當前是否發生了中斷或異常。若有發生,則會判斷中斷或異常的 vector number,在完成一系列的檢查與保護工作後,CPU 會執行 IDT 中對應該 vector number 的處理函式
Gate Descriptor
現在開始來看 IDT entry 內的 Interrupt descriptor 樣子,一個 16 bytes
- Interrupt-gate descriptor: 用於 Interrupt
- Trap-gate descriptor: 處理異常,用於 syscall
- Task-gate descriptor & Call-gate descriptor: 用於 task 切換
https://blog.csdn.net/ZZHinclude/article/details/117771884
https://wiki.osdev.org/Interrupt_Descriptor_Table
舉例
最後一樣通過流程更清楚了解運作原理
首先先通過 IDTR 去尋址 IDT address
Vector Number 代表的是 Interrupt 的類型,通過當前 CPU 觸發的 Interrupt 來去 IDT 找對應的 Interrupt Descriptor
從 Interrupt Description 中可以找到 offset,另外也可以找到一個 segment Selector,通過他來去 GDT 或 LDT 中找到對應的 segment address base
segment address base + offset 就可以找到對應的 ISR address,來去處理 Interrupt
More Information about Interrupt Vector:
https://www.csie.ntu.edu.tw/~wcchen/asm98/asm/proj/b85506061/chap4/overview.html
LDT & GDT & IDT
這邊說回最上面這張圖
這張圖大致描述了 分段機制(Segmentation) 和 中斷處理(Interrupt Handling)
首先是 IDT,IDT 會負責處理 Interrupt 相關事項,通過當前 CPU 的 Interrupt Vector 可以到 IDT 中(CPU 藉由 IDTR 知道 IDT base address)找到對應的 Interrupt Descriptor
而一個 IDT Descriptor 內對應到一個 segment selector ,另外也存有 offset,通過 segment selector 來去 access 到對應的 segment 並加上對應的 offset,來找到對應的 Interrupt Handler
而 GDT、LDT 不只是對應處理 IDT 而已,對於很多需要去 access 到 segment 的通過 GDT LDT 轉址,可以找到對應的 segment,另外 CPU 通過 GDTR、LDTR 可以知道 LDT GDT address
當然左上角還有一些相關的 register 之後可以詳細展開講
另外聽說 long mode 有些東西不太一樣,可能要在研究
一些 long mode 資料(收集中…)
http://blog.chinaunix.net/uid-587665-id-2732930.html
https://blog.csdn.net/ProgrammingRing/article/details/7425445
anothor resource
https://github.com/therealdreg/cgaty
https://liujunming.top/2020/01/18/%E6%B5%85%E8%B0%88tss/
https://elixir.bootlin.com/linux/v6.13.3/source/arch/x86/kernel/idt.c
after all
需要一些對 kernel 有研究的人,對於這些機制如果有深入了解的可以聯繫我
I need some people who have researched the kernel. If you have an in-depth understanding of these mechanisms, please contact me.
Discord: naup_sumire_hime
Gmail: naup96721@gmail.com