Home/GifIndexToTrueColor某堆溢出漏洞分析

Created Thu, 14 Jul 2022 12:09:13 +0800 Modified Fri, 23 Sep 2022 03:39:03 +0800
3159 Words

漏洞描述: 索引方式的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中每个索引对应pixelrgb值,在此期间出现溢出问题。

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.cngiflib.cngiflibSDL.cSDLaffgif.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返回值err1时错误并终止程序
    • 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_codeclrnpix>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 0020 00分别为宽和高
  • F2

0xFF 参考资料

GIF - Wikipedia

菜鸟浮出水