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 ...
随机推荐
- SpringMVC —— 请求参数(传递json数据)
接收请求中的json数据 注解 json格式(POJO) json数组(POJO) @RequestBody与@RequestParam区别
- 修改Kubernetes主节点(控制节点)名称
1.修改物理机主机名 hostnamectl set-hostname <hostname> 2.修改 /etc/kubernetes/manifests 目录下的文件,将文件内容包含旧主 ...
- Genuine Intel(R) CPU型号
起因: 在盘点固定资产的时候,发现有一台电脑CPU不显示具体型号,而是 英特尔 @ 2.60GHz (X2) ,通过主板型号来判断是至强系列的CPU,后经软件识别为 Genuine ,然后去查资料才了 ...
- JS数据类型&类型转换
基本数据类型 JS中的数据类型由原始值和对象共同组成,原始值一共有七种原始值: 数值(Number) 大整数(BigInt) 字符串(String) 布尔值(Boolean) 空值(Null) 未定义 ...
- USB协议详解第9讲(USB描述符-HID描述符)
1.HID设备概述 USB设备中有一大类就是HID设备,即Human Interface Devices,人机接口设备.这类设备包括鼠标.键盘.游戏手柄等,主要用于人与计算机进行交互.HID设备可以作 ...
- Lazy TLB Mode 的工作原理
Lazy TLB (Translation Lookaside Buffer) mode 是操作系统和处理器在管理虚拟内存时的一种优化技术,旨在提高处理器的性能.要理解 Lazy TLB mode,需 ...
- Trace32 simulator调试以及简单实用命令介绍
目录 Trace32 Simulator debug Trace32工具配置 Trace32命令简介 memory class 常见命令索引 v.v使用实例 不同CPU运行信息查看 Trace32 S ...
- Android复习(三)清单文件中的元素——>grant-uri-permission、instrumentation、intent-filter、manifest、meta-data
<grant-uri-permission> 语法: <grant-uri-permission android:path="string" android:pa ...
- Android复习(二)应用资源——>更多类型
更多资源类型 本页面定义了更多类型的可具体化的资源,包括: Bool 带有布尔值的 XML 资源. 颜色 带有颜色值(十六进制颜色)的 XML 资源. 尺寸 带有尺寸值(包含度量单位)的 XML 资源 ...
- SaaS架构:开放平台架构设计
大家好,我是汤师爷~ 今天聊聊开放平台架构设计. 为什么需要搭建开放平台 增强产品能力 开放平台能够让三方开发者和合作伙伴开发新的应用或服务,增加原有SaaS产品能力.这样就可以满足更多用户需求,从而 ...