

从内存加载.NET程序集(execute-assembly)的利用分析
source link: https://3gstudent.github.io/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/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

从内存加载.NET程序集(execute-assembly)的利用分析
0x00 前言
Cobalt Strike 3.11中,加入了一个名为”execute-assembly”的命令,能够从内存中加载.NET程序集。这个功能不需要向硬盘写入文件,十分隐蔽,而且现有的Powershell利用脚本能够很容易的转换为C#代码,十分方便。
本文将会对”execute-assembly”的原理进行介绍,结合多个开源代码,介绍实现方法,分析利用思路,最后给出防御建议
0x01 简介
本文将要介绍以下内容:
- 正常的实现方法
- 开源利用代码分析
0x02 基础知识
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,所以两个接口都可以使用
0x03 正常的实现方法
使用的实例代码:
https://code.msdn.microsoft.com/windowsdesktop/CppHostCLR-e6581ee0#content
这里将参考实例代码并做补充
通用的实现方法如下:
1.将CLR加载到进程中
(1)调用CLRCreateInstance函数以获取ICLRMetaHost或ICLRMetaHostPolicy接口
(2)调用ICLRMetaHost::EnumerateInstalledRuntimes, ICLRMetaHost::GetRuntime或者ICLRMetaHostPolicy::GetRequestedRuntime方法以获取有效的ICLRRuntimeInfo指针
三个任选一个
(3)使用ICorRuntimeHost或者ICLRRuntimeHost
二者都是调用ICLRRuntimeInfo::GetInterface方法,但是参数不同
ICorRuntimeHost:
支持v1.0.3705, v1.1.4322, v2.0.50727和v4.0.30319
指定CLSID_CorRuntimeHost为rclsid参数
指定IID_ICorRuntimeHost为RIID参数
ICLRRuntimeHost:
支持v2.0.50727和v4.0.30319
指定CLSID_CLRRuntimeHost为rclsid参数
指定IID_ICLRRuntimeHost为RIID参数
2.加载.NET程序集并调用静态方法
在代码实现上,使用ICLRRuntimeHost会比使用ICorRuntimeHost简单的多
3.清理CLR
释放步骤1中的指针
下面使用ICLRMetaHost::GetRuntime获取有效的ICLRRuntimeInfo指针,使用ICLRRuntimeHost从文件加载.NET程序集并调用静态方法,实现代码如下:
#include "stdafx.h"
#include <metahost.h>
#include <windows.h>
#pragma comment(lib, "MSCorEE.lib")
HRESULT RuntimeHost_GetRuntime_ICLRRuntimeInfo(PCWSTR pszVersion, PCWSTR pszAssemblyName, PCWSTR pszClassName, PCWSTR pszMethodName, PCWSTR pszArgName)
{
// Call the ICLRMetaHost::GetRuntime to get a valid ICLRRuntimeInfo.
// Call the ICLRRuntimeInfo:GetInterface method.
HRESULT hr;
ICLRMetaHost *pMetaHost = NULL;
ICLRRuntimeInfo *pRuntimeInfo = NULL;
ICLRRuntimeHost *pClrRuntimeHost = NULL;
DWORD dwLengthRet;
//
// Load and start the .NET runtime.
//
wprintf(L"Load and start the .NET runtime %s \n", pszVersion);
hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(&pMetaHost));
if (FAILED(hr))
{
wprintf(L"[!]CLRCreateInstance failed w/hr 0x%08lx\n", hr);
goto Cleanup;
}
// Get the ICLRRuntimeInfo corresponding to a particular CLR version. It
// supersedes CorBindToRuntimeEx with STARTUP_LOADER_SAFEMODE.
hr = pMetaHost->GetRuntime(pszVersion, IID_PPV_ARGS(&pRuntimeInfo));
if (FAILED(hr))
{
wprintf(L"[!]ICLRMetaHost::GetRuntime failed w/hr 0x%08lx\n", hr);
goto Cleanup;
}
// Check if the specified runtime can be loaded into the process. This
// method will take into account other runtimes that may already be
// loaded into the process and set pbLoadable to TRUE if this runtime can
// be loaded in an in-process side-by-side fashion.
BOOL fLoadable;
hr = pRuntimeInfo->IsLoadable(&fLoadable);
if (FAILED(hr))
{
wprintf(L"[!]ICLRRuntimeInfo::IsLoadable failed w/hr 0x%08lx\n", hr);
goto Cleanup;
}
if (!fLoadable)
{
wprintf(L"[!].NET runtime %s cannot be loaded\n", pszVersion);
goto Cleanup;
}
// Load the CLR into the current process and return a runtime interface
// pointer. ICorRuntimeHost and ICLRRuntimeHost are the two CLR hosting
// interfaces supported by CLR 4.0. Here we demo the ICLRRuntimeHost
// interface that was provided in .NET v2.0 to support CLR 2.0 new
// features. ICLRRuntimeHost does not support loading the .NET v1.x
// runtimes.
hr = pRuntimeInfo->GetInterface(CLSID_CLRRuntimeHost, IID_PPV_ARGS(&pClrRuntimeHost));
if (FAILED(hr))
{
wprintf(L"[!]ICLRRuntimeInfo::GetInterface failed w/hr 0x%08lx\n", hr);
goto Cleanup;
}
// Start the CLR.
hr = pClrRuntimeHost->Start();
if (FAILED(hr))
{
wprintf(L"[!]CLR failed to start w/hr 0x%08lx\n", hr);
goto Cleanup;
}
//
// Load the NET assembly and call the static method.
//
wprintf(L"[+]Load the assembly %s\n", pszAssemblyName);
// The invoked method of ExecuteInDefaultAppDomain must have the
// following signature: static int pwzMethodName (String pwzArgument)
// where pwzMethodName represents the name of the invoked method, and
// pwzArgument represents the string value passed as a parameter to that
// method. If the HRESULT return value of ExecuteInDefaultAppDomain is
// set to S_OK, pReturnValue is set to the integer value returned by the
// invoked method. Otherwise, pReturnValue is not set.
hr = pClrRuntimeHost->ExecuteInDefaultAppDomain(pszAssemblyName, pszClassName, pszMethodName, pszArgName, &dwLengthRet);
if (FAILED(hr))
{
wprintf(L"[!]Failed to call %s w/hr 0x%08lx\n", pszMethodName, hr);
goto Cleanup;
}
// Print the call result of the static method.
wprintf(L"[+]Call %s.%s(\"%s\") => %d\n", pszClassName, pszMethodName, pszArgName, dwLengthRet);
Cleanup:
if (pMetaHost)
{
pMetaHost->Release();
pMetaHost = NULL;
}
if (pRuntimeInfo)
{
pRuntimeInfo->Release();
pRuntimeInfo = NULL;
}
if (pClrRuntimeHost)
{
// Please note that after a call to Stop, the CLR cannot be
// reinitialized into the same process. This step is usually not
// necessary. You can leave the .NET runtime loaded in your process.
//wprintf(L"Stop the .NET runtime\n");
//pClrRuntimeHost->Stop();
pClrRuntimeHost->Release();
pClrRuntimeHost = NULL;
}
return hr;
}
int main()
{
RuntimeHost_GetRuntime_ICLRRuntimeInfo(L"v4.0.30319", L"ClassLibrary1.dll", L"ClassLibrary1.Class1", L"TestMethod", L"argstring");
return 0;
}
代码将会加载同级目录下.Net4.0开发的ClassLibrary1.dll,类名为Class1,方法为TestMethod,传入的参数为argstring
ClassLibrary1.dll的代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ClassLibrary1
{
public class Class1
{
public static int TestMethod(string str)
{
System.Diagnostics.Process p = new System.Diagnostics.Process();
p.StartInfo.FileName = "c:\\windows\\system32\\calc.exe";
p.Start();
return 0;
}
}
}
0x04 开源利用代码分析
1、Unmanaged CLR Hosting Assembly loader
https://github.com/caseysmithrc/AssemblyLoader
利用CLR从代码中定义好的数组读取shellcode,加载到内存并执行
实现方法如下:
1.将CLR加载到进程中
(1)调用CLRCreateInstance函数以获取ICLRMetaHost或ICLRMetaHostPolicy接口
(2)调用ICLRMetaHost::GetRuntime方法以获取有效的ICLRRuntimeInfo指针
(3)使用ICorRuntimeHost
注:
在使用ICorRuntimeHost时,需要添加对mscorlib.tlb的引用,c++代码如下:
// Import mscorlib.tlb (Microsoft Common Language Runtime Class Library).
#import "mscorlib.tlb" raw_interfaces_only \
high_property_prefixes("_get","_put","_putref") \
rename("ReportEvent", "InteropServices_ReportEvent")
using namespace mscorlib;
#pragma endregion
在ICorRuntimeHost中,从文件读取并加载.NET程序集的方法定义如下:
virtual HRESULT __stdcall Load_2 (
/*[in]*/ BSTR assemblyString,
/*[out,retval]*/ struct _Assembly * * pRetVal ) = 0;
从内存中读取并加载.NET程序集的方法定义如下:
virtual HRESULT __stdcall Load_3 (
/*[in]*/ SAFEARRAY * rawAssembly,
/*[out,retval]*/ struct _Assembly * * pRetVal ) = 0;
注:
方法定义来自mscorlib.tlh
这里使用了Load_3(…),先从数组中读取shellcode,再加载.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/e95a62c6afcf06ede52568fcd8187cc2/raw/f3498245c8309d44af38502a2cc7090c318e8adf/clr_via_native.c
值得注意的是这里调用ICLRMetaHost::EnumerateInstalledRuntimes获取有效的ICLRRuntimeInfo指针
接着使用ICLRRuntimeHost从文件加载.NET程序集并调用静态方法
4、metasploit-execute-assembly
https://github.com/b4rtik/metasploit-execute-assembly
首先创建进程notepad.exe,然后向notepad.exe注入HostingCLRx64.dll,HostingCLRx64.dll实现内存加载.Net程序集
这里我们只关注内存加载.Net程序集的细节,代码位置:
https://github.com/b4rtik/metasploit-execute-assembly/blob/master/HostingCLR_inject/HostingCLR/HostingCLR.cpp
细节如下:
- 使用.Net v4.0.30319
- 调用ICLRMetaHost::GetRuntime方法以获取有效的ICLRRuntimeInfo指针
- 使用ICorRuntimeHost接口
- 使用Load_3(…)从内存中读取并加载.NET程序集
同1和2基本相同
0x05 利用思路
综合0x04中的开源代码,execute-assembly通常有以下两种利用思路:
1.从内存中读取shellcode并加载.NET程序集
- 调用ICLRMetaHost::EnumerateInstalledRuntimes, ICLRMetaHost::GetRuntime或者ICLRMetaHostPolicy::GetRequestedRuntime方法以获取有效的ICLRRuntimeInfo指针
- 使用ICorRuntimeHost接口
- 使用Load_3(…)从内存中读取并加载.NET程序集
- 调用静态方法
2.从硬盘读取并加载.NET程序集
- 调用ICLRMetaHost::EnumerateInstalledRuntimes, ICLRMetaHost::GetRuntime或者ICLRMetaHostPolicy::GetRequestedRuntime方法以获取有效的ICLRRuntimeInfo指针
- 使用ICorRuntimeHost(使用Load_2(…))或者ICLRRuntimeHost接口
- 加载.NET程序集并调用静态方法
第一种利用思路要优于第二种,完整的利用过程如下:
- 创建一个正常的进程
- 通过Dll反射向进程注入dll
- dll实现从内存中读取shellcode并加载最终的.NET程序集
优点如下:
- 整个过程在内存执行,不写入文件系统
- Payload以dll形式存在,不会产生可疑的进程
- 最终的Payload为C#程序,现有的Powershell利用脚本转换为C#代码很方便
0x06 防御建议
整个利用过程必须要用到dll注入,可以对常见的dll注入方法(尤其是Dll反射)进行拦截
而对于dll本身,在使用CLR时,会加载系统的dll,例如:
- mscoree.dll
- mscoreei.dll
- mscorlib.dll
可对此进行监控
0x07 小结
本文结合多个开源代码,总结了”execute-assembly”的实现方法和利用思路,分析优点,最后给出防御建议
Recommend
-
80
-
38
简介 对于大多数App来说,内存占用主要就是图片。本文将从实用的角度分析,iOS图片的内存占用、测量、优化等。 iOS内存-有什么影响 在移动操作系统设备中,是不能像PC一样进行内存swap的,而随着用户的实...
-
15
从内存加载.NET程序集(Assembly.Load)的利用分析 0x00 前言 在之前的文章
-
25
利用Assembly Load & LoadFile绕过Applocker的分析总结 0x00 前言 最近bohops在文章《Ex...
-
7
0x00 前言 最近看到了一篇有趣的文章《Abusing Exported Functions and Exposed DCO...
-
11
0x00 前言 最近James Forshaw开源了一个工具DotNetToJScript,能够利用JS/Vbs脚本加载.Net程序,很有趣。 Casey Smith和Cn33liz都对此做了进一步研究,开源了他们的利用代码。 本文将要对该技术作系统整理,帮助大家更好的认识。
-
13
Go程序内存泄漏的分析以及避免2016-10-18给系统打压力,内存占用上去了,停止打压后,仍然降不下来,就可能是有泄漏。对于无状态的服务,连接上有请求过来,内存上去了。停了请求,但是内存仍然居高不下,等到连接断开内存才降,则sessi...
-
13
记一次 .NET医疗布草API程序 内存暴涨分析 1. 讲故事 我在年前写过一篇...
-
11
.NET Core分析程序集最优美的方法,不用Assembly.LoadFile(),超越ReflectionOnlyLoad 在...
-
1
1. 讲故事 前些天有位朋友找到我,说他的程序内存异常高,用 vs诊断工具 加载时间又太久,让我帮忙看一下到底咋回事,截图如下:
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK