IE安全系列之:中流砥柱(II)—Jscript 9處理淺析
本文將簡要介紹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
的,就是編譯出來的位元組碼:
該地址所在的記憶體頁的Allocation Base基本就是函式開頭,觀察該函式。
首先可以看到大量的NOP插入,如箭頭處,感覺沒有什麼大作用的語句會讓想從這裡拿到ROP的片段的攻擊者大為頭疼。
繼續往下,可以看到在Chakra(11.0.9600.17689)中,這兩個數字被解釋為:
1被儲存為了3 ,也即 1 * 2 + 1 (左移一位加一)。 -1 被儲存為了0xffffffff, -1 * 2 + 1 = -1 (同上)。
而由於編譯執行,所以編譯器自然地做了最佳化,將a++ 的值2按 * 2 + 1編碼後5 和 b— 的值 -2 ,編碼後-2 * 2 + 1 = -3 (0xfffffffd)提前計算好寫入了位元組碼中,如上圖,這個和流行的編譯器的最佳化邏輯如出一轍,只不過並沒有像是編譯器那樣那麼徹底,有點像Release開啟了O1 O2最佳化之後的感覺。但是明顯效率高於Debug版的。
讓我們看一下同樣程式碼在VS2008 Release開啟O2和Debug 預設以及Chakra的結果:
程式碼在Debug Vs2008生成如下操作:
可以看到就是逐句翻譯,真·機翻的感覺。
Release 開啟O2之後的效果,這裡已經把加加減減的值算好了,這個和Chakra的動作一樣。
程式碼比較簡單,所以開啟O1也是一個效果,所以用腳趾頭也可以得出結論,Chakra的位元組碼編譯過程中確實有最佳化。
不過不同於VS,如果你直接寫個變數加加減減最後哪兒也不用他,開啟OX最佳化的時候,這個變數根本就不會編譯進EXE,而Chakra則稍有不同,即使沒用,還是編譯進來了,只不過做了最大可能的最佳化處理。
如果全最佳化了,F12你怎麼除錯呢。
2、“2Byte”~“4Byte”之間的整數存取
這個區間內的數字比較特殊,採用了異或的方式儲存。異或的隨機金鑰在生成位元組碼時由編譯器確定。
考慮如下JS程式碼:
var a = 0x32132132; var b = -0x12312312;
a++; b--;
除錯可知,這裡的數字被“加密”了:
以
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
這回就看的很清楚了
這正是我們的第一個數,0x90909090;
第二個數也不例外,
可見,使用xmm的時候,Chakra並沒有給它們做任何的混淆,事實上也沒有什麼太大的必要混淆這類數字了,因此做法可以接受。
VIII.2 物件的操作與結構
作為一個物件導向的語言,JScript中寬鬆的定義使得物件操作簡單但是又“複雜” ,考慮如下程式碼。
var a = new Object;
a.X = 3; a.Y = 4;
此時a是一個含有X、Y兩個Number成員的Object,它的編譯方式是怎樣呢?
請一定要記得上一節的內容,3和4在記憶體裡面是7和9。
只有這樣,我們才能愉快地找到後兩句的位置,閱讀程式碼可知這個物件的記憶體分佈是:
+0: jscript9!Js::ArrayBufferParent::`vftable'
左邊是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;
首先,找到五個賦值語句的位置:
第一句的3,4
第二句的3,4
到此為止,這兩個物件都是一樣的,
直到這裡,可以看出來它給b的第三個元素(應為Z)賦值5,這裡a、b的情況我們在控制檯輸入一下。
a:
b:
可以看出來a、b除了成員變數之外別無二致,這正是Chakra中為了物件的快速存取而製作的多型特性:
VIII.3 函式的呼叫
最後,讓我們回到這個問題上,想在一章之內說完Chakra絕對無異於痴人說夢,所以這裡挑一個常見的地方來描述:在Chakra中的函式呼叫是如何的?
首先是基本的函式,這個和JScript5差不多,一些具體功能的運算子(或者在JS中稱為Token),例如new Object這樣無引數的new運算子,對應的函式是Js::JavascriptOperators::NewJavascriptObjectNoArg
。
而有些簡單的內容被編譯器直接計算值後顯示,例如Math.sqrt(5)在這裡直接存為了2.23606xxxxx
,而不會再現場呼叫函式來運算:
其他的一些無法最佳化的函式呼叫還是會照常呼叫,還是類似JScript5一樣看名字即可知道是做啥的,例如取隨機數的函式即為Js::SSE2::JavascriptMath::Random:
透過符號很容易看到各個函式的名稱和引數型別等:
而使用者自定義的函式、迴圈體等通常在位元組碼中不會作為行內函數存在,例如下列虛擬碼,它們的呼叫有點像是:
在生成位元組碼的時候它的範圍通常是:
顏色代表立場,呸,顏色代表不同的函式塊,上述函式在呼叫時最可能的棧像是:
Chakra的內容介紹到此結束,限於個人水平以及篇幅內容,先介紹的就是這麼多,其實基本除錯方法和除錯之前的程式碼沒啥兩樣,這裡的除錯很容易就能夠舉一反三。
自Chakra開始,IE的指令碼執行速度比先前提升了十多倍(微軟自己的SunSpider跑分),原圖我找不著了,但是在微軟的某個文章裡面還是看到了小圖……如下,IE9面世不久後的測試得分和當時的Chrome21的指令碼跑分不相上下。可惜悲劇的它出來的時候大家在用IE6,IE11出來的時候大家還是在用IE6,就是卡的不行啊怎麼辦,趕緊升級吧:)。
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
相關文章
- IE安全系列之:中流砥柱(I)—Jscript 5處理淺析2020-08-19JS
- IE安全系列之——IE中的ActiveX(II)2020-08-19
- IE安全系列:IE的自我介紹 (II)2020-08-19
- IE安全系列之——RES Protocol與列印預覽(II)2020-08-19Protocol
- IE安全系列:指令碼先鋒(II)2020-08-19指令碼
- IE安全系列之——RES Protocol2020-08-19Protocol
- 流式處理框架storm淺析(下篇)2019-03-04框架ORM
- Hadoop叢集之淺析安全模式2020-07-09Hadoop模式
- IE安全系列之——昨日黃花:IE中的ActiveX(I)2020-08-19
- 淺析RAID0/1安全差別及處理資料安全的應對方式2020-01-08AI
- Java NIO 系列文章之 淺析Reactor模式2018-09-20JavaReact模式
- ThreadPoolExecutor執行緒池內部處理淺析2023-11-30thread執行緒
- JVM 系列文章之 GC 演算法淺析2018-09-07JVMGC演算法
- 淺析Node是如何進行錯誤處理的2020-04-03
- IE安全系列:IE的自我介紹 (I)2020-08-19
- HashMap之淺析2018-12-05HashMap
- JavaScript之淺析Promise2019-01-07JavaScriptPromise
- 淺析Spring之IoC2019-03-23Spring
- webpack系列--淺析webpack的原理2019-06-14Web
- 執行緒安全處理之Threadlocal2019-04-29執行緒thread
- 資料安全運營淺析2020-07-21
- netty系列之:分離websocket處理器2022-01-10NettyWeb
- netty系列之:在netty中處理CORS2021-09-17NettyCORS
- IE安全系列:指令碼先鋒(I)2020-08-19指令碼
- 智慧合約語言 Solidity 教程系列9 - 錯誤處理2018-04-07Solid
- 淺析機器視覺在醫療影像處理中的應用2020-12-19視覺
- SpringMVC學習系列(10) 之 異常處理2020-11-27SpringMVC
- 淺析Kubernetes架構之workqueue2022-06-17架構
- Flutter 之 InheritWidget 原始碼淺析2021-03-24Flutter原始碼
- Flutter 原始碼系列:DropdownButton 原始碼淺析2019-07-03Flutter原始碼
- Spring入門系列:淺析知識點2023-04-10Spring
- java安全編碼指南之:異常處理2020-09-29Java
- 淺析容器安全與EDR的異同2019-12-05
- ES6系列之非同步處理實戰2018-10-29非同步
- ES6 系列之非同步處理實戰2019-02-16非同步
- nginx listen指令淺析之add listen2019-02-20Nginx
- 多執行緒系列(十八) -AQS原理淺析2024-03-13執行緒AQS
- W3C的事件處理和IE的事件處理有哪些區別?2024-12-03事件