CVE-2015-2425
是Hacking team
泄露出来的一个IE11
的0day
漏洞,影响了IE11及之前的版本。在一封Hacking Team
高层收到的来自Vectra Networks
安全公司的信件中被发现。Vectra Networks
公司的研究者在信中向Hacking Team
提供了对于Windows 7/8.1最新版的IE11的poc
代码。但Hacking Team
并没有购买,所以只泄露了poc
,并没有攻击代码。
测试环境是win8.1
32位,IE版本是IE11。
poc.html
:
把IE11附加到windbg
上,然后运行poc.html
,IE11崩溃到一个无法读取的地址:
由于崩溃在一个不可访问的地址,不好确定之前的指令,这时需要用到与栈回溯相关的命令,就是windbg
中的k
一系列命令:
每一行描述当前的一个栈帧,最上面的一行描述的是当前指令的返回地址:
由此可知崩溃的返回地址是63dfcf3c
,在JavascriptFunction::CallFunction<1>
中,看当前的esp
也可以得出同样的结论:
为了看到函数是怎么调用的,需要用到.frame
命令,使用.frame /c 1
回到崩溃栈的第一层,也就是上层函数调用时的状态:
看到回到了上层函数中,eip
的值为崩溃处的返回地址,在反汇编窗口可以看到上层函数,也可以用u
命令:
产生崩溃的地方是63dfcf39
的call
函数,调用的是[ebp-1Ch]
处的函数指针,在IDA
中看一下CallFunction<1>
的定义:
这个函数是__fastcall
方式调用的,__fastcall
是一种快速调用方式,规定将前两个参数由寄存器ecx
和edx
来传递,其余参数还是通过堆栈传递(从右到左),不同编译器编译的程序规定的寄存器不同。在Intel 386
平台上,使用ECX
和EDX
寄存器。
往前找更改ebp-1Ch
内容的指令,只有63dfcec6
处的mov
指令:
edx
是函数的第二个参数,也就是一个函数指针,所以63dfcf39
处的指令是调用了函数指针所指向的函数,这样的话,还需要向上层看,到底是什么样的函数指针,回到上一层函数:
看到edx
来源是[eax+0Ch]
,而eax
的来源是[ebx+4]
,ebx
来源于下面一句:
看一下这个函数的定义:
__thiscall
为了解决类成员调用中this指针传递而规定的,__thiscall
要求把this
指针放在特定寄存器中,该寄存器由编译器决定。VC
使用ecx
。所以这里ecx
里的指针就是this
指针。梳理一下这里的过程:
推测这里调用了某个对象的成员函数,而这个对象是JavascriptFunction
对象。
弄清楚后我们回到崩溃点所在的函数内,选择在call [ebp-1Ch]
处下断点,断点会多次触发,触发5次后,停下,步入函数看:
在63e0631b
处的call
后,eax
就会变成一段已经释放内存的地址,看到这句的jmp
指令跳到了eax
指向的值,把这段函数汇编看一下:
eax
里存放的应该是NativeCodeGenerator::CheckCodeGen
的返回值,这次在这个函数上下断点再次调试,会触发六次断点,在返回前调用了NativeCodeGenerator::CheckCodeGenDone
将eax
置为了不可访问的地址:
看看NativeCodeGenerator::CheckCodeGenDone
,这次还在调用NativeCodeGenerator::CheckCodeGen
上下断点,六次断点后进入CheckCodeGenDone
,发现里面还有一个call
改变了eax
的值,然后还有mov eax,edi
:
再次用相同的方式调试,这次进入ScriptFunction::UpdateThunkEntryPoint
看看,这个函数如下:
63e06604
的mov
语句改变了eax
:
这个函数返回后有一个mov eax,esi
的指令,但这里edi
值也已经是返回后eax
的值了,看来还要追踪edi
,看一下CheckCodeGenDone
函数:
edi
是作为参数传入UpdateThunkEntryPoint
的,并且在函数内没有被改变,那么还要往回追踪,经过回溯,从CheckCodeGenDone
函数开头开始跟,由于中间还有跳转,所以edi
的值还会改变,跟到edi
变成崩溃时的返回地址时可以得出:
那么还需要跟踪一下esi
,可以看到来自eax
,而eax
来自ecx
:
所以ecx
中是CheckCodeGen
的第一个参数:
是ScriptFunction
对象,说明在最后一次调用CheckCodeGen
前这个对象就已经被释放了,用poi
看看指针引用的过程,通过三次引用找到了那块已经释放的内存:
重启后在第五次断在63e0631b
时,ebx
指向了ScriptFunction
对象,这时看一下触发漏洞的指针,然后在第六次时看一下指针:
两次指针并不相同,说明指针被改写了一次,重新运行一样在第五次断点被触发时看一下这里的内存,此时还没有被改写:
在这里可以下一个内存写入断点,看看什么时候被改写了:
真正的写入发生在上一句mov
语句,这时的esi
为05a49120
,eax
为05a50000
:
而eax
里的是上一个call
的返回值,也就是InterpreterThunkEmitter::GetNextThunk
的返回值,这次在63e06cd7
下断点,会触发五次,最后一次时步入函数:
由于这个函数是__thiscall
,所以ecx
就是this
指针,63e06df5
就是把类的一个成员赋给了eax
。
为IE开启堆页,命令是:
再次下断点调试,看看这里堆的情况:
这时eax
指向的内容还没有被释放,是一段函数代码:
那我们要找到这段内存是如何释放的,还是回到63e06cd7
断点处,这次不进入函数,步过后那段内存并没有被释放,为了弄清楚在哪里被释放,给this
指针和那段uaf
的内存下访问断点:
断点触发时,09f90000
还没有被释放,函数返回时已经被释放了,内存就是用FreeAllocations
来释放,在EmitBufferManager
类中还有NewAllocation
函数,应该是分配内存的函数。
所以漏洞的成因应该是在内存被FreeAllocations
释放后又在JavascriptFunction::CallFunction<1>
中使用而造成的UAF
。由于这个漏洞的返回地址不可控,所以要用堆喷的方法的话可能还需要结合其他方法来绕过DEP
和ASLR。