0×00 引言


@瞌睡龙让俺仔细介绍一下,所以我就把之前文章http://drops.wooyun.org/papers/1020的一节的东西单独提出来了,之前百度浏览器5.0版本子窗口堆溢出的那版的程序我已经找不太到了,找到最接近的版本修复的只剩一个栈空间不足的问题而已了,这个小毛病我就搁在事后分析里面好了。

我找到的早期版本,比如4.5版,它的内存损坏和5.x的内存损坏效果类似,而且崩溃逻辑、位置都大体相同,5.x那个小报告中我提到的“低版本程序不测试直接转移到高版本”就是让这一个问题横跨几个世纪的主要原因。

当然,以下是我晚上几个小时调(口)试(胡)出来的,由于家里的电脑下WDK一直失败,所以没用上最新科技,只用了一个很老的windbg版本,有的地方还有些问题,所以我会粘来一点之前草稿里的数据,不过我确定这个对结果没有影响的。还有就是由于代码上班的时候调了一点,下班后回家继续调了后面的(俺是新开的例程),所以可能会有一些神推理名推测和超剧情发展的东西,这个请见谅。

由于代码实在太长,所以我会适当的标记出我在干什么,以及我想要得到什么结果,所以如果你看到了莫名其妙的黑色粗体标记,那肯定是我的注解。另外,由于这个windbg贴的代码本身实在已经是错综复杂了,所以我不会一句句的解释了,我会直接介绍函数在作甚,而且由于个人水平渣,写的时候卡壳了多次,所以这我已经预感到难免会出问题,这个也请大家见谅,各位发现不对的话请及时糊我熊脸!

0×01 基本需求


一个调试器是必要的,由于涉及事后调试和即时调试,俺就使用windbg来进行啦。由于目标程序是32位的,所以我选择了x86版的windbg。操作系统是Windows 7 sp1 简体中文版,说是oem但是看起来像盗版系统的系统。

然后就是几个基本的命令,t 步入,p 步过, gu 执行到返回,dd 查看dword, da 查看ansi, k 显示调用栈, poi 析取指针,等等,遇到新不明物种的话,windbg的F1里面的介绍是非常详尽的。

0×02 简单的事后分析(介绍)


事后分析我们就用百度浏览器5.0的一个栈内存不够分导致的Stack Buffer Overrun为例好了,poc还是那个,口胡我第一,那么开始呗。

0x02a 开始

首先,我们可以确定的是我们的poc可以导致这个程序崩溃,而通常我们的目标程序也会自带有一个dump文件,例如:

2014030816551761511.png
  图:通常在%temp%下可能有崩溃文件,也有可能在%appdata%

打开windbg,载入dump文件,进行简单的!analyze -v。显示信息如下:

#!bash
The stored exception information can be accessed via .ecxr.
(51fc.a978): Stack overflow - code c00000fd (first/second chance not available)
eax=00000000 ebx=00000000 ecx=0046700c edx=0061e3e4 esi=000002a8 edi=00000000
eip=77a0f8d1 esp=0061bbe0 ebp=0061bc4c iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ntdll!NtWaitForSingleObject+0x15:
77a0f8d1 83c404          add     esp,4
0:000> !analyze -v
*******************************************************************************
*                                                                             *
*                        Exception Analysis                                   *
*                                                                             *
*******************************************************************************

FAULTING_IP: 
browsercore+dbda7
033fbda7 8500            test    dword ptr [eax],eax

EXCEPTION_RECORD:  ffffffff -- (.exr 0xffffffffffffffff)
ExceptionAddress: 033fbda7 (browsercore+0x000dbda7)
   ExceptionCode: c00000fd (Stack overflow)
  ExceptionFlags: 00000000
NumberParameters: 2
   Parameter[0]: 00000000
   Parameter[1]: 00522000

PROCESS_NAME:  baidubrowser.exe

ERROR_CODE: (NTSTATUS) 0xc00000fd - <Unable to get error code text>

EXCEPTION_CODE: (NTSTATUS) 0xc00000fd - <Unable to get error code text>

EXCEPTION_PARAMETER1:  00000000

EXCEPTION_PARAMETER2:  00522000

NTGLOBALFLAG:  0

FAULTING_THREAD:  0000a978

DEFAULT_BUCKET_ID:  STACK_OVERFLOW

PRIMARY_PROBLEM_CLASS:  STACK_OVERFLOW

BUGCHECK_STR:  APPLICATION_FAULT_STACK_OVERFLOW_INVALID_POINTER_READ

LAST_CONTROL_TRANSFER:  from 045b7a85 to 033fbda7

STACK_TEXT:  
WARNING: Stack unwind information not available. Following frames may be wrong.
0061e2a8 045b7a85 0061e3e4 0061e634 00000001 browsercore+0xdbda7
0061e2c4 0337100c 0061e3e4 0061e634 0061e7f4 browsercore+0x1297a85
0061e620 03333292 0061e7f4 00000000 00000000 browsercore+0x5100c
……(略)
0061fe88 001e63bf 00100000 00000000 00352318 baidubrowser+0x65da
0061ff18 7703336a 7efde000 0061ff64 77a29f72 baidubrowser+0xe63bf
0061ff24 77a29f72 7efde000 2a534886 00000000 kernel32!BaseThreadInitThunk+0xe
0061ff64 77a29f45 001e6412 7efde000 00000000 ntdll!__RtlUserThreadStart+0x70
0061ff7c 00000000 001e6412 7efde000 00000000 ntdll!_RtlUserThreadStart+0x1b


STACK_COMMAND:  ~0s; .ecxr ; kb

FOLLOWUP_IP: 
browsercore+dbda7
033fbda7 8500            test    dword ptr [eax],eax

