內存Fuzz和WinAFL


文章一開始發表在微信公眾號

https://mp.weixin.qq.com/s/XSPrmBb44J8BUpKsj0cwGQ

內存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只顯示對圖片文件的操作

image-20191101230417573

查看監控API的調用棧可以發現讀取文件的模塊是 ConvertToPDF_x86.dll,看文件名也感覺是進行PDF轉換任務的。

image-20191101230455352

下面分析一下程序是如何使用這個庫的,首先用IDA分析這個DLL,先看看DLL的導出函數,因為其他模塊使用DLL的功能,肯定會使用DLL的導出函數。

image-20191104212226786

可以看到ConvertToPDF_x86.dll只有兩個導出函數,下面用windbg給導出函數下個斷點,看看這兩個函數的調用順序。首先打開程序,然后用 windbg 附加進程,用sxe命令設置模塊加載斷點,當ConvertToPDF_x86模塊加載起來時調試器會斷下來

sxe ld:ConvertToPDF_x86

斷下來后我們給模塊的所有導出函數下斷點

bm /a ConvertToPDF_x86!*

繼續運行后,程序首先會斷在CreateFXPDFConvertor函數

0:000> bm /a ConvertToPDF_x86!*
  1: 00000000`6d798e30 @!"ConvertToPDF_x86!DestorFXPDFConvertor"
  2: 00000000`6d79aaf0 @!"ConvertToPDF_x86!CreateFXPDFConvertor"
0:000> g
Breakpoint 2 hit
ConvertToPDF_x86!CreateFXPDFConvertor:
6d79aaf0 a10cd1c36d      mov     eax,dword ptr [ConvertToPDF_x86!CreateFXPDFConvertor+0x4a261c (6dc3d10c)] ds:002b:6dc3d10c=00000000

拿IDA分析一下這個函數

_DWORD *CreateFXPDFConvertor()
{
  _DWORD *result; // eax
  void *obj; // eax

  result = dword_104AD10C;
  if ( dword_104AD10C )
    return result;                              // 調用malloc分配內存
  obj = alloc(0x1BDCu);
  if ( obj )
  {
    result = init_obj(obj);                     // 初始化對象
    dword_104AD10C = result;
  }
  else
  {
    result = 0;
    dword_104AD10C = 0;
  }
  return result;
}

這個函數比較簡單首先會用 malloc 分配0x1BDC的內存,然后會調用init_obj初始化分配的內存塊,init_obj函數會先設置虛表,然后設置對象里的其他一些字段。

