漏洞描述: 索引方式的gif图转成true color(rgb)格式时由于
状态: 分析中
类型: heap, 堆溢出
0x00 漏洞摘要
- 漏洞复现OS: Ubuntu 16.04(只包含栈溢出)
- 漏洞类型: 堆溢出Heap Overflow
- crash原因:
gif2tga
程序中,读取gif图片后,rgb三色TrueColor可用于存储图片中每个pixel的颜色,gif
图的palette
部分记录所有颜色,存储在binary文件中某个范围二进制数据内。在模式设置为MODE_INDEX
时,通过调用GifIndexToTrueColor
依次获取gif图每个img
中每个索引对应pixel
的rgb
值,在此期间出现溢出问题。
0x01 漏洞复现
环境配置
在Ubuntu 16.04LTS下进行复现和分析
-
关闭ASLR:
su; echo 0 > /proc/sys/kernel/randomize_va_space
-
编译漏洞程序
gif2tga
-
运行
SRC_asan/build/bin/gif2tga --outbase /dev/null ./POC_rc-awd-q3
-
ASAN 报告
================================================================= ==29835==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60300000003c at pc 0x555555557ff4 bp 0x7fffffff8d50 sp 0x7fffffff8d48 READ of size 1 at 0x60300000003c thread T0 #0 0x555555557ff3 in GifIndexToTrueColor /home/user/share/test/test/ngiflib/SRC_asan/ngiflib.c:821 #1 0x55555555a2d3 in WritePixel /home/user/share/test/test/ngiflib/SRC_asan/ngiflib.c:124 #2 0x55555555a2d3 in DecodeGifImg /home/user/share/test/test/ngiflib/SRC_asan/ngiflib.c:537 #3 0x55555555c5bb in LoadGif /home/user/share/test/test/ngiflib/SRC_asan/ngiflib.c:802 #4 0x55555555724a in main /home/user/share/test/test/ngiflib/SRC_asan/gif2tga.c:95 #5 0x7ffff704609a in __libc_start_main ../csu/libc-start.c:308 #6 0x55555555654d in _start (/home/miner/MINER/Projects/GifIndexToTrueColor-HBO/SRC_asan/build/bin/gif2tga+0x254d) 0x60300000003c is located 20 bytes to the right of 24-byte region [0x603000000010,0x603000000028) allocated by thread T0 here: #0 0x7ffff72cc330 in __interceptor_malloc (/lib/x86_64-linux-gnu/libasan.so.5+0xe9330) #1 0x55555555b497 in LoadGif /home/user/share/test/test/ngiflib/SRC_asan/ngiflib.c:645 #2 0x55555555724a in main /home/user/share/test/test/ngiflib/SRC_asan/gif2tga.c:95 #3 0x7ffff704609a in __libc_start_main ../csu/libc-start.c:308 SUMMARY: AddressSanitizer: heap-buffer-overflow /home/user/share/test/test/ngiflib/SRC_asan/ngiflib.c:821 in GifIndexToTrueColor Shadow bytes around the buggy address: 0x0c067fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c067fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c067fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c067fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c067fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 =>0x0c067fff8000: fa fa 00 00 00 fa fa[fa]fa fa fa fa fa fa fa fa 0x0c067fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c067fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c067fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c067fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c067fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb ==29835==ABORTING
成功复现
0x02 漏洞机理
源码跟踪
程序比较精简,有效源码只有
gif2tga.c
、ngiflib.c
、ngiflibSDL.c
、SDLaffgif.c
四个
-
ngiflib_gif
数据结构(存储整个gif信息)struct ngiflib_gif { struct ngiflib_img * first_img; // 静态图片头 struct ngiflib_img * cur_img; // 当前图片 struct ngiflib_rgb * palette; // 颜色版 union { #ifndef NGIFLIB_NO_FILE FILE * file; #endif /* NGIFLIB_NO_FILE */ const u8 * bytes; } input; /* used by GetByte */ union ngiflib_pixpointer frbuff; /* frame buffer */ #ifndef NGIFLIB_NO_FILE FILE * log; /* to output log */ #endif /* NGIFLIB_NO_FILE */ #ifdef NGIFLIB_ENABLE_CALLBACKS ngiflib_palette_cb palette_cb; ngiflib_line_cb line_cb; /* void * priv; */ #endif /* NGIFLIB_ENABLE_CALLBACKS */ int nimg; int netscape_loop_count; /* from netscape animation extension */ u16 ncolors; u16 width; u16 height; u8 backgroundindex; u8 pixaspectratio; /* width/height = (pixaspectratio + 15) / 64 */ u8 imgbits; // 总比特大小 u8 colorresolution; u8 sort_flag; u8 mode; /* voir avant */ // NNGIFLIB_MODE_TRUE_COLOR u8 signature[7]; /* 0 terminated !=) */ };
-
ngiflib_img
数据结构(按序的一张张静态图,单向链表形式存储)struct ngiflib_img { struct ngiflib_img * next; struct ngiflib_gif * parent; struct ngiflib_rgb * palette; struct ngiflib_gce gce; u16 ncolors; u16 width; u16 height; u16 posX; u16 posY; u8 interlaced; u8 sort_flag; /* is local palette sorted by color frequency ? */ u8 localpalbits; /* bits/pixel ! (de 1 a 8) */ u8 imgbits; /* bits mini du code LZW (de 2 a 9 ?) */ };
-
ngiflib_decode_context
数据结构(解密之后的context)union ngiflib_pixpointer { u8 * p8; #ifndef NGIFLIB_INDEXED_ONLY u32 * p32; #endif /* NGIFLIB_INDEXED_ONLY */ }; struct ngiflib_decode_context { union ngiflib_pixpointer frbuff_p; /* current offset in frame buffer */ #ifdef NGIFLIB_ENABLE_CALLBACKS union ngiflib_pixpointer line_p; /* start of line pointer */ #endif /* NGIFLIB_ENABLE_CALLBACKS */ const u8 * srcbyte; u16 Xtogo; u16 curY; u16 lbyte; u16 max; /* maximum value = (1 << nbbit) - 1 */ u8 pass; /* current pass of interlaced image decoding */ u8 restbyte; u8 nbbit; /* bits courants du code LZW */ u8 restbits; u8 byte_buffer[256]; /* for get word */ };
-
了解input → 越界读位置
-
首先开始运行程序,调用
main
函数(gif2tga.c:11)outbase = "/dev/null" // 为接下来写入gif申请空间并清零 gif = (struct ngiflib_gif *)malloc(sizeof(struct ngiflib_gif)); memset(gif, 0, sizeof(struct ngiflib_gif)); if(indexed) { //因此此时git->mode为NGIFLIB_MODE_INDEXED模式 gif->mode |= NGIFLIB_MODE_INDEXED; printf("INDEXED MODE\n"); } else gif->mode |= NGIFLIB_MODE_TRUE_COLOR;
-
main函数中调用
LoadGif
函数加载Gif图像(gif2tga.c:95)/* MAIN LOOP */ do { err = LoadGif(gif); /* en retour 0=fini, 1=une image decodee, -1=ERREUR */ printf("LoadGif() returned %d\n", err); if(err==1) { int localpalsize;
- 循环方式加载GIF,
LoadGif
返回值err
为1
时错误并终止程序
- 循环方式加载GIF,
-
LoadGif
函数当加载到sign
值为0x2C
时(图像分割),调用DecodeGifImg
函数解码gif图像,否则失败;若成功则继续往下调用GetByte
函数,当为0时终结(ngiflib.c:802)———— TODO 注意POC的gif image values格式跟https://en.wikipedia.org/wiki/GIF#File_format里介绍格式有点对不上。。这个软件的lib库的代码也不长,干脆直接看着poc对照分析。GetWord
函数读取2字节-4位16进制数GetByte
函数读取1字节—2位16进制数
switch(sign) { // 循环读取sign case 0x3B: /* END OF GIF */ return 0; // ... case 0x2C: /* Image separator 图像分割*/ g->cur_img = ngiflib_malloc(sizeof(struct ngiflib_img)); // cur_img指当前img,malloc申请空间 if(g->cur_img == NULL) return -2; /* memory error */ g->first_img = g->cur_img; // 第一个img为链表头 } else { // 以链表形式存储,next指向下个img g->cur_img->next = ngiflib_malloc(sizeof(struct ngiflib_img)); if(g->cur_img->next == NULL) return -2; /* memory error */ g->cur_img = g->cur_img->next; } // 内存数据清零 ngiflib_memset(g->cur_img, 0, sizeof(struct ngiflib_img)); g->cur_img->parent = g; // g是父img if(gce.gce_present) { ngiflib_memcpy(&g->cur_img->gce, &gce, sizeof(struct ngiflib_gce)); } else { ngiflib_memset(&g->cur_img->gce, 0, sizeof(struct ngiflib_gce)); } // 解码当前gif图,若成功则记录成功gif图数量 if (DecodeGifImg(g->cur_img) < 0) return -1; g->nimg++; // 不断读取到g图的最后 tmp = GetByte(g);/* 读取gif下一个字节,final最后要为0则结束 */
-
DecodeGifImg
函数判断若act_code
为clr
且npix>0
(该img有像素),调用WritePixel
函数写像素(ngiflib.c:537)static int DecodeGifImg(struct ngiflib_img * i) { u16 act_code = 0; if(i->imgbits==1) { /* fix for 1bit images ? */ i->imgbits = 2; // 图片比特数 } clr = 1 << i->imgbits; // 当前静态img的比特数×2 eof = clr + 1; free = clr + 2; // ... // 返回变长的LZW代码(已解压)给contex,最大长度给act_code act_code = GetGifWord(i, &context); if(act_code==clr) { // clear // ... act_code = GetGifWord(i, &context); casspecial = (u8)act_code; // ... // 像素数 > 0,写入到context if(npix > 0) WritePixel(i, &context, casspecial); npix--; // 写入完成后,删除 // ...
act_code
是什么?-
通过调用
GetGifWord
函数返回值获取,为了可以应用正确的掩码以消除多余的位(即act_code
为当前img图像i
的最大值context.max
,使得img中每个context
值都可以存放进)// gif中的数据原本是经过LZW算法编码压缩的,需要该函数进行解码 static u16 GetGifWord(struct ngiflib_img * i, struct ngiflib_decode_context * context) { // 待解码的bits数 bits_todo = (int)context->nbbit - (int)context->restbits;
-
clr
为目前context
的最大值,context
为LZW算法解码后的字符串clr = 1 << i->imgbits; // img图的比特数×2 eof = clr + 1; // 该img图结束 free = clr + 2; // 无了 context.nbbit = i->imgbits + 1; // .max = context比特数×2 - 1 context.max = clr + clr - 1; /* (1 << context.nbbit) - 1 */
-
-
WritePixel
函数在符合if条件的情况下,调用GifIndexToTrueColor
函数将v
值像素写入缓冲区(ngiflib.c:124)static void WritePixel(struct ngiflib_img * i, struct ngiflib_decode_context * context, u8 v) { struct ngiflib_gif * p = i->parent; if(v!=i->gce.transparent_color || !i->gce.transparent_flag) { if(p->mode & NGIFLIB_MODE_INDEXED) { // ... else // frbuff_p指当前在frame buffer中的索引 *context->frbuff_p.p32 = GifIndexToTrueColor(i->palette, v);
-
gce为Graphic Control Extension,即图像渲染相关变量存储
switch(sign) { // ... case '!': /* Extension introducer 0x21 */ while( (size = GetByte(g)) ) { GetByteStr(g, ext, size); switch(id) { case 0xF9: /* Graphic Control Extension */ /* The scope of this extension is the first graphic * rendering block to follow. */ gce.gce_present = 1; gce.disposal_method = (ext[0] >> 2) & 7; gce.transparent_flag = ext[0] & 1; gce.user_input_flag = (ext[0] >> 1) & 1; gce.delay_time = ext[1] | (ext[2]<<8); gce.transparent_color = ext[3]; // ...
-
-
GifIndexToTrueColor
函数中,从该gif的palette
中获取索引v
对应的rgb
格式颜色值,返回32位数据写入frame buffer
u32 GifIndexToTrueColor(struct ngiflib_rgb * palette, u8 v) { return palette[v].b | (palette[v].g << 8) | (palette[v].r << 16); }
-
GDB Debug
gdb + https://github.com/pwndbg/pwndbg + https://github.com/vercel/hyper + https://github.com/bet4it/hyperpwn
通过gdb --args SRC_asan/build/bin/gif2tga --outbase /dev/null ./POC_rc-awd-q
进行调试。
-
根据ASAN给出的分析报告(也是上面源码分析的位置),下断点:
pwndbg> b SRC_asan/gif2tga.c:11 # main函数 pwndbg> b SRC_asan/gif2tga.c:95 # -> LoadGif pwndbg> b SRC_asan/ngiflib.c:802 # -> DecodeGifImg pwndbg> b SRC_asan/ngiflib.c:537 # -> WritePixel pwndbg> b SRC_asan/ngiflib.c:124 # -> GifIndexToTrueColor pwndbg> b SRC_asan/ngiflib.c:821 # in GifIndexToTrueColor
-
ins
b SRC_asan/gif2tga.c:11 b SRC_asan/gif2tga.c:95 b SRC_asan/ngiflib.c:802 b SRC_asan/ngiflib.c:537 b SRC_asan/ngiflib.c:124 b SRC_asan/ngiflib.c:821
int LoadGif(struct ngiflib_gif * g) { case 0x2C: /* Image separator */ if(g->nimg==0) { //=================== if (DecodeGifImg(g->cur_img) < 0) return -1; g->nimg++; static int DecodeGifImg(struct ngiflib_img * i) { clr = 1 << i->imgbits; eof = clr + 1; free = clr + 2; if(act_code==clr) { /* clear */ free = clr + 2; context.nbbit = i->imgbits + 1; context.max = clr + clr - 1; /* (1 << context.nbbit) - 1 */ act_code = GetGifWord(i, &context); casspecial = (u8)act_code; old_code = act_code; //======== if(npix > 0) WritePixel(i, &context, casspecial); npix--; static void WritePixel(struct ngiflib_img * i, struct ngiflib_decode_context * context, u8 v) { if(v!=i->gce.transparent_color || !i->gce.transparent_flag) { GifIndexToTrueColor(i->palette, v); u32 GifIndexToTrueColor(struct ngiflib_rgb * palette, u8 v) { return palette[v].b | (palette[v].g << 8) | (palette[v].r << 16); }
-
0x04 漏洞利用
poc分析
目前已有poc,通过hexedit工具查看二进制文件:hexedit ./POC_rc-awd-q3 --color
共0x40
字节
GIF89a
开头即47 49 46 38 39 61
表示是GIF89a版gif图片,初步认为其是gif图片文件- 接着的
20 00
和20 00
分别为宽和高 - F2