SYMBOL_STACK_INDEX:  0

SYMBOL_NAME:  browsercore+dbda7

FOLLOWUP_NAME:  MachineOwner

MODULE_NAME: browsercore

IMAGE_NAME:  browsercore.dll

DEBUG_FLR_IMAGE_TIMESTAMP:  526f3e67

FAILURE_BUCKET_ID:  STACK_OVERFLOW_c00000fd_browsercore.dll!Unknown

BUCKET_ID:  APPLICATION_FAULT_STACK_OVERFLOW_INVALID_POINTER_READ_browsercore+dbda7

WATSON_IBUCKET:  2118667

WATSON_IBUCKETTABLE:  17

WATSON_STAGEONE_URL:  http://watson.microsoft.com/StageOne/baidubrowser_exe/2_210_0_42889/526f3e61/browsercore_dll/10_0_0_17/526f3e67/c00000fd/000dbda7.htm?Retriage=1

Followup: MachineOwner
---------

我们可以由上得到很多重要的信息:

例如最上层的调用栈、BUCKET信息(分类漏洞)、出错的位置、出错的语句、它并没有对这个异常的处理程序等等,还有最重要的信息:我们根本没它的符号,淦。

0x01b 查看崩溃附近的数据

好在它没有做什么其他的事儿,我们查看faulting ip附近的代码即可知道他为什么会崩。

#!bash
FAULTING_IP: 
browsercore+dbda7
033fbda7 8500            test    dword ptr [eax],eax

执行

#!bash
uf browsercore+dbda7 

得到:

#!bash
0:000> uf browsercore+dbda7
Unable to load image C:\Program Files (x86)\baidu\BaiduBrowser\browsercore.dll, Win32 error 0n2
*** WARNING: Unable to verify timestamp for browsercore.dll
*** ERROR: Module load completed but symbols could not be loaded for browsercore.dll
browsercore+0xdbd94:
033fbd94 3bc8            cmp     ecx,eax
033fbd96 720a            jb      browsercore+0xdbda2 (033fbda2)

browsercore+0xdbd98:
033fbd98 8bc1            mov     eax,ecx
033fbd9a 59              pop     ecx
033fbd9b 94              xchg    eax,esp
033fbd9c 8b00            mov     eax,dword ptr [eax]
033fbd9e 890424          mov     dword ptr [esp],eax
033fbda1 c3              ret

browsercore+0xdbda2:
033fbda2 2d00100000      sub     eax,1000h

browsercore+0xdbda7:
033fbda7 8500            test    dword ptr [eax],eax  ;崩溃在此
033fbda9 ebe9            jmp     browsercore+0xdbd94 (033fbd94)

但是这是什么?看起来为什么这么像__alloca_probe的验证代码?对比一下实实在在的\__alloca_probe看来是没跑了。

#!bash
__alloca_probe:
push           ecx
cmp            eax,1000h
lea            ecx,[esp+8]
jb             lastpage
probepages:
sub            ecx,1000h
sub            eax,1000h
test           dword ptr [ecx],eax
cmp            eax,1000h
jae            probepages 
lastpage:
sub            ecx,eax
mov            eax,esp
test           dword ptr [ecx],eax
mov            esp,ecx
mov            ecx,dword ptr [eax]
mov            eax,dword ptr [eax+4]
push           eax
Ret 

崩在了最后的栈校验上。查看一下完整的调用栈,使用~*kvn查看所有线程的调用栈:

#!bash
0:000> ~*kvn

.  0  Id: 51fc.a978 Suspend: 0 Teb: 7efdd000 Unfrozen
 # ChildEBP RetAddr  Args to Child              
00 0061bbe0 76fa149d 000002a8 00000000 00000000 ntdll!NtWaitForSingleObject+0x15 (FPO: [3,0,0])
01 0061bc4c 77031194 000002a8 ffffffff 00000000 KERNELBASE!WaitForSingleObjectEx+0x98 (FPO: [SEH])
02 0061bc64 77031148 000002a8 ffffffff 00000000 kernel32!WaitForSingleObjectExImplementation+0x75 (FPO: [3,0,4])
03 0061bc78 001056c7 000002a8 ffffffff 006e9948 kernel32!WaitForSingleObject+0x12 (FPO: [2,0,0])
WARNING: Stack unwind information not available. Following frames may be wrong.
04 0061dd78 00105467 ac68c25f 0010542d 7710030c baidubrowser+0x56c7
05 0061dda0 7706fffb 0061de58 ac7abd84 00000000 baidubrowser+0x5467
06 0061de28 77a674ff 0061de58 77a673dc 00000000 kernel32!UnhandledExceptionFilter+0x127 (FPO: [SEH])
07 0061de30 77a673dc 00000000 0061ff64 77a1c550 ntdll!__RtlUserThreadStart+0x62 (FPO: [SEH])
08 0061de44 77a67281 00000000 00000000 00000000 ntdll!_EH4_CallFilterFunc+0x12 (FPO: [Uses EBP] [0,0,4])
09 0061de6c 77a4b499 fffffffe 0061ff54 0061dfa8 ntdll!_except_handler4+0x8e (FPO: [4,5,4])
0a 0061de90 77a4b46b 0061df58 0061ff54 0061dfa8 ntdll!ExecuteHandler2+0x26 (FPO: [Uses EBP] [5,3,1])
0b 0061deb4 77a4b40e 0061df58 0061ff54 0061dfa8 ntdll!ExecuteHandler+0x24 (FPO: [5,0,3])
0c 0061df40 77a00133 0061df58 0061dfa8 0061df58 ntdll!RtlDispatchException+0x127 (FPO: [2,25,4])
0d 0061df40 033fbda7 0061df58 0061dfa8 0061df58 ntdll!KiUserExceptionDispatcher+0xf (FPO: [2,0,0]) (CONTEXT @ 0061dfa8)
0e 0061e2a8 045b7a85 0061e3e4 0061e634 00000001 browsercore+0xdbda7
0f 0061e2c4 0337100c 0061e3e4 0061e634 0061e7f4 browsercore+0x1297a85
10 0061e620 03333292 0061e7f4 00000000 00000000 browsercore+0x5100c
11 0061e814 048b515c 0061e82c fffffffa 00000200 browsercore+0x13292
12 0061e854 03334634 02ec7d94 02ec2780 02ec2780 browsercore+0x159515c
13 0061e974 046e67c9 02ec7d94 02ec7d94 02e63c30 browsercore+0x14634

   1  Id: 51fc.9920 Suspend: 1 Teb: 7efda000 Unfrozen
 # ChildEBP RetAddr  Args to Child              
00 02aff61c 76fa15e9 00000003 02aff66c 00000001 ntdll!ZwWaitForMultipleObjects+0x15 (FPO: [5,0,0])
01 02aff6b8 770319fc 02aff66c 02aff6e0 00000000 KERNELBASE!WaitForMultipleObjectsEx+0x100 (FPO: [SEH])
02 02aff700 770341d8 00000003 7efde000 00000000 kernel32!WaitForMultipleObjectsExImplementation+0xe0 (FPO: [5,8,4])

…………blablabla

然后我们可以很开心的看到:

#!bash
0:000> ~*kvn

.  0  Id: 51fc.a978 Suspend: 0 Teb: 7efdd000 Unfrozen
 # ChildEBP RetAddr  Args to Child              
00 0061bbe0 76fa149d 000002a8 00000000 00000000 ntdll!NtWaitForSingleObject+0x15 (FPO: [3,0,0])
01 0061bc4c 77031194 000002a8 ffffffff 00000000 KERNELBASE!WaitForSingleObjectEx+0x98 (FPO: [SEH])
02 0061bc64 77031148 000002a8 ffffffff 00000000 kernel32!WaitForSingleObjectExImplementation+0x75 (FPO: [3,0,4])
03 0061bc78 001056c7 000002a8 ffffffff 006e9948 kernel32!WaitForSingleObject+0x12 (FPO: [2,0,0])
WARNING: Stack unwind information not available. Following frames may be wrong.
04 0061dd78 00105467 ac68c25f 0010542d 7710030c baidubrowser+0x56c7
05 0061dda0 7706fffb 0061de58 ac7abd84 00000000 baidubrowser+0x5467
06 0061de28 77a674ff 0061de58 77a673dc 00000000 kernel32!UnhandledExceptionFilter+0x127 (FPO: [SEH])
07 0061de30 77a673dc 00000000 0061ff64 77a1c550 ntdll!__RtlUserThreadStart+0x62 (FPO: [SEH])
08 0061de44 77a67281 00000000 00000000 00000000 ntdll!_EH4_CallFilterFunc+0x12 (FPO: [Uses EBP] [0,0,4])
09 0061de6c 77a4b499 fffffffe 0061ff54 0061dfa8 ntdll!_except_handler4+0x8e (FPO: [4,5,4])
0a 0061de90 77a4b46b 0061df58 0061ff54 0061dfa8 ntdll!ExecuteHandler2+0x26 (FPO: [Uses EBP] [5,3,1])
0b 0061deb4 77a4b40e 0061df58 0061ff54 0061dfa8 ntdll!ExecuteHandler+0x24 (FPO: [5,0,3])
0c 0061df40 77a00133 0061df58 0061dfa8 0061df58 ntdll!RtlDispatchException+0x127 (FPO: [2,25,4])
0d 0061df40 033fbda7 0061df58 0061dfa8 0061df58 ntdll!KiUserExceptionDispatcher+0xf (FPO: [2,0,0]) (CONTEXT @ 0061dfa8)
0e 0061e2a8 045b7a85 0061e3e4 0061e634 00000001 browsercore+0xdbda7
0f 0061e2c4 0337100c 0061e3e4 0061e634 0061e7f4 browsercore+0x1297a85

我们看看它的上层函数做了什么:

#!bash
0:000> uf 045b7a85
No code found, aborting

居然显示没有代码,看来事后调试已经满足不了我们了,不过既然跑到了_alloca_probe,那真相就只有一个了!对,在栈上分配的东西太大了,比栈还大,比栈还牛逼,于是栈不干了,结果就抛个异常罢工了。

这只是一个小演示而已,反正我们没符号,反正我们没代码,但是我们有PoC,我们需要的是实时调试,下面进入正题。

0×03 实时调试


下面使用的目标程序是百度浏览器v4.5,由于我们的poc简单粗暴,为了防止代码一加载就崩,我们在它崩溃前加一个alert(1),给我们和windbg一个心理准备,使得poc变成下面这样:

#!html
<script>
var s="A";
var i=1;
for(i=1;i<599559;i++)
s+="A";
alert(1);
window.open(s);
</script>

运行浏览器,最好是直接把html拖图标上,这样之后方便我们找哪个进程容纳着html,如下:

2014030816544725513.png
  图: 暴风雨前夕

姨妈大,给它Attach上,

2014030816542723049.png
  图:这样就能找到pid了

找到对应进程的PID,然后Attach,之后按下g让它跑起来:

#!bash
(9df0.3724): Break instruction exception - code 80000003 (first chance)
eax=7ef42000 ebx=00000000 ecx=00000000 edx=77a8f8ea esi=00000000 edi=00000000
eip=77a0000c esp=0c52fe4c ebp=0c52fe78 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ntdll!DbgBreakPoint:
77a0000c cc              int     3
0:034> g
……省略
STATUS_STACK_BUFFER_OVERRUN encountered
(9df0.3500): Break instruction exception - code 80000003 (first chance)
eax=00000000 ebx=55b23f30 ecx=77070174 edx=0653d16d esi=00000000 edi=001b7281
eip=7706ff55 esp=0653d3b4 ebp=0653d430 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
kernel32!UnhandledExceptionFilter+0x5f:
7706ff55 cc              int     3

程序崩溃。查看一下是怎么产生的,执行kvn

#!bash
0:006> kvn
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Program Files (x86)\baidu\BaiduBrowser\bdlogicmain.dll - 
 # ChildEBP RetAddr  Args to Child              
00 0653d430 55a57789 55b23f30 34bb28ee cb44d711 kernel32!UnhandledExceptionFilter+0x5f (FPO: [SEH])
WARNING: Stack unwind information not available. Following frames may be wrong.
01 0653d764 5580757b 001b7281 656c6966 00000000 bdlogicmain!BrowserLogicInit+0x198229
02 0653f500 55a4b5f4 0f1a0020 12960020 001b7281 bdlogicmain+0x757b
03 0653f5b8 559f51a6 12700020 0653f5e4 11bef338 bdlogicmain!BrowserLogicInit+0x18c094
04 0653f5fc 558c5b95 12700020 0653f640 559b4105 bdlogicmain!BrowserLogicInit+0x135c46
05 0653f608 559b4105 062b2a1c 559f5040 00000000 bdlogicmain!BrowserLogicInit+0x6635
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Program Files (x86)\baidu\BaiduBrowser\bdcommon.dll - 
06 0653f640 653d18ec 0eed1240 0653f688 653d1b77 bdlogicmain!BrowserLogicInit+0xf4ba5
07 0653f64c 653d1b77 0653f664 00f7cac9 03329ce0 bdcommon!Util::Help::GetMimeTypeByExt+0x314a
08 0653f688 653cf6a9 ffffffff 00000000 03329ce0 bdcommon!Util::Help::GetMimeTypeByExt+0x33d5
09 0653f6ac 653ceb15 00000001 03329bb0 03329bb0 bdcommon!Util::Help::GetMimeTypeByExt+0xf07
0a 0653f6cc 653cf0f7 03329bb0 00000000 00000001 bdcommon!Util::Help::GetMimeTypeByExt+0x373
0b 0653f6e0 653cefcd 03329bb0 03329bb0 653cf8d5 bdcommon!Util::Help::GetMimeTypeByExt+0x955
0c 0653f724 653cfe36 0653f798 653d2576 03329bb0 bdcommon!Util::Help::GetMimeTypeByExt+0x82b
0d 0653f72c 653d2576 03329bb0 c5a8d143 00000000 bdcommon!Util::Help::GetMimeTypeByExt+0x1694
0e 0653f798 653d2a0c 0653f7d8 653dc835 032cdeb8 bdcommon!Util::Help::GetMimeTypeByExt+0x3dd4
0f 0653f7a0 653dc835 032cdeb8 c5a8d103 00000000 bdcommon!Util::Help::GetMimeTypeByExt+0x426a
10 0653f7d8 653dc8bf 00000000 0653f7f0 7703336a bdcommon!Util::Common::Timer::EraseTimerCallback+0x5b6b
11 0653f7e4 7703336a 03329bb0 0653f830 77a29f72 bdcommon!Util::Common::Timer::EraseTimerCallback+0x5bf5
12 0653f7f0 77a29f72 03329bb0 2c33d9fc 00000000 kernel32!BaseThreadInitThunk+0xe (FPO: [1,0,0])
13 0653f830 77a29f45 653dc85b 03329bb0 00000000 ntdll!__RtlUserThreadStart+0x70 (FPO: [SEH])

以下行为用于将上下文定位到我们出错的代码上,而不是kernel32里面

然后,由于我们在UnhandledExceptionFilter里面,我们查看一下错误信息,

#!bash
 # ChildEBP RetAddr  Args to Child   
00 0653d430 55a57789 55b23f30 34bb28ee cb44d711 kernel32!UnhandledExceptionFilter+0x5f (FPO: [SEH])

UnhandledExceptionFilter该函数的定义是(msdn当然是我们的好帮手,实在不济百度百科也凑合吧……):

#!bash
LONG WINAPI UnhandledExceptionFilter(
  _In_  struct _EXCEPTION_POINTERS *ExceptionInfo
);

那第一个参数必然指向_EXCEPTION_POINTERS,而该结构体的定义为:

#!bash
typedef struct _EXCEPTION_POINTERS {
  PEXCEPTION_RECORD ExceptionRecord;
  PCONTEXT          ContextRecord;
} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;

而这则正是我们需要的内容,于是,我们先看看这个结构体在哪儿

#!bash
0:006> dt _EXCEPTION_POINTERS 55b23f30
ATL80!_EXCEPTION_POINTERS
   +0x000 ExceptionRecord  : 0x55bb98a0 _EXCEPTION_RECORD
   +0x004 ContextRecord    : 0x55bb98f8 _CONTEXT

然后,我们查看异常的信息

#!bash
0:006> .exr 0x55bb98a0
ExceptionAddress: 5580757b (bdlogicmain+0x0000757b)
   ExceptionCode: c0000409 (Stack buffer overflow)
  ExceptionFlags: 00000001
NumberParameters: 0

设置异常上下文

