Windows Kernel exploitation - Part 1
Author: 堇姬Naup
What is Kernel
kernel位於application和hardware之間,是OS的core也負責溝通hadware和process
負責了像是
I/O、process、memory、driver managemet或是syscall 等事情
kernel也提供了可以跑application的環境
application是run在ring3,而kernel則是ring0
1 | +------------------+ |
Kernel exploit on windows
target有像是driver或是kernel
driver:
- .sys
- win32k.sys
- srv2.sys
- …
kernel:
- C:\Windows\system32\ntoskrnl.exe
Overview
將它拆分成更細
- User Process: 就是我們平時在跑的應用程式等,都是一個process,像是browser、cmd.exe
- subsystem DLL: 先看甚麼是subsystem,可以參考這篇
http://www.fmddlmyy.cn/text5.html
所有的win32 api呼叫都指向subsystem dll(kernel32.dll、kernelbase.dll、gdi32.dll、user32.dll、ntdll.dll、win32u.dll…),api都在這實現 - NTDLL: 從ring3 到ring0的入口所有win32 api調用了subsystem dll後會去調用ntdll.dll中函數實現(NTDLL.DLL exports the WindowsNative API)
- Service Process: SCM(https://learn.microsoft.com/zh-tw/windows/win32/services/service-control-manager)管理的Process
- system process: 系統重要process(有很多),被中止非常有機會BSOD
- subsystem process: 控制圖形subsystem、manage process(csrss.exe)
- win32k.sys: windows kernel driver,負責處理圖形及窗口管理(處理GDI),https://learn.microsoft.com/zh-tw/windows/win32/api/winuser/nf-winuser-createwindowexa
這些由他處理 - executive: Upper layer of Ntoskrnl.exe,I/O、memory、Object manager
- kernel: 處理更底層事務,像是interrupt等
- device driver: 可以想像是程式對設備操作中間的介面,透過driver來去與device進行互動(A driver provides a software interface to hardware devices, enabling operating systems and other computer programs to access hardware functions without needing to know precise details about the hardware being used.)
- HAL
environment setting
HOST is windows 11 and user vmware + ubuntu 22.04 write exploit
- VMware + windows 24H2 VM (Need two windows to debug windows kernel)
https://www.microsoft.com/zh-tw/software-download/windows11 - Process Monitor
https://learn.microsoft.com/zh-tw/sysinternals/downloads/procmon - Process Explore
https://learn.microsoft.com/zh-tw/sysinternals/downloads/process-explorer - Windbg
https://learn.microsoft.com/zh-tw/windows-hardware/drivers/debugger/ - PEbear
https://github.com/hasherezade/pe-bear
how to debug kernel(use windbg attach kernel)
Debug kernel需要兩台windows
先進VM
首先可以先下bcdedit,如果上面沒有debug代表沒開
去找到msconfig,選boot > advanced option > debug打開
現在你去下bcdedit就會有debug: on了,另外如果你確認時跳出boot secure問題,就要進去boot把它關掉
有兩種debug方式,serial port跟net,這邊先用net方式debug
之後下這個commandbcdedit /DBGSETTINGS NET HOSTIP:IP PORT:50000 KEY:w.x.y.z
KEY是為了防止不要讓任何人都可以debug你的kernel
設好就重啟VM
現在回到你的HOST,用windbg的attack kernel,attach上去
如果你有看到attach kernel 代表成功了,不過有時候會失敗
所以我們試試看serial port
之後就到attach kernel 的COM把資訊填上去就行
反正我們已經attach好了
如果卡住按一下break就可以了
Basic Knowledge
附註: 以下的offset可能不同,實際情況用windbg直接追就可以了
process
甚麼是process應該不用解釋,當create process後會去call win32 API,
之後進到kernel層創建一個PCB(process control block),記錄整個process的資訊
PCB不太完整,準確來說 struct是EPROCESS
1 | --------------------- 0x0 |
- UID: process ID
- Active process link: 將EPROCESS串成一個double linklst(process被串起來)
- Token
- PEB
- Thread list head
PCB裡面長這樣
1 | --------------------- |
- DirectoryTableBase: PML4實體位址,context switch後變成cr3
- User Directory Table Base: PML4實體位址,return到usermode時改為存放cr3
至於cr3、PML4是甚麼在Virtual address to Physical address提到
Thread
createThread時會創建
https://learn.microsoft.com/zh-tw/windows/win32/api/processthreadsapi/nf-processthreadsapi-createthread
Thread間用linklist串在一起
跟process一樣創建一個ETHREAD,裡面的TCB(Thread control block),負責管理整個thread
ETHREAD struct
1 | ----------------- 0x0 |
TCB
1 | ----------------- 0x0 |
Integrity Level
將 Object、Process等進行分級,共用六個等級,低level不能存取高level
untrusted, low, medium, high, system、Protected
我們正常的程式都是跑在medium
Level | Description |
---|---|
Untrusted | Started by Anonymous group. Block most write access. |
Low | Used by AppContainer. Block most write access to most object (files and registry) on system. |
Medium | Used by normal application if UAC is enabled. |
High | Used by administrative application when UAC is enabled. |
System | Used by system services or system process. |
Protected | Currently unused by default. |
ACL
ACL:
https://learn.microsoft.com/zh-tw/windows/win32/secauthz/access-control-lists
ACE:
https://learn.microsoft.com/zh-tw/windows/win32/secauthz/access-control-entries
是存取控制項目ACE (ACL 可以有零個或多個 ACE。 每個 ACE 都會由指定的信任者控制或監視物件的存取)的清單
安全性實體物件的安全性描述項可以包含兩種類型的ACL:DACL和SACL
https://0xfocu5.github.io/posts/37e301d0/
SID
https://0xfocu5.github.io/posts/37e301d0/
Access Token
user登入windows時會拿到一組access token代表當前登入者
用來表示process security context
Windows會透過Token來判斷該物件是否可被存取及能做哪些操作
_TOKEN stuct
1 | ----------------- 0x0 |
priviledge (_SEP_TOKEN_PRIVILEDGE
1 | +-----------------+ |
primary token
當thread與process對secure Object進行互動時使用
用於描述與process的secure context
https://rootclay.gitbook.io/windows-access-control/access-token
impersonation token
control register(CPU Control)
負責控制及確定CPU模式、特性及行為,可以參考該docs的2.5(p.3069)
docs
Control registers (CR0, CR1, CR2, CR3, and CR4; see Figure 2-7) determine operating mode of the processor and the characteristics of the currently executing task.
- CR2: 當發生page fault時,會儲存異常的Virtual address
( Contains the page-fault linear address. The linear address is caused a page fault.) - CR3: CPU用來做address translate會用到,該register存放當前process的page table physical address(PML4)
- CR4: 用來啟用或調整CPU的各種進階功能和擴展
(Contains a group of flags that enable several architectural extensions, and indicate operating system or executive support for specific processor capabilities. Bits CR4[63:32] can only be used for IA-32e mode only features that are enabled after entering 64-bit mode. Bits CR4[63:32] do not have any effect outside of IA-32e mode.) - CR8: IRQL
每個bits 詳細功能也請直接參考該docs,其中最容易遇到的應該是CR4 20 21位的SMEP SMAP
MMU(Memory manager unit)
我們在process看到的記憶體是一串Virtual address,他透過映射的方式映射到Physical address(在Access的當下,CPU去將Virtual address轉成Physical address)
以下是Virtual Memory layout
1 | ------------------ |
另外透過Virtual Address可實現Isolation Process,只要看你給的address有沒有在Virtual address裡面就可以,頂多影響到自己process擁有的Physicsl address
也解決了跳轉問題,程式只需要跳轉到Virtual address,硬體會幫你映射到對應的Physical address
你就不需要對Physical address直接進行操作了
page
你可以想像是Memory的最小單位(若以segment作為memory單位太大了)
通常一page是4KB(0x1000),不過具體大小由CPU決定
詳細可以參考這個
https://wiki.osdev.org/Paging
page table
儲存虛擬位址到實體位址的對映
每個表上的對應被稱作PTE(page table entry)
PTE
每個PTE長這樣
PFN存的就是offset(一個頁表0x1000,用3byte剛好表示完)
Name of bit | Meaning |
---|---|
Nx | Non - execute |
PFN | Page Frame Number |
Ws | Write bit (software) |
Cw | Copy on write |
Gl | Global |
L | Large Page |
D | Dirty |
A | Accessed |
Cd | Cache disable |
U/S | User mode/supervisor bit |
W | Write bit (hardware) |
V | Valid |
demand page
當真正有去摸到那塊physical address
才會分配page給他
實際上R/W/X的時候才會分配physical address及建立PTE(但大多數API在alloc就會做讀寫了)
page fault
訪問的Virtual address在Physical Address未被載入時會觸發此錯誤
Swap in/out
把很久沒用到的page swap到disk
Virtual address to Physical address
假設要轉換這個0xffffffff8111c398
0b1111111111111111 | 111111111 | 111111110 | 000001000 | 100011100 | 001110011000
分別對應
第一段跟上述所說的一樣,AMD64只實現48bits,為規範address
接下來分別對應
PML4I 0b111111111 511
PDPI 0b111111110 510
PDI 0b000001000 8
PTI 0b100011100 284
Offset 0b001110011000 920
首先從 CR3 爬出 Page-Map Level-4 Table Base
第 PML4 個 Entry 紀載下一級頁表 PDP 的 Base
第 PDPT 個 Entry 紀載下一級頁表 PD 的 Base
第 PD 個 Entry 紀載下一級頁表 PT 的 Base
第 PT 個 Entry 紀載 Physical Page Frame Base
Physical Page Frame Base + Physical Page Offset 就完成了轉換
更詳細去拆解四個page table可以參考這篇
https://hackmd.io/@LJP/rkxtGgoIO
如何手算可以參考Physical Address
https://www.coresecurity.com/core-labs/articles/getting-physical-extreme-abuse-of-intel-based-paging-systems-part-2-windows
PS: 我要強調,這裡是從page table查到對應的page frame(等於找到page frame base),在該page上通過offset去找實體記憶體位置
gdb demo
首先一樣先attach上去kernel
我們來轉換nt好了
先對windbg下dp nt
1 | fffff801`81c00040 cd09b400`0eba1f0e 685421cd`4c01b821 |
來轉換 fffff801`81c00040
1 | kd> !process 0 0 cmd.exe |
首先是可以用vtop
用法是!vtop <PEB base> <Virtual Address>
1 | kd> !vtop 172636000 fffff8079a800040 |
用 !dp <address>
可以查看physical address上的值
將轉換出來的physical address查看一下發現值一樣,成功
1 | kd> !dp 100200040 |
不過當然這邊也手算一次,我們改用cmd.exe
先切換到cmd.exe process
.process /r /p ffffac8d897a8080
現在下dp ntdll就可以看到cmd.exe的ntdll
1 | kd> dq ntdll |
通過ntdll去找PTE
1 | kd> !pte 00007ffae2680000 |
試著轉換00007ffae2680000
我寫了腳本
1 | def PAtoVA_4page_cal(address): |
轉換結果是
PML4_offset: 0xff
PDPT_offset: 0x1eb
PD_offset: 0x113
PT_offset: 0x80
PhysicalAddress_offset: 0x0
PML4就是這個 DirBase: 172636000
(也是CR3存的值)
把他加上offset * 8後會找到該表上面的值
1 | kd> !dp 172636000+(0xff*8) |
這就是第二層(PFN 0xa00000172642867)
記得去掉低12bits跟高1bits,剩下的PFN(0x172642000)
以此類推第二層
1 | kd> !dp 0x172642000+(0x1eb*8) |
第三層(0x00172545000)
1 | kd> !dp 0x00172545000+(0x113*8) |
第四層(0x00225b46000)
1 | kd> !dp 0x00225b46000+(0x80*8) |
找到 page frame base (0x00010010d000)
最後加上offset 0x0就是physical address了(看到資料一樣代表成功了)
1 | kd> !dp 0x00010010d000 |
用vtop看的話也確認是正確的
windows driver
讓程式與device溝通的一個橋樑,通常可以利用syscall溝通
目前主流架構是Windows driver model
一個WDM載入device方式
- 呼叫 IoCreateDevice 建立一個 Device
- 呼叫 IoCreateSymbolicLink 建立一個 Symbolic Link 連結到上一步建立的 Device,如此應用程式就可以透過呼叫 CreateFile 取得這個 Device 的 Handle
- 設定處理每個 IRP 請求的函數
怎麼寫code可以參考
https://ithelp.ithome.com.tw/articles/10322132
另外在逆向時MajorFunction可能只是index,可以參考這個
1 | #define IRP_MJ_CREATE 0x00 |
那windows driver掛上去了,互動的流程是甚麼
- 當User application呼叫DeviceIoControl,由I/O manager建立並對windows driver送出IRP
- windows driver收到IRP並進行處理
- 回傳IoCompleteRequest,告知I/O manager完成
如果要試著寫windows driver可以參考
https://www.alex-ionescu.com/
https://www.apriorit.com/dev-blog/791-driver-windows-driver-model
Debug windows driver
https://ithelp.ithome.com.tw/m/articles/10326802
IRP
I/O request packet
https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_irp
Windows 中用於請求 I/O 操作的資料結構。驅動程式可以使用 IRP 來與 Kernel 溝通,例如讀取文件或進行網路傳輸。開發者也可以透過發 IRP 給 I/O Manager,並將 IRP 轉發給相應的驅動程式,從而執行對應的行為
IRP struct如下
1 | ------------------------ |
How to install windows driver
由於你的driver沒有簽章驗證,所以你必須開啟testing mode及關閉簽章驗證來掛上去你的driver
bcdedit /set testsigning on
bcdedit /set loadoptions DDISABLE_INTEGRITY_CHECKS
bcdedit /set nointegritychecks on
之後重啟應該會看到testing
接下來把driver掛起來sc create <your driver name> binPath=<Path of driver on windows> type=kernel
sc start <your driver name>
sc delete <your driver name>
如何掛起kernelmode windows driver可以參考
https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/debug-universal-drivers---step-by-step-lab--echo-kernel-mode-
how to use your driver
他會去與\\.\BreathofShadow
進行交互
https://learn.microsoft.com/zh-tw/windows/win32/api/fileapi/nf-fileapi-createfilew
https://learn.microsoft.com/zh-tw/windows/win32/api/ioapiset/nf-ioapiset-deviceiocontrol
PS: \.\\
是device namespace
https://superuser.com/questions/1583205/what-kind-of-file-path-starts-with
https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file
1 |
|
Protection
KASLR
每次開機時隨機化kernel address,只要不重新開機就不會變動
SMEP
kernel mode不能執行userspace的code
是否開啟位於cr4 bit 20
SMAP
kernel mode不能直接存取userspace data
是否開啟位於cr4 bit 21
KVA shadow
在linux kernel把它叫做KPTI
kernel/Userland Page Table Isolation
在kernel mode時所有user space的PML4 NX bit會開起來,導致你就算disable SMEP也不能執行user space code
Stack Cookies
就是stack canary的概念
https://breaking-bits.gitbook.io/breaking-bits/exploit-development/linux-kernel-exploit-development/stack-cookies
1 | .--------------------.-----------------------. |
information leak
如果process的integrity是medium
通過調用NtQuerySystemInformation可以獲得所有sys或nt address,及handle對應Object位置
https://learn.microsoft.com/zh-tw/windows/win32/api/winternl/nf-winternl-ntquerysysteminformation
ret2usr
核心概念是透過stack overflow,來Return 到userspace上的shellcode上,為何要跳回userspace原因是要在沒有任意讀任意寫情況下在kernel space 放shellcode並執行有難度,所以在userspace給一塊rwx個memory,並把shellcode寫進去跳進去
不過首先會遇到一個問題 SMAP/SMEP
不過在有stack overflow可以堆ROP時繞過他蠻簡單的,透過將cr4的第20bit 21bit蓋成0就可以了
pop rcx; ret
mov cr4, rcx ; ret
接下來透過shellcode來抓出token(高權限),並寫掉自己的token來提權
HITCON CTF 2019 Qual breathofshadow
https://github.com/scwuaptx/CTF/tree/master/2019-writeup/hitcon/breathofshadow
analyze
首先他給了一個windows drive,來分析看看
reverse windows driver 我有看到一篇不錯的文章可以參考
https://v1k1ngfr.github.io/winkernel-reverse-ida-ghidra/
1 | __int64 __fastcall sub_140006000(PDRIVER_OBJECT DriverObject) |
這裡他建立了一個Device(IoCreateDevice)
L"\\Device\\BreathofShadow"
是deivce name
然後去設定MajorFunction,處理IRP
第一個createdeleteHandle沒做甚麼就直接回傳IofCompleteRequest
1 |
|
這邊重要的是
有些symbol爛了自己修一下
這邊補充一下IoStackLocation
https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_io_stack_location
詳細IoStackLocation可以參考這個
https://v1k1ngfr.github.io/pimp-my-pid/
1 |
|
如果IoControlCode是0x9C40240B,就Call進去
一樣是symbol全爛,自己修一下
NtDeviceIoControlFile:
- Parameters.DeviceIoControl.OutputBufferLength(0x8)
- Parameters.DeviceIoControl.InputBufferLength(0x10)
- Parameters.DeviceIoControl.IoControlCode(0x18)
- Parameters.DeviceIoControl.Type3InputBuffer(0x20)
- Parameters.QuerySecurity(0x28)
我們互動的東西會存在IoStackLocation的inputbuffer(可控)
他會將InputBuffer丟進去Dst(一個有限大小的buffer)
這裡就有一個buffer overflow
1 | __int64 __fastcall sub_140005000(__int64 IRP, __int64 IO_STACK_LOCATON) |
另外我們還需要information leak
原因是因為cookie(像是stack canary)、XorKey、KernelBase(ROP need)需要leak出來
這邊其實information leak洞非常明顯
如果inputbuffer塞超小
outputbuffer塞超大
DST值會只有少部分被xor
而DST被copy到inputbuffer(根據output buffer)
這樣就可以任意讀stack上的value(也就有information leak)
IoControl互動方式就跟上述一樣
1 | BOOL DeviceIoControl( |
很顯然ret2usr
條件已經湊齊了
接著來看EoP
EoP
https://www.matteomalvica.com/blog/2019/07/06/windows-kernel-shellcode/#token-stealing
目標是取得system integrity 的 process token,並寫進自己的process token
當我們有任意執行shellcode時,透過去traverse整個EPROCESS(linklist),並找到system process,然後換掉自己的token成system process,來達到EoP
1 | +------------------------+ +------------------------+ +------------------------+ |
要拿token代表要拿到system EPROCESS
代表要找到一個EPORCESS透過ActiveProcessLinks尋找
要如何找到呢?
有一塊struct叫做KPCR(Kernel Process Control Region)
在Windows 64 bits時gs register恆指向該struct
1 | +--------------------+ 0x0 |
裡面有一塊叫做PRCB(Kernel Process Control Block)
PRCB裡有Current Thread(KTHREAD)
ETHREAD裡面有TCB,TCB有Process指向EPROCESS
1 | +---------------+ 0xf0 |
所以就可以透過gs拿到EPROCESS了
1 | +----------------------------+ GS:[0x0] -> KPCR |
遍歷EPROCESS時可以查看是否PID是4,因為這支就是system
那以上就是EoP的方法
來整理一下攻擊流程
- Information leak vuln leak stack cookie, kernel base, xor key.
- Use Stack overflow control RIP, and then use ROP change cr4 and Page table’s NX bypass SMEP, SMAP, KVA shadow
- And then jump to usermode’s shellcode
- Use gs find structure _KPCR -> _ETHREAD -> EPROCESS
- Loop traverse EPROCESS linklist and find pid 4’s process. It is system integrity process’s token
- Change yourself process token and return (You must recover register about protection, because kernel will check.)
- Get shell and you get priviledge
PS: 偷偷爆個雷,當你get shell後會發現該process砍不掉,原因是因為沒有IoCompleteRequest導致driver一直在等待,所以最後記得回傳,這樣可以讓exploit更穩定
順便補充gs register、KPCR是啥
gs register主要用於存取與當前 CPU 或Thread相關的數據結構
KPCR 的主要目的是為每個CPU維護專屬的處理器控制數據,並在多CPU系統中進行高效管理。KPCR 位於kernel space,提供一些關鍵資訊供內核、驅動程式以及低層次的系統組件使用
KPCR represents the Kernel Processor Control Region. The KPCR contains per-CPU information which is shared by the kernel and the HAL. There are as many KPCR in the system as there are CPUs.
debug and install windows driver
參考上面的如何attach kernel 跟 install kernel
總之就先開啟testing mode
之後掛上去kernel driver
這邊再用windbg時候沒有顯示我們install上去的kernel driver
我就試著加載了一下symbol就看到了(我沒有很理解就是了).sympath srv*C:\Symbols*http://msdl.microsoft.com/download/symbols
.reload /f
exploit
真的得先熟悉windows 32 API怎麼用QQ
寫腳本寫的好卡
1 |
|
1 |
|