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

wyzsk發表於2020-08-19
作者: blast · 2015/06/26 11:10

本文將簡要介紹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的直譯器,它的程式碼結構是類似於下圖的:

enter image description here

因此,讓我們從實際例子觀察一下這個直譯器。

針對以下程式碼:

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”的移動過程事實上發生在:

enter image description here

上述loc_63382650段正是switch(*vPC++){}的程式碼,該switch中共有0x0 ~ 0x9f(159 DEC)個switch專案,及一個default專案。IDA也計算好了共有160 DEC cases。在靜態閱讀的時候,可以找找Switch Table,align 4後開始第一項為0,後面依次數一下就可以手動切換到對應的case上(下文也稱dispatch)。

enter image description here

各個DispatchCode對應某個或某些模式,例如賦值(assignment,例如p=1)對應0x8的Dispatch:

enter image description here

再來,為了完成Number.toString()之類的方法呼叫,. (accessfield)對應的dispatch id為0x2d。包括它之後的函式等,這個完整的語法解析後會將方法名傳送給VAR::InvokeByName以完成呼叫toString()。

enter image description here

enter image description here

enter image description here

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):

enter image description here

並最後InvokeDispatch呼叫對應的函式:

enter image description here

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陣列。

enter image description here

讓我們在JS中驗證一下。為了方便定位,我們執行escape(“ABCD”)。然後斷下函式jscript!JsEscape。 這一塊VII.3還會提到,這裡先如此繼續除錯。

enter image description here

jscript!JsEscape函式是一個簡單的Helper,會做簡單的判斷,然後將內容傳給jscript!ScrEscape。

enter image description here

為了方便除錯,在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*)即可讀取到這些函式。

enter image description here

舉例來看,例如一些數學函式,僅僅是直接呼叫對應的函式,例如JsExp(對應JScript: exp())

enter image description here

圖: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())

enter image description here

enter image description here

圖:Jscript random()方法

以及做一些預處理後呼叫處理方法的函式,例如JsJSONStringify,對應JSON.stringify().

enter image description here

enter image description here

圖: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

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

相關文章