安全矩阵

 找回密码
 立即注册
搜索
查看: 4189|回复: 0

内存Fuzz与WINAFL实战

[复制链接]

114

主题

158

帖子

640

积分

高级会员

Rank: 4

积分
640
发表于 2020-9-25 12:41:17 | 显示全部楼层 |阅读模式
内存Fuzz与WINAFL实战

原创 hackedbylh
来自于公众号:OPPO安全应急响应中心
原文链接:https://mp.weixin.qq.com/s?__biz=MzUyNzc4Mzk3MQ==&mid=2247486314&idx=1&sn=b565ecae04440df88ff009992bd9a53a&chksm=fa7b0a26cd0c83309f1a997547c8d7bcba7364950bd557a901b9ceb807460bb8204f86336110&mpshare=1&scene=23&srcid=092416uFPtks7rzHRhplOnHI&sharer_sharetime=1600944766456&sharer_shareid=ff83fe2fe7db7fcd8a1fcbc183d841c4#rd

概述
本节以FoxitReader和IrfanView为例介绍内存 Fuzz的实现以及 WINAFL 的常规使用技巧。


FoxitReader
软件分析
目前Fuzz大型软件的常用方式是对大型软件分析,找到软件中的负责数据处理的模块,然后编写一个Loader把模块加载起来后进行测试。本节以FoxitReader为例介绍如何分析软件并进行内存Fuzz以及用WinAFL来Fuzz程序。

FoxitReader是一款PDF工具,可以查看、创建和修改PDF文件,它还可以通过图片来创建PDF文件,使用图片创建PDF时,FoxitReader会对图片进行解析然后再去创建PDF文件,由于图片文件的格式复杂多样,软件在解析图片时就有可能会产生漏洞,下面就去看看如何Fuzz FoxitReader中负责图片解析的模块。

首先我们需要定位当通过图片创建PDF时,处理图片的逻辑在哪个模块,后面才能针对性地去Fuzz。Process Monitor可以监控程序执行过程的注册表、文件和网络操作,对于一些关键的API还会记录调用栈。为了定位图片处理所在的模块,首先打开Process Monitor监控事件,然后用图片来创建一个PDF文件,PDF创建好后让Process Monitor停止监控事件,下面就可以去分析日志了。使用过滤功能Process Monitor只显示对图片文件的操作


查看监控API的调用栈可以发现读取文件的模块是 ConvertToPDF_x86.dll,看文件名也感觉是进行PDF转换任务的。



下面分析一下程序是如何使用这个库的,首先用IDA分析这个DLL,先看看DLL的导出函数,因为其他模块使用DLL的功能,肯定会使用DLL的导出函数。


可以看到ConvertToPDF_x86.dll只有两个导出函数,下面用windbg给导出函数下个断点,看看这两个函数的调用顺序。首先打开程序,然后用 windbg 附加进程,用sxe命令设置模块加载断点,当ConvertToPDF_x86模块加载起来时调试器会断下来
  1. sxe ld:ConvertToPDF_x86
复制代码

断下来后我们给模块的所有导出函数下断点
  1. bm /a ConvertToPDF_x86!*
复制代码

继续运行后,程序首先会断在CreateFXPDFConvertor函数
  1. 0:000> bm /a ConvertToPDF_x86!*
  2. 1: 00000000`6d798e30 @!"ConvertToPDF_x86!DestorFXPDFConvertor"
  3. 2: 00000000`6d79aaf0 @!"ConvertToPDF_x86!CreateFXPDFConvertor"
  4. 0:000> g
  5. Breakpoint 2 hit
  6. ConvertToPDF_x86!CreateFXPDFConvertor:
  7. 6d79aaf0 a10cd1c36d      mov     eax,dword ptr [ConvertToPDF_x86!CreateFXPDFConvertor+0x4a261c (6dc3d10c)] ds:002b:6dc3d10c=00000000
复制代码


拿IDA分析一下这个函数

  1. _DWORD *CreateFXPDFConvertor()
  2. {
  3. _DWORD *result; // eax
  4. void *obj; // eax

  5. result = dword_104AD10C;
  6. if ( dword_104AD10C )
  7. return result;                              // 调用malloc分配内存
  8. obj = alloc(0x1BDCu);
  9. if ( obj )
  10. {
  11. result = init_obj(obj);                     // 初始化对象
  12. dword_104AD10C = result;
  13. }
  14. else
  15. {
  16. result = 0;
  17. dword_104AD10C = 0;
  18. }
  19. return result;
  20. }
复制代码


