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
表达式/ImpliciteCall
lamda
表达式/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
表达式/ImpliciteCall
lamda
表达式/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/