ღ Miranda

CVE-2017-0234

CVE-2017-0234Chakra引擎在进行JIT代码优化的时候优化过度,去掉了数组边界检查而导致的漏洞。

首先在github上找到对应的有漏洞的ChakraCore版本,可以在release-note里搜索CVE-2017-0234,发现:

那么最后一个有漏洞的版本就是v1.4.3,在release版里提供了二进制文件和源代码文件,binary文件包含了如下内容:

其中ch.exe就是chakracore的可执行文件,可以直接解释执行.js文件,ch.pdb是符号文件,也可以用源代码手动编译,可以进行源码调试。

poc代码如下:

cmd中运行ch.exe:

可知当前版本v1.4.3.0,用法是ch.exe <js file>

接下来就可以用windbgChakraCore进行调试,首先在gflag.exe中对ch.exe开启page heapheap taggingstack trace database

win7上用windbg打开ch.exe得到崩溃:

但在win10上似乎不会产生崩溃,所以接下来还是手动编译,使用VS2015编译源文件,在win10上产生崩溃:

由栈回溯可知是interpreterstackframe.cpp这个文件中的函数出现了问题,DoLoopBodyStart中调用了CallLoopBody,最后执行了JIT代码导致了崩溃,相关代码如下:

CallLoopBody的参数就是JIT代码的地址,在LoopEntryPointInfo结构体偏移0x18处:

InterpreterStackFrame::CallLoopBody:

this->CallLoopBody(entryPointInfo->jsMethod);和漏洞发生点下断点,由poc代码可知,write函数中的循环生成了JIT代码,在调用write(0,0x4000,1,0x1234);不会有问题,而在调用第二次write时发生了越界,JIT代码如下:

溢出时rsi是数组基地址,r9是数组索引,显然是索引过大导致了溢出,可以看到数组缓冲区内有第一次write填进去的值0x1234:

看函数调用流程,根据栈回溯信息可知,在不调用JIT代码时,调用关系如下:

InterpreterHelper中调用Process()函数,Process函数处理当前执行js代码中的单次循环,在Process内返回ProcessProfiled()函数,ProcessProfiled是一个宏定义的函数,指向了INTERPRETERLOOPNAME这个函数,这个函数处理bytecode,将bytecode使用对应的函数处理,这个循环涉及到的主要有:

在没有JIT代码时,每次循环都会被OP_ProfiledLoopBodyStart处理,每次currentLoopCounter都会加一,然后调用ProfiledLoopBodyStart。在LoopEntryPointInfo结构体偏移0xd0的地方是currentLoopCounter当值为0x97的时候就会把循环转换成JIT执行。

通过下断点调试可知array缓冲区地址为000000b7735c0000,可以看到,当处于JIT代码内时,000000b7735c0260之前已经填入了0x1234,也就是说,在循环了300次之后,解释引擎开始生成了JIT代码,并且之后的赋值都在JIT中完成。

DoLoopBodyStart中有如下代码,loopInterpretCount0x96,当loopHeader->interpretCount也就是currentLoopCounter0x97的时候会进入如下的判断中:

GenerateLoopBody函数生成JIT代码,调用的是NativeCodeGenerator::GenerateLoopBody:

加入队列后,就会有单独的线程去把循环变成JIT代码,有了JIT后,第二次调用write时函数流程为:

省去了循环计数。

根据github上关于CVE-2017-0234的修补,出问题的代码如下:

isProfilableStElem的值为true导致进入了if判断,当传入的opcodeStElemC时,isProfilableStElem被置为true:

并且赋值eliminatedLowerBoundCheckeliminatedUpperBoundChecktrue,去掉了边界检查。在GlobOpt::OptArraySrc下断点,当currentLoopCounter0x97的时候进入了JIT代码的生成过程,断在了GlobOpt::OptArraySrc函数中,调用栈如下:

在单独线程中调用了NativeCodeGenerator::CodeGen生成了JIT代码,然后对代码使用GlobOpt类进行优化,在JIT代码生成后被后台线程FunctionBody::SetLoopBodyEntryPoint加入到LoopEntryPointInfo结构体中,下次循环执行时就会执行JIT

对应修改:

增加了对是否使用asm.js的判断,并且在对边界检查进行优化时检查了边界。

发表评论

电子邮件地址不会被公开。