_DWORD *__thiscall init_obj(int this)
{
  int v1; // esi

  v1 = this;
  *this = &CFX_PDFConvertor::`vftable'; // 給對象設置虛表
  sub_1000A720((this + 4));
  *(v1 + 7104) = 0;
  *(v1 + 7108) = 0;
  *(v1 + 7112) = 0;
  *(v1 + 7120) = 0;
  *(v1 + 7116) = 1;
  dword_104AD104 = 0;
  *(v1 + 7124) = 0;
  *(v1 + 7128) = 0;
  return v1;
}

虛表的結構如下

.rdata:10336F1C ; const CFX_PDFConvertor::`vftable'
.rdata:10336F1C ??_7CFX_PDFConvertor@@6B@ dd offset sub_1000B060
.rdata:10336F1C                                         ; DATA XREF: init_obj+6↑o
.rdata:10336F1C                                         ; sub_1000A8F0+BA↑o
.rdata:10336F20                 dd offset sub_10009B40
.rdata:10336F24                 dd offset sub_1000A2D0
.rdata:10336F28                 dd offset sub_1000A8F0

虛表里面有4個函數,按照正常的程序邏輯,程序在創建模塊對象后,肯定會調用對象的函數來使用模塊提供的功能,接下來給這4個函數下斷點,看看這些函數的調用關系以及參數信息。windbg的打印信息如下

0:000:x86> lm m Conver*
start             end                 module name
6d790000 6dcb5000   ConvertToPDF_x86   (export symbols)       C:\Program Files (x86)\Foxit Software\Foxit Reader\Plugins\Creator\x86\ConvertToPDF_x86.dll
0:000:x86> dd 0x6dac6f1c l4      // 查看虛表的函數的實際地址
6dac6f1c  6d79b060 6d799b40 6d79a2d0 6d79a8f0
0:000:x86> bp 6d79b060 
0:000:x86> bp 6d799b40 
0:000:x86> bp 6d79a2d0 
0:000:x86> bp 6d79a8f0
0:000:x86> bl
 0 e x86 6d79b060     0001 (0001)  0:**** ConvertToPDF_x86!CreateFXPDFConvertor+0x570
 1 e x86 6d798e30     0001 (0001)  0:**** ConvertToPDF_x86!DestorFXPDFConvertor
 2 e x86 6d79aaf0     0001 (0001)  0:**** ConvertToPDF_x86!CreateFXPDFConvertor
 3 e x86 6d799b40     0001 (0001)  0:**** ConvertToPDF_x86!DestorFXPDFConvertor+0xd10
 4 e x86 6d79a2d0     0001 (0001)  0:**** ConvertToPDF_x86!DestorFXPDFConvertor+0x14a0
 5 e x86 6d79a8f0     0001 (0001)  0:**** ConvertToPDF_x86!DestorFXPDFConvertor+0x1ac0
0:000:x86> g
Breakpoint 3 hit
ConvertToPDF_x86!DestorFXPDFConvertor+0xd10:
6d799b40 55              push    ebp        
0:000:x86> r    // 首先調用6d799b40函數
eax=6d799b40 ebx=0ad6a1a8 ecx=0ad6a1a8 edx=6dac6f1c esi=0088fb68 edi=00000002
eip=6d799b40 esp=002cce0c ebp=002cece8 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ConvertToPDF_x86!DestorFXPDFConvertor+0xd10:
6d799b40 55              push    ebp
0:000:x86> dps esp l4  # 參數為 2
002cce0c  02398a54 FoxitReader!CryptUIWizExport+0x885514
002cce10  00000002
002cce14  711d4f33
002cce18  02c2f814 FoxitReader!CryptUIWizExport+0x111c2d4
0:000:x86> g
Breakpoint 4 hit
ConvertToPDF_x86!DestorFXPDFConvertor+0x14a0:
6d79a2d0 55              push    ebp
0:000:x86> r  // 然后調用6d79a2d0
eax=002cec10 ebx=0ad6a10b ecx=0ad6a1a8 edx=6d79a2d0 esi=0ad6a1a8 edi=00000000
eip=6d79a2d0 esp=002cce0c ebp=002cece8 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ConvertToPDF_x86!DestorFXPDFConvertor+0x14a0:
6d79a2d0 55              push    ebp
0:000:x86> dps esp l6
002cce0c  02398c22 FoxitReader!CryptUIWizExport+0x8856e2
002cce10  002cec10
002cce14  711d4f33
002cce18  02c2f814 FoxitReader!CryptUIWizExport+0x111c2d4
002cce1c  05d8149c
002cce20  0088f9c0
0:000:x86> du 002cec10  # 參數是一個unicode字符串
002cec10  "Foxit Reader PDF Printer"
0:000:x86> g
Breakpoint 0 hit
ConvertToPDF_x86!CreateFXPDFConvertor+0x570:
6d79b060 55              push    ebp
0:000:x86> r    # 調用 6d79b060
eax=6dac6f1c ebx=0ad6a10b ecx=0ad6a1a8 edx=6d79b060 esi=0ad6a1a8 edi=00000000
eip=6d79b060 esp=002cce04 ebp=002cece8 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ConvertToPDF_x86!CreateFXPDFConvertor+0x570:
6d79b060 55              push    ebp
0:000:x86> dps esp l6
002cce04  02398c3f FoxitReader!CryptUIWizExport+0x8856ff
002cce08  002cce50
002cce0c  00000000
002cce10  00000000
002cce14  711d4f33
002cce18  02c2f814 FoxitReader!CryptUIWizExport+0x111c2d4
0:000:x86> du 002cce50     # 參數開頭是被處理圖片的地址
002cce50  "C:\Users\XinSai\Desktop\honeyvie"
002cce90  "w\5mb.jpg"
0:000:x86> g
Breakpoint 1 hit
ConvertToPDF_x86!DestorFXPDFConvertor:
6d798e30 55              push    ebp
0:000:x86> r   # 調用DestorFXPDFConvertor銷毀創建的對象
eax=00000000 ebx=0ad6a10b ecx=2156cf22 edx=0ad65078 esi=0ad6a1a8 edi=00000000
eip=6d798e30 esp=002cce0c ebp=002cece8 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ConvertToPDF_x86!DestorFXPDFConvertor:
6d798e30 55              push    ebp
0:000:x86> g
Breakpoint 5 hit
ConvertToPDF_x86!DestorFXPDFConvertor+0x1ac0:
6d79a8f0 a110d1c36d      mov     eax,dword ptr [ConvertToPDF_x86!CreateFXPDFConvertor+0x4a2620 (6dc3d110)] ds:002b:6dc3d110=0ad68e70

通過調試的信息以及IDA識別出來的參數個數,可以看到對象虛表中的4個函數調用的順序和參數信息大概如下

sub_10009B4       參數為 this , 2
sub_1000A2D0      參數為 "Foxit Reader PDF Printer" , unicode字符串
sub_1000B060      參數為 this, 輸入文件的全路徑, 0, 0
DestorFXPDFConvertor  函數會調用sub_1000A8F0去銷毀對象

可以看到sub_1000B060函數的功能應該就是讀取圖片文件並轉換為PDF文件,下面寫一個簡單的c程序把DLL加載起來然后根據調用關系和參數信息調用相應的API

int main()
{

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

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

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

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

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

		vtb->p_sub_10009B40(obj, 2);
		vtb->p_sub_1000A2D0(_T("Foxit Reader PDF Printer"));
		vtb->p_sub_1000B060(obj, (char*)_T("C:\\Users\\XinSai\\Desktop\\honeyview\\5mb.jpg"),0,0);
		pDestorFXPDFConvertor(obj);
	}
    return 0;
}

