Unmanaged PowerShell

简介

在渗透测试当中经常会使用到PowerShell来执行脚本, 但是直接使用PowerShell.exe是一个非常敏感的行为, EDR等产品对PowerShell.exe进程的创建监控的很密切, 并且随着PowerShell的渗透测试工具的普及, 越来越多的EDR会利用微软提供的AMSI接口对PS脚本进行扫描, 但是对于低版本的PowerShell并没有引入AMSI;
如果我们可以自己实现一个PowerShell, 而不是去调用系统的PowerShell.exe来执行PS脚本, 就会使得我们的行为更加的隐蔽, 甚至我们可以将自实现的PowerShell模块注入到一个第三方进程中去(例如svchost.exe), 可能会使行为更加隐蔽;

通过C#实现PS调用

Powershell实际上是属于C#的子集(System.Management.Automation), 所以实际上我们在C#中调用Powershell就是调用 System.Management.Automation对象:

using System;
using System.Reflection;
using System.Text;
using System.Management.Automation;
using System.Collections.ObjectModel;
using System.Management.Automation.Runspaces;

namespace Test
{
    class Program
    {
        public static void Main(string[] args)
        {
            InvokePS("PS> ");
        }

        public static int InvokePS(string ps)
        {
            Runspace runspace = RunspaceFactory.CreateRunspace();
            runspace.Open();
            while (true)
            {
                try
                {
                    Console.Write(ps);
                    string cmd = Console.ReadLine();
                    if (cmd.Contains("exit"))
                    {
                        break;
                    }
                    Pipeline pipeline = runspace.CreatePipeline();
                    pipeline.Commands.AddScript(cmd);
                    pipeline.Commands.Add("Out-String");
                    Collection<PSObject> results = pipeline.Invoke();
                    StringBuilder stringBuilder = new StringBuilder();
                    foreach (PSObject obj in results)
                    {
                        foreach (string line in obj.ToString().Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None))
                        {
                            stringBuilder.AppendLine(line.TrimEnd());
                        }
                    }
                    Console.Write(stringBuilder.ToString());
                }
                catch (Exception e)
                {
                    string errorText = e.Message + "\n";
                    Console.Write(errorText);
                }
            }
            return 0;
        }
    }
}

c#_ps
需要注意的是在引用System.Management.Automation时, 需要手动找System.Management.Automation.dll的路径, 然后添加到引用中去:
System.Management.Automation.dll
有了Test.exe我们就可以执行PowerShell命令了, 同时我们可以以反射的形式调用InvokePS函数, 我们把Test.exe文件进行base64编码, 然后通过Assembly.Load的形式反射加载调用InvokePS函数:

using System;
using System.Reflection;
using System.Text;
using System.Management.Automation;
using System.Collections.ObjectModel;
using System.Management.Automation.Runspaces;

namespace Test
{
    class Program
    {
        public static void Main(string[] args)
        {
            string base64str = "TVqQAAMAAAAEAAAA...."; // Test.exe文件的base64编码
            byte[] buffer = Convert.FromBase64String(base64str);
            Assembly assembly = Assembly.Load(buffer);
            Type type = assembly.GetType("Test.Program");
            MethodInfo method = type.GetMethod("InvokePS");
            Object obj = assembly.CreateInstance(method.Name);
            //object[] methodArgs = new object[] { new string[] { } };
            string PS = "PS> ";
            object[] methodArgs = new object[] { PS };
            method.Invoke(obj, methodArgs);
        }
    }
}

C Native Call PowerShell

M1

上面我们通过C#代码加载了Test.exe然后调用了InvokePS函数来执行PS命令, 同样我们可以利用C/C++来自己构造CLR运行时, 然后通过"反射"的方式来执行InvokePS函数:

#include <iostream>
#include <windows.h>
#include <mscoree.h>
#include <metahost.h>
#include <string>
#include <comdef.h>
#include <metahost.h>
#pragma comment(lib, "mscoree.lib")
using namespace std;

LPCWSTR NetVersion = L"v2.0.50727"; // L"v2.0.50727"  "v4.0.30319"

