cajviewer逆向分析-HN文件格式分析和010editor模板开发
文章首发于
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;
};
然后设置CCAJReaderStruct的vtbl的类型为CCAJReaderVtableStruct*,这样再看虚函数调用时就可以很方便的定位到目标函数,其他用到的类也用这种方式逆向即可,继续往下看

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

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

就我们这个例子实际构建的对象为FileStream,创建完后就会调用FileStream::open和FileStream::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个字节里面解析出当前page的heigh和width,然后后面是具体的文本数据,然后判断文本数据开头是否有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_count个page_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模板开发的更多相关文章
- Linux内核分析——ELF文件格式分析
ELF文件(目标文件)格式主要三种: 1)可重定向文件:文件保存着代码和适当的数据,用来和其他的目标文件一起来创建一个可执行文件或者是一个共享目标文件.(目标文件或者静态库文件,即linux通常后缀为 ...
- [Android Security] DEX文件格式分析
copy from : https://segmentfault.com/a/1190000007652937 0x00 前言 分析 dex 文件格式最好的方式是找个介绍文档,自己再写一个简单的 de ...
- 多媒体(2):WAVE文件格式分析
目录 多媒体(1):MCI接口编程 多媒体(2):WAVE文件格式分析 多媒体(3):基于WindowsAPI的视频捕捉卡操作 多媒体(4):JPEG图像压缩编码 多媒体(2):WAVE文件格式分析
- 实践2.4 ELF文件格式分析
实践2.4 ELF文件格式分析 1.ELF文件头 查看/usr/include/elf.h文件: #define EI_NIDENT (16) typedef struct { unsigned ch ...
- libpcap文件格式分析
第一部分:PCAP包文件格式 一 基本格式: 文件头 数据包头数据报数据包头数据报...... 二.文件头: 文件头结构体 sturct pcap_file_header { DWOR ...
- wav文件格式分析详解
wav文件格式分析详解 文章转载自:http://blog.csdn.net/BlueSoal/article/details/932395 一.综述 WAVE文件作为多媒体中使用的声波文件格式 ...
- AMR音频文件格式分析
AMR音频文件格式分析 1 概要 如今非常多智能手机都支持多媒体功能,特别是音频和视频播放功能,而AMR文件格式是手机端普遍支持的音频文件格式.AMR,全称是:Adaptive Multi-Rate, ...
- PE文件格式分析
PE文件格式分析 PE 的意思是 Portable Executable(可移植的执行体).它是 Win32环境自身所带的执行文件格式.它的一些特性继承自Unix的Coff(common object ...
- Linux课题实践四——ELF文件格式分析
2.4 ELF文件格式分析 20135318 刘浩晨 ELF全称Executable and Linkable Format,可执行连接格式,ELF格式的文件用于存储Linux程序.ELF文件(目 ...
- linux第三次实践:ELF文件格式分析
linux第三次实践:ELF文件格式分析 标签(空格分隔): 20135328陈都 一.概述 1.ELF全称Executable and Linkable Format,可执行连接格式,ELF格式的文 ...
随机推荐
- MyBatis——案例——查询-单条件查询-动态条件查询
单条件查询-动态条件查询(choose(when,otherwise)) 从多个条件中选择一个 choose(when,otherwise) 选择,类似于java中的Switch语句(w ...
- ceph-rbd和cephfs使用
目录 1 用户权限管理和授权流程 1.1 列出用户 1.2 用户管理 1.2.1 ceph auth add 1.2.3 ceph auth get-or-create 1.2.4 ceph auth ...
- Linux内核虚拟内存管理之匿名映射缺页异常分析
今天我们就来讨论下这种缺页异常,让大家彻底理解它.注:本文使用linux-5.0内核源代码.文章分为以下几节内容: 匿名映射缺页异常的触发情况 0页是什么?为什么使用0页? 源代码分析 3.1 触发条 ...
- Windows10 安装使用 Docker
Windows10 安装使用 Docker 下载安装 Docker Desktop https://docs.docker.com/docker-for-windows/install/ 点击运行 D ...
- .Net 理解异步的学习
// 异步 - 在方法中使用 // 异步约等于线程 async await 一起使用 // 异步只有三种返回值 // 1. Task // 2. Task<T> // 3. void 几乎 ...
- MYSQL存储过程-练习2 while 循环
MYSQL存储过程-练习2 while 循环 1 #WHILE循环 2 DELIMITER $$ 3 4 CREATE PROCEDURE `sp_while`() 5 BEGIN 6 DECLARE ...
- Unity 华为快游戏JS桥接 实现写日志等功能
之前接入微信小游戏本身代码js桥接比较完善,抖音小游戏有缺少但也没缺的这么多,华为这边的API,大残啊!官方转换插件Github仓库上一次提交在3月份.(截至现在)API给的很简略,接入js代码那里说 ...
- Taylor series Explicit Euler Implicit Euler
1 Taylor series \[\begin{gathered}\sum_{n=0}^{\infty}\frac{f^{(n)}\left(a\right)}{n!}\left(x-a\right ...
- Tony Bai · Go语言第一课 _个人笔记 04|初窥门径:一个Go程序的结构是怎样的?
Tony Bai · Go语言第一课 _个人笔记 04|初窥门径:一个Go程序的结构是怎样的? 1.配置国内的Go模块的镜像 配置国内镜像代理(使用阿里云镜像) go env -w GOPROXY=h ...
- fiddler限速配置&mock配置
一.限速配置 1.开启性能选项 2.找到对应的参数入口 3.修改对应的从参数 解释下 这2个参数是如何做到限速的 4.request-trickle-delay(上传数据限制) 默认值是300,他的意 ...