如何卸载microsoft .net framework 4_从内存加载.NET程序集(executeassembly)的利用分析

fb7e6cf44bf72b7fec1e40b0797d6325.gif

7376c599119f1fbd3cc5d0bb52d544e8.png0x00 前言

Cobalt Strike 3.11中,加入了一个名为"execute-assembly"的命令,能够从内存中加载.NET程序集。这个功能不需要向硬盘写入文件,十分隐蔽,而且现有的Powershell利用脚本能够很容易的转换为C#代码,十分方便。

本文将会对"execute-assembly"的原理进行介绍,结合多个开源代码,介绍实现方法,分析利用思路,最后给出防御建议。

7376c599119f1fbd3cc5d0bb52d544e8.png0x01 简介

本文将要介绍以下内容:

· 基础知识

· 正常的实现方法

· 开源利用代码分析

· 利用思路

· 防御建议

7376c599119f1fbd3cc5d0bb52d544e8.png0x02 基础知识

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,所以两个接口都可以使用。

2a1b0d5ae0b9fec7bd28309f07ad61f7.png0x03 正常的实现方法

使用的实例代码:

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

#include

#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;

        }

    }

}

2a1b0d5ae0b9fec7bd28309f07ad61f7.png0x04 开源利用代码分析

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基本相同。

2a1b0d5ae0b9fec7bd28309f07ad61f7.png0x05 利用思路

综合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#代码很方便。

2a1b0d5ae0b9fec7bd28309f07ad61f7.png0x06 防御建议

整个利用过程必须要用到dll注入,可以对常见的dll注入方法(尤其是Dll反射)进行拦截。

而对于dll本身,在使用CLR时,会加载系统的dll,例如:

· mscoree.dll

· mscoreei.dll

· mscorlib.dll

可对此进行监控。

2a1b0d5ae0b9fec7bd28309f07ad61f7.png0x07 小结

本文结合多个开源代码,总结了"execute-assembly"的实现方法和利用思路,分析优点,最后给出防御建议。

796762532c3583b1742977be7a8fb088.png

c4c9c9f835160a371d8087fed12c77a3.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值