cajviewer逆向分析与漏洞挖掘
文章首发于
https://mp.weixin.qq.com/s/7STPL-2nCUKC3LHozN6-zg
前言
CAJViewer是一个论文查看工具,主要用于查看caj文件格式的论文。本文介绍对该软件进行逆向分析和漏洞挖掘的过程。
代码地址
https://github.com/hac425xxx/cajviewer-fuzz-data
正文
逆向分析
首先分析的是CAJViewer的Windows版本,由于我们的目的是挖掘软件的漏洞,通过介绍我们知道CAJViewer本质上是一个文件解析程序,因此该软件的高危模块应该是软件中解析文件数据的部分,因此首先应该大概定义软件数据处理部分所在位置,Windows平台下可以使用 process monitor来进行初步的分析。
首先打开process monitor并开始捕获事件,然后使用CAJViewer打开一个caj文件,等文件解析完成后停止捕获事件。

然后我们可以过滤一下需要查看的事件,比如上图设置了只查看文件操作并且只查看对 input.caj 文件的操作,该文件就是之前让CAJViewer打开的文件。
然后我们可以找一下读文件的操作(ReadFile),因为大部分文件解析逻辑应该读一部分文件内容解析一部分,因此通过查看读文件时的调用栈就可以大概定位解析数据的模块,然后双击就可以查看调用相应函数的调用栈。

通过查看多个数据读取的调用栈,可以发现ReaderEx.dll在调用栈中出现多次,因此大概可以猜测ReaderEx.dll应该主要负责处理文件数据。
逆向了一会ReaderEx.dll后,发现CAJViewer今年还发布了Linux版本,于是下载下来分析了一下。下载下来后是一个可执行文件CAJViewer-x86_64-libc-2.24.AppImage,执行起来查看进程的maps发现其实软件会在tmp目录把打包好的二进制解压,然后去执行tmp目录下的二进制。

这里可以直接把/tmp/.mount_CAJVierjayBH/拷贝到一个目录,然后就可以直接执行 cajviewer了。

查看解压处理的二进制发现一个libreaderex_x64.so,看名字应该是ReaderEx.dll的Linux版本,然后使用IDA打开,发现比Windows版本的要好分析一点,信息也比ReaderEx.dll的多。于是接下来决定对Linux版本的二进制进行分析。
首先看看主程序cajviewer,查看main函数可以发现软件是用qt写的