#!bash
0:006> .cxr 0x55bb98f8
eax=00000000 ebx=0f1a0020 ecx=34bb2841 edx=00414141 esi=12960020 edi=001b7281
eip=5580757b esp=0653d76c ebp=0653f500 iopl=0         nv up ei pl nz ac pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000216
bdlogicmain+0x757b:
5580757b 8be5            mov     esp,ebp

现在我们的异常上下文已经被更正如上。

再回溯一次:

#!bash
0:006> kvn
  *** Stack trace for last set context - .thread/.cxr resets it
 # ChildEBP RetAddr  Args to Child              
WARNING: Stack unwind information not available. Following frames may be wrong.
00 0653f500 55a4b5f4 0f1a0020 12960020 001b7281 bdlogicmain+0x757b
01 0653f5b8 559f51a6 12700020 0653f5e4 11bef338 bdlogicmain!BrowserLogicInit+0x18c094
02 0653f5fc 558c5b95 12700020 0653f640 559b4105 bdlogicmain!BrowserLogicInit+0x135c46
03 0653f608 559b4105 062b2a1c 559f5040 00000000 bdlogicmain!BrowserLogicInit+0x6635
04 0653f640 653d18ec 0eed1240 0653f688 653d1b77 bdlogicmain!BrowserLogicInit+0xf4ba5
05 0653f64c 653d1b77 0653f664 00f7cac9 03329ce0 bdcommon!Util::Help::GetMimeTypeByExt+0x314a
06 0653f688 653cf6a9 ffffffff 00000000 03329ce0 bdcommon!Util::Help::GetMimeTypeByExt+0x33d5
07 0653f6ac 653ceb15 00000001 03329bb0 03329bb0 bdcommon!Util::Help::GetMimeTypeByExt+0xf07
08 0653f6cc 653cf0f7 03329bb0 00000000 00000001 bdcommon!Util::Help::GetMimeTypeByExt+0x373
09 0653f6e0 653cefcd 03329bb0 03329bb0 653cf8d5 bdcommon!Util::Help::GetMimeTypeByExt+0x955
0a 0653f724 653cfe36 0653f798 653d2576 03329bb0 bdcommon!Util::Help::GetMimeTypeByExt+0x82b
0b 0653f72c 653d2576 03329bb0 c5a8d143 00000000 bdcommon!Util::Help::GetMimeTypeByExt+0x1694
0c 0653f798 653d2a0c 0653f7d8 653dc835 032cdeb8 bdcommon!Util::Help::GetMimeTypeByExt+0x3dd4
0d 0653f7a0 653dc835 032cdeb8 c5a8d103 00000000 bdcommon!Util::Help::GetMimeTypeByExt+0x426a
0e 0653f7d8 653dc8bf 00000000 0653f7f0 7703336a bdcommon!Util::Common::Timer::EraseTimerCallback+0x5b6b
0f 0653f7e4 7703336a 03329bb0 0653f830 77a29f72 bdcommon!Util::Common::Timer::EraseTimerCallback+0x5bf5
10 0653f7f0 77a29f72 03329bb0 2c33d9fc 00000000 kernel32!BaseThreadInitThunk+0xe (FPO: [1,0,0])
11 0653f830 77a29f45 653dc85b 03329bb0 00000000 ntdll!__RtlUserThreadStart+0x70 (FPO: [SEH])
12 0653f848 00000000 653dc85b 03329bb0 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [2,2,0])

现在看起来舒服多了,我们可以清楚的看到崩溃的位置代码如下:

#!bash
0:006> ub bdlogicmain+0x757b
bdlogicmain+0x7565:
55807565 8b4dfc          mov     ecx,dword ptr [ebp-4]
55807568 83c40c          add     esp,0Ch
5580756b c6441eff00      mov     byte ptr [esi+ebx-1],0
55807570 5e              pop     esi
55807571 33cd            xor     ecx,ebp
55807573 33c0            xor     eax,eax
55807575 5b              pop     ebx
55807576 e823f82400      call    bdlogicmain!BrowserLogicInit+0x19783e (55a56d9e); 之后崩溃

找到崩溃点之后,阅读主程序代码,了解崩溃的原因

简单计算可知bdlogicmain+0×7576就是我们的目标,那么我们就在这儿看一下程序是如何崩溃的吧。

退出程序,重新打开主程序,直接Attach,然后bp bdlogicmain+0×7576:

#!bash
(a6c.8b4): Break instruction exception - code 80000003 (first chance)
eax=7ef4b000 ebx=00000000 ecx=00000000 edx=77abf8ea esi=00000000 edi=00000000
eip=77a3000c esp=0abafe40 ebp=0abafe6c iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ntdll!DbgBreakPoint:
77a3000c cc              int     3
0:031> bp bdlogicmain+0x7576
breakpoint 0 redefined
0:031> bl
 0 e 62067576     0001 (0001)  0:**** bdlogicmain+0x7576
0:031> g

之后载入PoC,断在:

#!bash
Breakpoint 0 hit
eax=00000000 ebx=0bcd0020 ecx=d8dfe9d1 edx=00414141 esi=28920020 edi=001b7278
eip=62067576 esp=002fc6fc ebp=002fe490 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200246
bdlogicmain+0x7576:
62067576 e823f82400      call    bdlogicmain!BrowserLogicInit+0x19783e (622b6d9e)

我们继续,可以看见这之中会检查是否有调试器加载(IsDebuggerPresentStub)并抛出异常,看来这个函数应该是处理异常用的,

