免杀基础–windows shellcode编写入门

0x00 介绍

作为一名红队人员,CobalStrike和metasploit是我们常用的工具。Cobalt Strike 从3.11版本开始,加入了一个名为”execute-assembly”的命令,能够从内存中加载.NET程序集。这个功能不需要向硬盘写入文件,十分隐蔽。不仅能直接利用现成C#的程序,而且现有的Powershell利用脚本能够很容易的转换为C#代码。在免杀上也能起到一定的效果。

我们知道CS或者MSF的被控端都是C/C的代码编写生成的可执行程序,而execute-assembly执行的是C#程序。之前在分析一个恶意样本时,也见过这这种方式。C/C代码是怎么执行C#程序的呢?

0x01 利用思路

execute-assembly的实现通常有以下两种思路:

1.从内存中读取shellcode并加载.NET程序集

1.调用ICLRMetaHost::EnumerateInstalledRuntimes, ICLRMetaHost::GetRuntime或者ICLRMetaHostPolicy::GetRequestedRuntime方法以获取有效的ICLRRuntimeInfo指针  
2.使用ICorRuntimeHost接口  
3.使用Load_3(…)从内存中读取并加载.NET程序集 
4.调用静态方法

2.从硬盘读取并加载.NET程序集

 1.调用ICLRMetaHost::EnumerateInstalledRuntimes, ICLRMetaHost::GetRuntime或者ICLRMetaHostPolicy::GetRequestedRuntime方法以获取有效的ICLRRuntimeInfo指针
 2.使用ICorRuntimeHost(使用Load_2(…))或者ICLRRuntimeHost接口
 3.加载.NET程序集并调用静态方法

第一种利用思路要优于第二种,完整的利用过程如下:

  1. 创建一个正常的进程
  2. 通过Dll反射向进程注入dll
  3. dll实现从内存中读取shellcode并加载最终的.NET程序集

优点如下:

  • 整个过程在内存执行,不写入文件系统(俗称不落地)
  • Payload以dll形式存在,不会产生可疑的进程
  • 最终的Payload为C#程序

0x02 代码实现分析

参考开源代码:https://github.com/etormadiv/HostingCLR

提示:

若不能成功生成目标可执行文件,在stdafx.h开头加上#pragma warning(disable:4146)再次生成。

  1. 调用CLRCreateInstance函数以获取ICLRMetaHost接口实例
    ICLRMetaHost* pMetaHost = NULL;

    HRESULT hr;

    /* 获取 ICLRMetaHost 实例*/

    hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (VOID**)&pMetaHost);

    if(FAILED(hr))
    {
        printf("[!] CLRCreateInstance(...) failed\n");

        getchar();

        return -1;
    }

    printf("[+] CLRCreateInstance(...) succeeded\n");
  1. 调用ICLRMetaHost::GetRuntime方法以获取有效的ICLRRuntimeInfo指针.(在此固定加载v4.0.30319版本的.net,可通过目标程序集的版本加载对应版本的.net)
    ICLRRuntimeInfo* pRuntimeInfo = NULL;

    /* Get ICLRRuntimeInfo instance */

    hr = pMetaHost->GetRuntime(L"v4.0.30319", IID_ICLRRuntimeInfo, (VOID**)&pRuntimeInfo);

    if(FAILED(hr))
    {
        printf("[!] pMetaHost->GetRuntime(...) failed\n");

        getchar();

        return -1;
    }

    printf("[+] pMetaHost->GetRuntime(...) succeeded\n");
  1. 使用ICorRuntimeHost
    IUnknownPtr pAppDomainThunk = NULL;

    hr = pRuntimeHost->GetDefaultDomain(&pAppDomainThunk);

    if(FAILED(hr))
    {
        printf("[!] pRuntimeHost->GetDefaultDomain(...) failed\n");

        getchar();

        return -1;
    }

    printf("[+] pRuntimeHost->GetDefaultDomain(...) succeeded\n");

    _AppDomainPtr pDefaultAppDomain = NULL;

    /* Equivalent of System.AppDomain.CurrentDomain in C# */

    hr = pAppDomainThunk->QueryInterface(__uuidof(_AppDomain), (VOID**) &pDefaultAppDomain);

    if(FAILED(hr))
    {
        printf("[!] pAppDomainThunk->QueryInterface(...) failed\n");

        getchar();

        return -1;
    }

    printf("[+] pAppDomainThunk->QueryInterface(...) succeeded\n");
  1. 程序集载入内存 AssemblyPtr
  2. 执行程序集
_MethodInfoPtr pMethodInfo = NULL;

    /* Assembly.EntryPoint Property */

    hr = pAssembly->get_EntryPoint(&pMethodInfo);

    if(FAILED(hr))
    {
        printf("[!] pAssembly->get_EntryPoint(...) failed\n");

        getchar();

        return -1;
    }

    printf("[+] pAssembly->get_EntryPoint(...) succeeded\n");

    VARIANT retVal;
    ZeroMemory(&retVal, sizeof(VARIANT));

    VARIANT obj;
    ZeroMemory(&obj, sizeof(VARIANT));
    obj.vt = VT_NULL;

    SAFEARRAY *psaStaticMethodArgs = SafeArrayCreateVector(VT_VARIANT, 0, 0);

    //

    hr = pMethodInfo->Invoke_3(obj, psaStaticMethodArgs, &retVal);

    if(FAILED(hr))
    {
        printf("[!] pMethodInfo->Invoke_3(...) failed, hr = %X\n", hr);

        getchar();

        return -1;
    }

    printf("[+] pMethodInfo->Invoke_3(...) succeeded\n");

测试程序

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;

namespace TestHostCLR
{
    static class Program
    {
        /// <summary>
        /// 应用程序的主入口点。
        /// </summary>
        [STAThread]
        static void Main()
        {
            System.Diagnostics.Process p = new System.Diagnostics.Process();
            p.StartInfo.FileName = "c:\\windows\\system32\\calc.exe";
            p.Start();

        }
    }
}

0x03 生成演示