这个函数比较简单首先会用 malloc 分配0x1BDC的内存,然后会调用init_obj初始化分配的内存块,init_obj函数会先设置虚表,然后设置对象里的其他一些字段。
  1. _DWORD *__thiscall init_obj(int this)
  2. {
  3. int v1; // esi

  4.   v1 = this;
  5.   *this = &CFX_PDFConvertor::`vftable'; // 给对象设置虚表
  6.   sub_1000A720((this + 4));
  7.   *(v1 + 7104) = 0;
  8.   *(v1 + 7108) = 0;
  9.   *(v1 + 7112) = 0;
  10.   *(v1 + 7120) = 0;
  11.   *(v1 + 7116) = 1;
  12.   dword_104AD104 = 0;
  13.   *(v1 + 7124) = 0;
  14.   *(v1 + 7128) = 0;
  15. return v1;
  16. }
复制代码


虚表的结构如下

  1. .rdata:10336F1C ; const CFX_PDFConvertor::`vftable'
  2. .rdata:10336F1C ??_7CFX_PDFConvertor@@6B@ dd offset sub_1000B060
  3. .rdata:10336F1C                                         ; DATA XREF: init_obj+6↑o
  4. .rdata:10336F1C                                         ; sub_1000A8F0+BA↑o
  5. .rdata:10336F20                 dd offset sub_10009B40
  6. .rdata:10336F24                 dd offset sub_1000A2D0
  7. .rdata:10336F28                 dd offset sub_1000A8F0
复制代码


虚表里面有4个函数,按照正常的程序逻辑,程序在创建模块对象后,肯定会调用对象的函数来使用模块提供的功能,接下来给这4个函数下断点,看看这些函数的调用关系以及参数信息。windbg的打印信息如下
  1. 0:000:x86> lm m Conver*start             end                 module name6d790000 6dcb5000   ConvertToPDF_x86   (export symbols)       C:\Program Files (x86)\Foxit Software\Foxit Reader\Plugins\Creator\x86\ConvertToPDF_x86.dll0:000:x86> dd 0x6dac6f1c l4      // 查看虚表的函数的实际地址6dac6f1c  6d79b060 6d799b40 6d79a2d0 6d79a8f00:000:x86> bp 6d79b060 0:000:x86> bp 6d799b40 0:000:x86> bp 6d79a2d0 0:000:x86> bp 6d79a8f00:000:x86> bl0 e x86 6d79b060     0001 (0001)  0:**** ConvertToPDF_x86!CreateFXPDFConvertor+0x5701 e x86 6d798e30     0001 (0001)  0:**** ConvertToPDF_x86!DestorFXPDFConvertor2 e x86 6d79aaf0     0001 (0001)  0:**** ConvertToPDF_x86!CreateFXPDFConvertor3 e x86 6d799b40     0001 (0001)  0:**** ConvertToPDF_x86!DestorFXPDFConvertor+0xd104 e x86 6d79a2d0     0001 (0001)  0:**** ConvertToPDF_x86!DestorFXPDFConvertor+0x14a05 e x86 6d79a8f0     0001 (0001)  0:**** ConvertToPDF_x86!DestorFXPDFConvertor+0x1ac00:000:x86> gBreakpoint 3 hitConvertToPDF_x86!DestorFXPDFConvertor+0xd10:6d799b40 55              push    ebp        0:000:x86> r    // 首先调用6d799b40函数eax=6d799b40 ebx=0ad6a1a8 ecx=0ad6a1a8 edx=6dac6f1c esi=0088fb68 edi=00000002eip=6d799b40 esp=002cce0c ebp=002cece8 iopl=0         nv up ei pl zr na pe nccs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246ConvertToPDF_x86!DestorFXPDFConvertor+0xd10:6d799b40 55              push    ebp0:000:x86> dps esp l4  # 参数为 2002cce0c  02398a54 FoxitReader!CryptUIWizExport+0x885514002cce10  00000002002cce14  711d4f33002cce18  02c2f814 FoxitReader!CryptUIWizExport+0x111c2d40:000:x86> gBreakpoint 4 hitConvertToPDF_x86!DestorFXPDFConvertor+0x14a0:6d79a2d0 55              push    ebp0:000:x86> r  // 然后调用6d79a2d0eax=002cec10 ebx=0ad6a10b ecx=0ad6a1a8 edx=6d79a2d0 esi=0ad6a1a8 edi=00000000eip=6d79a2d0 esp=002cce0c ebp=002cece8 iopl=0         nv up ei pl zr na pe nccs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246ConvertToPDF_x86!DestorFXPDFConvertor+0x14a0:6d79a2d0 55              push    ebp0:000:x86> dps esp l6002cce0c  02398c22 FoxitReader!CryptUIWizExport+0x8856e2002cce10  002cec10002cce14  711d4f33002cce18  02c2f814 FoxitReader!CryptUIWizExport+0x111c2d4002cce1c  05d8149c002cce20  0088f9c00:000:x86> du 002cec10  # 参数是一个unicode字符串002cec10  "Foxit Reader PDF Printer"0:000:x86> gBreakpoint 0 hitConvertToPDF_x86!CreateFXPDFConvertor+0x570:6d79b060 55              push    ebp0:000:x86> r    # 调用 6d79b060eax=6dac6f1c ebx=0ad6a10b ecx=0ad6a1a8 edx=6d79b060 esi=0ad6a1a8 edi=00000000eip=6d79b060 esp=002cce04 ebp=002cece8 iopl=0         nv up ei pl zr na pe nccs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246ConvertToPDF_x86!CreateFXPDFConvertor+0x570:6d79b060 55              push    ebp0:000:x86> dps esp l6002cce04  02398c3f FoxitReader!CryptUIWizExport+0x8856ff002cce08  002cce50002cce0c  00000000002cce10  00000000002cce14  711d4f33002cce18  02c2f814 FoxitReader!CryptUIWizExport+0x111c2d40:000:x86> du 002cce50     # 参数开头是被处理图片的地址002cce50  "C:\Users\XinSai\Desktop\honeyvie"002cce90  "w\5mb.jpg"0:000:x86> gBreakpoint 1 hitConvertToPDF_x86!DestorFXPDFConvertor:6d798e30 55              push    ebp0:000:x86> r   # 调用DestorFXPDFConvertor销毁创建的对象eax=00000000 ebx=0ad6a10b ecx=2156cf22 edx=0ad65078 esi=0ad6a1a8 edi=00000000eip=6d798e30 esp=002cce0c ebp=002cece8 iopl=0         nv up ei pl zr na pe nccs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246ConvertToPDF_x86!DestorFXPDFConvertor:6d798e30 55              push    ebp0:000:x86> gBreakpoint 5 hitConvertToPDF_x86!DestorFXPDFConvertor+0x1ac0:6d79a8f0 a110d1c36d      mov     eax,dword ptr [ConvertToPDF_x86!CreateFXPDFConvertor+0x4a2620 (6dc3d110)] ds:002b:6dc3d110=0ad68e70
复制代码


通过调试的信息以及IDA识别出来的参数个数,可以看到对象虚表中的4个函数调用的顺序和参数信息大概如下

  1. sub_10009B4       参数为 this , 2
  2. sub_1000A2D0      参数为 "Foxit Reader PDF Printer" , unicode字符串
  3. sub_1000B060      参数为 this, 输入文件的全路径, 0, 0
  4. DestorFXPDFConvertor  函数会调用sub_1000A8F0去销毁对象
复制代码


可以看到sub_1000B060函数的功能应该就是读取图片文件并转换为PDF文件,下面写一个简单的c程序把DLL加载起来然后根据调用关系和参数信息调用相应的API

  1. int main()
  2. {

  3.   HMODULE hMod = LoadLibrary(_T("C:\\Program Files (x86)\\Foxit Software\\Foxit Reader\\plugins\\Creator\\x86\\ConvertToPDF_x86.dll"));
  4. if (hMod) {
  5.     pCreateFXPDFConvertor = (CreateFXPDFConvertor)GetProcAddress(hMod, "CreateFXPDFConvertor");
  6.     pDestorFXPDFConvertor = (DestorFXPDFConvertor)GetProcAddress(hMod, "DestorFXPDFConvertor");

  7. printf("CreateFXPDFConvertor:%p\n", pCreateFXPDFConvertor);

  8. char* obj = pCreateFXPDFConvertor();
  9. printf("create convertor:%p\n", obj);

  10. char* mod_base = (char*)hMod;
  11.     vtable* vtb = (vtable*)(mod_base + 0x336f1c); // get vtable offset

  12. printf("module base:%p\n", mod_base);
  13. printf("vtb:%p\n", vtb);

  14.     vtb->p_sub_10009B40(obj, 2);
  15.     vtb->p_sub_1000A2D0(_T("Foxit Reader PDF Printer"));
  16.     vtb->p_sub_1000B060(obj, (char*)_T("C:\\Users\\XinSai\\Desktop\\honeyview\\5mb.jpg"),0,0);
  17.     pDestorFXPDFConvertor(obj);
  18.   }
  19. return 0;
  20. }
复制代码


编译执行发现我们的程序crash了,拿windbg调试发现崩溃的位置如下

  1. 0:000:x86> lm m Conver*
  2. start             end                 module name
  3. 6d790000 6dcb5000   ConvertToPDF_x86   (export symbols)       C:\Program Files (x86)\Foxit Software\Foxit Reader\plugins\Creator\x86\ConvertToPDF_x86.dll
  4. 0:000:x86> r
  5. eax=00000000 ebx=00000000 ecx=00750070 edx=00000000 esi=0027ec60 edi=00278360
  6. eip=6d79995e esp=0022fa10 ebp=0022fa1c iopl=0         nv up ei pl zr na pe nc
  7. cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010246
  8. ConvertToPDF_x86!DestorFXPDFConvertor+0xb2e:
  9. 6d79995e 8b7910          mov     edi,dword ptr [ecx+10h] ds:002b:00750080=????????
  10. 0:000:x86> kb 4
  11. ChildEBP RetAddr  Args to Child              
  12. WARNING: Stack unwind information not available. Following frames may be wrong.
  13. 0022fa1c 6d79ac6b 00750070 00000000 ffffffff ConvertToPDF_x86!DestorFXPDFConvertor+0xb2e
  14. 0022fa4c 6d79aed5 0027ec60 002776ec 002776e8 ConvertToPDF_x86!CreateFXPDFConvertor+0x17b
  15. 0022fa68 6d79b0e6 00000000 a9d0a4e7 0022fbb4 # 在sub_1000B060里面
  16. 0022faa4 013b67b6 00000000 00000000 00000000 ConvertToPDF_x86!CreateFXPDFConvertor+0x5f6
复制代码


在IDA中分析发生crash的位置

  1. char *__thiscall crash_func(_DWORD *this, int invaild_address, unsigned int a3, unsigned int a4)
  2. {

  3.   obj = this;
  4.   path_ = invaild_address;
  5.   v6 = *(invaild_address + 16);// 解引 invaild_address+16 时异常
复制代码


invaild_address是上层传下来的是一个不合法的指针,程序解引时发生了crash。继续往上追踪该函数的调用者
  1. void __thiscall sub_1000ABA0(unsigned int *this, unsigned int a2)
  2. {
  3.   .....................
  4.   .....................
  5.   .....................
  6. if ( v8 )
  7.     {
  8.       v8[5] = 7;
  9.       v8[4] = 0;
  10.       *v8 = 0;
  11.       crash_func(v8, a2, 0, 0xFFFFFFFF); // 调用发送crash的函数
复制代码


可以看到这里直接把该函数的第二个参数传给了crash_func,继续向上跟

  1. char *__thiscall sub_1000AC90(char *this, int path_info)
  2. {
  3.   .....................
  4.   .....................
  5.   .....................
  6.   p = *(path_info + 3188);
  7. if ( (*(path_info + 3192) - p) / 28 )
  8.   {
  9.     idx = 0;
  10. do
  11.     {
  12.       sub_1000ABA0(v3 + 797, p + idx);          // 第二个参数为一个非法指针
  13.       p = v2[797];
  14.       ++v29;
  15.       idx += 28;
  16.     }
  17. while ( v29 < (v2[798] - p) / 28 );
  18.   }
复制代码


可以看到这里从sub_1000AC90的第二参数的偏移3188处取了一个指针,然后传入了sub_1000ABA0继续再往上追踪,发现sub_1000AC90的第二个参数其实就是sub_1000B060的第二个参数。

  1. signed int __thiscall sub_1000B060(const WCHAR *this, int path_info, int a3, char *a4)
  2. {

  3.   path_info_ = path_info;
  4.   ...........
  5.   ...........
  6.   sub_1000AC90(this + 4, path_info_);
复制代码


path_info是我们传入的,我们现在是给它直接传了一个unicode字符串的指针。
  1. vtb->p_sub_1000B060(obj, (char*)_T("C:\\Users\\XinSai\\Desktop\\honeyview\\5mb.jpg"),0,0);
复制代码


通过分析sub_1000AC90里面对path_info的处理,可以看到path_info这个参数应该是一个结构体

  1. *(v3 + 936) = *(path_info + 3744); // 取结构体的字段
  2. *(v3 + 937) = *(path_info + 3748);
  3. *(v3 + 938) = *(path_info + 3752);
  4. *(v3 + 939) = *(path_info + 3756);
  5. *(v3 + 940) = *(path_info + 3760);
  6. *(v3 + 934) = *(path_info + 3736);
  7.   p = *(path_info + 3188);
  8.   if ( (*(path_info + 3192) - p) / 28 )
  9.   {
  10.     idx = 0;
  11.     do
  12.     {
  13.       sub_1000ABA0(v3 + 797, p + idx);          // 第二个参数为一个非法指针
  14.       p = v2[797];
  15.       ++v29;
  16.       idx += 28;
  17.     }
  18.     while ( v29 < (v2[798] - p) / 28 );
  19.   }
复制代码


下面我们去看看实际FoxitReader在使用该DLL时传进来的结构体里面的数据是怎么样的

  1. 0:000:x86> r
  2. eax=003ece88 ebx=00000000 ecx=0a55a1ac edx=6d79b060 esi=0a55a1a8 edi=0a55a1ac
  3. eip=6d79ac90 esp=003ece00 ebp=003ece38 iopl=0         nv up ei pl zr na pe nc
  4. cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
  5. ConvertToPDF_x86!CreateFXPDFConvertor+0x1a0:
  6. 6d79ac90 55              push    ebp
  7. 0:000:x86> dd esp l2
  8. 003ece00  6d79b0e6 003ece88
  9. 0:000:x86> db 003ece88
  10. 003ece88  43 00 3a 00 5c 00 55 00-73 00 65 00 72 00 73 00  C.:.\.U.s.e.r.s.
  11. 003ece98  5c 00 58 00 69 00 6e 00-53 00 61 00 69 00 5c 00  \.X.i.n.S.a.i.\.
  12. 003ecea8  44 00 65 00 73 00 6b 00-74 00 6f 00 70 00 5c 00  D.e.s.k.t.o.p.\.
  13. 003eceb8  68 00 6f 00 6e 00 65 00-79 00 76 00 69 00 65 00  h.o.n.e.y.v.i.e.
  14. 003ecec8  77 00 5c 00 35 00 6d 00-62 00 2e 00 6a 00 70 00  w.\.5.m.b...j.p.
  15. 003eced8  67 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  g...............
  16. 003ecee8  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
  17. 003ecef8  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
  18. 0:000:x86> db 003ece88+0x208
  19. 003ed090  43 00 3a 00 5c 00 55 00-73 00 65 00 72 00 73 00  C.:.\.U.s.e.r.s.
  20. 003ed0a0  5c 00 58 00 69 00 6e 00-53 00 61 00 69 00 5c 00  \.X.i.n.S.a.i.\.
  21. 003ed0b0  41 00 70 00 70 00 44 00-61 00 74 00 61 00 5c 00  A.p.p.D.a.t.a.\.
  22. 003ed0c0  4c 00 6f 00 63 00 61 00-6c 00 5c 00 54 00 65 00  L.o.c.a.l.\.T.e.
  23. 003ed0d0  6d 00 70 00 5c 00 31 00-35 00 37 00 33 00 30 00  m.p.\.1.5.7.3.0.
  24. 003ed0e0  33 00 37 00 34 00 38 00-32 00 2e 00 70 00 64 00  3.7.4.8.2...p.d.
  25. 003ed0f0  66 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  f...............
  26. 003ed100  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
  27. 0:000:x86> db 003ece88+0x410
  28. 003ed298  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
  29. 003ed2a8  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
  30. 0:000:x86> db 003ece88+0x618
  31. 003ed4a0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
  32. 003ed4b0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
  33. 0:000:x86> db 003ece88+0x820
复制代码


通过dump内存可以发现在path_info开头存的是图片的路径,在path_info+0x208的位置存放的是生成的临时PDF文件的路径,其他的区域都是0,根据访问的区域来看path_info结构体的大致大小为0x10C8字节。我们可以往上跟一下看能不能找到path_info实际分配的内存大小

  1. 0:000:x86> lm m Convert*
  2. start             end                 module name
  3. 6d790000 6dcb5000   ConvertToPDF_x86   (export symbols)       C:\Program Files (x86)\Foxit Software\Foxit Reader\Plugins\Creator\x86\ConvertToPDF_x86.dll
  4. 0:000:x86> kb 4
  5. ChildEBP RetAddr  Args to Child              
  6. WARNING: Stack unwind information not available. Following frames may be wrong.
  7. 003ecdfc 6d79b0e6 003ece88 c6266dd3 00000000 ConvertToPDF_x86!CreateFXPDFConvertor+0x1a0
  8. 003ece38 02398c3f 00000000 00000000 00000000 ConvertToPDF_x86!CreateFXPDFConvertor+0x5f6
  9. 003eed20 02396b76 082cad3c 08351664 00000000 FoxitReader!CryptUIWizExport+0x8856ff
  10. 003eef74 023997e0 00adf9c0 082cad30 083087c0 FoxitReader!CryptUIWizExport+0x883636
  11. 0:000:x86> lm m FoxitReader
  12. start             end                 module name
  13. 00f00000 03cd3000   FoxitReader   (export symbols)       C:\Program Files (x86)\Foxit Software\Foxit Reader\FoxitReader.exe
复制代码


可以发现path_info是从FoxitReader的0x1498c3f处传进来的,跟过去看看。

  1. int __thiscall sub_1898690(_DWORD *this, int a2, int a3, int a4, int a5)
  2. {
  3. char path_info[520]; // [esp+3Ch] [ebp-1E98h]
  4. char v83[2608]; // [esp+244h] [ebp-1C90h]
  5. int v84; // [esp+C74h] [ebp-1260h]
  6. int v85; // [esp+F00h] [ebp-FD4h]
  7. char v86[404]; // [esp+1A64h] [ebp-470h]
  8. char v87; // [esp+1BF8h] [ebp-2DCh]
  9. void *v88; // [esp+1DE0h] [ebp-F4h]
  10. int v89; // [esp+1DF0h] [ebp-E4h]
  11. unsigned int v90; // [esp+1DF4h] [ebp-E0h]
  12.   __int16 v91[100]; // [esp+1DFCh] [ebp-D8h]
  13. int v92; // [esp+1ED0h] [ebp-4h]

  14.       v81 = ((*v81)[2])(v81, v91);
  15. if ( !v81 )
  16.         v81 = (**v65)(v65, path_info, 0, 0); // 调用sub_1000B060
复制代码


可以看到path_info是栈变量,查看IDA的栈布局可以大概估算path_info的大小

  1. 00001E98 path_info       db 520 dup(?)
  2. -00001C90 var_1C90        db 2608 dup(?)
  3. -00001260 var_1260        dd ?
  4. -0000125C                 db ? ; undefined
  5. -0000125B                 db ? ; undefined
复制代码


根据栈帧布局,可以知道 path_info最大为 0x1c90字节,这里我们分配0x2000字节给path_info然后设置好输入和输出路径到path_info。

  1. int main()
  2. {

  3.   HMODULE hMod = LoadLibrary(_T("C:\\Program Files (x86)\\Foxit Software\\Foxit Reader\\plugins\\Creator\\x86\\ConvertToPDF_x86.dll"));
  4. if (hMod) {
  5.     pCreateFXPDFConvertor = (CreateFXPDFConvertor)GetProcAddress(hMod, "CreateFXPDFConvertor");
  6.     pDestorFXPDFConvertor = (DestorFXPDFConvertor)GetProcAddress(hMod, "DestorFXPDFConvertor");

  7. printf("CreateFXPDFConvertor:%p\n", pCreateFXPDFConvertor);

  8. char* obj = pCreateFXPDFConvertor();
  9. printf("create convertor:%p\n", obj);

  10. char* mod_base = (char*)hMod;
  11.     vtable* vtb = (vtable*)(mod_base + 0x336f1c); // get vtable offset

  12. printf("module base:%p\n", mod_base);
  13. printf("vtb:%p\n", vtb);

  14.     vtb->p_sub_10009B40(obj, 2);
  15.     vtb->p_sub_1000A2D0(_T("Foxit Reader PDF Printer"));

  16. unsigned int info_size = 0x2000;
  17. char* path_info = (char*)malloc(info_size);
  18. memset(path_info, 0, info_size);

  19. wchar_t* input_image = _T("C:\\Users\\XinSai\\Desktop\\honeyview\\5mb.jpg");
  20. wchar_t* output_pdf = _T("C:\\tmp.pdf");

  21. // 设置输入图片的路径
  22.     wcscpy((wchar_t*)path_info, input_image);

  23. //设置输出图片的路径
  24.     wcscpy((wchar_t*)(path_info + 0x208), output_pdf);
  25. printf("path info:%p\n", path_info);

  26. int ret = vtb->p_sub_1000B060(obj, path_info,0,0);

  27. printf("sub_1000B060 return: %d\n", ret);

  28.     pDestorFXPDFConvertor(obj);
  29. free(path_info);
  30.   }
  31. return 0;
  32. }
复制代码


执行完后在C:\tmp.pdf生成创建好的图片,至此我们基本分析清楚ConvertToPDF_x86.dll的调用方式,目前可以通过C语言直接调用模块内的函数完成PDF的制作,下面分别介绍如何对ConvertToPDF模块进行内存Fuzz,以及如何用WinAFL去Fuzz该模块。


内存Fuzz

内存Fuzz的原理是在内存中不断的生成测试数据,然后调用目标函数来实现Fuzz,这里采用hook的方式来实现内存Fuzz,首先在 DllMain里面获取sub_1000B060函数的地址并hook该函数。

  1. class MyClass
  2. {
  3. public:
  4. int __thiscall my_sub_1000B060(char* path_info, int a, int b) {
  5. char* input_image = "c:\\fuzz.jpg";
  6.     wcscpy((wchar_t*)path_info, char_to_wchar(input_image));


  7. char* init_file = "c:\\init.jpg";
  8.     INIT_SEED.buffer = read_file(init_file, &INIT_SEED.length);

  9. int ret = 0;
  10. while (true)
  11.     {
  12.       fuzz(input_image); // 变异初始用例,并写入input_image
  13.       ret = raw_sub_1000B060((char*)this, path_info, a, b); // 调用原始的目标函数
  14.       debugger_printf("fuzzed function return:%d\n", ret);
  15.     }
  16.   }

  17. private:

  18. };

  19. int __stdcall DllMain(HINSTANCE hinstDLL, DWORD  fdwReason, LPVOID lpReserved)
  20. {
  21. char* base = NULL;  
  22. unsigned long hook_func_addr = NULL;

  23. switch (fdwReason)
  24.   {
  25. case DLL_PROCESS_ATTACH://加载时候
  26.     base = (char*)GetModuleHandle(_T("ConvertToPDF_x86.dll"));

  27.     debugger_printf("base:%p\n", base);

  28.     raw_sub_1000B060 = (sub_1000B060)(base + 0xB060);

  29. char buf[0x20];
  30. sprintf(buf, "%u", &MyClass::my_sub_1000B060);
  31.     hook_func_addr = atoll(buf);

  32. if (Mhook_SetHook((PVOID*)&raw_sub_1000B060, (void *)hook_func_addr)) {
  33.       debugger_printf("base: %p, func:%p", base, hook_func_addr);
  34.     }

  35. break;
  36. default:
  37. break;
  38.   }
  39. return TRUE;
  40. }
复制代码


由于sub_1000B060的调用约定是thiscall,所以代码里面实现了一个类函数用来作为hook函数,并且使用了一个小技巧来获取函数的地址

  1. char buf[0x20];
  2. sprintf(buf, "%u", &MyClass::my_sub_1000B060);
  3.     hook_func_addr = atoll(buf);
复制代码


设置好hook后,在my_sub_1000B060函数里面首先读取初始用例到内存,然后不断地对初始数据变异,最后把数据写入图片文件,然后调用目标函数去处理数据。

代码编译好后会生成mhook-test.dll,然后打开FoxitReader,用图片创建一个PDF,这样ConvertToPDF_x86.dll就会被加载到内存。此时使用用DLL注入工具把mhook-test.dll注入到FoxitReader进程。


当mhook-test.dll被注入到进程后就会执行DllMain,这样就可以把目标函数hook住,然后再次用图片创建一个PDF,触发目标函数的调用,这时程序会进入hook函数my_sub_1000B060,在my_sub_1000B060里面开始不断变异数据,Fuzz目标函数。

启动FoxitReader后建议用windbg附加进程,mhook-test.dll会使用调试器的输出接口打印一些调试日志,同时使用调试器还可以在程序发生异常时断下来辅助调试,运行截图如下


发送crash时的截图


WinAFL

WinAFL是AFL的Windows移植版,WinAFL使用dynamorio来获取程序执行的覆盖率,为了提升测试速度,WinAFL实现了一种类似于内存Fuzz的机制,示意图如下

WinAFL会使用dynamorio来hook目标函数,当执行到被测函数时,首先执行pre_fuzz_handler,该函数会完成和AFL的通信并且会把统计覆盖率的共享内存初始化,最后退出函数继续往下会执行目标函数,执行完后会进入post_fuzz_handler,这里面会再次跳转到pre_fuzz_handler等待下次Fuzz。由于WinAFL的实现机制,为了能高效的Fuzz,WinAFL建议目标函数要满足以下要求:

  • 首先目标函数会打开一个文件读取数据,处理数据,最后会把文件关闭。
  • 目标函数不会调用exit之类会结束进程的函数。


为了能够用WinAFL来测试 ConvertToPDF_x86.dll,我们需要把之前写的dllloader改造以下便于WinAFL测试。测试dllloader的主要代码如下
  1. void fuzz_func(char* path) {
  2.   vtb->p_sub_10009B40(obj, 2);
  3.   vtb->p_sub_1000A2D0(_T("Foxit Reader PDF Printer"));
  4. memset(path_info, 0, 0x2000);

  5. wchar_t* input_image = _T("C:\\Users\\XinSai\\Desktop\\honeyview\\5mb.jpg");
  6. wchar_t* output_pdf = _T("C:\\tmp.pdf");

  7.   wcscpy((wchar_t*)path_info, char_to_wchar(path));
  8.   wcscpy((wchar_t*)(path_info + 0x208), output_pdf);
  9. int ret = vtb->p_sub_1000B060(obj, path_info, 0, 0);
  10. printf("sub_1000B060 return: %d\n", ret);
  11. }

  12. int main(int argc, char** argv)
  13. {
  14. if (argc < 2) {
  15. printf("fuzz.exe path\n");
  16. return 1;
  17.   }

  18. //加载dll到内存
  19.   HMODULE hMod = LoadLibrary(_T("C:\\Program Files (x86)\\Foxit Software\\Foxit Reader\\plugins\\Creator\\x86\\ConvertToPDF_x86.dll"));
  20. if (hMod) {
  21.     pCreateFXPDFConvertor = (CreateFXPDFConvertor)GetProcAddress(hMod, "CreateFXPDFConvertor");
  22.     pDestorFXPDFConvertor = (DestorFXPDFConvertor)GetProcAddress(hMod, "DestorFXPDFConvertor");

  23. printf("CreateFXPDFConvertor:%p\n", pCreateFXPDFConvertor);

  24. // 创建好对象
  25.     obj = pCreateFXPDFConvertor();
  26. printf("create convertor:%p\n", obj);

  27. //初始化全局虚表指针到DLL的虚表
  28. char* mod_base = (char*)hMod;
  29.     vtb = (vtable*)(mod_base + 0x336f1c); // get vtable offset
  30. printf("module base:%p\n", mod_base);
  31. printf("vtb:%p\n", vtb);

  32. //调用函数处理数据
  33.     fuzz_func(argv[1]);

  34. //释放分配的对象
  35.     pDestorFXPDFConvertor(obj);

  36.   }
  37. return 0;
  38. }
复制代码


主要就是把一些全局初始化的操作放到fuzz_func外面执行,这样就可以减少重复的代码,提升测试的速度。在main函数里面首先把DLL加载起来,然后获取一些函数的地址和对象虚表指针并保持到全局变量里面,之后调用fuzz_func读取文件并处理图片数据。然后使用WinAFL开始Fuzzing,执行的命令如下
  1. afl-fuzz.exe -i in -o out -D C:\Users\XinSai\Desktop\winafl-master\DynamoRIO-Windows-7.91.18187-0\bin32 -t 20000 -- -coverage_module ConvertToPDF.dll -fuzz_iterations 50000 -target_module dllloader.exe -target_method fuzz_func -nargs 1 -- C:\Users\XinSai\Desktop\foxitfuzz\dllloader\Debug\dllloader.exe @@
复制代码


其中

  1. -D:指定dynamorio 的drrun.exe所在的路径
  2. -coverage_module:指定需要获取覆盖率的模块
  3. -target_method:目标函数的函数名,winafl会不断的执行该函数
  4. -nargs:target_method函数的参数个数
  5. -target_module:target_method所在的模块名
  6. -fuzz_iterations:指定target_method最多执行次数,达到次数后会重启程序
复制代码


建议在使用WinAFL之前可以用 winafl.dll 执行目标程序看看程序能不能正常在dynamorio下执行,以及获取一些调试信息,命令如下
  1. path\of\dynamorio\bin32\drrun.exe -c winafl.dll -debug -- C:\Users\XinSai\Desktop\foxitfuzz\dllloader\Debug\dllloader.exe c:\init.jpg
复制代码


执行完后会在当前目录生成一个.log文件,文件里面保存了程序加载的所有DLL。

  1. Module loaded, dllloader.exe
  2. ............................
  3. ............................
  4. Module loaded, ConvertToPDF.dll
复制代码


可以看到目标程序加载的DLL名字为ConvertToPDF.dll,而不是我们代码里写的ConvertToPDF_x86.dll。执行截图如下


IrfanView

IrfanView是一款图片浏览器,支持各种各样的图片格式,本节介绍如何使用WinAFL来测试IrfanView。首先我们用procmon监控IrfanView打开文件时的API调用记录,用IrfanView打开文件的命令行如下

  1. "C:\Program Files (x86)\IrfanView\i_view32.exe" C:\example_2000.dwg
复制代码


等程序执行完后,让procmon停止监控事件,然后我们去分析处理输入文件(example_2000.dwg)的函数调用,查看读取文件时函数的调用栈可以发现是在BabaCAD4Image.dll里面打开并解析DWG文件。


此时我们可以像上一小节一样通过编写一个dll loader的方式来Fuzz这个库,具体的方式是写一个loader把BabaCAD4Image.dll加载起来,然后模拟IrfanView的行为,调用库里面的函数来处理DWG文件,最后使用WinAFL来Fuzz写好的loader就可以了。

本节介绍一种新的Fuzzing方式,即直接用WinAFL去Fuzz目标程序而不需要去写Loader。我们知道WinAFL是通过不断执行target_method来实现Fuzzing,WinAFL对target_method的要求是这个函数首先会打开目标文件,然后处理文件数据,最后关闭文件。所以如果IrfanView中打开并处理文件的函数可以在内存中被反复调用的话,我们就可以直接复用IrfanView里面的代码来用WinAFL Fuzz目标函数。

通过查看procmon里面捕获到的文件操作,我们可以大概确定IrfanView处理DWG文件的流程大致是首先调用BabaCAD4Image.dll的ReadDWG函数,然后在ReadDWG函数里面会先调用CreateFile打开文件,然后接着调用了几次ReadFile读取文件内容,最后会用CloseFile关闭文件,可以看到ReadDWG函数符合WinAFL的目标函数的要求,那么我们可以设置WinAFL测试目标函数为ReadDWG,通过IDA可以知道ReadDWG所在的偏移为0x1C20,那么执行WinAFL的命令如下
  1. afl-fuzz.exe -i dwg -o dwgoutput -D C:\Users\XinSai\Desktop\winafl-master\DynamoRIO-Windows-7.91.18187-0\bin32 -t 20000 -- -coverage_module BabaCAD4Image.dll -fuzz_iterations 50000 -target_module BabaCAD4Image.dll -target_offset 0x1C20 -nargs 1 -- "C:\Program Files (x86)\IrfanView\i_view32.exe" @@
复制代码


其中
  1. -target_offset: 指定被测函数在模块中的偏移
复制代码


跑起来的状态图如下

可以看到WinAFL能够正常执行用例并且可以发现新路径。


参考


  1. https://www.apriorit.com/dev-blog/644-reverse-vulnerabilities-software-no-code-dynamic-fuzzing
  2. https://www.gosecure.net/blog/2019/07/30/fuzzing-closed-source-pdf-viewers
  3. http://hdwsec.fr/blog/20161208-foxit/
复制代码




回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-4-20 11:11 , Processed in 0.016539 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表