#!bash
0:000> 
eax=27201628 ebx=0bcd0020 ecx=d8dfe9d1 edx=00414141 esi=28920020 edi=001b7278
eip=622b7763 esp=002fc3cc ebp=002fc6f4 iopl=0         nv up ei pl nz ac pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200216
bdlogicmain!BrowserLogicInit+0x198203:
622b7763 ff150cf12e62    call    dword ptr [bdlogicmain!BrowserLogicInit+0x1cfbac (622ef10c)] ds:002b:622ef10c={kernel32!IsDebuggerPresentStub (756249fd)}
0:000> 
eax=27201628 ebx=0bcd0020 ecx=d8dfe9d1 edx=00414141 esi=28920020 edi=001b7278
eip=756249fd esp=002fc3c8 ebp=002fc6f4 iopl=0         nv up ei pl nz ac pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200216
kernel32!IsDebuggerPresentStub:
756249fd eb05            jmp     kernel32!IsDebuggerPresent (75624a04)
0:000> k
ChildEBP RetAddr  
002fc3c4 622b7769 kernel32!IsDebuggerPresentStub
WARNING: Stack unwind information not available. Following frames may be wrong.
002fc6f4 6206757b bdlogicmain!BrowserLogicInit+0x198209
002fe490 622aeb21 bdlogicmain+0x757b

那么这个bdlogicmain+0×7576附近又有什么代码呢?既然这儿处理异常了,那肯定是之前某个地方出了问题,让我们使用uf查看一下代码:

#!bash
bdlogicmain+0x7550:
62067550 8bb56ce2ffff    mov     esi,dword ptr [ebp-1D94h]
62067556 56              push    esi
62067557 8d95fcefffff    lea     edx,[ebp-1004h]
6206755d 52              push    edx
6206755e 53              push    ebx
6206755f ff15c4f42e62    call    dword ptr [bdlogicmain!BrowserLogicInit+0x1cff64 (622ef4c4)]
62067565 8b4dfc          mov     ecx,dword ptr [ebp-4]
62067568 83c40c          add     esp,0Ch
6206756b c6441eff00      mov     byte ptr [esi+ebx-1],0
62067570 5e              pop     esi
62067571 33cd            xor     ecx,ebp
62067573 33c0            xor     eax,eax
62067575 5b              pop     ebx
62067576 e823f82400      call    bdlogicmain!BrowserLogicInit+0x19783e (622b6d9e) ;fails here
6206757b 8be5            mov     esp,ebp
6206757d 5d              pop     ebp
6206757e c3              ret

看起来这个函数很像是最终用来检测Security Cookie的函数。让我们对比一下其他Security Cookie的处理调用:

#!bash
mov  ecx, [ebp+SOMETHING]              ; get the adjusted cookie.
xor  ecx, ebp                          ; un-adjust it, since
                                       ;   ((N xor X) xor X) == N.
call @__sec_check_cookie               ; check the cookie.

不直观吗?看看随便一个C++程序的编译后结果:

#!bash
.text:004010D4                 mov     ecx, [ebp+var_4]  
.text:004010D7                 xor     ecx, ebp  
.text:004010D9                 xor     eax, eax  
.text:004010DB                 pop     esi  
.text:004010DC                 call    @[email protected] ; __security_check_cookie(x)   ;对比下其他程序,其实就是__security_check_cookie失败了
.text:004010E1                 mov     esp, ebp  
.text:004010E3                 pop     ebp  
.text:004010E4                 retn  
.text:004010E4 _wmain          endp  

好吧,看来我们得出第一个结论:Security Cookie校验失败了。

那我们在函数稍前的位置(除了Security cookie check的上一个Call)设置断点:

#!bash
0:030> bp bdlogicmain+0x7550
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Program Files (x86)\Baidu\BaiduBrowser\bdlogicmain.dll - 
0:030> g

重复上述步骤,断在了:

#!bash
Breakpoint 0 hit
eax=00000000 ebx=28a80020 ecx=4b8aa8e3 edx=00000420 esi=0038c220 edi=001b7278
eip=6c3e7550 esp=0038c214 ebp=0038dfb0 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200246
bdlogicmain+0x7550:
6c3e7550 8bb56ce2ffff    mov     esi,dword ptr [ebp-1D94h] ss:002b:0038c21c=001b7278
0:000> u
bdlogicmain+0x7550:
6c3e7550 8bb56ce2ffff    mov     esi,dword ptr [ebp-1D94h]
6c3e7556 56              push    esi
6c3e7557 8d95fcefffff    lea     edx,[ebp-1004h]
6c3e755d 52              push    edx
6c3e755e 53              push    ebx
6c3e755f ff15c4f4666c    call    dword ptr [bdlogicmain!BrowserLogicInit+0x1cff64 (6c66f4c4)]
6c3e7565 8b4dfc          mov     ecx,dword ptr [ebp-4]
6c3e7568 83c40c          add     esp,0Ch

我们可以看到有一个可能有三个参数的函数调用:

#!bash
6c3e7556 56              push    esi
6c3e7557 8d95fcefffff    lea     edx,[ebp-1004h]
6c3e755d 52              push    edx
6c3e755e 53              push    ebx
6c3e755f ff15c4f4666c    call    dword ptr [bdlogicmain!BrowserLogicInit+0x1cff64 (6c66f4c4)]

由于没有符号,我们只好t步入:

#!bash
0:000> t
eax=00000000 ebx=28a80020 ecx=4b8aa8e3 edx=00000420 esi=001b7278 edi=001b7278
eip=6c3e7556 esp=0038c214 ebp=0038dfb0 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200246
bdlogicmain+0x7556:
6c3e7556 56              push    esi
0:000> dd esi
001b7278  00000000

看来第三个参数是0,