int main()
{
	// 初始化COM
	HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
	if (FAILED(hr))
	{
		cout << "CoInitializeEx Error: " << std::hex << std::showbase << hr << endl;
		return hr;
	}

	// 创建CLR运行时宿主
	ICLRMetaHost* pMetaHost = NULL;
	hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (LPVOID*)&pMetaHost);
	if (FAILED(hr))
	{
		cout << "CLRCreateInstance Error: " << std::hex << std::showbase << hr << endl;
		CoUninitialize();
		return hr;
	}

	// 设置CLR版本
	ICLRRuntimeInfo* pRuntimeInfo = NULL;
	hr = pMetaHost->GetRuntime(NetVersion, IID_ICLRRuntimeInfo, (LPVOID*)&pRuntimeInfo);
	if (FAILED(hr))
	{
		cout << "GetRuntime Error: " << std::hex << std::showbase << hr << endl;
		pMetaHost->Release();
		CoUninitialize();
		return hr;
	}

	// GetInterface
	ICLRRuntimeHost* pClrRuntimeHost = NULL;
	hr = pRuntimeInfo->GetInterface(CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (LPVOID*)&pClrRuntimeHost);
	if (FAILED(hr))
	{
		cout << "GetInterface Error: " << std::hex << std::showbase << hr << endl;
		pRuntimeInfo->Release();
		pMetaHost->Release();
		CoUninitialize();
		return hr;
	}

	// 启动CLR
	hr = pClrRuntimeHost->Start();
	if (FAILED(hr))
	{
		cout << "pClrRuntimeHost Error: " << std::hex << std::showbase << hr << endl;
		pClrRuntimeHost->Release();
		pRuntimeInfo->Release();
		pMetaHost->Release();
		CoUninitialize();
		return hr;
	}

	// 调用.NET程序的Main方法
	DWORD dwRet = 0;
	hr = pClrRuntimeHost->ExecuteInDefaultAppDomain(L"//The_Path_Of//Test.exe", L"Test.Program", L"InvokePS", L"PS> ", &dwRet);
	if (FAILED(hr))
	{
		cout << "ExecuteInDefaultAppDomain Error: " << std::hex << std::showbase << hr << endl;
		pClrRuntimeHost->Release();
		pRuntimeInfo->Release();
		pMetaHost->Release();
		CoUninitialize();
		return hr;
	}

	// 释放资源
	pClrRuntimeHost->Release();
	pRuntimeInfo->Release();
	pMetaHost->Release();
	CoUninitialize();

	return 0;
}

这里需要注意的是GetRuntime设置CLR版本时需要和我们的Test.exe的版本一样, 这里都是v2.0.50727;
还有一个问题是在上面C#代码中我们的InvokePS函数的返回值是int, 如果我们把返回值设置为string等, 我们在执行ExecuteInDefaultAppDomain时会报COR_E_MISSINGMETHOD(0x80131513)错误;

M2

上面M1这种调用方式需要Test.exe文件落盘, 不是很方便, 参考Metasploit等工具, 我们发现可以这样初始化并调用函数, 可以实现和上面C#调用相似的效果:
UnMangaedPS.h:

#pragma once
//------------------------------------------------------------
//-----------       Created with 010 Editor        -----------
//------         www.sweetscape.com/010editor/          ------
//
// File    : Test.exe
// Address : 0 (0x0)
// Size    : 5632 (0x1600)
//------------------------------------------------------------
unsigned char  PowerShellRunner_dll[5632] = {
	0x4D, 0x5A, 0x90, 0x00, 0x03
};
const unsigned int PowerShellRunner_dll_len = 5632;

UnMangaedPS.cpp

#include "UnMangaedPS.h"
#include <iostream>
#include <windows.h>
#include <mscoree.h>
#include <metahost.h>
#include <string>
#include <comutil.h>
#include <comdef.h>
#include <metahost.h>
#pragma comment(lib, "mscoree.lib")
// Import mscorlib.tlb (Microsoft Common Language Runtime Class Library).
#import "mscorlib.tlb" auto_rename raw_interfaces_only				\
    high_property_prefixes("_get","_put","_putref")		\
    rename("ReportEvent", "InteropServices_ReportEvent")
using namespace mscorlib;
using namespace std;

VOID Cleanup(ICorRuntimeHost *pCorRuntimeHost) {
	if (pCorRuntimeHost)
	{
		pCorRuntimeHost->Release();
		pCorRuntimeHost = NULL;
		exit(-1);
	}
}

typedef HRESULT(WINAPI *funcCLRCreateInstance)(
	REFCLSID  clsid,
	REFIID     riid,
	LPVOID  * ppInterface
	);

typedef HRESULT(WINAPI *funcCorBindToRuntime)(
	LPCWSTR  pwszVersion,
	LPCWSTR  pwszBuildFlavor,
	REFCLSID rclsid,
	REFIID   riid,
	LPVOID*  ppv);

HRESULT createHost(const wchar_t* version, ICorRuntimeHost** ppCorRuntimeHost)
{
	bool hostCreated = false;

	HMODULE hMscoree = LoadLibrary(L"mscoree.dll");

	if (hMscoree)
	{
		HRESULT hr = NULL;
		funcCLRCreateInstance pCLRCreateInstance = NULL;
		ICLRMetaHost *pMetaHost = NULL;
		ICLRRuntimeInfo *pRuntimeInfo = NULL;
		bool hostCreated = false;

		pCLRCreateInstance = (funcCLRCreateInstance)GetProcAddress(hMscoree, "CLRCreateInstance");
		if (pCLRCreateInstance == NULL)
		{
			wprintf(L"Could not find .NET 4.0 API CLRCreateInstance");
			goto Cleanup;
		}

		hr = pCLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(&pMetaHost));
		if (FAILED(hr))
		{
			// Potentially fails on .NET 2.0/3.5 machines with E_NOTIMPL
			wprintf(L"CLRCreateInstance failed w/hr 0x%08lx\n", hr);
			goto Cleanup;
		}

		// v2.0.50727   v4.0.30319
		hr = pMetaHost->GetRuntime(version, 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.
		BOOL loadable;
		hr = pRuntimeInfo->IsLoadable(&loadable);
		if (FAILED(hr))
		{
			wprintf(L"ICLRRuntimeInfo::IsLoadable failed w/hr 0x%08lx\n", hr);
			goto Cleanup;
		}

		if (!loadable)
		{
			wprintf(L".NET runtime v2.0.50727 cannot be loaded\n");
			goto Cleanup;
		}

		// Load the CLR into the current process and return a runtime interface
		hr = pRuntimeInfo->GetInterface(CLSID_CorRuntimeHost, IID_PPV_ARGS(ppCorRuntimeHost));
		if (FAILED(hr))
		{
			wprintf(L"ICLRRuntimeInfo::GetInterface failed w/hr 0x%08lx\n", hr);
			goto Cleanup;
		}

		hostCreated = true;

	Cleanup:
		if (pMetaHost)
		{
			pMetaHost->Release();
			pMetaHost = NULL;
		}
		if (pRuntimeInfo)
		{
			pRuntimeInfo->Release();
			pRuntimeInfo = NULL;
		}

		return hostCreated;
	}
	return hostCreated;
}

void InvokeMethod(_TypePtr spType, PCWSTR method, PCWSTR command)
{
	HRESULT hr;
	bstr_t bstrStaticMethodName(method);
	SAFEARRAY *psaStaticMethodArgs = NULL;
	variant_t vtStringArg(command);
	variant_t vtPSInvokeReturnVal;
	variant_t vtEmpty;


	psaStaticMethodArgs = SafeArrayCreateVector(VT_VARIANT, 0, 1);
	LONG index = 0;
	hr = SafeArrayPutElement(psaStaticMethodArgs, &index, &vtStringArg);
	if (FAILED(hr))
	{
		wprintf(L"SafeArrayPutElement failed w/hr 0x%08lx\n", hr);
		return;
	}

	// Invoke the method from the Type interface.
	hr = spType->InvokeMember_3(
		bstrStaticMethodName,
		static_cast<BindingFlags>(BindingFlags_InvokeMethod | BindingFlags_Static | BindingFlags_Public),  // BindingFlags_InvokeMethod
		NULL,
		vtEmpty,
		psaStaticMethodArgs,
		&vtPSInvokeReturnVal);

	if (FAILED(hr))
	{
		wprintf(L"Failed to invoke InvokePS w/hr 0x%08lx\n", hr);
		return;
	}
	else
	{
		// Print the output of the command
		wprintf(vtPSInvokeReturnVal.bstrVal);
	}


	SafeArrayDestroy(psaStaticMethodArgs);
	psaStaticMethodArgs = NULL;
}


HRESULT RuntimeHost(PCWSTR pszVersion, PCWSTR pszAssemblyName, PCWSTR pszClassName, PCWSTR pszMethodName, PCWSTR pszArgName) {
	HRESULT hr;
	ICorRuntimeHost *pCorRuntimeHost = NULL;
	IUnknownPtr spAppDomainThunk = NULL;
	_AppDomainPtr spDefaultAppDomain = NULL;

	// The .NET assembly to load. PowerShellRunner
	bstr_t bstrAssemblyName(pszAssemblyName);  // Test
	_AssemblyPtr spAssembly = NULL;

	// The .NET class to instantiate. PowerShellRunner.PowerShellRunner TestCalc.Program
	bstr_t bstrClassName(pszClassName); // Test.Program
	_TypePtr spType = NULL;


	// Create the runtime host v2.0.50727  v4.0.30319
	if (!createHost(pszVersion, &pCorRuntimeHost))
	{
		wprintf(L"Failed to create the runtime host\n");
		Cleanup(pCorRuntimeHost);
	}

	// Start the CLR
	hr = pCorRuntimeHost->Start();
	if (FAILED(hr))
	{
		wprintf(L"CLR failed to start w/hr 0x%08lx\n", hr);
		Cleanup(pCorRuntimeHost);
	}

	DWORD appDomainId = NULL;
	hr = pCorRuntimeHost->GetDefaultDomain(&spAppDomainThunk);
	if (FAILED(hr))
	{
		wprintf(L"RuntimeClrHost::GetCurrentAppDomainId failed w/hr 0x%08lx\n", hr);
		Cleanup(pCorRuntimeHost);
	}

	// Get a pointer to the default AppDomain in the CLR.
	hr = pCorRuntimeHost->GetDefaultDomain(&spAppDomainThunk);
	if (FAILED(hr))
	{
		wprintf(L"ICorRuntimeHost::GetDefaultDomain failed w/hr 0x%08lx\n", hr);
		Cleanup(pCorRuntimeHost);
	}

	hr = spAppDomainThunk->QueryInterface(IID_PPV_ARGS(&spDefaultAppDomain));
	if (FAILED(hr))
	{
		wprintf(L"Failed to get default AppDomain w/hr 0x%08lx\n", hr);
		Cleanup(pCorRuntimeHost);
	}

	// Load the .NET assembly.
	// (Option 1) Load it from disk - usefully when debugging the PowerShellRunner app (you'll have to copy the DLL into the same directory as the exe)
	// hr = spDefaultAppDomain->Load_2(bstrAssemblyName, &spAssembly);

	// (Option 2) Load the assembly from memory
	SAFEARRAYBOUND bounds[1];
	bounds[0].cElements = PowerShellRunner_dll_len;
	bounds[0].lLbound = 0;

	SAFEARRAY* arr = SafeArrayCreate(VT_UI1, 1, bounds);
	SafeArrayLock(arr);
	memcpy(arr->pvData, PowerShellRunner_dll, PowerShellRunner_dll_len);
	SafeArrayUnlock(arr);
	hr = spDefaultAppDomain->Load_3(arr, &spAssembly);
	printf("Load the assembly from memory\n");

	if (FAILED(hr))
	{
		wprintf(L"Failed to load the assembly w/hr 0x%08lx\n", hr);
		Cleanup(pCorRuntimeHost);
	}

	// Get the Type of PowerShellRunner.
	hr = spAssembly->GetType_2(bstrClassName, &spType);
	if (FAILED(hr))
	{
		wprintf(L"Failed to get the Type interface w/hr 0x%08lx\n", hr);
		Cleanup(pCorRuntimeHost);
	}

	// Call the static method of the class InvokePS
	InvokeMethod(spType, pszMethodName, pszArgName);

	return hr;
}

int main()
{
	LPCWSTR NetVersion = L"v2.0.50727"; // L"v2.0.50727"  "v4.0.30319"
	RuntimeHost(NetVersion, L"Test", L"Test.Program", L"InvokePS", L"PS> ");
	return 0;
}

通过这种方式调用就不需要Test.exe落盘了, 然后可以反射加载的形式执行InvokePS函数, 并且这种即使InvokePS函数返回string类型也不错报错了;
这里需要注意的是在#import "mscorlib.tlb"的时候可能报错说找不到文件, 可以在属性 -> VC++目录 -> 附加目录中添加mscorlib.tlb所在目录(例如C:\Windows\Microsoft.NET\Framework\v2.0.50727);

总结

这里编译的形式是exe, 在实际使用中可以为DLL, 甚至shellcode; 更成熟的代码可以参考UnmanagedPowerShell
等项目;
参考:

  1. https://begtostudy-tech.blogspot.com/2011/03/use-clr4-hosting-api-to-invoke-net.html
  2. https://github.com/leechristensen/UnmanagedPowerShell
  3. https://3gstudent.github.io/backup-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/
  • 8
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值