編譯執行發現我們的程序crash了,拿windbg調試發現崩潰的位置如下

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

在IDA中分析發生crash的位置

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

  obj = this;
  path_ = invaild_address;
  v6 = *(invaild_address + 16);// 解引 invaild_address+16 時異常

invaild_address是上層傳下來的是一個不合法的指針,程序解引時發生了crash。繼續往上追蹤該函數的調用者

void __thiscall sub_1000ABA0(unsigned int *this, unsigned int a2)
{
	.....................
	.....................
	.....................
    if ( v8 )
    {
      v8[5] = 7;
      v8[4] = 0;
      *v8 = 0;
      crash_func(v8, a2, 0, 0xFFFFFFFF); // 調用發送crash的函數

可以看到這里直接把該函數的第二個參數傳給了crash_func,繼續向上跟

char *__thiscall sub_1000AC90(char *this, int path_info)
{
  .....................
  .....................
  .....................
  p = *(path_info + 3188);
  if ( (*(path_info + 3192) - p) / 28 )
  {
    idx = 0;
    do
    {
      sub_1000ABA0(v3 + 797, p + idx);          // 第二個參數為一個非法指針
      p = v2[797];
      ++v29;
      idx += 28;
    }
    while ( v29 < (v2[798] - p) / 28 );
  }

可以看到這里從sub_1000AC90的第二參數的偏移3188處取了一個指針,然后傳入了sub_1000ABA0繼續再往上追蹤,發現sub_1000AC90的第二個參數其實就是sub_1000B060的第二個參數。

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

  path_info_ = path_info;
  ...........
  ...........
  sub_1000AC90(this + 4, path_info_);

path_info是我們傳入的,我們現在是給它直接傳了一個unicode字符串的指針。

vtb->p_sub_1000B060(obj, (char*)_T("C:\\Users\\XinSai\\Desktop\\honeyview\\5mb.jpg"),0,0);

通過分析sub_1000AC90里面對path_info的處理,可以看到path_info這個參數應該是一個結構體

  *(v3 + 936) = *(path_info + 3744); // 取結構體的字段
  *(v3 + 937) = *(path_info + 3748);
  *(v3 + 938) = *(path_info + 3752);
  *(v3 + 939) = *(path_info + 3756);
  *(v3 + 940) = *(path_info + 3760);
  *(v3 + 934) = *(path_info + 3736);
  p = *(path_info + 3188);
  if ( (*(path_info + 3192) - p) / 28 )
  {
    idx = 0;
    do
    {
      sub_1000ABA0(v3 + 797, p + idx);          // 第二個參數為一個非法指針
      p = v2[797];
      ++v29;
      idx += 28;
    }
    while ( v29 < (v2[798] - p) / 28 );
  }

下面我們去看看實際FoxitReader在使用該DLL時傳進來的結構體里面的數據是怎么樣的

0:000:x86> r
eax=003ece88 ebx=00000000 ecx=0a55a1ac edx=6d79b060 esi=0a55a1a8 edi=0a55a1ac
eip=6d79ac90 esp=003ece00 ebp=003ece38 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ConvertToPDF_x86!CreateFXPDFConvertor+0x1a0:
6d79ac90 55              push    ebp
0:000:x86> dd esp l2
003ece00  6d79b0e6 003ece88
0:000:x86> db 003ece88
003ece88  43 00 3a 00 5c 00 55 00-73 00 65 00 72 00 73 00  C.:.\.U.s.e.r.s.
003ece98  5c 00 58 00 69 00 6e 00-53 00 61 00 69 00 5c 00  \.X.i.n.S.a.i.\.
003ecea8  44 00 65 00 73 00 6b 00-74 00 6f 00 70 00 5c 00  D.e.s.k.t.o.p.\.
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.
003ecec8  77 00 5c 00 35 00 6d 00-62 00 2e 00 6a 00 70 00  w.\.5.m.b...j.p.
003eced8  67 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  g...............
003ecee8  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
003ecef8  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
0:000:x86> db 003ece88+0x208
003ed090  43 00 3a 00 5c 00 55 00-73 00 65 00 72 00 73 00  C.:.\.U.s.e.r.s.
003ed0a0  5c 00 58 00 69 00 6e 00-53 00 61 00 69 00 5c 00  \.X.i.n.S.a.i.\.
003ed0b0  41 00 70 00 70 00 44 00-61 00 74 00 61 00 5c 00  A.p.p.D.a.t.a.\.
003ed0c0  4c 00 6f 00 63 00 61 00-6c 00 5c 00 54 00 65 00  L.o.c.a.l.\.T.e.
003ed0d0  6d 00 70 00 5c 00 31 00-35 00 37 00 33 00 30 00  m.p.\.1.5.7.3.0.
003ed0e0  33 00 37 00 34 00 38 00-32 00 2e 00 70 00 64 00  3.7.4.8.2...p.d.
003ed0f0  66 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  f...............
003ed100  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
0:000:x86> db 003ece88+0x410
003ed298  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
003ed2a8  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
0:000:x86> db 003ece88+0x618
003ed4a0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
003ed4b0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
0:000:x86> db 003ece88+0x820

通過dump內存可以發現在path_info開頭存的是圖片的路徑,在path_info+0x208的位置存放的是生成的臨時PDF文件的路徑,其他的區域都是0,根據訪問的區域來看path_info結構體的大致大小為0x10C8字節。我們可以往上跟一下看能不能找到path_info實際分配的內存大小

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

可以發現path_info是從FoxitReader的0x1498c3f處傳進來的,跟過去看看。

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

      v81 = ((*v81)[2])(v81, v91);
      if ( !v81 )
        v81 = (**v65)(v65, path_info, 0, 0); // 調用sub_1000B060

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

00001E98 path_info       db 520 dup(?)
-00001C90 var_1C90        db 2608 dup(?)
-00001260 var_1260        dd ?
-0000125C                 db ? ; undefined
-0000125B                 db ? ; undefined

根據棧幀布局,可以知道 path_info最大為 0x1c90字節,這里我們分配0x2000字節給path_info然后設置好輸入和輸出路徑到path_info。

int main()
{

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

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

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

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

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

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

		unsigned int info_size = 0x2000;
		char* path_info = (char*)malloc(info_size);
		memset(path_info, 0, info_size);

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

		// 設置輸入圖片的路徑
		wcscpy((wchar_t*)path_info, input_image);
		
		//設置輸出圖片的路徑
		wcscpy((wchar_t*)(path_info + 0x208), output_pdf);
		printf("path info:%p\n", path_info);

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

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

		pDestorFXPDFConvertor(obj);
		free(path_info);
	}
    return 0;
}

執行完后在C:\tmp.pdf生成創建好的圖片,至此我們基本分析清楚ConvertToPDF_x86.dll的調用方式,目前可以通過C語言直接調用模塊內的函數完成PDF的制作,下面分別介紹如何對ConvertToPDF模塊進行內存Fuzz,以及如何用WinAFL去Fuzz該模塊。

內存Fuzz

內存Fuzz的原理是在內存中不斷的生成測試數據,然后調用目標函數來實現Fuzz,這里采用hook的方式來實現內存Fuzz,首先在 DllMain里面獲取sub_1000B060函數的地址並hook該函數。

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


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

		int ret = 0;
		while (true)
		{
			fuzz(input_image); // 變異初始用例,並寫入input_image
			ret = raw_sub_1000B060((char*)this, path_info, a, b); // 調用原始的目標函數
			debugger_printf("fuzzed function return:%d\n", ret);
		}
	}

private:

};

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

	switch (fdwReason)
	{
	case DLL_PROCESS_ATTACH://加載時候
		base = (char*)GetModuleHandle(_T("ConvertToPDF_x86.dll"));

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

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

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

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

		break;
	default:
		break;
	}
	return TRUE;
}