#!bash
0:000> t
eax=00000000 ebx=28a80020 ecx=4b8aa8e3 edx=00000420 esi=001b7278 edi=001b7278
eip=6c3e7557 esp=0038c210 ebp=0038dfb0 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200246
bdlogicmain+0x7557:
6c3e7557 8d95fcefffff    lea     edx,[ebp-1004h]
0:000> 
eax=00000000 ebx=28a80020 ecx=4b8aa8e3 edx=0038cfac esi=001b7278 edi=001b7278
eip=6c3e755d esp=0038c210 ebp=0038dfb0 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200246
bdlogicmain+0x755d:
6c3e755d 52              push    edx
0:000> dd edx
0038cfac  656c6966 2f2f2f3a 552f3a45 73726573
0038cfbc  616c422f 53547473 7365442f 706f746b
0038cfcc  4141412f 41414141 41414141 41414141
0038cfdc  41414141 41414141 41414141 41414141
0038cfec  41414141 41414141 41414141 41414141
0038cffc  41414141 41414141 41414141 41414141
0038d00c  41414141 41414141 41414141 41414141
0038d01c  41414141 41414141 41414141 41414141
0:000> t
eax=00000000 ebx=28a80020 ecx=4b8aa8e3 edx=0038cfac esi=001b7278 edi=001b7278
eip=6c3e755e esp=0038c20c ebp=0038dfb0 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200246
bdlogicmain+0x755e:
6c3e755e 53              push    ebx

第二个参数,edx现在存储着指向我们字符串的指针,而且字符只有0×1004字节。

看看第一个参数ebx,这是它的信息:

#!bash
0:000> !address 28a80020
 ProcessParametrs 007607f0 in range 00760000 00860000
 Environment 09e98c48 in range 09e10000 0a210000
    28a80000 : 28a80000 - 001b8000
                    Type     00020000 MEM_PRIVATE
                    Protect  00000004 PAGE_READWRITE
                    State    00001000 MEM_COMMIT
                    Usage    RegionUsageHeap
                    Handle   07790000

它指向一片空的内存,应该是刚刚申请的,指针位于头部后0×20个字节。

#!bash
0:000> ?(28c38000-28a80000)
Evaluate expression: 1802240 = 001b8000

这片内存堆的可用大小为1802240字节。

这三个参数都知道了(1:ebx,一个很大空间的缓冲区, 2:edx,指向我们地址的指针, 3:0),继续走,

#!bash
0:000> t
eax=00000000 ebx=28a80020 ecx=4b8aa8e3 edx=0038cfac esi=001b7278 edi=001b7278
eip=6c3e755f esp=0038c208 ebp=0038dfb0 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200246
bdlogicmain+0x755f:
6c3e755f ff15c4f4666c    call    dword ptr [bdlogicmain!BrowserLogicInit+0x1cff64 (6c66f4c4)] ds:002b:6c66f4c4={MSVCR100!strncpy (6d0e2ad0)}

我们可以看到它其实是strncpy,这样,我们就知道它的调用了。

我们p出来:

#!bash
0:000> p
eax=28a80020 ebx=28a80020 ecx=00000000 edx=00414141 esi=001b7278 edi=001b7278
eip=6c3e7565 esp=0038c208 ebp=0038dfb0 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00200246
bdlogicmain+0x7565:
6c3e7565 8b4dfc          mov     ecx,dword ptr [ebp-4] ss:002b:0038dfac=4bb27741

看看返回值上有什么:

#!bash
0:000> dd eax
28a80020  656c6966 2f2f2f3a 552f3a45 73726573
28a80030  616c422f 53547473 7365442f 706f746b
28a80040  4141412f 41414141 41414141 41414141
28a80050  41414141 41414141 41414141 41414141
28a80060  41414141 41414141 41414141 41414141
28a80070  41414141 41414141 41414141 41414141
28a80080  41414141 41414141 41414141 41414141
28a80090  41414141 41414141 41414141 41414141

确实是拷贝进去了。

0×04 试验


试验环境:

1&2、VS2010,Debug+Release,也就是分别看看文中的malloc(0)会弄出什么情况。
3&4、gcc,Debug+Release查看malloc(0)是什么情况。
所谓正确的实践是检验真理的唯一标准,我们分别看看会产生什么问题。
注:以下都是从调试器直接启动的,堆是调试器友好的。

0x04a 直观的编译、运行

1、VC2010 Debug + malloc(0)

2014030816534264313.png
  返回了一个非NULL的堆。

2014030816531984074.png
 

损坏。

2、 VC2010 Release + malloc(0)

2014030816524787877.png
  情形类似

HEAP[testMalloc.exe]: Heap block at 005455B8 modified at 005455C1 past requested size of 1 Windows 已在 testMalloc.exe 中触发一个断点。

其原因可能是内存损坏,这说明 testMalloc.exe 中或它所加载的任何 DLL 中有 Bug。

原因也可能是用户在 testMalloc.exe 具有焦点时按下了 F12。

输出窗口可能提供了更多诊断信息。 程序“[4256] testMalloc.exe: 本机”已退出,返回值为 0 (0×0)。

3、gcc Debug+malloc(0)

2014030816522134427.png
  分配了一个非NULL但是可以释放的堆。

2014030816515591239.png
  成功覆盖了堆的信息

2014030816512347292.png
  同样崩溃

4、gcc Release+malloc(0)

2014030816505532197.png
  情形类似

果不其然四个情况下都表明:VS和G++编译器编译后malloc(0)返回的不是NULL,而是一个活生生的堆,而且你动它一下,它就赖地上让你赔钱了。

0x04b 具体发生了什么?

虽然和浏览器堆破坏的这一例不一样,但是为了演示一下malloc(0)分配的内存到底为何不能操作,我们还是使用VC2010+Relase+malloc(0) 32位环境下的程序来试试看吧:

0x04b.1 编译源程序

