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 中的所有函数。