Kernel mode & user mode switch
Author: 堇姬Naup
Usermode to Kernel mode
- call swapgs
這裡先解釋幾個東西
GS register、IA32_GS_BASE、IA32_KERNEL_GS_BASE、swapgs 是甚麼
- GS reg: 用途主要圍繞上下文切換、內核態與用戶態的切換以及訪問每個 CPU 的數據結構,在 64 位模式下,GS 指向一個base address,允許進行高效的offset訪問,例如通過
gs:[offset]
訪問data。 - IA32_GS_BASE: 用於保存當前 GS base(MSR 地址為 0xC0000101)
- IA32_KERNEL_GS_BASE: 用來保存kernel mode GS base address(MSR 地址為 0xC0000102)
- swapgs
快速切換 GS 段寄存器的address value
user mode的 GS base address(存儲在 IA32_GS_BASE register中)和 kernel mode的 GS base address(存儲在 IA32_KERNEL_GS_BASE register中)交換
現在要切換至kernel mode,gs存的是usermode的base address(IA32_GS_BASE),會和 kernel base address交換(IA32_KERNEL_GS_BASE)
- 接下來,將當前stack 頂部(usermode)放在per CPU內,並將per CPU中的kernel stack頂部放入rsp/rbp
PS: per CPU 變數用於在多處理器(SMP, Symmetric Multi-Processing)環境下,為每個 CPU 分配一份獨立的變數空間
偷偷塞個東西(大致是這種概念):
1 | SystemCallEntryPoint: |
- 之後通過push保存每個register的值
大概是通過以下方式,會先切換至kernel stack
將register壓進kernel stack(一整個形成一個struct,pt_regs)
1 | ENTRY(entry_SYSCALL_64) |

這是pt_regs struct
1 | struct pt_regs { |
補一下
- unsigned long rip:指令指標寄存器(Instruction Pointer Register),指向當前執行的指令地址。
- unsigned long cs:代碼段選擇器(Code Segment Selector),描述代碼段的特性。
- unsigned long eflags:標誌寄存器(Flags Register),保存處理器狀態標誌。
- unsigned long rsp:堆疊指標寄存器(Stack Pointer Register),指向堆疊頂部。
- unsigned long ss:堆疊段選擇器(Stack Segment Selector),描述堆疊段的特性。
這樣就進入到kernel mode了
Kernel mode to usermode(No KPTI)
- call swapgs
- 通過swapgs gs會拿回usermode base address
- 接下來通過 sysretq 或 iretq 恢復到user space
- 若使用 iretq 需要再給出userspace額外的資訊,也就是trap_frame
1 | struct trap_frame { |
iret
這是詳細的iret實現方式
1 | SYM_INNER_LABEL(swapgs_restore_regs_and_return_to_usermode, SYM_L_GLOBAL) |
Kernel mode to usermode(Open KPTI)
不能單純透過call swapgs; iret
來切換
還需要去切換page table
首先是四層頁表(基本跟windows kernel沒有差,可以參考我的windows kernel exploitation那篇)

通過 PGD->PUD->PMD->PTE 找到對應的page,再透過offset找到該page內的正確address,來將virtual address 映射到 physical address
CR3 register則儲存了PGD位置
而KPTI讓userspace只有少量的kernel page table(必要的如中斷等)
簡單來說就是原本的一張表usermode、kernel mode用同一張
但是開啟KPTI後會拆成兩個如下圖

在切換mode時同時也需要去更改cr3存的page table address
另外再KPTI開啟時,其映射的userspace memory被標記NX,使ret2usr不可用(因為你就算能寫上shellcode到userspace,但跳上去也不能執行shellcode)
知道了上述後來看有KPTI後會如何執行切換
詳細的可以去 swapgs_restore_regs_and_return_to_usermode
看
1 | 0xffffffff81c00fb0 <swapgs_restore_regs_and_return_to_usermode>: nop DWORD PTR [rax+rax*1+0x0] |
通過呼叫這function也能直接幫你修好cr3
1 | mov rdi, cr3 |
cr3 可以參考該docs的2.5(p.3069)
docs
總之第十三位(cr3[12])紀錄當前是kernel or user mode page table(PGD address)
為了實現快速切換,因此將kernel mode PGD跟user mode PGD緊鄰著(4kb + 4kb = 8kb),kernel PGD在low address,user在high address
通過去 or cr3[12]就可以快速操作
cr3
1 | cr3[12] |
after all
不得不說網路上的文章真的很亂www
另外kernel文章真的很難寫,寫一寫很容易寫亂,所以應該不會像heap一樣一篇完成,而是拆成小篇小篇(不然我已經寫壞了4、5篇了)
總之有心力就寫吧 OwOb