我们模拟一下那个创建小窗口前的一些准备工作:

#!cpp
// testMalloc.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <stdlib.h>
#include <string>
#include <windows.h>

#define URL_LENGTH 26

int _tmain(int argc, _TCHAR* argv[])
{
    TCHAR * test = (TCHAR *)malloc(0); //很不幸,这儿的参数是0
    memset(test, 0x11 , URL_LENGTH); //为了方便查看,我把改成了0x11

    TCHAR url[27] = _T("ABCDEFGHIJKLMNOPQRSTUVWXYZ"); 
    //正好26字,假设我们的url就是这个拉

    int i = WideCharToMultiByte(CP_ACP, 0, url, -1, NULL, 0, 0,FALSE); 
    char * szTmp = new char[i];
    WideCharToMultiByte(CP_ACP, 0, url, -1, szTmp, i, 0, FALSE);

    memcpy(test, szTmp, i); //复制到我们的堆中

    free(test);

    return 0;
}

0x04b.2 载入windbg中:

在wmain处下断点,l-t之后并用p执行完malloc,然后查看当前分配来的堆的信息:

2014030816503217634.png
  可以看到当前的堆内数据如下,其中数据使用了堆的填充模式abababe8 abababab,而这看起来像是一个已分配的堆空间的末尾的填充模式(为什么呢?因为如果分配了字节的话,堆会用baadf00d这个填充模式来填充将来可能会被使用的空间,而这个abababe8 blablabla则是像是这个填充模式的末尾,或者,更像是说ANSI字符串的中止符号“”的东西):

2014030816500856704.png
  这是memset完,我们26个字符填充进去之后的效果:

2014030816494124061.png  

这个相当于什么?相当于在一个堆的用户可操作区域之外做写入操作,这样数据就已然超出了堆的申请大小,让它活生生的演变成了堆溢出,之后程序还会对它进行free,必然是错上加错。

0x04b.3 证明我们的想法

让我们换一点代码,我们把malloc(0)修改为malloc(27),重新载入。

2014030816490529344.png

这回,我们看到了一个新的填充模式,baadf00d ,而这正是我们之前提到的可用但是未初始化的堆空间的填充模式。

而如此如此运行之后,程序正常退出。

2014030816484971812.png

0x04c 几个小名词

调试友好

当在调试器下启动进程的时候,堆管理器将修改所有新堆的请求,并且设置创建标志,用于启用“调试友好”的堆,这会应用到内存里面所有堆,包括默认的进程堆,这种堆与原先的最大的差异是堆块中包含了额外的“填充模式”区域,这个区域位于用户可访问的部分之后,堆管理器通过这个区域来确认堆的完整性。如果这个填充模式被修改了,堆管理器会立刻中断进入调试器内。

填充模式

堆管理器在分配堆时自动的使用某个填充模式来初始化某片内存,填充模式的内容取决于堆块的状态,当堆块最初被返回给调用者时,堆管理器将使用填充模式来填充堆块中用户可访问的部分,其中在填充模式中包含的值就是baadf00d,这表示这个堆块虽然分配成功,但是没有初始化。如果有程序在没有初始化堆块之前就对其进行解引用操作,就会产生一个存取违例的异常;如果程序正确的初始化了堆块,那么程序将继续执行,这个堆块被释放后,堆管理器将再次对堆块中用户可以访问的部分进行填充,使用的值是feeefeee。设置完这些个填充模式之后,调试器将通过检测这个填充模式的值是否被修改,来跟踪在释放后堆块上所发生的访问操作。  

__alloca_probe

这个函数在栈上分配空间,超过0×1000的每次按0×1000的大小(如果剩余未分配的大小大于0×1000的话)不断在栈上分配内存(sub ecx,1000h; sub eax,1000h; test [ecx],eax; ecx是之前参数在栈中的地址,这样可以检测是否真的分配上了0×1000字节),test [ecx],eax用来检查是否栈已经不够用了(栈的大小是固定的,所以如果分配的地址取不到的话[ecx]就会存取违例了),如果已经撑爆了通常会报告“Stack Overrun”(也会看到Stack Overflow)的报警。

Security cookie

当应用程序启动时,程序的cookie(4字节(dword),无符号整型)被计算出来(伪随机数)并保存在加载模块的.data节中,在函数的开头这个cookie被拷贝到栈中,位于EBP和返回地址的正前方(位于返回地址和局部变量的中间)。 

[buffer][cookie][savedEBP][savedEIP]

在函数的结尾处,程序会把这个cookie和保存在.data节中的cookie进行比较。 
如果不相等,就说明进程栈被破坏,进程必须被终止。 

为了尽量减少额外的代码行对性能带来的影响,只有当一个函数中包含字符串缓冲区或使用_alloca函数在栈上分配空间的时候编译器才在栈中保存cookie。另外,当缓冲区至少于5个字节时,在栈中也不保存cookie。 

在典型的缓冲区溢出中,栈上的返回地址会被数据所覆盖,但在返回地址被覆盖之前,cookie早已经被覆盖了,因此就导致了exploit的失效(但仍然可以导致拒绝服务),因为在函数的结尾程序会发现cookie 已经被破坏,接着应用程序会被结束。

BUCKET

微软分类崩溃类型,用一系列牛逼算法所生成并使用的一个标识符号。

0×05 修复


官方是修复了,不过纵观全局他这代码也太粗心了,俺的最大的建议就是每个返回值都检查一遍,千万别嫌麻烦……

0×06 参考资料


Mario Heweardt, Daniel Pravat 《windows高级调试》

http://bbs.pediy.com/archive/index.php?t-126858.html

http://msdn.microsoft.com/en-us/library/ee480951.aspx