Linux內(nèi)核pwn之基礎(chǔ)rop提權(quán)
1. linux kernel pwn
kernel 也是一個(gè)程序,用來(lái)管理軟件發(fā)出的數(shù)據(jù) I/O 要求,將這些要求轉(zhuǎn)義為指令,交給 CPU 和計(jì)算機(jī)中的其他組件處理,kernel 是現(xiàn)代操作系統(tǒng)最基本的部分。
以上便是ctf wiki原話 ,所以大家也不要太過(guò)于認(rèn)為其很難,其實(shí)跟咱們用戶態(tài)就是不同而已,也可能就涉及那么些底層知識(shí)罷了(師傅輕噴,我就口嗨一下)。
而kernel 最主要的功能有兩點(diǎn):
-
控制并與硬件進(jìn)行交互
-
提供 application 能運(yùn)行的環(huán)境
包括I/O,權(quán)限控制,系統(tǒng)調(diào)用,進(jìn)程管理,內(nèi)存管理等多項(xiàng)功能都可以歸結(jié)到上邊兩點(diǎn)中。
需要注意的是,kernel 的crash 通常會(huì)引起重啟。(所以咱們這點(diǎn)調(diào)試的時(shí)候就挺不方便的了,相比于用戶態(tài)而言),不過(guò)這里也可能我剛開(kāi)始學(xué)比較笨而已。
2. Ring Model(等級(jí)制度森嚴(yán))
(1)intel CPU 將 CPU 的特權(quán)級(jí)別分為 4 個(gè)級(jí)別:Ring 0, Ring 1, Ring 2, Ring 3。
(2)Ring0 只給 OS 使用,Ring 3 所有程序都可以使用,內(nèi)層 Ring 可以隨便使用外層 Ring 的資源。
(3)使用 Ring Model 是為了提升系統(tǒng)安全性,例如某個(gè)間諜軟件作為一個(gè)在 Ring 3 運(yùn)行的用戶程序,在不通知用戶的時(shí)候打開(kāi)攝像頭會(huì)被阻止,因?yàn)樵L問(wèn)硬件需要使用 being 驅(qū)動(dòng)程序保留的 Ring 1 的方法。
注意大多數(shù)的現(xiàn)代操作系統(tǒng)只使用了 Ring 0 和 Ring 3。
3. syscall
也就是系統(tǒng)調(diào)用,指的是用戶空間的程序向操作系統(tǒng)內(nèi)核請(qǐng)求需要更高權(quán)限的服務(wù),比如 IO 操作或者進(jìn)程間通信。系統(tǒng)調(diào)用提供用戶程序與操作系統(tǒng)間的接口,部分庫(kù)函數(shù)(如scanf,puts 等 IO 相關(guān)的函數(shù)實(shí)際上是對(duì)系統(tǒng)調(diào)用的封裝(read 和 write))。
4. 狀態(tài)轉(zhuǎn)換
user space to kernel space
當(dāng)發(fā)生 系統(tǒng)調(diào)用,產(chǎn)生異常,外設(shè)產(chǎn)生中斷等事件時(shí),會(huì)發(fā)生用戶態(tài)到內(nèi)核態(tài)的切換,具體的過(guò)程為:
(1)通過(guò)swapgs切換 GS 段寄存器,將 GS 寄存器值和一個(gè)特定位置的值進(jìn)行交換,目的是保存 GS 值,同時(shí)將該位置的值作為內(nèi)核執(zhí)行時(shí)的 GS 值使用。
(2)將當(dāng)前棧頂(用戶空間棧頂)記錄在 CPU 獨(dú)占變量區(qū)域里,將 CPU 獨(dú)占區(qū)域里記錄的內(nèi)核棧頂放入 rsp/esp。(這里我在調(diào)試的時(shí)候發(fā)現(xiàn)沒(méi)整rbp,我最開(kāi)始就發(fā)現(xiàn)這里怎么只保存了rsp,這個(gè)問(wèn)題暫時(shí)還不是很了解)
(3)通過(guò) push 保存各寄存器值,具體的代碼如下:
ENTRY(entry_SYSCALL_64)
/* SWAPGS_UNSAFE_STACK是一個(gè)宏,x86直接定義為swapgs指令 */
SWAPGS_UNSAFE_STACK
/* 保存棧值,并設(shè)置內(nèi)核棧 */
movq %rsp, PER_CPU_VAR(rsp_scratch)
movq PER_CPU_VAR(cpu_current_top_of_stack), %rsp
/* 通過(guò)push保存寄存器值,形成一個(gè)pt_regs結(jié)構(gòu) */
/* Construct struct pt_regs on stack */
pushq $ __USER_DS /* pt_regs->ss */
pushq PER_CPU_VAR(rsp_scratch) /* pt_regs->sp */
pushq %r11 /* pt_regs->flags */
pushq $__USER_CS /* pt_regs->cs */
pushq %rcx /* pt_regs->ip */
pushq %rax /* pt_regs->orig_ax */
pushq %rdi /* pt_regs->di */
pushq %rsi /* pt_regs->si */
pushq %rdx /* pt_regs->dx */
pushq %rcx tuichu /* pt_regs->cx */
pushq $-ENOSYS /* pt_regs->ax */
pushq %r8 /* pt_regs->r8 */
pushq %r9 /* pt_regs->r9 */
pushq %r10 /* pt_regs->r10 */
pushq %r11 /* pt_regs->r11 */
sub $(6*8), %rsp /* pt_regs->bp, bx, r12-15 not saved */
(4)通過(guò)匯編指令判斷是否為 x32_abi。
(5)通過(guò)系統(tǒng)調(diào)用號(hào),跳到全局變量 sys_call_table 相應(yīng)位置繼續(xù)執(zhí)行系統(tǒng)調(diào)用。
這里再給出保存棧的結(jié)構(gòu)示意圖,這里我就引用下別的師傅的圖了。注意這是保存在內(nèi)核棧中:
5. kernel space to user space
退出時(shí),流程如下:
(1)通過(guò) swapgs 恢復(fù) GS 值
(2)通過(guò) sysretq 或者 iretq 恢復(fù)到用戶控件繼續(xù)執(zhí)行。如果使用 iretq 還需要給出用戶空間的一些信息(CS, eflags/rflags, esp/rsp 等)
6. struct cred
咱們要管理進(jìn)程的權(quán)限,那么內(nèi)核必定會(huì)維護(hù)一些數(shù)據(jù)結(jié)構(gòu)來(lái)保存,他是用 cred 結(jié)構(gòu)體記錄的,每個(gè)進(jìn)程中都有一個(gè) cred 結(jié)構(gòu),這個(gè)結(jié)構(gòu)保存了該進(jìn)程的權(quán)限等信息(uid,gid 等),如果能修改某個(gè)進(jìn)程的 cred,那么也就修改了這個(gè)進(jìn)程的權(quán)限。
下面就是cred的數(shù)據(jù)結(jié)構(gòu)源碼:
struct cred {
atomic_t usage;
#ifdef CONFIG_DEBUG_CREDENTIALS
atomic_t subscribers; /* number of processes subscribed */
void *put_addr;
unsigned magic;
#define CRED_MAGIC 0x43736564
#define CRED_MAGIC_DEAD 0x44656144
#endif
kuid_t uid; /* real UID of the task */
kgid_t gid; /* real GID of the task */
kuid_t suid; /* saved UID of the task */
kgid_t sgid; /* saved GID of the task */
kuid_t euid; /* effective UID of the task */
kgid_t egid; /* effective GID of the task */
kuid_t fsuid; /* UID for VFS ops */
kgid_t fsgid; /* GID for VFS ops */
unsigned securebits; /* SUID-less security management */
kernel_cap_t cap_inheritable; /* caps our children can inherit */
kernel_cap_t cap_permitted; /* caps we're permitted */
kernel_cap_t cap_effective; /* caps we can actually use */
kernel_cap_t cap_bset; /* capability bounding set */
kernel_cap_t cap_ambient; /* Ambient capability set */
#ifdef CONFIG_KEYS
unsigned char jit_keyring; /* default keyring to attach requested
* keys to */
struct key __rcu *session_keyring; /* keyring inherited over fork */
struct key *process_keyring; /* keyring private to this process */
struct key *thread_keyring; /* keyring private to this thread */
struct key *request_key_auth; /* assumed request_key authority */
#endif
#ifdef CONFIG_SECURITY
void *security; /* subjective LSM security */
#endif
struct user_struct *user; /* real user ID subscription */
struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
struct group_info *group_info; /* supplementary groups for euid/fsgid */
struct rcu_head rcu; /* RCU deletion hook */
} __randomize_layout;
基礎(chǔ)知識(shí)介紹完畢,咱們開(kāi)始介紹咱們內(nèi)核pwn的最主要的目的。
二
目的
借用arttnba3師傅的原話:“毫無(wú)疑問(wèn),對(duì)于內(nèi)核漏洞進(jìn)行利用,并最終提權(quán)到 root,在黑客界是一種最為 old school 的美學(xué)。
咱們?cè)趦?nèi)核pwn中,最重要以及最廣泛的那就是提權(quán)了,其他諸如dos攻擊等也行,但是主要是把人家服務(wù)器搞崩之類的,并沒(méi)有提權(quán)來(lái)的高效。
1. 提權(quán)(Elevation of authority)
所謂提權(quán),直譯也即提升權(quán)限,是在咱們已經(jīng)在得到一個(gè)shell之后,咱們進(jìn)行深入攻擊的操作,那么請(qǐng)問(wèn)如何得到一個(gè)shell呢,那就請(qǐng)大伙好好學(xué)習(xí)用戶模式下的pwn吧。
而與提權(quán)息息相關(guān)的那不外乎兩個(gè)函數(shù),不過(guò)咱們先不揭曉他們,咱們先介紹一個(gè)結(jié)構(gòu)體:在內(nèi)核中使用結(jié)構(gòu)體 task_struct 表示一個(gè)進(jìn)程,該結(jié)構(gòu)體定義于內(nèi)核源碼include/linux/sched.h中,代碼比較長(zhǎng)就不在這里貼出了。
一個(gè)進(jìn)程描述符的結(jié)構(gòu)應(yīng)當(dāng)如下圖所示:
注意到task_struct的源碼中有如下代碼:
/* Process credentials: */
/* Tracer's credentials at attach: */
const struct cred __rcu *ptracer_cred;
/* Objective and real subjective task credentials (COW): */
const struct cred __rcu *real_cred;
/* Effective (overridable) subjective task credentials (COW): */
const struct cred __rcu *cred;
看到熟悉的字眼沒(méi),對(duì),那就是cred結(jié)構(gòu)體指針。前面我們講到,一個(gè)進(jìn)程的權(quán)限是由位于內(nèi)核空間的cred結(jié)構(gòu)體進(jìn)行管理的,那么我們不難想到:只要改變一個(gè)進(jìn)程的cred結(jié)構(gòu)體,就能改變其執(zhí)行權(quán)限。
在內(nèi)核空間有如下兩個(gè)函數(shù),都位于kernel/cred.c中:
- struct cred* prepare_kernel_cred(struct task_struct* daemon):該函數(shù)用以拷貝一個(gè)進(jìn)程的cred結(jié)構(gòu)體,并返回一個(gè)新的cred結(jié)構(gòu)體,需要注意的是daemon參數(shù)應(yīng)為有效的進(jìn)程描述符地址或NULL,如果傳入NULL,則會(huì)返回一個(gè)root權(quán)限的cred
- int commit_creds(struct cred *new):該函數(shù)用以將一個(gè)新的cred結(jié)構(gòu)體應(yīng)用到進(jìn)程。所以我們最重要的目的是類似于用戶態(tài)下調(diào)用system("/bin/sh")一樣,咱們內(nèi)核態(tài)就需要調(diào)用commit_creds(prepare_kernel_cred(NULL))即可達(dá)成提權(quán)功能!
這里我們也可以看到prepare_kernel_cred()函數(shù)源碼:
struct cred *prepare_kernel_cred(struct task_struct *daemon)
{
const struct cred *old;
struct cred *new;
new = kmem_cache_alloc(cred_jar, GFP_KERNEL);
if (!new)
return NULL;
kdebug("prepare_kernel_cred() alloc %p", new);
if (daemon)
old = get_task_cred(daemon);
else
old = get_cred(&init_cred);
三
保護(hù)措施
1. KASLR
與用戶態(tài)ASLR類似,在開(kāi)啟了 KASLR 的內(nèi)核中,內(nèi)核的代碼段基地址等地址會(huì)整體偏移。
2. FGKASLR
KASLR 雖然在一定程度上能夠緩解攻擊,但是若是攻擊者通過(guò)一些信息泄露漏洞獲取到內(nèi)核中的某個(gè)地址,仍能夠直接得知內(nèi)核加載地址偏移從而得知整個(gè)內(nèi)核地址布局,因此有研究者基于 KASLR 實(shí)現(xiàn)了 FGKASLR,以函數(shù)粒度重新排布內(nèi)核代碼。
3. STACK PROTECTOR
類似于用戶態(tài)程序的 canary,通常又被稱作是 stack cookie,用以檢測(cè)是否發(fā)生內(nèi)核堆棧溢出,若是發(fā)生內(nèi)核堆棧溢出則會(huì)產(chǎn)生 kernel panic
內(nèi)核中的 canary 的值通常取自 gs 段寄存器某個(gè)固定偏移處的值。
4. SMAP/SMEP
SMAP即管理模式訪問(wèn)保護(hù)(Supervisor Mode Access Prevention),SMEP即管理模式執(zhí)行保護(hù)(Supervisor Mode Execution Prevention),這兩種保護(hù)通常是同時(shí)開(kāi)啟的,用以阻止內(nèi)核空間直接訪問(wèn)/執(zhí)行用戶空間的數(shù)據(jù),完全地將內(nèi)核空間與用戶空間相分隔開(kāi),用以防范ret2usr(return-to-user,將內(nèi)核空間的指令指針重定向至用戶空間上構(gòu)造好的提權(quán)代碼)攻擊。
SMEP保護(hù)的繞過(guò)有以下兩種方式:
- 利用內(nèi)核線性映射區(qū)對(duì)物理地址空間的完整映射,找到用戶空間對(duì)應(yīng)頁(yè)框的內(nèi)核空間地址,利用該內(nèi)核地址完成對(duì)用戶空間的訪問(wèn)(即一個(gè)內(nèi)核空間地址與一個(gè)用戶空間地址映射到了同一個(gè)頁(yè)框上),這種攻擊手法稱為 ret2dir;
- Intel下系統(tǒng)根據(jù)CR4控制寄存器的第20位標(biāo)識(shí)是否開(kāi)啟SMEP保護(hù)(1為開(kāi)啟,0為關(guān)閉),若是能夠通過(guò)kernel ROP改變CR4寄存器的值便能夠關(guān)閉SMEP保護(hù),完成SMEP-bypass,接下來(lái)就能夠重新進(jìn)行 ret2usr,但對(duì)于開(kāi)啟了 KPTI 的內(nèi)核而言,內(nèi)核頁(yè)表的用戶地址空間無(wú)執(zhí)行權(quán)限,這使得 ret2usr 徹底成為過(guò)去式。
四
環(huán)境利用
首先咱們拿到個(gè)ctf題目之后,咱們一般是先解包,會(huì)發(fā)現(xiàn)有這些個(gè)文件:
1.baby.ko
baby.ko是包含漏洞的程序,一般使用ida打開(kāi)分析,可以根據(jù)init文件的路徑去rootfs.cpio里面找。
2.bzImage
bzImage是打包的內(nèi)核代碼,一般通過(guò)它抽取出vmlinx,尋找gadget也是在這里。
3.initramfs.cpio
initramfs.cpio是內(nèi)核采用的文件系統(tǒng)。
4.startvm.sh
startvm.sh是啟動(dòng)QEMU的腳本
5.vmlinux
靜態(tài)編譯,未壓縮的內(nèi)核文件,可以在里面找ROP
6.init文件
在rootfs.cpio文件解壓可以看到,記錄了系統(tǒng)初始化時(shí)的操作,一般在文件里insmod一個(gè)內(nèi)核模塊.ko文件,通常是有漏洞的文件
7..ko文件:需要拖到IDA里面分析找漏洞的文件,也即一般的漏洞出現(xiàn)的文件
---
之后咱們可以利用rootfs.cpio解壓的文件中看到init腳本,此即為加載文件系統(tǒng)的腳本,在一般為boot.sh或start.sh腳本中也記錄了qemu的啟動(dòng)參數(shù)
如何將exp送入本地調(diào)試
我的辦法比較笨,那就是本地編譯然后放到文件系統(tǒng)里面在壓縮為cpio,這樣再啟動(dòng)虛擬機(jī)的時(shí)候就會(huì)重新加載這個(gè)文件系統(tǒng)了。
-
內(nèi)核
+關(guān)注
關(guān)注
3文章
1346瀏覽量
40152 -
Linux
+關(guān)注
關(guān)注
87文章
11164瀏覽量
208469 -
PWN
+關(guān)注
關(guān)注
0文章
11瀏覽量
16654
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論