winAFL 对 Foxit Phantom 的测试实例

介绍

Foxit Phantom 是Foxit的pdf编辑版。编辑版功能较多,因而攻击面可能会比阅读版多一些。

观察之后,选定2个dll进行测试。

  • FoxitComponent.dll
  • ConvertToPDF_x86.dll

FoxitComponent.dll

这个Dll 是编辑版的新功能,OCR文字识别,是个付费功能。

它的作用就是把PDF文件,或者图片中的文字处理之后,能够进行编辑。

支持传入图片,它会先把图片转成pdf。然后方可进行编辑。

IDA 分析

首先看看导出函数有什么。

其中有个 ImageToPdf,可以尝试对这个函数进行 fuzz,编写Wrapper。

仔细分析处理过程之后(这里不赘述),得知调用 ImageToPdf 之前,会先调用 Init ,其次 ImageToPdf 的参数比较简单,就2个 wchar 型的字符串指针。

所以Wrapper写起来就非常简单,代码参考 ImageToPdf.cpp

接着用 winAFL 的以下命令分别进行debug测试,计算覆盖率,精简样本,fuzz。

1
2
3
4
5
6
7
8
9
10
11
# debug测试
drrun.exe -c winafl.dll -debug -coverage_module FoxitComponent.dll -target_module OCR.exe -target_method fuzz -fuzz_iterations 10 -nargs 2 -- OCR.exe PageTest.jpf

# 计算覆盖率
drrun.exe -t drcov -- OCR.exe test.png

# 精简样本
python winafl-cmin.py --working-dir G:\winAFL\winafl-master\b32\bin\Release -D G:\winAFL\DynamoRIO-Windows-8.0.0-1\bin32 -t 100000 -i G:\Fuzzing\test2 -o G:\Fuzzing\test2-minset -coverage_module FoxitComponent.dll -target_module OCR.exe -target_method fuzz -nargs 2 -- OCR.exe @@

# fuzz
afl-fuzz.exe -i G:\Fuzzing\minset -o G:\Fuzzing\results -D G:\winAFL\DynamoRIO-Windows-8.0.0-1\bin32 -t 20000 -- -coverage_module FoxitComponent.dll -fuzz_iterations 5000 -target_module OCR.exe -target_method fuzz -nargs 2 -- OCR.exe @@

目前跑了2天,看看结果。

exec speed 代表fuzz执行速度,它这里非常慢。可能遇到了一些特殊的样本,这个样本测试完后,速度就会提升到 500 左右。

uniq crashs 代表发现的 crash 样本,有3个。我都分析了一下其中2个是一样的,空指针引用问题。另一个不会导致崩溃,但是会导致生成很大的pdf文件。

ConvertToPDF_x86.dll

这个dll是 foxit 把图片,txt这样的文件转换为 pdf 的核心dll。我之前分析过。那个时候分析的是 Foxit Reader 的。这里测试的是 Foxit Phantom。稍稍有些许不同。

这个Dll应该被很多人跑过了。这里我想再试试。

IDA 分析

具体的分析参考 ConverToPdf.docx,其中的过程大同小异。当时的情况,我没有写出来wrapper。

现在主要讲 wrapper 怎么写。

Wrapper

该 dll 解析的核心函数是 sub_1003DA70,这个函数的参数非常多。

1
LPCWSTR __thiscall sub_1003DA70(_DWORD *this, _DWORD *a2, char a3, int a4, int a5, int a6, int a7, int a8, int a9, int a10, int a11, int a12, int a13)

但实际分析之后, a3 到 a13 都可以写为 0。关键是 a2 与 this 指针。

this 指针经过分析知道是来自 CreateFXPDFConvertor() 函数 (CreateFXPDFConvertor是个导出函数)的返回值。所以this指针构造不成问题。

剩下特殊的 a2 指针。

a2 指针指向一片大小在 0x1800 左右的内存空间。其中加 0xb12 的位置放着输入的文件路径,而且是 wchar 型的。另外,在加 0x104 的地方放着输出的图片路径。这个路径是 foxit 把图片转换完成之后,临时存放pdf的路径。不过,最关键的地方是 a2 开头的地址放这一个 函数指针

a2 指针的布局实际如下。

a2 中函数指针指向的是上层主进程 Foxit Phantom 中的函数。那这可不好构造。

所以我最开始打算置为 0 ,看是否会影响到这个函数的解析。

调试发现在某一处会调用到这个函数指针中的内容。

eax 就是这里的 函数指针,它会去调用指向 + 4 位置的函数。