之后翻了一下函数列表,发现了MainWindow::OpenFile,看名称应该是打开一个文件。
__int64 __fastcall MainWindow::OpenFile(MainWindow *this, const QString *a2)
{
v2 = this;
QString::toUtf8_helper(&v16, a2);
memset(v19, 0, sizeof(v19));
*v19 = 0x2D8;
*&v19[4] = 256;
*&v19[8] = CAJFILE_CreateErrorObject(&v20);
v3 = *&v19[8];
if ( *v16 > 1 || (v5 = *(v16 + 2), v4 = v16, v5 != 24) )
{
QByteArray::reallocData(&v16, v16[1] + 1, *(v16 + 11) >> 31);
v4 = v16;
v5 = *(v16 + 2);
}
v6 = CAJFILE_OpenEx1(v4 + v5, v19); // 打开文件
这里对输入的QString进行一些处理后,调用了CAJFILE_OpenEx1函数,该函数位于libreaderex_x64.so。
Fuzz测试
Fuzz CAJFILE_OpenEx1函数
函数代码如下

函数的第一个参数是要解析的文件路径,第二个参数是一块内存,这个参数的结构可以查看MainWindow::OpenFile调用点。

可以看到in_buf的结构如下
+0: 4个字节 in_buf的长度
+4: 4个字节 一个整形值
+8: 一个指针, 存放构造好的 ErrorObject
使用调试器在这个函数下个断点,然后打开一个文件就可以看到入参如下

之后有简单的翻了一些该函数的实现,以及使用该函数的位置可以大概确定CAJFILE_OpenEx1用于打开一个文件,并会对文件的内容进行解析,因此下面打算使用AFL Qemu模式Fuzz一下这个函数。Fuzz之前需要写一点代码把so加载到内存,然后构造参数对目标函数进行测试。
首先需要把SO加载到内存中并获取目标函数的地址
void my_init(void) __attribute__((constructor)); //告诉gcc把这个函数扔到init section
void my_init(void)
{
void *handle;
handle = dlopen("/home/hac425/cajviewer/cajviewer-bin/usr/lib/libreaderex_x64.so", RTLD_LAZY);
struct link_map *lm = (struct link_map *)handle;
printf("%lx\n", lm->l_addr);
p_CAJFILE_OpenEx1 = dlsym(handle, "CAJFILE_OpenEx1");
p_CAJFILE_CreateErrorObject = dlsym(handle, "CAJFILE_CreateErrorObject");
}
my_init会在main函数之前执行,代码流程如下
- 首先dlopen把so加载到内存,并把so在内存中的基地址打印到屏幕,便于后续测试。
- 然后使用dlsym获取CAJFILE_OpenEx1和CAJFILE_CreateErrorObject函数的地址。
然后在main函数中就会构造参数调用目标函数
int main(int argc, char **argv)
{
char buf[0x2D8];
printf("main:%p\n", main);
memset(buf, 0, 0x2D8);
*(unsigned int *)buf = 0x2D8;
// *(unsigned int *)(buf + 4) = 256;
// *(char* *)(buf + 8) = p_CAJFILE_CreateErrorObject();
char *ret = p_CAJFILE_OpenEx1(argv[1], buf);
return 0;
}
代码逻辑很简单,首先构造CAJFILE_OpenEx1函数的第二个参数,然后把argv[1]作为文件路径传入函数。
然后编译一下
gcc CAJFILE_OpenEx1.c -o test_CAJFILE_OpenEx1_dbg -ldl -lheapasan -L libheapasan/ -g
编译后执行一下,可以看到正常执行完了,并打印出so的基地址和main函数的地址。
harness$ ./test_CAJFILE_OpenEx1_dbg ~/input.caj
string to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstringto intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to int
image base:0x7f6d87bff000
p_CAJFILE_OpenEx1:0x7f6d881e486c
main:0x555ed124cb71
接下来再使用afl-qemu-trace执行一下,获取一些地址用于Fuzz,使用afl-qemu-trace执行一个可执行程序时,其进程的so的地址都是固定的。
harness$ ~/AFLplusplus-2.66c/afl-qemu-trace ./test_CAJFILE_OpenEx1_dbg ~/input.caj
string to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstringto intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to intstring to int
image base:0x400133e000
p_CAJFILE_OpenEx1:0x400192386c
main:0x4000000b71
可以看到libreaderex_x64.so的基地址为0x400133e000, test_CAJFILE_OpenEx1_dbg 的main函数的地址为0x4000000b71。
然后去IDA中查看libreaderex_x64.so中代码段的范围

所以可以得到afl-qemu-trace执行时libreaderex_x64.so中代码段的范围为
开始地址: 0x400133e000+0x3D4880 = 0x4001712880
结束地址: 0x400133e000+0x90984F = 0x4001c4784f
然后可以使用AFL进行测试了
export AFL_CODE_START=0x4001712880
export AFL_CODE_END=0x4001c4784f
export AFL_ENTRYPOINT=0x4000000b71
/home/hac425/AFLplusplus-2.66c/afl-fuzz -m none -Q -t 20000 -i in -o out -- ./test_CAJFILE_OpenEx1_dbg @@
其中设置的环境变量的作用如下
AFL_CODE_START 和 AFL_CODE_END 表示需要统计覆盖率的范围
AFL_ENTRYPOINT 表示开启forkserver的位置

Fuzz UnCompressImage函数
在测试CAJFILE_OpenEx1时,去翻了一下libreaderex_x64.so里面的其他函数,在查看字符串时发现了一些源码路径。

拿路径去网上搜了一下,发现是用到了Kakadu_V2.2.3这个开源库,这个库很古老了(2008年的),用于解析jpeg2000格式,版本老往往表示存在漏洞几率较大,而且jpeg2000格式很复杂,在其他软件中也发现了很多漏洞,于是下面仔细的看了下。
下载到这个库的代码,然后一路回溯发现libreaderex_x64.so应该是在 jpeg2000.cpp里面实现了部分代码,最后一路跟到了DecodeJpeg2000函数,并基于开源代码把DecodeJpeg2000的参数基本弄清楚了。继续往上跟DecodeJpeg2000,找到了UnCompressImage函数,这个函数应该是解析图片数据的统一接口了。
CAJViewer在解析CAJ等文件时,如果文件中嵌入了图片数据时,就会会使用libreaderex_x64.so中的UnCompressImage函数来对图片数据进行解析。

函数的参数信息如下:
buffer: 保存从文件中提取出的图片数据
type: 图片的类型
buffer_length: 图片数据的长度
剩下两个参数a4,a5: 个人猜测可能是需要将图片缩放的大小
然后编写代码,my_init的主要逻辑和 CAJFILE_OpenEx1函数的一致,只是需要hook一些函数,避免比Fuzz识别为crash,比如在代码里面有很多assert,如果直接执行到这个函数的话,会被afl识别为crash.

因此这里使用plt hook,把libreaderex_x64.so模块中的一些函数给hook了。
int my_assert_fail()
{
printf("my_assert_fail\n");
exit(1);
return 0;
}
int my_cxa_throw()
{
printf("my_cxa_throw\n");
exit(1);
return 0;
}
void my_init(void)
{
........................................
........................................
plt_hook_function("libreaderex_x64.so", "__assert_fail", my_assert_fail);
plt_hook_function("libreaderex_x64.so", "__cxa_throw", my_cxa_throw);
}
然后再main函数中调用目标函数
int main(int argc, char **argv)
{
printf("main:%p\n", main);
int f_sz = 0;
char* buffer = read_to_buf(argv[1], &f_sz);
char *ret = p_UnCompressImage(buffer, 4, f_sz, 100, 100);
return 0;
}
然后其他的操作和Fuzz CAJFILE_OpenEx1函数时一致,只是环境变量需要重新设置
/home/hac425/AFLplusplus-2.66c/afl-fuzz -m none -Q -t 20000 -i image_fuzz/ -o UnCompressImageOutput -- ./test_UnCompressImage @@
部分漏洞分析
CImage::LoadBMP 内存为初始化漏洞
Cajviewer For Linux 在解析BMP图片时会进入 CImage::LoadBMP 函数,该函数中存在内存未初始化漏洞。

函数的流程如下
第8行,调用BaseStream::streamLength获取文件的大小。
第9行,调用FileStream::read从文件中读出14字节的文件头。
第10行,调用gmalloc分配内存用于存放文件的其他数据,这里实际上是直接调用malloc分配内存。
第12行,这里将分配的内存没有初始化直接传入FindDIBBits,该函数计算一个地址保存到this->DIBBits域。
第14行,这里会从this->DIBBits中读取数据,导致crash。
下面看看FindDIBBits的实现

这里取出a1的开始4个字节作为一个偏移值 v1,然后调用PaletteSize,这个函数的返回值的可以为0,128等数字值。
由于a1这个内存没有初始化,故v1有可能会很大,进而导致FindDIBBits会返回一个越界的地址。
然后在CImage::CalibrateColor中就会去访问这个内存。

CImage::DecodeJbig 越界读写漏洞
CajViewer在解析CAJ等文件时,如果需要解析文件中嵌入的图片数据时,会使用libreaderex_x64.so中的函数来对图片进行解析,其中如果带解析的文件类型为Jbig文件时,会进入CImage::DecodeJbig函数进行解析:

其中重要函数的参数和作用如下:
- buf: 保存从文件中提取出的图片数据
- len: buf的长度
其中buf一开始是一个JbigInfo的结构,结构体的定义如下:

然后然后会进入CImage::CImage进行简单的文件解析。

首先使用JbigInfo中的字段计算一个sz, 然后使用 gmalloc分配内存,之后会使用memcpy 拷贝数据。
漏洞位于在计算sz时会导致整数溢出,进而导致会分配一个小于4LL * (1 << jbig_info->width2)的内存,然后在下面memcpy时会导致越界写。
此外整个过程没有校验jbig_info的长度,所以会导致越界读。
总结
本文介绍了如何分析一个软件并使用afl qemu模式来测试闭源二进制。
cajviewer逆向分析与漏洞挖掘的更多相关文章
- 【读书笔记】Android平台的漏洞挖掘和分析
最近比较关注移动端的安全,以后也打算向安卓平台的安全发展.这篇博文主要是记录一些研究Android安全的读书笔记. Fuzzing技术的核心是样本生成技术 测试Android平台的组件间通信功能使用的 ...
- Vuzzer自动漏洞挖掘工具简单分析附使用介绍
Vuzzer 是由计算机科学机构 Vrije Universiteit Amsterdam.Amsterdam Department of Informatics 以及 International ...
- (转) exp1-1:// 一次有趣的XSS漏洞挖掘分析(1)
from http://www.cnblogs.com/hookjoy/p/3503786.html 一次有趣的XSS漏洞挖掘分析(1) 最近认识了个新朋友,天天找我搞XSS.搞了三天,感觉这一套 ...
- 路由器逆向分析------Running Debian MIPS Linux in QEMU
本文博客地址:http://blog.csdn.net/qq1084283172/article/details/70176583 下面的文章内容主要参考英文博客<Running Debian ...
- 路由器逆向分析------在QEMU MIPS虚拟机上运行MIPS程序(ssh方式)
本文博客地址:http://blog.csdn.net/qq1084283172/article/details/69652258 在QEMU MIPS虚拟机上运行MIPS程序--SSH方式 有关在u ...
- 路由器逆向分析------MIPS系统网络的配置(QEMU)
本文博客地址:http://blog.csdn.net/qq1084283172/article/details/69378333 MIPS系统网络的配置 使用QEMU 模拟正在运行的MIPS系统并 ...
- 关于PHP代码审计和漏洞挖掘的一点思考
这里对PHP的代码审计和漏洞挖掘的思路做一下总结,都是个人观点,有不对的地方请多多指出. PHP的漏洞有很大一部分是来自于程序员本身的经验不足,当然和服务器的配置有关,但那属于系统安全范畴了,我不太懂 ...
- 小白日记36:kali渗透测试之Web渗透-手动漏洞挖掘(二)-突破身份认证,操作系统任意命令执行漏洞
手动漏洞挖掘 ###################################################################################### 手动漏洞挖掘 ...
- SG Input 软件安全分析之逆向分析
前言 通过本文介绍怎么对一个 windows 程序进行安全分析.分析的软件版本为 2018-10-9 , 所有相关文件的链接 链接:https://pan.baidu.com/s/1l6BuuL-HP ...
- [Android Security] Smali和逆向分析
copy : https://blog.csdn.net/u012573920/article/details/44034397 1.Smali简介 Smali是Dalvik的寄存器语言,它与Java ...
随机推荐
- 【赵渝强老师】MongoDB中的索引(上)
索引是提高查询查询效率最有效的手段.索引是一种特殊的数据结构,索引以易于遍历的形式存储了数据的部分内容(如:一个特定的字段或一组字段值),索引会按一定规则对存储值进行排序,而且索引的存储位置在内存中, ...
- 【赵渝强老师】什么是Spark SQL?
一.Spark SQL简介 Spark SQL是Spark用来处理结构化数据的一个模块,它提供了一个编程抽象叫做DataFrame并且作为分布式SQL查询引擎的作用. 为什么要学习Spark SQL? ...
- CMake 属性之目标属性
[写在前面] CMake 可以通过属性来存储信息.它就像是一个变量,但它被附加到一些其他的实体上,像是一个目录或者是一个目标.例如一个全局的属性可以是一个有用的非缓存的全局变量. 在 CMake 的众 ...
- 2. 说一下vue2和vue3的区别 ?
1. vue3 使用 proxy 替换Object.defineProperty 实现数据响应式 ,所以vue3 的性能得到了提升 : 2. vue3 使用组合式 API 替代了 vue2 中的选项式 ...
- 阿里云服务器安装mysql镜像
新创建的服务器首先需要创建安全组,开放端口然后重启服务器 登陆远程服务器,具体操作步骤如下 #拉取镜像 docker pull mysql:5.7 #查看镜像是否拉取到 docker images # ...
- Nginx UI:全新的 Nginx 在线管理平台
前言 Nginx在程序部署中扮演着至关重要的角色,其高性能.高安全性.易于配置和管理的特点,使得它成为现代Web应用部署中不可或缺的一部分.今天大姚给大家分享一款实用的 Nginx Web UI 工具 ...
- docker对的tomcat、mysql、redis、nginx的安装
本章篇章主要讲解了docker对常用软件的安装说明 总体步骤:搜索镜像.拉取镜像.查看镜像.启动镜像.停止容器.移除容器 tomcat docker seacher tomcat//也可以在docke ...
- 通过自定义字符串内插处理程序(InterpolatedStringHandler)和CallerArgumentExpression特性来实现一个好玩的场景
背景知识介绍 什么是自定义字符串内插处理程序? 简单来讲就是自定义一个高性能的字符串拼接程序 通过 $"{a}{b}"的方式. 什么是CallerArgumentExpressio ...
- 云原生周刊:K8s 上的 gRPC 名称解析和负载平衡
开源项目推荐 Kraken Kraken 是一个基于 P2P 的 Docker 注册表,专注于可扩展性和可用性.它专为混合云环境中的 Docker 镜像管理.复制和分发而设计.借助可插拔的后端支持,K ...
- KubeKey 离线部署 KubeSphere v3.4.1 和 K8s v1.26 实战指南
作者:运维有术 前言 知识点 定级:入门级 了解清单 (manifest) 和制品 (artifact) 的概念 掌握 manifest 清单的编写方法 根据 manifest 清单制作 artifa ...