简介

ROP的全称为Return-oriented Programming,主要思想是在栈缓冲区溢出的基础上,利用程序中已有的小片段 (gadgets) 来改变某些寄存器或者变量的值,从而控制程序的执行流程;这种攻击方法在用户态的条件中运用的比较多,ret2shellcode,ret2libc,ret2text等ret2系列都利用到了ROP的思想,当然这种攻击手法在内核态同样是有用的,并且手法都基本一样....
这里我以2018年的强网杯中的core来进行演示和学习的,环境我已经放到的了github上面了,需要的可以自行下载学习....

前置知识

kernel space to user space

我们知道Linux操作系统中用户态和内核态是相互隔离的,所以当系统从内核态返回到用户态的时候就必须要进行一些操作,才可以是两个状态分开,具体操作是:

  1. 通过swapgs指令恢复用户态GS的值;
  2. 通过sysretq或者iretq指令恢复到用户控件继续执行;如果使用iretq指令则还需要给出用户空间的一些信息(CS, eflags/rflags, esp/rsp等);
    比如这里利用的iretq指令,在栈中就给出CS,eflags,sp,ss等信息:

    当然,我们可以通过下来这这个函数来获取并保存这些信息:
unsigned long user_cs, user_ss, user_eflags, user_sp;

void save_stats(){
    asm(
        "movq %%cs, %0\n"
        "movq %%ss, %1\n"
        "movq %%rsp, %3\n"
        "pushfq\n"
        "popq %2\n"
        :"=r"(user_cs), "=r"(user_ss), "=r"(user_eflags),"=r"(user_sp)
        :
        : "memory"
    );
}

提权函数

在内核态提权到root,一种简单的方法就是是执行下面这个函数:

commit_creds(prepare_kernel_cred(0));

这个函数会使我们分配一个新的cred结构(uid=0, gid=0等)并且把它应用到调用进程中,此时我们就是root权限了;
commit_credsprapare_kernel_cred都是内核函数,一般可以通过cat /proc/kallsyms查看他们的地址,但是必须需要root权限....

具体分析

现在我们可以先分析一下这个core.ko驱动了:
首先查看一下这个ko文件的保护机制有哪些:

开启了canary保护....
core_ioctl:

这个函数定义了三条命令,分别调用core_read(),core_copy_func(),并且可以设置全局变量off;
core_copy_func:

这个函数会根据用户的输入长度,从name这个全局变量中往栈上写数据,并且函数在判断我们输入的这个a1变量类型的时候是signed long long,但是qmemcpy的时候就变成了unsigned __int16了,所以这里存在一个截断,当我们输入如0xf000000000000000|0x100这样的数据就可以绕过限制,就可以造成内核的栈溢出了;
core_read:

这个函数会从栈上读出长度为0x40的数据,并且读的起始位置我们可以通过改变off这个全局变量的大小来控制,也就是说这个我们可以越界访问数据,将栈上面的返回地址,canary等信息读到....
core_write:


最后这个函数我们可以向全局变量name中写入一个长度不大于0x800的字符串....

思路方法

所以现在我们思路比较清晰了:

  1. 首先通过ioctl函数设置全局变量off的大小,然后通过core_read()leak出canary;
  2. 然后通过core_write()向全局变量name中写入我们构造的ROPchain;
  3. 通过设置合理的长度利用core_copy_func()函数把name的ROPchain向v2变量上写,进行ROP攻击;
  4. ROP调用commit_creds(prepare_kernel_cred(0)),然后swapgs,iretq到用户态;
  5. 用户态起shell,get root;

所以这里最重要的就是我们的ROPchain的构造了....
为了方便调试,我们修改一下init文件:

- setsid /bin/cttyhack setuidgid 1000 /bin/sh
+ setsid /bin/cttyhack setuidgid 0 /bin/sh

这样我们start的时候就是root权限了,方便我们查看一些函数的地址;

获得基地址

首先我们查看一下qume中函数的地址:

然后通过gdb调试查看core_read的栈内容:

基本我们能够从栈中泄露vmlinux和core.ko的基地址了....
通过这些位置的地址减去偏移就是基地址了,这个和用户态找libc的基地址的方法是一样的.....
然后我们可以利用ropper工具来查找我们需要的gadget了:

ropper --file core.ko --search "pop|ret"

这里建议使用ropper而不是ROPgadget,因为ROPgadget太慢了,ropper可以直接通过pip install ropper来安装;

EXP

poc.c:

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int fd;
点击收藏 | 1 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