Intro: Simple analysis of CVE-2018-0840
Reference
POC
1 | function opt(arr, index) { |
Root Cause
1 | template <class Fn> |
SetImplicitCallFlag 函数调用在 implicitCall 函数之后,这是极其危险的:倘若 implicitCall 函数中执行 throw, SetImplicitCallFlag 函数将不会被运行。进一步来看,如果 throw 以某种方式被忽略掉的话,那么我们就可以 bypass ImplicitCallFlags checks。比较巧合的是,typeof 可以做到这一点,原因来自于
Opcode的设计缺陷Try Catch & Throw的C++处理
Analysis
首先在 Js::Var ExecuteImplicitCall 断下来,查看函数调用栈:

我们可以初步判断编译器的行为如下:
JIT代码内调用了TyeofElem的Runtime代码- 在
TyeofElem内调用了GetIndexType GetIndexType最终调用了ExecuteImplicitCall
结合漏洞描述,我们可以预知的接下来的行为可能是:
ExecuteImplicitCall调用了lamda表达式/ImpliciteCalllamda表达式/ImpliciteCall内部调用解释器处理js代码- (
unkown) TyeofElem捕捉到throw
( unkown ) 的地方是暂时不确定 Chakracore 如何处理 js 的 throw 语句,以及如何将 throw 传递到外部的 C++ 代码中。当然我们会在后面进行具体的调试。
断在 Js::Var ExecuteImplicitCall 之后,继续单步执行
单步步过
程序来到了 TyeofElem 的异常处理代码。
疑问是,implicitCall 如何产生 throw,接下来使用 TTD 调试。
TTD 调试
针对 TTD 调试,有一个技巧是:
- 找到一个比较上层的函数设置断点
- 单步步过并在每一个函数调用的位置设置断点
- 如果某次步过触发了大的跳转或是
Crash,则Go Back回到上一个状态,然后单步步入。 - 继续第二步,知道准确定位问题函数。
例如本次分析我们首先在 JavascriptOperators::TypeofElem 函数下断点


单步步过,一路设置断点,跳转到 catch。我们想弄清楚如何 chakra 如何发出 throw 的。
因此 Goback,回到上一个断点,并步入。


同理接着往下执行,最终来到了解释器选择 opcode 的地方
这里值得注意的地方是栈帧窗口,描述了解释器调用一个 js 函数的基本过程:

继续按照上述方式执行。
若干步之后,程序执行到了 chakracore!_CxxThrowException(该函数需要在反汇编窗口调试) 。实际上这里我们就可以大致通过名称猜出,该函数产生了一个 C++ 可以捕捉的异常。
对应的函数调用栈如下
注意到 chakracore!_CxxThrowException 在末尾调用了 chakracore!_imp_RaiseException ,紧接着调用 KERNELBASE!_imp_RtlRaiseException 完成异常抛出。

回过头来看,除了 JavascriptOperators::TypeofElem 内部的 catch 模块,我们未在其他位置遇到异常处理。因此该 catch 会捕捉 chakracore!_CxxThrowException 产生的异常。
因此我们可以补全编译器的行为:
ExecuteImplicitCall调用了lamda表达式/ImpliciteCalllamda表达式/ImpliciteCall内部调用解释器处理js代码- 解释器处理
throw opcode的时候产生了C++可以捕捉的异常 TyeofElem捕捉到throw
Patch

Patch 分析
- 针对
throw之后跳过的flag更新的代码进行析构处理
Patch 策略
- 由于
chakra针对try/catch/throw的处理均转化为C++的处理,因此原本在Callback调用js function之后有一些其他的工作,但是由于解释器在解释throw的时候直接转化为C++的throw,这些其他的工作被忽略了。 - 对应的
Patch是试图采用析构函数来补救被忽略的代码 - 该
patch中忽略的工作是ImplicitCallFlag的更新
对漏洞挖掘的启发
- 本漏洞的
root cause在于chakra的try catch throw的设计模式的缺陷,patch并没有从根本上解决这个问题,仅仅弥补了在ImplicitCall的时候由于throw产生的关键代码跳过的问题,并且该关键代码仅仅指ImplicitCallFlag更新。因此可以从以下角度出发审计源码:- 除了
ImplicitCall,还有什么逻辑是可以调用用户js来执行throw的,以及可以绕过什么关键的函数? - 除了
ImplicitCallFlag,ImplicitCall中调用throw还可以忽略什么问题?
- 除了
Resource
【+】https://xlab.tencent.com/cn/2015/12/24/bypass-cfg-using-chakra-engine/