这样的话,置0就不行。必须得复现出来。

复现 eax 函数指针

eax 的函数值来之主进程 FoxitPhantom.exe ,那就要把 FoxitPhantom.exe 当成Dll载入进来。

这里省略具体步骤,只说遇到的问题。

当调用到FoxitPhantom.exe中的函数的时候,由于 FoxitPhantom.exe 本身是个 exe ,它是被当成dll用 LoadLibrary 加载的,所以它不会像dll一样把自己初始化(exe 与 dll 入口都不一样)。所以,加载的 FoxitPhantom.exe 里面很多需要重定位的函数(即:从其他 dll 导入的函数),其地址都是错误的。

可以参考这篇文章:

https://www.codeproject.com/Articles/1045674/Load-EXE-as-DLL-Mission-Possible

这篇文章提供的办法并没有解决我的问题。因为他的exe是自己写的,可以重写代码改动,而我的exe是别人写的。

改 DLL

既然函数指针无法搞定,那我就去掉了调用这个指针的所有地方。

我把 dll 中这个位置的 call 给改了。后续发现并不影响解析过程(非常幸运)。这样一来,就可以直接把 eax 开头的内存地址设置为 0 就行了。

最后,这样的 wrapper 就能写了。代码参考 c2pdf.cpp

同样,使用命令进行精简样本调试等等操作。

1
2
3
4
5
6
7
8
9
10
11
# debug 调试
drrun.exe -c winafl.dll -debug -coverage_module ConvertToPDF.dll -target_module c2pdf.exe -target_method fuzz -fuzz_iterations 10 -nargs 2 -- c2pdf.exe test_lossless.j2k

# 覆盖率
drrun.exe -t drcov -- c2pdf.exe test.png

# 精简样本
python winafl-cmin.py --working-dir G:\winAFL\winafl-master\b32\bin\Release -D G:\winAFL\DynamoRIO-Windows-8.0.0-1\bin32 -t 100000 -i G:\Fuzzing\test2-minset -o G:\Fuzzing\test2-minset-result -coverage_module ConvertToPDF.dll -target_module c2pdf.exe -target_method fuzz -nargs 2 -- c2pdf.exe @@

# fuzz
afl-fuzz.exe -i G:\Fuzzing\test2-minset-result -o G:\Fuzzing\test2-Fuzzing-results -D G:\winAFL\DynamoRIO-Windows-8.0.0-1\bin32 -t 20000 -- -coverage_module ConvertToPDF.dll -fuzz_iterations 5000 -target_module c2pdf.exe -target_method fuzz -nargs 2 -- c2pdf.exe @@

这里有个坑点。就是 deubg 测试的时候,它加载的 dll 名字与原来的 ConvertToPDF_x86.dll 不同。它加载的是 ConvertToPDF.dll 。所以把 dll 改名为 ConvertToPDF.dll,不然会出错的。

另外精简样本的时候,用低版本的 DynamoRIO 跑不起来,用 DynamoRIO-Windows-8.0.0-1 就可以。

测试结果与改进

测试的速度不是很快。因为这个 dll 解析的时候会生成临时的 pdf 文件,并且会把内容写进去。这就与磁盘有太多的交互,导致速度起不来。

我最开始打算把创建文件的地方都改成创建失败,但是害怕该太多导致问题。就打算改动最小的地方。

改进的办法就是找到 WriteFile 的调用,把里面表示写入数据长度的参数: nNumberOfBytesToWrite ,直接改成 push 0 。

这样改动的情况最少,提高了fuzz速度。但是它走到某些路径的时候,执行速度依然很慢。看来还有待改进。

拓展 load exe AS dll

如果不修改Dll,就把它要用到的函数给它还原。同样会遇到问题。

当需要的指针被还原时,它调用会走到如下函数。

这是正常的调用流程,这里会去 call esi ,且 esi中的值是 Kernel32!FlsSetValue 。

FlsSetValue 函数涉及到线程存储了。而我这里是静态加载的EXE,然后通过偏移去取到的函数,

执行这个函数肯定会失败。

所以,还是不用还原这个指针。

还原上面的指针的时候,函数来自于FoxitPhantom.exe 。这是个EXE,用 LoadLibrary 的方法来加载,必然出错。EXE 不会像dll一样做初始化,所以导入表中的函数地址是错误的,所有用到重定位的地方也是错误的。

故,需要修复导入表和重定位的地方。好在这个exe有重定位表,但不是所有的exe都会有。

修复之后,可以通过基地址 + 偏移来取到 EXE 中的所有函数。