# IE安全系列之：中流砥柱（II）—Jscript 9处理浅析

Author: 乌云历史资料库 (@wooyun_archive)
Published: 2015-09-08T02:33:00Z
Canonical: https://wepostx.com/topics/1019

> 乌云历史资料归档
>
> **原始作者：** blast
> **原始编号：** superkieran-wooyundrops:746
> **原始发布时间：** 2015-09-08 10:33
> **声明：内容仅用于技术研究和个人使用，版权归 wooyun.org。**

---

本文将简要介绍Jscript 9 （Charka）引擎处理数字、对象、函数操作的一些内容。

# 0x00 Jscript9 的安全机制

IE9开始，微软使用了全新的JavaScript引擎—工程代号Charka版本9的JavaScript引擎。而IE8中它的版本还是5，也就是说5到9之间其实没有什么其他版本了，既然版本号跳跃如此之大，那Charka（下均称Jscript 9）到底有什么改进呢？
首先，与Jscript 5不同的是，JScript9引入了一个新的JIT，由此指令的执行变得更加线性化，不用像之前一样跳来跳去的执行各个分支，源代码经过解析器生成AST，然后经过字节码生成器产生字节码并由其JIT解释成机器码并执行。也达到了之前类似Malzilla Firefox所称Javascript执行性能已经达到原生C++编译出的代码的水平。
本文中我们将关注：

- 1、常量（数字、字符串）是如何表现在字节码中的以及代码优化和MSVC的编译器的异同之处；

- 2、代码的跳入跳出逻辑，对象以及函数的调用；
大改之后的JScript，由于引入了字节码执行方式，相对于之前的解释型执行，必然会为攻击者提供一些新的思路—ROP。
字节码的编译过程中，如果像编译普通EXE一样产生字节码，必然会遇到如下的问题：

- 1、生成代码固定、可预测；

- 2、内存中执行的这些代码基本受到攻击者控制；
在微软的层层保护之下，如果字节码不设防，那么从字节码中进行ROP将会是一个十分轻松愉悦的过程。由攻击者控制的JS代码（和生成的字节码）中产生Gadget将会十分简单，这个可能给UAF类型的漏洞的利用常严重的影响，会大幅降低攻击者攻击的难度。
因此，在JScript9的JIT引擎中，引入了大量的安全概念[1](http://drops.wooyun.org/wp-content/uploads/2015/08/117.png)：
（1）代码是默认开启DEP的；
（2）代码对齐随机化，字节码存放在各个新申请的内存中，“顶格”放可能会被预测地址，因此在对齐方面预留了这样的一手；
（3）随机NOP插入，也是同样防止攻击者预测指令地址的，在调试JIT生成的字节码中，你也许会发现有很多莫名其妙的指令，例如mov ecx,ecx， lea ecx, [ecx]，不用理会，这些都是它插入的NOP；
（4）常量加密存储，你会发现DWORD值在字节码中并不是按照原样存放的，而是xor了一个随机的值存放，并在使用它的时候再次xor解开；
（5）以及JIT内存分配的上限，以及页的随机化。
让我们看一下最为直观的（4），在阅读NDSS15的一篇论文时，我关注到了作者所述“Charka中小于2字节的常量都是XOR一个值并存放起来，并在使用时解开”。但是在之后的调试中我发现这个描述是不正确的。看起来也是作者的笔误。
经过调试，可知整数的存取整理如下：
32位IE中，
（1）2 byte以上32位整数常量全部以(n * 2 + 1) xor (some value)过的形式存在内存中；xor key随机，需要一段代码解开才能继续使用；
（2）n > 0x7fffffff的值无法* 2 + 1放在EXX寄存器中了，这个时候会转入SSE寄存器中（xmmX）来处理；
（3）2 byte以下常量将以n * 2 + 1的方式保存，并在需要使用时将其右移一位再使用。
以下是具体的例子：

#### 1、“2Byte” 以下的正整数和负整数。
2 byte以下常量将以n * 2 + 1的方式保存，并在需要使用时将其右移一位再使用。
正常来说，32位整数，包括负数在Chakra中均采用有符号数来处理，那-1即0xffffffff，让我们通过实例来确定是否存在这个情况。
考虑如下JS代码： var a = 1; var b = -1; a--; b++;
挂上调试器，栈中看到这种解释不出来名字的，跟在Js::JavascriptFunction::CallFunction的，就是编译出来的字节码：

![原文图片](/media/2016/06/d6399b79bb3bb072e86e316b42152665)

该地址所在的内存页的Allocation Base基本就是函数开头，观察该函数。
首先可以看到大量的NOP插入，如箭头处，感觉没有什么大作用的语句会让想从这里拿到ROP的片段的攻击者大为头疼。

![原文图片](/media/2016/06/fb3c2c54cdd8e36bf7ae977e564a507f)

继续往下，可以看到在Chakra（11.0.9600.17689）中，这两个数字被解释为：

![原文图片](/media/2016/06/d0ef8042fadc61a96896b0cd1b49987a)

1被存储为了3 ，也即 1 * 2 + 1 （左移一位加一）。 -1 被存储为了0xffffffff， -1 * 2 + 1 = -1 （同上）。

![原文图片](/media/2016/06/bcc69e8c3b7482e6dee0a704323d929a)

而由于编译执行，所以编译器自然地做了优化，将a++ 的值2按 * 2 + 1编码后5 和 b— 的值 -2 ，编码后-2 * 2 + 1 = -3 （0xfffffffd）提前计算好写入了字节码中，如上图，这个和流行的编译器的优化逻辑如出一辙，只不过并没有像是编译器那样那么彻底，有点像Release开启了O1 O2优化之后的感觉。但是明显效率高于Debug版的。
让我们看一下同样代码在VS2008 Release开启O2和Debug 默认以及Chakra的结果：

![原文图片](/media/2016/06/ccd091c3b13932f344200e7141c758e7)

代码在Debug Vs2008生成如下操作：

![原文图片](/media/2016/06/f90c801a2ba9dd582f2c9ea18bc2bce3)

可以看到就是逐句翻译，真·机翻的感觉。

![原文图片](/media/2016/06/7a5bb0c519a8cbe9497f6bbdff1f9607)

![原文图片](/media/2016/06/da600401ad7d55f80e6ec5dfa13616bd)

Release 开启O2之后的效果，这里已经把加加减减的值算好了，这个和Chakra的动作一样。

![原文图片](/media/2016/06/6a90e0d9956cc9b33c2cc3b844bec074)

![原文图片](/media/2016/06/c0f27f2ce571c970e0e83046604401c2)

代码比较简单，所以开启O1也是一个效果，所以用脚趾头也可以得出结论，Chakra的字节码编译过程中确实有优化。
不过不同于VS，如果你直接写个变量加加减减最后哪儿也不用他，开启OX优化的时候，这个变量根本就不会编译进EXE，而Chakra则稍有不同，即使没用，还是编译进来了，只不过做了最大可能的优化处理。
如果全优化了，F12你怎么调试呢。

#### 2、“2Byte”～“4Byte”之间的整数存取
这个区间内的数字比较特殊，采用了异或的方式存储。异或的随机密钥在生成字节码时由编译器确定。
考虑如下JS代码：
var a = 0x32132132; var b = -0x12312312;
a++; b--;
调试可知，这里的数字被“加密”了：

![原文图片](/media/2016/06/9c9fa9265526d6bb581a2234dfc71ae8)

以

```text
062a003f bad8359075 mov edx,offset USP10!pScriptProperties+0xd0 (759035d8) 062a0044 81f2bd77b611 xor edx,11B677BDh
```
为例 （a = 0x32132132;）

```text
0:008> g Breakpoint 0 hit eax=9ef4b531 ebx=03f2bad0 ecx=03419720 edx=db9db9db esi=03f2b8bc edi=062a0000 eip=062a003f esp=03f2b8a0 ebp=03f2b8a8 iopl=0 nv up ei pl zr na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 062a003f bad8359075 mov edx,offset USP10!pScriptProperties+0xd0 (759035d8) 0:008> p eax=9ef4b531 ebx=03f2bad0 ecx=03419720 edx=759035d8 esi=03f2b8bc edi=062a0000 eip=062a0044 esp=03f2b8a0 ebp=03f2b8a8 iopl=0 nv up ei pl zr na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 062a0044 81f2bd77b611 xor edx,11B677BDh 0:008> eax=9ef4b531 ebx=03f2bad0 ecx=03419720 edx=64264265 esi=03f2b8bc edi=062a0000 eip=062a004a esp=03f2b8a0 ebp=03f2b8a8 iopl=0 nv up ei pl nz na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206 062a004a 899108060000 mov dword ptr [ecx+608h],edx ds:002b:03419d28=67422664
```
将edx右移一位，得到0x32132132即a的值。
负数的处理也是一样，以第二句为例：

```text
062a0056 ba170c6687 mov edx,87660C17h 062a005b 81f2cab5fb5c xor edx,5CFBB5CAh 0:008> eax=9ef4b531 ebx=03f2bad0 ecx=03419720 edx=64264265 esi=03f2b8bc edi=062a0000 eip=062a0056 esp=03f2b8a0 ebp=03f2b8a8 iopl=0 nv up ei pl nz na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206 062a0056 ba170c6687 mov edx,87660C17h 0:008> eax=9ef4b531 ebx=03f2bad0 ecx=03419720 edx=87660c17 esi=03f2b8bc edi=062a0000 eip=062a005b esp=03f2b8a0 ebp=03f2b8a8 iopl=0 nv up ei pl nz na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206 062a005b 81f2cab5fb5c xor edx,5CFBB5CAh 0:008> eax=9ef4b531 ebx=03f2bad0 ecx=03419720 edx=db9db9dd esi=03f2b8bc edi=062a0000 eip=062a0061 esp=03f2b8a0 ebp=03f2b8a8 iopl=0 nv up ei ng nz na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000286 062a0061 89910c060000 mov dword ptr [ecx+60Ch],edx ds:002b:03419d2c=dbb99ddb
```
得到edx后，将其右移一位得到0xEDCEDCEE (记得符号位不要也一起右移了)。正是var b = -0x12312312;。
下方a++运算结果和2byte的例子一样，我们就简单看一个

```text
062a006d ba30059380 mov edx,80930530h 062a0072 81f25747b5e4 xor edx,0E4B54757h
```
异或后：64264267 右移一位：32132133 （32132132 + 1）

#### 3、4byte以上的结果
作为32位有符号数，大于4byte的就不好弄了，让我们看看

```text
var a=0x90909090; var b=0x99999999; a++;b++;
```
的结果：
针对它的加法，JS已经选择用SSE2的函数了：

```text
07030080 68b8b31803 push 318B3B8h 07030085 53 push ebx 07030086 e88539e46b call jscript9!Js::SSE2::JavascriptMath::Increment_Full (72e73a10) 0703008b 8bf8 mov edi,eax 0703008d e9b8ffffff jmp 0703004a
```
它们的值被提前算好存储起来了：

```text
063b0067 47 inc edi 063b0068 8b1504612c03 mov edx,dword ptr ds:[32C6104h] 063b006e 81fa804f2c03 cmp edx,32C4F80h 063b0074 0f8562000000 jne 063b00dc 063b007a 8b1508612c03 mov edx,dword ptr ds:[32C6108h] 063b0080 8bff mov edi,edi 063b0082 899a08060000 mov dword ptr [edx+608h],ebx 063b0088 8b1508612c03 mov edx,dword ptr ds:[32C6108h] 063b008e 89b20c060000 mov dword ptr [edx+60Ch],esi 063b0094 8b1508612c03 mov edx,dword ptr ds:[32C6108h] 063b009a 898208060000 mov dword ptr [edx+608h],eax 063b00a0 8b1508612c03 mov edx,dword ptr ds:[32C6108h] 063b00a6 898a0c060000 mov dword ptr [edx+60Ch],ecx
```
ebx、esi等其实是存储着mmword 的指针，但这看起来并不直观，所以我们需要有更直观的方式：

```text
var a=0x90909090; var b=0x99999999; b = a+b; 068f0026 8d6424f4 lea esp,[esp-0Ch] 068f002a 57 push edi 068f002b 56 push esi 068f002c 53 push ebx 068f002d bbf0805503 mov ebx,35580F0h 068f0032 be00815503 mov esi,3558100h 068f0037 33ff xor edi,edi 068f0039 f20f10056c490003 movsd xmm0,mmword ptr ds:[300496Ch] 068f0041 f20f100db4f96e00 movsd xmm1,mmword ptr ds:[6EF9B4h] 068f0049 f20f58c1 addsd xmm0,xmm1 068f004d 8b0538b8fb02 mov eax,dword ptr ds:[2FBB838h] 068f0053 8d4810 lea ecx,[eax+10h] 068f0056 3b0d34b8fb02 cmp ecx,dword ptr ds:[2FBB834h] 068f005c 0f875b000000 ja 068f00bd
```
这回就看的很清楚了

![原文图片](/media/2016/06/23f065b91797dc5453e6cbba8a52998d)

![原文图片](/media/2016/06/30ba70d86cbe247aa92e05d35122bd01)

这正是我们的第一个数，0x90909090；

![原文图片](/media/2016/06/9aa198a33af8a592f0a4a7fac1529dc2)

第二个数也不例外，

![原文图片](/media/2016/06/24afa15daee06755b395062b1dd36f29)

可见，使用xmm的时候，Chakra并没有给它们做任何的混淆，事实上也没有什么太大的必要混淆这类数字了，因此做法可以接受。
VIII.2 对象的操作与结构
作为一个面向对象的语言，JScript中宽松的定义使得对象操作简单但是又“复杂” ，考虑如下代码。

```text
var a = new Object; a.X = 3; a.Y = 4;
```
此时a是一个含有X、Y两个Number成员的Object，它的编译方式是怎样呢？
请一定要记得上一节的内容，3和4在内存里面是7和9。

![原文图片](/media/2016/06/3f0bc5a647fa774717a7e2651c386ac1)

只有这样，我们才能愉快地找到后两句的位置，阅读代码可知这个对象的内存分布是：

```text
+0: jscript9!Js::ArrayBufferParent::`vftable'
```

![原文图片](/media/2016/06/9568f794ae3f7eb03eec1fb933eca276)

左边是Object，右边是成员的两个Number，在Windbg中可以跟踪得到：

```text
0:007> dd [edi+8] 0b69a030 00000007 00000009 030eb040 030eb040 0b69a040 00000000 00000000 00000000 00000000
```
这样当然是不够的，让我们再看看将这个对象（或者更形象的说“Property Bag”）复制后再扩展一个成员（Slot），代码如下

```text
#!c function xObj(x,y) { this.X = x; this.Y = y; } var a=new xObj(3,4); var b=new xObj(3,4); b.Z = 5;
```
首先，找到五个赋值语句的位置：

![原文图片](/media/2016/06/5232ecb49dad6d2e3b2f6740c18e9bd8)

第一句的3，4

![原文图片](/media/2016/06/1e7cb60f8f6fb348cbc8147aadbacda6)

第二句的3，4
到此为止，这两个对象都是一样的，

![原文图片](/media/2016/06/0a519357047a2f928448a4812ef3ad3f)

直到这里，可以看出来它给b的第三个元素（应为Z）赋值5，这里a、b的情况我们在控制台输入一下。
a：

![原文图片](/media/2016/06/849d9b14c61a67061779daa1ca942912)

b：

![原文图片](/media/2016/06/db647b007d98063a50c2a662562339bc)

可以看出来a、b除了成员变量之外别无二致，这正是Chakra中为了对象的快速存取而制作的多态特性：

![原文图片](/media/2016/06/c702039c02f059a62c7fa045989bf529)

VIII.3 函数的调用
最后，让我们回到这个问题上，想在一章之内说完Chakra绝对无异于痴人说梦，所以这里挑一个常见的地方来描述：在Chakra中的函数调用是如何的？
首先是基本的函数，这个和JScript5差不多，一些具体功能的操作符（或者在JS中称为Token），例如new Object这样无参数的new操作符，对应的函数是Js::JavascriptOperators::NewJavascriptObjectNoArg。
而有些简单的内容被编译器直接计算值后显示，例如Math.sqrt(5)在这里直接存为了2.23606xxxxx，而不会再现场调用函数来运算：

![原文图片](/media/2016/06/8e8d4a2bc170f9ed4e48ecb4b4a92d72)

其他的一些无法优化的函数调用还是会照常调用，还是类似JScript5一样看名字即可知道是做啥的，例如取随机数的函数即为Js::SSE2::JavascriptMath::Random：

![原文图片](/media/2016/06/493e433fed99608a770da4739b92e1c4)

通过符号很容易看到各个函数的名称和参数类型等：

![原文图片](/media/2016/06/f8bba651d926abf468e2633927bb2687)

而用户自定义的函数、循环体等通常在字节码中不会作为内联函数存在，例如下列伪代码，它们的调用有点像是：

![原文图片](/media/2016/06/4c78e8599837e350303d3b29536b034f)

在生成字节码的时候它的范围通常是：

![原文图片](/media/2016/06/66b8da9b07120769ff9ef35be262db12)

颜色代表立场，呸，颜色代表不同的函数块，上述函数在调用时最可能的栈像是：

![原文图片](/media/2016/06/6469fcbc0d4009cb42f77ba5cc0d68ca)

Chakra的内容介绍到此结束，限于个人水平以及篇幅内容，先介绍的就是这么多，其实基本调试方法和调试之前的代码没啥两样，这里的调试很容易就能够举一反三。
自Chakra开始，IE的脚本执行速度比先前提升了十多倍（微软自己的SunSpider跑分），原图我找不着了，但是在微软的某个文章里面还是看到了小图……如下，IE9面世不久后的测试得分和当时的Chrome21的脚本跑分不相上下。可惜悲剧的它出来的时候大家在用IE6，IE11出来的时候大家还是在用IE6，就是卡的不行啊怎么办，赶紧升级吧:)。

![原文图片](/media/2016/06/2fe215698aeb6b4ad30a541a5d35b214)

[1](http://drops.wooyun.org/wp-content/uploads/2015/08/117.png) [http://files.accuvant.com/web/files/images/webbrowserresearch_v1_0.pdf](http://files.accuvant.com/web/files/images/webbrowserresearch_v1_0.pdf)
[2](http://drops.wooyun.org/wp-content/uploads/2015/08/218.png) [http://users.ics.forth.gr/~elathan/papers/ndss15.pdf](http://users.ics.forth.gr/~elathan/papers/ndss15.pdf)
[3](http://drops.wooyun.org/wp-content/uploads/2015/08/315.png) [https://blog.indutny.com/5.allocating-numbers](https://blog.indutny.com/5.allocating-numbers)

## Replies
