发布于2022年11月8日2年前 0x00 前言 钴罢工3.11中,加入了一个名为"执行-汇编"的命令,能够从内存中加载。网程序集。这个功能不需要向硬盘写入文件,十分隐蔽,而且现有的Powershell利用脚本能够很容易的转换为C#代码,十分方便。 本文将会对"执行-汇编"的原理进行介绍,结合多个开源代码,介绍实现方法,分析利用思路,最后给出防御建议 0x01 简介 本文将要介绍以下内容: 基础知识 正常的实现方法 开源利用代码分析 利用思路 防御建议 0x02 基础知识 1.CLR 全称公共语言运行时(公共语言运行库),是一个可由多种编程语言使用的运行环境 清除(清除的缩写)是100 .净框架的主要执行引擎,作用之一是监视程序的运行: 在清除(清除的缩写)监视之下运行的程序属于"托管的"(托管)代码 不在清除(清除的缩写)之下、直接在裸机上运行的应用或者组件属于"非托管的"(非托管)的代码 2.Unmanaged API 参考资料: https://份文件。微软。com/en-us/dot net/framework/unmanaged-API/ 用于将。网程序集加载到任意程序中的应用程序接口 支持两种接口: ICorRuntimeHost接口 ICLRRuntimeHost接口 3.ICorRuntimeHost Interface 参考资料: https://份文件。微软。com/en-us/dot net/framework/unmanaged-API/hosting/icorruntimehost-interface 支持1.0.3705版,1.1.4322版,2.0.50727版和4.0.30319版 4.ICLRRuntimeHost Interface 参考资料: https://份文件。微软。com/en-us/dot net/framework/unmanaged-API/hosting/iclrruntimehost-interface 支持v2.0.50727和4.0.30319版 在100 .净框架2.0中,ICLRRuntimeHost用于取代ICorRuntimeHost 在实际程序开发中,很少会考虑100 .净框架1.0,所以两个接口都可以使用 0x03 正常的实现方法 使用的实例代码: https://代码。msdn。微软。com/windows desktop/CppHostCLR-e 6581 ee0 #内容 这里将参考实例代码并做补充 通用的实现方法如下: 1.将CLR加载到进程中 (1)调用CLRCreateInstance函数以获取ICLRMetaHost或ICLRMetaHostPolicy接口 (2)调用ICLRMetaHost:EnumerateInstalledRuntimes,ICLRMetaHost:GetRuntime或者ICLRMetaHostPolicy:GetRequestedRuntime方法以获取有效的ICLRRuntimeInfo指针 三个任选一个 (3)使用ICorRuntimeHost或者ICLRRuntimeHost 二者都是调用ICLRRuntimeInfo:GetInterface方法,但是参数不同 ICorRuntimeHost: 支持1.0.3705版,1.1.4322版,2.0.50727版和4.0.30319版 指定CLSID _ CorRuntimeHost为rclsid参数 指定IID_ICorRuntimeHost为RIID参数 ICLRRuntimeHost: 支持v2.0.50727和4.0.30319版 指定CLSID_CLRRuntimeHost为rclsid参数 指定IID_ICLRRuntimeHost为RIID参数 2.加载.NET程序集并调用静态方法 在代码实现上,使用ICLRRuntimeHost会比使用ICorRuntimeHost简单的多 3.清理CLR 释放步骤一中的指针 下面使用ICLRMetaHost:GetRuntime获取有效的ICLRRuntimeInfo指针,使用ICLRRuntimeHost从文件加载。网程序集并调用静态方法,实现代码如下: #include 'stdafx.h ' #包括 #包括 #杂注注释(lib,' MSCorEE.lib ') HRESULT运行时host _ get runtime _ ICLRRuntimeInfo(PCWSTR PSZ版本,PCWSTR pszAssemblyName,PCWSTR pszClassName,PCWSTR pszMethodName,PCWSTR pszArgName) { //调用ICLRMetaHost:GetRuntime获取有效的ICLRRuntimeInfo . //调用ICLRRuntimeInfo:GetInterface方法。 hr结果HR ICLRMetaHost * pMetaHost=NULL ICLRRuntimeInfo * pRuntimeInfo=NULL; ICLRRuntimeHost * pClrRuntimeHost=NULL; 双字 // //加载并启动100 .净运行时。 // 加载并启动100 .净运行时“%s \n”,PSZ版本); HR=CLRCreateInstance(CLSID _ CLRMetaHost,IID _ PPV _ ARGS(pMetaHost)); 如果(失败(小时)) { wprintf(L'[!]CLRCreateInstance失败w/HR0xlx\n',hr); 转到清理; } //获取与特定清除(clear的缩写)版本对应的ICLRRuntimeInfo .它 //用启动_加载程序_安全模式取代CorBindToRuntimeEx . HR=pMetaHost-get runtime(PSZ版,IID _ PPV _ ARGS(pRuntimeInfo)); 如果(失败(小时)) { wprintf(L'[!]ICLRMetaHost:GetRuntime失败w/HR0xlx\n',hr); 转到清理; } //检查指定的运行时是否可以加载到进程中。这 //方法将考虑可能已经被 //加载到进程中,并将pbLoadable设置为真(如果此运行时可以) //以进程内并行方式加载。 布尔浮动; HR=pRuntimeInfo-可加载(可浮动); 如果(失败(小时)) { wprintf(L'[!]ICLRRuntimeInfo:IsLoadable失败w/HR0xlx\n',hr); 转到清理; } 如果(!可浮动) { wprintf(L'[!].无法加载。网运行库“% s \ n”,PSZ版本); 转到清理; } //将清除(clear的缩写)加载到当前进程中,并返回运行时接口 //指针106 . ICorRuntimeHost和ICLRRuntimeHost是两个清除(clear的缩写)宿主 CLR 4.0支持的接口。这里我们演示ICLRRuntimeHost //中提供的接口. NET v2.0支持CLR 2.0新 //特性ICLRRuntimeHost。不支持加载. NET v1.x //运行时。 HR=pRuntimeInfo-get接口(CLSID _ CLRRuntimeHost,IID _ PPV _ ARGS(pClrRuntimeHost)); 如果(失败(小时)) { wprintf(L'[!]ICLRRuntimeInfo:GetInterface失败w/HR0xlx\n',hr); 转到清理; } //启动CLR . HR=pClrRuntimeHost-Start(); 如果(失败(小时)) { wprintf(L'[!]CLR未能启动w/HR0xlx\n',hr); 转到清理; } // //加载网程序集并调用静态方法。 // wprintf(L '[]加载程序集“%s\n”,pszAssemblyName); ExecuteInDefaultAppDomain的调用方法必须具有 //以下签名:静态int pwzMethodName(字符串pwzArgument) //其中pwzMethodName表示被调用方法的名称,而 //pwzArgument表示作为参数传递给 //方法。如果ExecuteInDefaultAppDomain的HRESULT结果返回值为 //设置为S_OK,预返回值设置为 //调用的方法。否则,不设置预转价值. HR=pClrRuntimeHost-ExecuteInDefaultAppDomain(pszAssemblyName,pszClassName,pszMethodName,pszArgName,dwLengthRet); 如果(失败(小时)) { wprintf(L'[!]未能调用% s w/HR0xlx\n',pszmethodname,hr); 转到清理; } //打印静态方法的调用结果。 wprintf(L '[]Call % s . % s(' % s ')=% d \ n ',pszClassName,pszMethodName,pszArgName,dwlength ret); 清理: if (pMetaHost) { pMetaHost-Release(); pMetaHost=NULL } if (pRuntimeInfo) { pRuntimeInfo-Release(); pRuntimeInfo=NULL } if (pClrRuntimeHost) { //请注意,在调用停止之后,CLR不能 //重新初始化到同一进程中。这一步通常不是 //必要。你可以离开100 .净运行库加载到您的进程中。 //wprintf(L '停止100 .净运行时\ n’); //pClrRuntimeHost-Stop(); pClrRuntimeHost-Release(); pClrRuntimeHost=NULL } 回报HR; } int main() { runtime host _ get runtime _ ICLRRuntimeInfo(L ' v 4。0 .30319 ',L'ClassLibrary1.dll ',L'ClassLibrary1.Class1 ',L'TestMethod ',L ' argstring '); 返回0; } 代码将会加载同级目录下。网络4.0开发的ClassLibrary1.dll,类名为第一类,方法为测试方法,传入的参数为argstring ClassLibrary1.dll的代码如下: 使用系统; 使用系统。集合。泛型; 使用系统100 . Linq 使用系统。文本; 使用系统。线程。任务; 命名空间类库一 { 公共类1级 { 公共静态int测试方法(字符串str) { 系统。诊断。过程p=新系统诊断。process(); 页(第页的缩写)StartInfo。FileName=' c:\ \ windows \ \ system32 \ \ calc。exe '; 页(第页的缩写)start(); 返回0; } } } 0x04 开源利用代码分析 1、Unmanaged CLR Hosting Assembly loader https://github.com/caseysmithrc/AssemblyLoader 利用清除(清除的缩写)从代码中定义好的数组读取外壳代码,加载到内存并执行 实现方法如下: 1.将CLR加载到进程中 (1)调用CLRCreateInstance函数以获取ICLRMetaHost或ICLRMetaHostPolicy接口 (2)调用ICLRMetaHost:GetRuntime方法以获取有效的ICLRRuntimeInfo指针 (3)使用ICorRuntimeHost 注: 在使用ICorRuntimeHost时,需要添加对mscorlib.tlb的引用,c代码如下: //导入mscorlib.tlb(微软公共语言运行时类库)。 # import ' mscorlib . TLB ' raw _ interfaces _ only \ high_property_prefixes('_get ',' _put ',' _putref')\ 重命名(' ReportEvent ',' InteropServices_ReportEvent ') 使用命名空间mscorlib #杂注endregion 在ICorRuntimeHost中,读取和装入的方法。来自文件的. NET程序集的定义如下: 虚拟HRESULT __stdcall Load_2( /*[in]*/BSTR程序集字符串, /*[out,retval]*/struct _ Assembly * * pret val)=0; 读取和加载的方法。来自内存的. NET程序集的定义如下: 虚拟HRESULT __stdcall Load_3( /*[in]*/SAFEARRAY *原始程序集, /*[out,retval]*/struct _ Assembly * * pret val)=0; 注: 方法定义来自mscorlib.tlh 这里,Load_3(…)用于在加载。NET程序集。 2.加载.NET程序集并调用静态方法 3.清理CLR 2、Executing a .NET Assembly from C++ in Memory (CLR Hosting) https://github.com/etormadiv/HostingCLR caseysmith的方法和调用ICLRMetaHost:GetRuntime方法获取有效的ICLRRuntimeInfo指针基本相同,使用ICorRuntimeHost接口,使用Load_3(…)读取加载。内存中的. NET程序集。 3、CLR via native code https://gist . githubusercontent . com/xpn/e 95 a 62 c 6 afcf 06 ede 52568 fcd 8187 cc 2/raw/f 3498245 c 8309d 44 af 38502 a2 cc 7090 c 318 E8 ADF/clr _ via _ native . c 值得注意的是,这里调用iclrmetahost:enumerateinstalleduntimes是为了获取有效的ICLRRuntimeInfo指针。 然后使用ICLRRuntimeHost进行加载。NET程序集并调用静态方法。 4、metasploit-execute-assembly https://github.com/b4rtik/metasploit-execute-assembly 首先,创建一个进程notepad.exe,然后将HostingCLRx64.dll注入notepad.exe,HostingCLRx64.dll实现内存加载。Net程序集。 这里我们只关注内存加载的细节。Net程序集,代码位置: https://github . com/B4 rtik/metasploit-execute-assembly/blob/master/hosting clr _ inject/hosting clr/hosting clr . CPP 详情如下: 使用。网络4.0.30319版 调用ICLRMetaHost:GetRuntime方法以获取有效的ICLRRuntimeInfo指针。 使用ICorRuntimeHost接口 使用Load_3(…)进行读取和加载。内存中的. NET程序集。 基本和1、2一样。 0x05 利用思路 综合0x04中的开源代码,execute-assembly通常有以下两种利用思路: 1.从内存中读取shellcode并加载.NET程序集 请调用iclrmetahost:enumerateinstalldruntimes、iclrmetahost: getruntime或iclrmetahostpolicy:getrequestedruntime方法来获取有效的ICLRRuntimeInfo指针。 使用ICorRuntimeHost接口 使用Load_3(…)进行读取和加载。内存中的. NET程序集。 调用静态方法 2.从硬盘读取并加载.NET程序集 请调用iclrmetahost:enumerateinstalldruntimes、iclrmetahost: getruntime或iclrmetahostpolicy:getrequestedruntime方法来获取有效的ICLRRuntimeInfo指针。 使用icorruntimeost(使用Load_2(…))或ICLRRuntimeHost接口。 加载。NET程序集并调用静态方法 第一种利用思路优于第二种,完整的利用过程如下: 创建一个正常流程。 通过DLL反射将DLL注入进程 Dll从内存中读取外壳代码并加载最终。NET程序集。 优点如下: 整个过程在内存中执行,而不是写入文件系统。 Payload以dll的形式存在,不会产生任何可疑的进程。 最终的有效载荷是一个C#程序,通过使用脚本将现有的Powershell转换成C#代码很方便。 0x06 防御建议 必须全程使用dll注入,常见的Dll注入方法(尤其是Dll反射)都可以拦截。 至于dll本身,当使用CLR时,会加载系统的dll,例如: mscoree.dll mscoreei.dll mscorlib.dll 这是可以监控的。 0x07 小结 结合几个开源代码,总结了“执行-汇编”的实现方法和利用思路,分析了其优势,最后给出了一些防御建议。 留下回复
创建帐户或登录后发表意见