execute-assembly实现原理(非托管C++代码调用C#)
execute-assembly实现原理(非托管C++代码调用C#)
cs中新加了一个功能,直接加载C#编译的文件:execute-assembly
命令
看了下3gstudent大佬的文章,然后简单学习了下这个的原理,三好学生使用ICLRMetaHost::GetRuntime
获取有效的ICLRRuntimeInfo
指针。这里我找到了另一个方法就是ExecuteInDefaultAppDomain
来调用Assembly
方法
实现原理
ICLRRuntimeHost::ExecuteInDefaultAppDomain 方法
演示代码其实就是非托管 C++ 代码调用 C# dll
实现的功能和上一个Assembly Load
学习中实现的差不多。
在非托管代码中手动启动 CLR 加载应用程序域来运行托管的 dll,从而调用其中的方法。
利用Unmanaged API
中的ICLRRuntimeHost
接口,啟用 Unmanaged 主應用程式,將 Common Language Runtime 載入處理序 (Process)。
#include <Windows.h>
#include <MSCorEE.h>
#include <stdio.h>
#include <metahost.h>
using namespace std;
#pragma comment(lib, "mscoree.lib")
int main(int argc)
{
ICLRMetaHost *pMetaHost = nullptr;
ICLRMetaHostPolicy *pMetaHostPolicy = nullptr;
ICLRRuntimeHost *pRuntimeHost = nullptr;
ICLRRuntimeInfo *pRuntimeInfo = nullptr;
HRESULT hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (LPVOID*)&pMetaHost);
hr = pMetaHost->GetRuntime(L"v4.0.30319", IID_PPV_ARGS(&pRuntimeInfo));
if (FAILED(hr)) {
wprintf(L"failed to call csharp dll.\n");
}
hr = pRuntimeInfo->GetInterface(CLSID_CLRRuntimeHost, IID_PPV_ARGS(&pRuntimeHost));
hr = pRuntimeHost->Start();
DWORD dwRet = 0;
hr = pRuntimeHost->ExecuteInDefaultAppDomain(L"ClassLibrary1.dll", //不会产生新的进程
L"DllDemo1.Aaaaa1",
L"Bbbbb",
L"aaa",
&dwRet);
hr = pRuntimeHost->Stop();
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DllDemo1
{
public class Aaaaa1
{
public static int Bbbbb(string str)
{
System.Diagnostics.Process p = new System.Diagnostics.Process();
p.StartInfo.FileName = "c:\\windows\\system32\\calc.exe";
p.Start();
return 0;
}
}
}
测试可以加载exe运行。
以下是3gstudent文章演示代码:
ICLRMetaHost::GetRuntime方法
使用ICLRMetaHost::GetRuntime获取有效的ICLRRuntimeInfo指针。
#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)
{
HRESULT hr;
ICLRMetaHost *pMetaHost = NULL;
ICLRRuntimeInfo *pRuntimeInfo = NULL;
ICLRRuntimeHost *pClrRuntimeHost = NULL;
DWORD dwLengthRet;
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;
}
hr = pMetaHost->GetRuntime(pszVersion, IID_PPV_ARGS(&pRuntimeInfo));
if (FAILED(hr))
{
wprintf(L"[!]ICLRMetaHost::GetRuntime failed w/hr 0x%08lx\n", hr);
goto Cleanup;
}
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;
}
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;
}
hr = pClrRuntimeHost->Start();
if (FAILED(hr))
{
wprintf(L"[!]CLR failed to start w/hr 0x%08lx\n", hr);
goto Cleanup;
}
wprintf(L"[+]Load the assembly %s\n", pszAssemblyName);
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;
}
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)
{
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;
}
}
}
以后会分析以下一些开源的Assembly loader工具,以上两个方法都是在硬盘中加载,而根据三好学生大佬的总结
execute-assembly通常有以下两种利用思路:
1.从内存中读取shellcode并加载.NET程序集
2.从硬盘读取并加载.NET程序集
第一种利用思路要优于第二种,完整的利用过程如下:
创建一个正常的进程
通过Dll反射向进程注入dll
dll实现从内存中读取shellcode并加载最终的.NET程序集
优点如下:
整个过程在内存执行,不写入文件系统
Payload以dll形式存在,不会产生可疑的进程
最终的Payload为C#程序,现有的Powershell利用脚本转换为C#代码很方便