由於sub_1000B060的調用約定是thiscall,所以代碼里面實現了一個類函數用來作為hook函數,並且使用了一個小技巧來獲取函數的地址

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

設置好hook后,在my_sub_1000B060函數里面首先讀取初始用例到內存,然后不斷地對初始數據變異,最后把數據寫入圖片文件,然后調用目標函數去處理數據。

代碼編譯好后會生成mhook-test.dll,然后打開FoxitReader,用圖片創建一個PDF,這樣ConvertToPDF_x86.dll就會被加載到內存。此時使用用DLL注入工具把mhook-test.dll注入到FoxitReader進程。

image-20191106221102911

當mhook-test.dll被注入到進程后就會執行DllMain,這樣就可以把目標函數hook住,然后再次用圖片創建一個PDF,觸發目標函數的調用,這時程序會進入hook函數my_sub_1000B060,在my_sub_1000B060里面開始不斷變異數據,Fuzz目標函數。

啟動FoxitReader后建議用windbg附加進程,mhook-test.dll會使用調試器的輸出接口打印一些調試日志,同時使用調試器還可以在程序發生異常時斷下來輔助調試,運行截圖如下

image-20191106222228681

發送crash時的截圖

image-20191107230122739

WinAFL

WinAFL是AFL的Windows移植版,WinAFL使用dynamorio來獲取程序執行的覆蓋率,為了提升測試速度,WinAFL實現了一種類似於內存Fuzz的機制,示意圖如下

