IE安全系列之:中流砥柱(II)—Jscript 9處理淺析

wyzsk發表於2020-08-19
作者: blast · 2015/09/08 10:33

本文將簡要介紹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

(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的,就是編譯出來的位元組碼:

enter image description here

該地址所在的記憶體頁的Allocation Base基本就是函式開頭,觀察該函式。

首先可以看到大量的NOP插入,如箭頭處,感覺沒有什麼大作用的語句會讓想從這裡拿到ROP的片段的攻擊者大為頭疼。

enter image description here

繼續往下,可以看到在Chakra(11.0.9600.17689)中,這兩個數字被解釋為:

enter image description here

1被儲存為了3 ,也即 1 * 2 + 1 (左移一位加一)。 -1 被儲存為了0xffffffff, -1 * 2 + 1 = -1 (同上)。

enter image description here

而由於編譯執行,所以編譯器自然地做了最佳化,將a++ 的值2按 * 2 + 1編碼後5 和 b— 的值 -2 ,編碼後-2 * 2 + 1 = -3 (0xfffffffd)提前計算好寫入了位元組碼中,如上圖,這個和流行的編譯器的最佳化邏輯如出一轍,只不過並沒有像是編譯器那樣那麼徹底,有點像Release開啟了O1 O2最佳化之後的感覺。但是明顯效率高於Debug版的。

讓我們看一下同樣程式碼在VS2008 Release開啟O2和Debug 預設以及Chakra的結果:

enter image description here

程式碼在Debug Vs2008生成如下操作:

enter image description here

可以看到就是逐句翻譯,真·機翻的感覺。

enter image description here

enter image description here

Release 開啟O2之後的效果,這裡已經把加加減減的值算好了,這個和Chakra的動作一樣。

enter image description here

enter image description here

程式碼比較簡單,所以開啟O1也是一個效果,所以用腳趾頭也可以得出結論,Chakra的位元組碼編譯過程中確實有最佳化。

不過不同於VS,如果你直接寫個變數加加減減最後哪兒也不用他,開啟OX最佳化的時候,這個變數根本就不會編譯進EXE,而Chakra則稍有不同,即使沒用,還是編譯進來了,只不過做了最大可能的最佳化處理。

如果全最佳化了,F12你怎麼除錯呢。

2、“2Byte”~“4Byte”之間的整數存取

這個區間內的數字比較特殊,採用了異或的方式儲存。異或的隨機金鑰在生成位元組碼時由編譯器確定。

考慮如下JS程式碼:

var a = 0x32132132; var b = -0x12312312;

a++; b--;

除錯可知,這裡的數字被“加密”了:

enter image description here

062a003f bad8359075      mov     edx,offset USP10!pScriptProperties+0xd0 (759035d8)
062a0044 81f2bd77b611    xor     edx,11B677BDh

為例 (a = 0x32132132;)

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的值。

負數的處理也是一樣,以第二句為例:

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的例子一樣,我們就簡單看一個

062a006d ba30059380      mov     edx,80930530h
062a0072 81f25747b5e4    xor     edx,0E4B54757h

異或後:64264267 右移一位:32132133 (32132132 + 1)

3、4byte以上的結果

作為32位有符號數,大於4byte的就不好弄了,讓我們看看

var a=0x90909090;
var b=0x99999999;
a++;b++;

的結果:

針對它的加法,JS已經選擇用SSE2的函式了:

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

它們的值被提前算好儲存起來了:

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 的指標,但這看起來並不直觀,所以我們需要有更直觀的方式:

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

這回就看的很清楚了

enter image description here

enter image description here

這正是我們的第一個數,0x90909090;

enter image description here

第二個數也不例外,

enter image description here

可見,使用xmm的時候,Chakra並沒有給它們做任何的混淆,事實上也沒有什麼太大的必要混淆這類數字了,因此做法可以接受。

VIII.2 物件的操作與結構

作為一個物件導向的語言,JScript中寬鬆的定義使得物件操作簡單但是又“複雜” ,考慮如下程式碼。

var a = new Object;  
a.X = 3; a.Y = 4;

此時a是一個含有X、Y兩個Number成員的Object,它的編譯方式是怎樣呢?

請一定要記得上一節的內容,3和4在記憶體裡面是7和9。

enter image description here

只有這樣,我們才能愉快地找到後兩句的位置,閱讀程式碼可知這個物件的記憶體分佈是:

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

enter image description here

左邊是Object,右邊是成員的兩個Number,在Windbg中可以跟蹤得到:

0:007> dd [edi+8]
0b69a030  00000007 00000009 030eb040 030eb040
0b69a040  00000000 00000000 00000000 00000000

這樣當然是不夠的,讓我們再看看將這個物件(或者更形象的說“Property Bag”)複製後再擴充套件一個成員(Slot),程式碼如下

#!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;

首先,找到五個賦值語句的位置:

enter image description here

第一句的3,4

enter image description here

第二句的3,4

到此為止,這兩個物件都是一樣的,

enter image description here

直到這裡,可以看出來它給b的第三個元素(應為Z)賦值5,這裡a、b的情況我們在控制檯輸入一下。

a:

enter image description here

b:

enter image description here

可以看出來a、b除了成員變數之外別無二致,這正是Chakra中為了物件的快速存取而製作的多型特性:

enter image description here

VIII.3 函式的呼叫

最後,讓我們回到這個問題上,想在一章之內說完Chakra絕對無異於痴人說夢,所以這裡挑一個常見的地方來描述:在Chakra中的函式呼叫是如何的?

首先是基本的函式,這個和JScript5差不多,一些具體功能的運算子(或者在JS中稱為Token),例如new Object這樣無引數的new運算子,對應的函式是Js::JavascriptOperators::NewJavascriptObjectNoArg

而有些簡單的內容被編譯器直接計算值後顯示,例如Math.sqrt(5)在這裡直接存為了2.23606xxxxx,而不會再現場呼叫函式來運算:

enter image description here

其他的一些無法最佳化的函式呼叫還是會照常呼叫,還是類似JScript5一樣看名字即可知道是做啥的,例如取隨機數的函式即為Js::SSE2::JavascriptMath::Random:

enter image description here

透過符號很容易看到各個函式的名稱和引數型別等:

enter image description here

而使用者自定義的函式、迴圈體等通常在位元組碼中不會作為行內函數存在,例如下列虛擬碼,它們的呼叫有點像是:

enter image description here

在生成位元組碼的時候它的範圍通常是:

enter image description here

顏色代表立場,呸,顏色代表不同的函式塊,上述函式在呼叫時最可能的棧像是:

enter image description here

Chakra的內容介紹到此結束,限於個人水平以及篇幅內容,先介紹的就是這麼多,其實基本除錯方法和除錯之前的程式碼沒啥兩樣,這裡的除錯很容易就能夠舉一反三。

自Chakra開始,IE的指令碼執行速度比先前提升了十多倍(微軟自己的SunSpider跑分),原圖我找不著了,但是在微軟的某個文章裡面還是看到了小圖……如下,IE9面世不久後的測試得分和當時的Chrome21的指令碼跑分不相上下。可惜悲劇的它出來的時候大家在用IE6,IE11出來的時候大家還是在用IE6,就是卡的不行啊怎麼辦,趕緊升級吧:)。

enter image description here

1 http://files.accuvant.com/web/files/images/webbrowserresearch_v1_0.pdf

2 http://users.ics.forth.gr/~elathan/papers/ndss15.pdf

3 https://blog.indutny.com/5.allocating-numbers

本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章