IE安全系列之:中流砥柱(I)—Jscript 5處理淺析
本文將簡要介紹Jscript5.8引擎處理相關程式碼、字元操作、函式操作的一些內容,下一篇中流砥柱(II)將以同樣的方式介紹Jscript 9 (Charka)的處理方式。
VII.1 Jscript5 處理淺析
IE中的JavaScript解釋引擎有兩種,一種是IE9(不含)之前的瀏覽器,採用的Jscript 5.8-,包括現在的WSH都是採用這個直譯器。該直譯器是典型的switch-dispatch threading interpreter,與之相關的也可以參考參考資料1QQ大講堂的文章,這個型別的直譯器就相當於提前預設好了一堆case,待詞法分析器分析到上面之後,會採用dispatch的方式跳轉到各個處理函式中去。
這裡,以我的電腦上的JScript(版本:5.8.9600.16428,Win 7 Sp1 X86)為例,簡單看一下JScript的相關程式碼結構。
JScript解釋程式碼採用瞭解析程式碼(COleScript::ParseScriptText)時先生成抽象語法樹(AST),該樹的節點即為Javascript 的token(可以認為是保留字或運算子,例如=、!=、+、for等等),然後呼叫直譯器進行解釋執行的模式,JScript的直譯器是CScriptRuntime::Run(VAR *),這是一個體積巨大的函式,這給我們閱讀程式碼帶來了一些不便,至於它體積巨大的原因,首先我們需要知道,一個switch dispatch的直譯器,它的程式碼結構是類似於下圖的:
因此,讓我們從實際例子觀察一下這個直譯器。
針對以下程式碼:
var p=0x11223344;
p.toString();
可以看到JScript的呼叫棧如下(無關棧內容有省略):
#!bash
0:000> k
ChildEBP RetAddr
0016e244 757efbd0 msvcrt!_ui64toa_s+0x42
0016e254 60eab798 msvcrt!_ltow+0x22
0016e274 60eab7d7 jscript!GetStringForNumber+0x32
0016e4c8 60eacc92 jscript!ConvertToString+0xb9
0016e504 60e95950 jscript!JsNumToString+0x7b
0016e56c 60e95876 jscript!NatFncObj::Call+0xce
0016e5f4 60e94546 jscript!NameTbl::InvokeInternal+0x1d6
0016e7a0 60e95396 jscript!VAR::InvokeByName+0x445
0016e7ec 60e9534e jscript!VAR::InvokeDispName+0x3e
0016e8cc 60e92bbb jscript!AutBlock::AddRef+0x7df3
0016ec64 60e92ea6 jscript!CScriptRuntime::Run+0xe59
0016ed50 60e92d19 jscript!ScrFncObj::CallWithFrameOnStack+0x170
0016ed9c 60e93317 jscript!ScrFncObj::Call+0x7b
0016ee30 60e9fb9f jscript!CSession::Execute+0x1f6
0016ee90 60e9dd02 jscript!COleScript::ExecutePendingScripts+0x293
0016eeac 00c219ee jscript!COleScript::SetScriptState+0x51
此處,可以猜測JScript的Run過程透過Name搜尋其對應的Dispatch,下層透過一個Name Table來找到對應的函式,並呼叫函式,然後獲取返回結果。這看起來像一個非常標準的流程。讓我們從程式碼中閱讀一下它的邏輯。
回去找找它的vPC++指標是在哪兒移動的吧。透過跟蹤程式碼或者直接閱讀即可得知“vPC”的移動過程事實上發生在:
上述loc_63382650段正是switch(*vPC++){}的程式碼,該switch中共有0x0 ~ 0x9f(159 DEC)個switch專案,及一個default專案。IDA也計算好了共有160 DEC cases。在靜態閱讀的時候,可以找找Switch Table,align 4後開始第一項為0,後面依次數一下就可以手動切換到對應的case上(下文也稱dispatch)。
各個DispatchCode對應某個或某些模式,例如賦值(assignment,例如p=1)對應0x8的Dispatch:
再來,為了完成Number.toString()之類的方法呼叫,. (accessfield)對應的dispatch id為0x2d。包括它之後的函式等,這個完整的語法解析後會將方法名傳送給VAR::InvokeByName以完成呼叫toString()。
IDA由Symbol進行反crack後得到的函式及型別是VAR::InvokeByName(CSession *,SYM *,ulong,VAR *,int,VAR *)。由它讓我們看看它的分發過程。可能看到InvokeByName這個名字的時候已經有一些疑惑,在展開它的程式碼後,閱讀前面一點即可知道,這個過程和自動化過程中的IDispatch::InvokeByName幾乎一樣。
VAR::InvokeByName的原型實際上正是 :
#!c++
HRESULT GetIDsOfNames(
REFIID riid,
OLECHAR FAR* FAR* rgszNames,
unsigned int cNames,
LCID lcid,
DISPID FAR* rgDispId
);
可以簡單的證實一下:
#!bash
0:000> bp VAR::InvokeByName
0:000> g
Breakpoint 1 hit
eax=003ce9fc ebx=00000000 ecx=003cea10 edx=00000080 esi=00223960 edi=00000000
eip=52f333f1 esp=003ce9dc ebp=003cea24 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
jscript!VAR::InvokeByName:
52f333f1 8bff mov edi,edi
0:000> kvn 1
# ChildEBP RetAddr Args to Child
00 003ce9d8 52f35396 00223960 003ce9fc 00000001 jscript!VAR::InvokeByName (FPO: [6,95,4])
0:000> du poi(poi(esp+8))
00225998 "toString"
從程式碼中也可以看到,這裡它正是呼叫了IDispatch::GetIDsOfNames,獲得了DISPID (v84 / v94):
並最後InvokeDispatch呼叫對應的函式:
VII.2 簡單的字串的處理
看完了上述對直譯器的簡單介紹,讓我們再著眼看一下JS中另一個重要的部分:字串。
由於大量採用了COM,因此即使之前沒接觸,大致也是能猜出來的,JS中的字串是與BSTR有關的。
BSTR是一個與自動化相容的字串型別,作業系統提供類似SysAllocString的函式來管理它。BSTR實際上可以看作一個COM字串(但是如果你深究的話,實際上它本質就是PWCHAR)。
定義在wtypes.h中的BSTR如下:
typedef wchar_t WCHAR;
typedef WCHAR OLECHAR;
typedef OLECHAR *BSTR;
普通字串一般使用\0結尾,但是這樣的字串在COM 元件間傳遞不方便。因此有了這個BSTR的規範,標準的BSTR是一個有長度字首(前4位元組,長度計算時不包含結尾的\0)和\0的OLECHAR陣列。
讓我們在JS中驗證一下。為了方便定位,我們執行escape(“ABCD”)。然後斷下函式jscript!JsEscape。 這一塊VII.3還會提到,這裡先如此繼續除錯。
jscript!JsEscape函式是一個簡單的Helper,會做簡單的判斷,然後將內容傳給jscript!ScrEscape。
為了方便除錯,在ScrEscape處斷下。
0:008> bp jscript!ScrEscape
0:008> g
Breakpoint 0 hit
eax=00000000 ebx=00000003 ecx=005f35c8 edx=005f56b8 esi=005f5130 edi=001aef70
eip=68d61627 esp=001aea58 ebp=001aea7c iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
jscript!ScrEscape:
68d61627 8bff mov edi,edi
ScrEscape原型有4個引數,這個函式是fastcall (MS),因此前兩個引數的傳遞使用edx,ecx傳遞。它的第一個引數就是str,因此不出意外的話,edx即為BSTR strToEscape:
005f56b8 "ABCD"
0:000> du edx
005f56b8 "ABCD"
0:000> dd edx-4
005f56b4 00000008 00420041 00440043 baad0000
005f56c4 ba99838a 00000000 00000008 0000000f
005f56d4 00000014 00000120 00000001 00000001
005f56e4 00000000 00000000 00000027 00000000
005f56f4 00000001 0053004a 00720063 00700069
005f5704 00200074 0020002d 00630073 00690072
005f5714 00740070 00620020 006f006c 006b0063
005f5724 baad0000 00680077 006c0069 00280065
果不其然,情況正是如此。為了更直觀一點,我再手動加一個字,ABCDE,此時,長度DWORD@(edx-4)應該是0000000A。
執行即可知道確實如此:
0:008> bp jscript!ScrEscape;g;du edx;dd edx-4;
Breakpoint 0 hit
004656b8 "ABCDE"
004656b4 0000000a 00420041 00440043 00000045
004656c4 ba9a838a 00000000 00000008 0000000f
004656d4 00000015 00000124 00000001 00000001
004656e4 00000000 00000000 00000028 00000000
004656f4 00000001 0053004a 00720063 00700069
00465704 00200074 0020002d 00630073 00690072
00465714 00740070 00620020 006f006c 006b0063
00465724 baad0000 00680077 006c0069 00280065
知道了字串的儲存,我們還需要知道和字串相關的操作,例如連線。
考慮下列程式碼:
var p = "ABCD";
var q = "EFGH";
var r = p + q;
有這樣一個疑惑,就是一般情況下,p+q的結果是否會基於p或者q的記憶體進行擴充?除錯可知:
0:000> du 004d5e10
004d5e10 "ABCD"
0:000> !heap -x 0x4d5e10
Entry User Heap Segment Size PrevSize Unused Flags
-----------------------------------------------------------------------------
004d5da8 004d5db0 004d0000 004d0000 2c8 908 1c busy extra fill
0:000> du 004d5e3c
004d5e3c "EFGH"
0:000> !heap -x 0x4d5e3c
Entry User Heap Segment Size PrevSize Unused Flags
-----------------------------------------------------------------------------
004d5da8 004d5db0 004d0000 004d0000 2c8 908 1c busy extra fill
0:000> du 0097a6ec
0097a6ec "ABCDEFGH"
0:000> !heap -x 0x97a6ec
Entry User Heap Segment Size PrevSize Unused Flags
-----------------------------------------------------------------------------
0097a6e0 0097a6e8 00930000 00930000 38 28 18 busy extra fill
可見明顯兩個常量“相加”(+, case 0x7)之後的結果並沒有擴充之前的記憶體。原因是其中有兩個常量,這兩個常量的值在解析時就已經分配好了,而連線後的結果是現場生成的。
使用 gflags -p /enable [image filename] 可以開啟頁堆,斷到地址後可使用!heap -p -a ADDRESS觀察該記憶體對應堆的分配棧。
常量的分配棧大致如下,可見實在分析語法的時候就已經分配好的,這個不難理解,要不然語法樹也沒法建立了:
74ad9d45 msvcrt!malloc+0x0000008d
68d3845e jscript!Parser::GenerateCode+0x000001b2
68d3955d jscript!Parser::ParseSource+0x0000053d
68d38e24 jscript!COleScript::Compile+0x00000145
68d420d4 jscript!COleScript::CreateScriptBody+0x000000e5
68d41f7c jscript!COleScript::ParseScriptTextCore+0x0000021c
68d4065f jscript!COleScript::ParseScriptText+0x00000029
00772986 wscript!CScriptingEngine::Compile+0x00000067
再做這樣一個測試,測試都是非常量的情況會如何:
var r = p + q;
var r = r + r;
0:000> !heap -x 005c1d28
Entry User Heap Segment Size PrevSize Unused Flags
-----------------------------------------------------------------------------
005c1d28 005c1d30 00560000 00560000 58 50 10 busy extra
0:000> !heap -x 005c1e04
Entry User Heap Segment Size PrevSize Unused Flags
-----------------------------------------------------------------------------
005c1dd8 005c1de0 00560000 00560000 68 58 10 busy extra
結果也是重新分配了。而Jscript 5中,針對字串的操作最初還是比較低效率的,每一次+(Plus)操作都會導致分配新記憶體,這包括“a”+“b”+“c”,假定“a”是1個單位長度,這個操作會先在堆上分配2個單位長度的緩衝區,將“ab”複製進去後,又申請3個單位的緩衝區,將“abc”複製進去並返回。toString()的操作也是類似,也是分配一個緩衝區再複製進去,即使是String.toString()也是如此。
JScript中還有一個AString類,是用於處理多位元組的一個類,如果你在除錯中遇到了它,將其da poi(poi(ADDR) + 8)即可看到AString中儲存的ANSI String(特別是你在除錯呼叫了VAR::ConvertASTRtoBSTR的函式時可能會想要透過這個方式瞭解它轉的字串是什麼)。限於篇幅,這塊之後如果有介紹到相關具體漏洞的時候再詳細介紹。
VII.3 一見即知:JScript5引擎的函式與JScript指令碼的函式
JScript中儲存了許多Helper(Wrapper),這些Helper的名字其實和之前幾章說到的CElement一樣有規律可循,通常一些JScript的原生函式都會形成有JsXXXX的Helper函式,這些Helper會對傳入的引數做簡單處理並呼叫具體邏輯的實現的函式。
這些函式的名稱在微軟的Symbol 中,所以使用IDA或者Windbg(x jscript!Js*)即可讀取到這些函式。
舉例來看,例如一些數學函式,僅僅是直接呼叫對應的函式,例如JsExp(對應JScript: exp())
圖:Javascript exp()方法
#!c++
(IDA生成的虛擬碼,有少量改動)
double __stdcall Exp(double a1)
{
return _exp(a1);
}
int __stdcall DoMathFnc(double a1, int a2, int a3, void (__stdcall *pFunc)(_DWORD, _DWORD))
{
int result; // [email protected]
__int16 v1; // [sp+18h] [bp-10h]@1
double arg_1; // [sp+20h] [bp-8h]@2
GetParam(0, &v1);
if ( 5 == v1 || (result = ConvertToScalar(&v1, 5, 1), result >= 0) )
{
pFunc(LODWORD(arg_1), HIDWORD(arg_1));
VAR::SetNumber(arg_1);
result = 0;
}
return result;
}
int __stdcall JsExp(int a1, int a2, int a3, int a4, int a5)
{
return DoMathFnc(a3, a4, a5, Exp);
}
也有直接在裡面進行實現的,例如JsRandom (對應Math.random())
圖:Jscript random()方法
以及做一些預處理後呼叫處理方法的函式,例如JsJSONStringify,對應JSON.stringify().
圖:JSON.stringify()
除去需要分析這些函式本身的用途之外,這些函式還有一些比較方便的作用,那就是可以作為一個類似中斷的東西而存在。拋開alert()之類的阻塞型,如果想要跟蹤某個字串,那麼完全可以透過:
var s =”string that blast want to track how it goes”;
escape(s);
然後,在指令碼未執行時下斷點:bp jscript!JsEscape,等跟蹤到這個函式時,其引數就是我們想要拿到的這個字串。這樣會方便除錯程式碼,尤其是一些不容易跟蹤的東西,例如一個數字之類的。
本篇到此為止介紹了Jscript 5中的一些簡單實現,一些具體的內容大家也可以參考Microsoft Javascript Blog/ Microsoft IE Blog的文章,當然他們不會說的很細,如果對這個感興趣的話,可以使用wscript.exe或者iexplore.exe(推薦IE8-)來跟蹤除錯程式碼。下篇將介紹Jscript 9 (Charka)的一些簡單實現。
(1] http://security.tencent.com/index.php/blog/msg/43
(2] 淺析JavaScript引擎的技術變遷 http://djt.qq.com/article/view/489
(3] http://www.w3school.com.cn/jsref/jsref_exp.asp
(4] https://msdn.microsoft.com/library/cc836459(v=vs.94).aspx
相關文章
- IE安全系列之:中流砥柱(II)—Jscript 9處理淺析2020-08-19JS
- IE安全系列之——昨日黃花:IE中的ActiveX(I)2020-08-19
- IE安全系列:IE的自我介紹 (I)2020-08-19
- IE安全系列:指令碼先鋒(I)2020-08-19指令碼
- IE安全系列之——IE中的ActiveX(II)2020-08-19
- IE安全系列之——RES Protocol2020-08-19Protocol
- 流式處理框架storm淺析(下篇)2019-03-04框架ORM
- Hadoop叢集之淺析安全模式2020-07-09Hadoop模式
- i5處理器效能排行榜 i5處理器排行天梯圖最新20222022-03-26
- 淺析RAID0/1安全差別及處理資料安全的應對方式2020-01-08AI
- Java NIO 系列文章之 淺析Reactor模式2018-09-20JavaReact模式
- ThreadPoolExecutor執行緒池內部處理淺析2023-11-30thread執行緒
- JVM 系列文章之 GC 演算法淺析2018-09-07JVMGC演算法
- React 深入系列5:事件處理2019-03-01React事件
- 淺析Node是如何進行錯誤處理的2020-04-03
- IE安全系列:IE的自我介紹 (II)2020-08-19
- HashMap之淺析2018-12-05HashMap
- IE安全系列之——RES Protocol與列印預覽(II)2020-08-19Protocol
- JavaScript之淺析Promise2019-01-07JavaScriptPromise
- 淺析Spring之IoC2019-03-23Spring
- Java安全之Commons Collections5分析2020-10-28Java
- webpack系列--淺析webpack的原理2019-06-14Web
- 簡析5G時代的MART流處理2020-12-25
- 執行緒安全處理之Threadlocal2019-04-29執行緒thread
- i7和i5的處理器哪個好 英特爾i7和i5cpu哪個更好2022-04-12
- 處理器i5和i7有什麼區別 電腦i5和i7哪個更好2022-04-29
- 資料安全運營淺析2020-07-21
- netty系列之:分離websocket處理器2022-01-10NettyWeb
- netty系列之:在netty中處理CORS2021-09-17NettyCORS
- IE安全系列:指令碼先鋒(II)2020-08-19指令碼
- 淺析機器視覺在醫療影像處理中的應用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