image-20191106223817383

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的主要代碼如下

void fuzz_func(char* path) {
	vtb->p_sub_10009B40(obj, 2);
	vtb->p_sub_1000A2D0(_T("Foxit Reader PDF Printer"));
	memset(path_info, 0, 0x2000);

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

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

int main(int argc, char** argv)
{
	if (argc < 2) {
		printf("fuzz.exe path\n");
		return 1;
	}
	
	//加載dll到內存
	HMODULE hMod = LoadLibrary(_T("C:\\Program Files (x86)\\Foxit Software\\Foxit Reader\\plugins\\Creator\\x86\\ConvertToPDF_x86.dll"));
	if (hMod) {
		pCreateFXPDFConvertor = (CreateFXPDFConvertor)GetProcAddress(hMod, "CreateFXPDFConvertor");
		pDestorFXPDFConvertor = (DestorFXPDFConvertor)GetProcAddress(hMod, "DestorFXPDFConvertor");

		printf("CreateFXPDFConvertor:%p\n", pCreateFXPDFConvertor);
		
		// 創建好對象
		obj = pCreateFXPDFConvertor();
		printf("create convertor:%p\n", obj);
		
		//初始化全局虛表指針到DLL的虛表
		char* mod_base = (char*)hMod;
		vtb = (vtable*)(mod_base + 0x336f1c); // get vtable offset
		printf("module base:%p\n", mod_base);
		printf("vtb:%p\n", vtb);
		
		//調用函數處理數據
		fuzz_func(argv[1]);
		
		//釋放分配的對象
		pDestorFXPDFConvertor(obj);

	}
    return 0;
}

主要就是把一些全局初始化的操作放到fuzz_func外面執行,這樣就可以減少重復的代碼,提升測試的速度。在main函數里面首先把DLL加載起來,然后獲取一些函數的地址和對象虛表指針並保持到全局變量里面,之后調用fuzz_func讀取文件並處理圖片數據。然后使用WinAFL開始Fuzzing,執行的命令如下

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 @@

其中

-D: 指定dynamorio 的drrun.exe所在的路徑
-coverage_module:指定需要獲取覆蓋率的模塊
-target_method:目標函數的函數名,winafl會不斷的執行該函數
-nargs:target_method函數的參數個數
-target_module:target_method所在的模塊名
-fuzz_iterations:指定target_method最多執行次數,達到次數后會重啟程序

建議在使用WinAFL之前可以用 winafl.dll 執行目標程序看看程序能不能正常在dynamorio下執行,以及獲取一些調試信息,命令如下

path\of\dynamorio\bin32\drrun.exe -c winafl.dll -debug -- C:\Users\XinSai\Desktop\foxitfuzz\dllloader\Debug\dllloader.exe c:\init.jpg

執行完后會在當前目錄生成一個.log文件,文件里面保存了程序加載的所有DLL。

Module loaded, dllloader.exe
............................
............................
Module loaded, ConvertToPDF.dll

可以看到目標程序加載的DLL名字為ConvertToPDF.dll,而不是我們代碼里寫的ConvertToPDF_x86.dll。執行截圖如下

image-20191107221518044

IrfanView

IrfanView是一款圖片瀏覽器,支持各種各樣的圖片格式,本節介紹如何使用WinAFL來測試IrfanView。首先我們用procmon監控IrfanView打開文件時的API調用記錄,用IrfanView打開文件的命令行如下

"C:\Program Files (x86)\IrfanView\i_view32.exe" C:\example_2000.dwg

等程序執行完后,讓procmon停止監控事件,然后我們去分析處理輸入文件(example_2000.dwg)的函數調用,查看讀取文件時函數的調用棧可以發現是在BabaCAD4Image.dll里面打開並解析DWG文件。

image-20191110211344770

此時我們可以像上一小節一樣通過編寫一個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的命令如下

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" @@

其中

-target_offset: 指定被測函數在模塊中的偏移

跑起來的狀態圖如下

image-20191110213910901

可以看到WinAFL能夠正常執行用例並且可以發現新路徑。

參考

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM