文章首发于

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

概述

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

样例文件和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字节,下面看看类的构造函数

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

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

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

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

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

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

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

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

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

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

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

首先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解析具体的页面信息

函数逻辑比较简单,就是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加载页面的文本数据

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

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

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

这里会根据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里面对页面的某个图片数据进行解析

函数的流程也简单,首先根据图片的索引在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;

解析完后的效果图如下:

构造POC的技巧

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

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

cajviewer逆向分析-HN文件格式分析和010editor模板开发的更多相关文章

  1. Linux内核分析——ELF文件格式分析

    ELF文件(目标文件)格式主要三种: 1)可重定向文件:文件保存着代码和适当的数据,用来和其他的目标文件一起来创建一个可执行文件或者是一个共享目标文件.(目标文件或者静态库文件,即linux通常后缀为 ...

  2. [Android Security] DEX文件格式分析

    copy from : https://segmentfault.com/a/1190000007652937 0x00 前言 分析 dex 文件格式最好的方式是找个介绍文档,自己再写一个简单的 de ...

  3. 多媒体(2):WAVE文件格式分析

    目录 多媒体(1):MCI接口编程 多媒体(2):WAVE文件格式分析 多媒体(3):基于WindowsAPI的视频捕捉卡操作 多媒体(4):JPEG图像压缩编码 多媒体(2):WAVE文件格式分析

  4. 实践2.4 ELF文件格式分析

    实践2.4 ELF文件格式分析 1.ELF文件头 查看/usr/include/elf.h文件: #define EI_NIDENT (16) typedef struct { unsigned ch ...

  5. libpcap文件格式分析

    第一部分:PCAP包文件格式 一 基本格式: 文件头 数据包头数据报数据包头数据报...... 二.文件头: 文件头结构体  sturct pcap_file_header  {       DWOR ...

  6. wav文件格式分析详解

    wav文件格式分析详解 文章转载自:http://blog.csdn.net/BlueSoal/article/details/932395 一.综述    WAVE文件作为多媒体中使用的声波文件格式 ...

  7. AMR音频文件格式分析

    AMR音频文件格式分析 1 概要 如今非常多智能手机都支持多媒体功能,特别是音频和视频播放功能,而AMR文件格式是手机端普遍支持的音频文件格式.AMR,全称是:Adaptive Multi-Rate, ...

  8. PE文件格式分析

    PE文件格式分析 PE 的意思是 Portable Executable(可移植的执行体).它是 Win32环境自身所带的执行文件格式.它的一些特性继承自Unix的Coff(common object ...

  9. Linux课题实践四——ELF文件格式分析

    2.4   ELF文件格式分析 20135318 刘浩晨 ELF全称Executable and Linkable Format,可执行连接格式,ELF格式的文件用于存储Linux程序.ELF文件(目 ...

  10. linux第三次实践:ELF文件格式分析

    linux第三次实践:ELF文件格式分析 标签(空格分隔): 20135328陈都 一.概述 1.ELF全称Executable and Linkable Format,可执行连接格式,ELF格式的文 ...

随机推荐

  1. MyBatis——案例——删除(单个删除与批量删除)

    删除一个   1.编写接口方法:Mapper接口     参数:id     结果:void /** * 删除 */ int deleteById(int id);   2.编写sql语句:SQL映射 ...

  2. Java日期时间API系列33-----Jdk8中java.time包中的新的日期时间API类应用,格式化常用模板大全,新增Excel常用格式。

    从Java日期时间API系列10-----Jdk8中java.time包中的新的日期时间API类的DateTimeFormatter中可以知道常用字符有超过20几种,各种组合结果非常多.但常用的组合就 ...

  3. Vue3 的 nextTick 函数

    作用: DOM 渲染是异步耗时的, vue2.x 需要等到 DOM 渲染完成之后做某个事情,需要使用 this.$nextTick , vue3.x 则直接提供了 nextTick 这个方法去实现 : ...

  4. 9. JS的数据类型,区别

    js 有2大数据类型分类 : 基本数据类型: 1. string 字符串 使用单.双引号包裹,或者使用反引号包裹 2. number 数字类型 3. boolean 布尔值 true false 4. ...

  5. 04 统计语言模型(n元语言模型)

    博客配套视频链接: https://space.bilibili.com/383551518?spm_id_from=333.1007.0.0 b 站直接看 配套 github 链接:https:// ...

  6. kotlin更多语言结构——>空安全

    可空类型与非空类型 Kotlin 的类型系统旨在从我们的代码中消除 NullPointerException .NPE 的唯一可能的原因可能是: -  显式调用 throw NullPointerEx ...

  7. Kali Linux 各版本开启ssh 服务

    Kali Linux 各版本开启ssh 服务 2019版kali Linux SSH链接办法 修改kali关于SSH服务默认配置并重启SSH服务,步骤如下: 打开sshd_config文件 leafp ...

  8. "山海经“ 讲解----线段树

    "山海经"--线段树 讲解 1.题面: http://cogs.pro/cogs/problem/problem.php?pid=775 2.题目大意及分析: i:大概就是说给了你 ...

  9. PowerPoint 修改默认导出图片的大小

    1.运行rededit打开注册表编辑器 2.找到HKEY_CURRENT_USER\Software\Microsoft\Office\16.0\PowerPoint\Options 3.其中不同Of ...

  10. NES 系统架构

    主机 NES(FC.红白机.小霸王)的系统架构可用下图表示: 系统中最核心的组件是 CPU,其它组件都可以算作 CPU 的外设.CPU 的外设包括:PPU(图像处理器).APU(音频处理器).WRAM ...