zoukankan      html  css  js  c++  java
  • cajviewer逆向分析-HN文件格式分析和010editor模板开发

    文章首发于

    https://mp.weixin.qq.com/s/7STPL-2nCUKC3LHozN6-zg
    

    概述

    本文介绍对cajviewer中对HN文件格式的逆向分析并介绍如何编写相应的010editor模板,最后介绍通过分析如何构造POC,触发cajviewer在解析HN文件中的图片时的漏洞。HN文件是cajviewer支持的其中一种文件格式,这个文件类似于PDF,可以包含文字、图片等,下图是一个HN文件应用模板后的截图,具体的分析过程请看正文部分。

    image.png

    样例文件和010模板

    https://github.com/hac425xxx/cajviewer-fuzz-data
    https://github.com/hac425xxx/cajviewer-fuzz-data/releases/download/2020-8-2/sample.7z
    

    正文

    解析文件头

    基于上文的分析,我们知道cajviewer使用CAJFILE_OpenEx1函数来打开和解析一个文件,因此这个函数就是我们的分析入口.

    CCAJReaderStruct *__fastcall CAJFILE_OpenEx1(char *fpath, char *a2)
    {
    
      file_type = CAJFILE_GetDocTypeEx1(fpath, a2, 0LL);// 获取文档类型
      switch ( file_type )
      {
        case 1u:
        case 2u:
        case 8u:
        case 0xAu:
        case 0x1Bu:
          ccaj_reader = operator new(0x210uLL);
          a2 = v12;
          CCAJReader::CCAJReader(ccaj_reader, v12); // 根据文件类型,构造Reader对象
    

    函数首先调用CAJFILE_GetDocTypeEx1根据文件头和文件名返回一个表示文档类型的int值,对于样本文件来说会进入CCAJReader::CCAJReader 构造文档对象用于后续的解析。

    通过分析类的构造函数可以大概了解对象的内存布局,比如通过new函数的参数可以知道 CCAJReader::CCAJReader 对象的大小为 0x210字节,下面看看类的构造函数

    image.png

    首先赋值虚表为 vtable for CCAJReader + 2,其实就是0xB19B0

    image.png

    我们可以把这个抠出来,作为一个结构体以便后续分析

    struct CCAJReaderVtableStruct
    {
      void *_ZN10CCAJReaderD2Ev;
      void *_ZN10CCAJReaderD0Ev;
      ....................................
      ....................................
      void *_ZN7CReader16InternalFileOpenEPKc;
      void *_ZN7CReader18InternalFileLengthEPv;
      void *_ZN7CReader16InternalFileSeekEPvll;
      void *_ZN7CReader16InternalFileReadEPvS0_l;
      void *_ZN7CReader17InternalFileCloseEPv;
      void *_ZN7CReader19InternalFileIsReadyEPKcijj;
    };
    

    然后设置CCAJReaderStructvtbl的类型为CCAJReaderVtableStruct*,这样再看虚函数调用时就可以很方便的定位到目标函数,其他用到的类也用这种方式逆向即可,继续往下看

    image.png

    这里调用CCAJReader::Open对文件进行初步解析,该函数实际会进入CAJDoc::Open读取文件内容并解析

    image.png

    首先这里调用BaseStream::getStream来创建一个stream对象,在cajviewer里面通过stream对象来从各种来源读取数据,比如网络、文件、内存等。

    image.png

    就我们这个例子实际构建的对象为FileStream,创建完后就会调用FileStream::openFileStream::seek打开文件并把文件指针重定向到文件开头。

    image.png

    然后会进入 CAJDoc::OpenNHCAJFile 进行具体的解析,第二个参数为0,在该函数里面首先会调用FileStream::read读取文件开头的0x88字节,并进行简单的判断

    image.png

    校验了前0x88字节的部分数据后,会再次读取 0x50字节的数据(0x10+0x40)

    image.png

    其中buffer_0x10.page_count表示文件中包含的页面数,这个通过观察下面的引用来推测,继续往下

    image.png

    这里首先校验buffer_0x10.field_0是否大于 0x18f ,如果大于0x18f就会再次读取一些内容作为元数据,然后会根据这个值设置item_size

    image.png

    首先cajdoc->current_offset在前面读取内容时会进行调整,从cajdoc->current_offset开始就是表示CAJPage的信息数组,数组中每一项的大小为 cajdoc->item_size,类的构造函数的最重要的参数是第三个参数,表示该CAJPage在文件中的偏移,后面解析时会用到这些。

    至此我们可以得到文件开头的格式为

    0x88字节的hn_header
    0x10字节的buffer_0x10;
    0x40字节的buffer_0x40;
    如果buffer_0x10.field_0 > 0x18F,后面还会跟一个 0x84字节的buffer_0x84 和 308 * buffer_0x84.count 字节的内存
    然后是buffer_0x10.page_count个page_info结构,每个结构的大小item_size为12或者20,item_size 根据buffer_0x10.field_0来判断
    

    此时我们可以写一个简单的010editor模板,来解析文件头的数据

    
    typedef struct{
        ubyte data[0x88];
    }HN_FILE_HEADER;
    
    typedef struct{
        uint32 field_0;
        uint32 field_4;
        uint32 page_count;
        uint32 field_0xc;
    }BUFFER_0X10;
    
    typedef struct{
        ubyte gap[12];
        uint16 w1;
        uint16 w2;
        uint32 unknown_dword;
        uint32 dword_20;
        ubyte data[40];
    }BUFFER_0X40;
    
    typedef struct{
        ubyte data[0x80];
        uint32 count;
    }BUFFER_0X84;
    
    local uint32 item_size = 12;
    
    HN_FILE_HEADER hn_header;
    BUFFER_0X10 buffer_0x10;
    BUFFER_0X40 buffer_0x40;
    
    local uint64 page_info_offset = FTell();
    
    if(buffer_0x10.field_0 > 0x18F)
    {
        BUFFER_0X84 buffer_0x84;
        local uint64 cur_pos = FTell();    
        page_info_offset = 308 * buffer_0x84.count + cur_pos;
    }
    
    if(buffer_0x10.field_0 <= 0xC7)
    {
        item_size = 12;
    }
    else
    {
        item_size = 20;
    }
    
    FSeek(page_info_offset);
    

    这里有几个关键的点,在010editor的模板中类型定义和local开头的局部变量不会导致文件指针的移动,当直接定义结构体变量时就会导致010editor读取文件内容并进行解析。

    HN_FILE_HEADER hn_header;
    

    比如这个代表010editor会读取0x88字节到hn_header 并会移动文件指针,最后会使用FSeek(page_info_offset)把文件指针移动到page_info开始的位置,详细的教程和语法可以看下面的链接

    https://bbs.pediy.com/thread-257797.htm
    

    解析页面数据

    解析完文件头的数据后会调用CAJPage::LoadPageInfo解析具体的页面信息

    image.png

    函数逻辑比较简单,就是FileStream::seek到指定的文件偏移,然后读取item_size数据用于page_info,然后会把page_info的数据保存到当前page对应的结构体里面, page_info的结构如下

    struct page_info
    {
      int file_offset;  // page数据在文件中的偏移
      int size;	// page数据的大小
      __int16 pic_count;  // page中的图片个数
      __int16 field_A;
      __int64 field_C;
    };
    

    然后会跳到page_info.file_offset,读取page数据的前0x20个字节,然后从里面解析了一些数据,用途不明。

    加载完page_info后会调用CAJPage::LoadPage加载页面的文本数据

    image.png

    这里首先跳转到page数据所在的文件偏移,然后把页面的数据读出来

    image.png

    这里对文件内容解析,首先从头8个字节里面解析出当前pageheighwidth,然后后面是具体的文本数据,然后判断文本数据开头是否有COMPRESSTEXT,如果是表示文本数据是压缩过的会使用UnCompress对文本数据进行解压。

    image.png

    解析完page的文本数据后会把page的图片数据在文件的起始偏移记录在page->pic_info_foffset里面,解析完之后会进入CAJPage::LoadPicInfo加载图片的元数据

    image.png

    这里会根据page->page_info.pic_count创建CAJ_FILE_PICINFO数组,数组中的每个元素为pic_info结构,结构体定义如下

    struct pic_info_struct
    {
      int type;  // 图像类型
      int offset; // 图像数据在文件中的偏移
      int size; // 图像数据的大小
    };
    

    通过这个函数每个page的图片信息会保存到page->caj_picinfo_list里面,然后会在CAJPage::LoadImage里面对页面的某个图片数据进行解析

    image.png

    函数的流程也简单,首先根据图片的索引在cajpage->caj_picinfo_list里面找到图片的picinfo结构,然后根据该结构读取图片的数据并使用UnCompressImage对图片数据进行解析。

    至此我们可以得到page数据的组织方式如下

    首先在文件头后面是buffer_0x10.page_countpage_info结构,page_info结构里面记录了页面的数据所在的文件偏移、内容的大小以及页面包含图片的个数,然后根据这些信息可以得到页面的文本数据和图片数据(图片数据紧跟在文本数据的后面)。

    这部分的010模板如下

    typedef struct{
        uint32 type;
        uint32 file_offset;
        uint32 size;
        local uint64 backup_offset = FTell(); 
    
        FSeek(file_offset);  // move to data offset
        ubyte pic_data[size];   // page_data
        FSeek(backup_offset);  // move back
    }PICINFO;
    
    
    typedef struct (uint32 size){
        PAGE_CONENT_HEADER page_hdr;
    
    
        local char tmp[12];
        ReadBytes(tmp, FTell(), 12);
    
        if(Memcmp(tmp, "COMPRESSTEXT", 12) == 0)
        {
            char compress_sig[12];
            uint32 decompressed_size;
            char compressed_data[size - 12 - 4 - sizeof(PAGE_CONENT_HEADER)];
        }
        else
        {
            ubyte page_text_content[size - sizeof(PAGE_CONENT_HEADER)];   // page_data
        }
        
    }PAGE_CONTENT;
    
    typedef struct _PAGE_INFO_ITEM{
        uint32 file_offset;
        uint32 size;
        uint16 pic_count;
        uint16 field_A;
      
        if(item_size==20)
        {
            uint64 field_C;
        }
    
        local uint64 backup_offset = FTell(); 
        
        FSeek(file_offset);  // move to data offset
        
        PAGE_CONTENT page_content(size);
        
        local uint32 i = 0;
    
        while(i < pic_count)
        {
            PICINFO pic_info;
            i++;
        }
    
        FSeek(backup_offset);  // move back
    }PAGE_INFO_ITEM;
    

    解析完后的效果图如下:

    image.png

    构造POC的技巧

    通过前面的分析可知UnCompress会对页面文本数据进行解压,简单的看下UnCompress的实现我们可以知道该函数调用了zlib 1.1.3版本解压文本数据,这个版本有很多漏洞,如果我们想触发UnCompress的漏洞就可以把文件中压缩文本数据替换成zlib的poc数据即可

    image.png

    如果是要触发解析图片的的漏洞时也是一样的思路,替换掉正常文件中的某个图片数据即可

    image.png

  • 相关阅读:
    HDU 5585 Numbers
    HDU 3308 LCIS
    POJ 2991 Crane
    POJ 1436 Horizontally Visible Segments
    POJ 3667 Hotel
    HaiHongOJ 1003 God Wang
    【SDOI 2008】 递归数列
    5月19日省中提高组题解
    【HDU 1588】 Gauss Fibonacci
    【POJ 3233】Matrix Power Series
  • 原文地址:https://www.cnblogs.com/hac425/p/14437749.html
Copyright © 2011-2022 走看看