[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fjtPI1L5Y2ArbUENDVGMGqf_Im4icjbWxQoG3TGUiBS4":3},{"id":4,"question":5,"answer":6,"answerHtml":7,"slug":8,"keywords":9,"article":10,"status":33,"aiModel":30,"aiConfidence":30,"updatedAt":49,"createdAt":49,"_status":48},104,"What is Cobalt Strike's execute-assembly command and why is it considered stealthy?","Cobalt Strike's `execute-assembly` command, introduced in version 3.11, loads .NET assemblies directly from memory without writing them to disk. This avoids file-based detection techniques, making it highly stealthy. It uses the CLR hosting API to load and execute managed code within an unmanaged process, as detailed in [Analysis of .NET Assembly Loading from Memory (execute-assembly) Exploitation](\u002Fnews\u002Fanalysis-of-net-assembly-loading-from-memory-execute-assembly-exploitation).","\u003Cp>Cobalt Strike&#39;s `execute-assembly` command, introduced in version 3.11, loads .NET assemblies directly from memory without writing them to disk. This avoids file-based detection techniques, making it highly stealthy. It uses the CLR hosting API to load and execute managed code within an unmanaged process, as detailed in [Analysis of .NET Assembly Loading from Memory (execute-assembly) Exploitation](\u002Fnews\u002Fanalysis-of-net-assembly-loading-from-memory-execute-assembly-exploitation).\u003C\u002Fp>\u003Cp>\u003Ca href=\"\u002Fnews\u002Fanalysis-of-net-assembly-loading-from-memory-execute-assembly-exploitation\">Read the related One Day Sec article\u003C\u002Fa>\u003C\u002Fp>","what-is-cobalt-strikes-execute-assembly-command-and-why-is-it-considered-stealth-1777485211636","Cobalt Strike, execute-assembly, .NET assembly, fileless, CLR hosting, stealth",{"id":11,"title":12,"slug":13,"description":14,"content":15,"contentHtml":27,"cover":30,"author":31,"views":19,"readingTime":32,"status":33,"publishedAt":34,"seo":35,"tags":39,"qaPairs":40,"meta":45,"updatedAt":46,"createdAt":47,"_status":48},27,"Analysis of .NET Assembly Loading from Memory (execute-assembly) Exploitation","analysis-of-net-assembly-loading-from-memory-execute-assembly-exploitation","Analyze .NET assembly loading from memory (execute-assembly) exploitation, covering principles, implementation methods, open-source code analysis, and defense strategies.",{"root":16},{"type":17,"format":18,"indent":19,"version":20,"children":21,"direction":29},"root","",0,1,[22],{"type":23,"format":18,"indent":19,"version":20,"children":24,"direction":29},"paragraph",[25],{"mode":26,"text":27,"type":28,"style":18,"detail":19,"format":19,"version":20},"normal","\u003Chtml>\u003Chead>\u003C\u002Fhead>\u003Cbody>\u003Ch2>0x00 Preface\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>In Cobalt Strike 3.11, a command named \"execute-assembly\" was introduced, capable of loading .NET assemblies from memory. This feature does not require writing files to the hard disk, making it highly stealthy. Moreover, existing PowerShell exploitation scripts can be easily converted to C# code, offering great convenience.\u003C\u002Fp>\u003Cp>This article will introduce the principles of \"execute-assembly\", combine multiple open-source codes to explain implementation methods, analyze exploitation approaches, and finally provide defense recommendations.\u003C\u002Fp>\u003Ch2>0x01 Introduction\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article will cover the following topics:\u003C\u002Fp>\u003Cul>\u003Cli>Basic Knowledge\u003C\u002Fli>\u003Cli>Normal Implementation Methods\u003C\u002Fli>\u003Cli>Analysis of Open-Source Exploitation Code\u003C\u002Fli>\u003Cli>Exploitation Approaches\u003C\u002Fli>\u003Cli>Defense Recommendations\u003C\u002Fli>\u003C\u002Ful>\u003Ch2>0x02 Basic Knowledge\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Ch3>1.CLR\u003C\u002Fh3>\u003Cp>Full name Common Language Runtime, is a runtime environment that can be used by multiple programming languages.\u003C\u002Fp>\u003Cp>CLR is the main execution engine of the .NET Framework, one of its functions is to monitor the operation of programs:\u003C\u002Fp>\u003Cul>\u003Cli>Programs running under the supervision of CLR belong to \"managed\" code.\u003C\u002Fli>\u003Cli>Applications or components that run directly on bare metal without CLR belong to \"unmanaged\" code.\u003C\u002Fli>\u003C\u002Ful>\u003Ch3>2.Unmanaged API\u003C\u002Fh3>\u003Cp>Reference:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fdocs.microsoft.com\u002Fen-us\u002Fdotnet\u002Fframework\u002Funmanaged-api\u002F\u003C\u002Fp>\u003Cp>API for loading .NET assemblies into arbitrary programs.\u003C\u002Fp>\u003Cp>Supports two interfaces:\u003C\u002Fp>\u003Cul>\u003Cli>ICorRuntimeHost Interface\u003C\u002Fli>\u003Cli>ICLRRuntimeHost Interface\u003C\u002Fli>\u003C\u002Ful>\u003Ch3>3.ICorRuntimeHost Interface\u003C\u002Fh3>\u003Cp>Reference:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fdocs.microsoft.com\u002Fen-us\u002Fdotnet\u002Fframework\u002Funmanaged-api\u002Fhosting\u002Ficorruntimehost-interface\u003C\u002Fp>\u003Cp>Supports v1.0.3705, v1.1.4322, v2.0.50727, and v4.0.30319\u003C\u002Fp>\u003Ch3>4. ICLRRuntimeHost Interface\u003C\u002Fh3>\u003Cp>References:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fdocs.microsoft.com\u002Fen-us\u002Fdotnet\u002Fframework\u002Funmanaged-api\u002Fhosting\u002Ficlrruntimehost-interface\u003C\u002Fp>\u003Cp>Supports v2.0.50727 and v4.0.30319\u003C\u002Fp>\u003Cp>In .NET Framework 2.0, ICLRRuntimeHost is used to replace ICorRuntimeHost\u003C\u002Fp>\u003Cp>In actual program development, .NET Framework 1.0 is rarely considered, so both interfaces can be used\u003C\u002Fp>\u003Ch2>0x03 Normal Implementation Method\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>Example code used:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fcode.msdn.microsoft.com\u002Fwindowsdesktop\u002FCppHostCLR-e6581ee0#content\u003C\u002Fp>\u003Cp>Here, we will reference the example code and provide additional details\u003C\u002Fp>\u003Cp>The general implementation method is as follows:\u003C\u002Fp>\u003Ch4>1. Load the CLR into the process\u003C\u002Fh4>\u003Cp>(1) Call the CLRCreateInstance function to obtain the ICLRMetaHost or ICLRMetaHostPolicy interface\u003C\u002Fp>\u003Cp>(2) Call ICLRMetaHost::EnumerateInstalledRuntimes, ICLRMetaHost::GetRuntime, or ICLRMetaHostPolicy::GetRequestedRuntime method to obtain a valid ICLRRuntimeInfo pointer\u003C\u002Fp>\u003Cp>Choose any one of the three\u003C\u002Fp>\u003Cp>(3) Use ICorRuntimeHost or ICLRRuntimeHost\u003C\u002Fp>\u003Cp>Both call the ICLRRuntimeInfo::GetInterface method, but with different parameters\u003C\u002Fp>\u003Cp>\u003Cstrong>ICorRuntimeHost:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>Supports v1.0.3705, v1.1.4322, v2.0.50727, and v4.0.30319\u003C\u002Fp>\u003Cp>Specify CLSID_CorRuntimeHost as the rclsid parameter\u003C\u002Fp>\u003Cp>Specify IID_ICorRuntimeHost as the RIID parameter\u003C\u002Fp>\u003Cp>\u003Cstrong>ICLRRuntimeHost:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>Supports v2.0.50727 and v4.0.30319\u003C\u002Fp>\u003Cp>Specify CLSID_CLRRuntimeHost as the rclsid parameter\u003C\u002Fp>\u003Cp>Specify IID_ICLRRuntimeHost as the RIID parameter\u003C\u002Fp>\u003Ch4>2. Load .NET assembly and call static methods\u003C\u002Fh4>\u003Cp>In code implementation, using ICLRRuntimeHost is much simpler than using ICorRuntimeHost\u003C\u002Fp>\u003Ch4>3. Clean up CLR\u003C\u002Fh4>\u003Cp>Release the pointer from step 1\u003C\u002Fp>\u003Cp>Below, use ICLRMetaHost::GetRuntime to obtain a valid ICLRRuntimeInfo pointer, then use ICLRRuntimeHost to load a .NET assembly from a file and invoke a static method. The implementation code is as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>#include \"stdafx.h\"\u003Cbr>#include \u003Cmetahost.h>\u003Cbr>#include \u003Cwindows.h>\u003Cbr>#pragma comment(lib, \"MSCorEE.lib\")\u003Cbr>\u003Cbr>HRESULT RuntimeHost_GetRuntime_ICLRRuntimeInfo(PCWSTR pszVersion, PCWSTR pszAssemblyName, PCWSTR pszClassName, PCWSTR pszMethodName, PCWSTR pszArgName)\u003Cbr>{\u003Cbr>\t\u002F\u002F Call the ICLRMetaHost::GetRuntime to get a valid ICLRRuntimeInfo.\u003Cbr>\t\u002F\u002F Call the ICLRRuntimeInfo:GetInterface method.\u003Cbr>\tHRESULT hr;\u003Cbr>\tICLRMetaHost *pMetaHost = NULL;\u003Cbr>\tICLRRuntimeInfo *pRuntimeInfo = NULL;\u003Cbr>\tICLRRuntimeHost *pClrRuntimeHost = NULL;\u003Cbr>\tDWORD dwLengthRet;\u003Cbr>\t\u002F\u002F \u003Cbr>\t\u002F\u002F Load and start the .NET runtime.\u003Cbr>\t\u002F\u002F \u003Cbr>\twprintf(L\"Load and start the .NET runtime %s \\n\", pszVersion);\u003Cbr>\thr = CLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(&amp;pMetaHost));\u003Cbr>\tif (FAILED(hr))\u003Cbr>\t{\u003Cbr>\t\twprintf(L\"[!]CLRCreateInstance failed w\u002Fhr 0x%08lx\\n\", hr);\u003Cbr>\t\tgoto Cleanup;\u003Cbr>\t}\u003Cbr>\t\u002F\u002F Get the ICLRRuntimeInfo corresponding to a particular CLR version. It \u003Cbr>\t\u002F\u002F supersedes CorBindToRuntimeEx with STARTUP_LOADER_SAFEMODE.\u003Cbr>\thr = pMetaHost-&gt;GetRuntime(pszVersion, IID_PPV_ARGS(&amp;pRuntimeInfo));\u003Cbr>\tif (FAILED(hr))\u003Cbr>\t{\u003Cbr>\t\twprintf(L\"[!]ICLRMetaHost::GetRuntime failed w\u002Fhr 0x%08lx\\n\", hr);\u003Cbr>\t\tgoto Cleanup;\u003Cbr>\t}\u003Cbr>\t\u002F\u002F Check if the specified runtime can be loaded into the process. This \u003Cbr>\t\u002F\u002F method will take into account other runtimes that may already be \u003Cbr>\t\u002F\u002F loaded into the process and set pbLoadable to TRUE if this runtime can \u003Cbr>\t\u002F\u002F be loaded in an in-process side-by-side fashion. \u003Cbr>\tBOOL fLoadable;\u003Cbr>\thr = pRuntimeInfo-&gt;IsLoadable(&amp;fLoadable);\u003Cbr>\tif (FAILED(hr))\u003Cbr>\t{\u003Cbr>\t\twprintf(L\"[!]ICLRRuntimeInfo::IsLoadable failed w\u002Fhr 0x%08lx\\n\", hr);\u003Cbr>\t\tgoto Cleanup;\u003Cbr>\t}\u003Cbr>\tif (!fLoadable)\u003Cbr>\t{\u003Cbr>\t\twprintf(L\"[!].NET runtime %s cannot be loaded\\n\", pszVersion);\u003Cbr>\t\tgoto Cleanup;\u003Cbr>\t}\u003Cbr>\t\u002F\u002F Load the CLR into the current process and return a runtime interface \u003Cbr>\t\u002F\u002F pointer. ICorRuntimeHost and ICLRRuntimeHost are the two CLR hosting  \u003Cbr>\t\u002F\u002F interfaces supported by CLR 4.0. Here we demo the ICLRRuntimeHost \u003Cbr>\t\u002F\u002F interface that was provided in .NET v2.0 to support CLR 2.0 new \u003Cbr>\t\u002F\u002F features. ICLRRuntimeHost does not support loading the .NET v1.x \u003Cbr>\t\u002F\u002F runtimes.\u003Cbr>\thr = pRuntimeInfo-&gt;GetInterface(CLSID_CLRRuntimeHost, IID_PPV_ARGS(&amp;pClrRuntimeHost));\u003Cbr>\tif (FAILED(hr))\u003Cbr>\t{\u003Cbr>\t\twprintf(L\"[!]ICLRRuntimeInfo::GetInterface failed w\u002Fhr 0x%08lx\\n\", hr);\u003Cbr>\t\tgoto Cleanup;\u003Cbr>\t}\u003Cbr>\t\u002F\u002F Start the CLR.\u003Cbr>\thr = pClrRuntimeHost-&gt;Start();\u003Cbr>\tif (FAILED(hr))\u003Cbr>\t{\u003Cbr>\t\twprintf(L\"[!]CLR failed to start w\u002Fhr 0x%08lx\\n\", hr);\u003Cbr>\t\tgoto Cleanup;\u003Cbr>\t}\u003Cbr>\t\u002F\u002F \u003Cbr>\t\u002F\u002F Load the NET assembly and call the static method.\u003Cbr>\t\u002F\u002F \u003Cbr>\twprintf(L\"[+]Load the assembly %s\\n\", pszAssemblyName);\u003Cbr>\t\u002F\u002F The invoked method of ExecuteInDefaultAppDomain must have the \u003Cbr>\t\u002F\u002F following signature: static int pwzMethodName (String pwzArgument)\u003Cbr>\t\u002F\u002F where pwzMethodName represents the name of the invoked method, and \u003Cbr>\t\u002F\u002F pwzArgument represents the string value passed as a parameter to that \u003Cbr>\t\u002F\u002F method. If the HRESULT return value of ExecuteInDefaultAppDomain is \u003Cbr>\t\u002F\u002F set to S_OK, pReturnValue is set to the integer value returned by the \u003Cbr>\t\u002F\u002F invoked method. Otherwise, pReturnValue is not set.\u003Cbr>\thr = pClrRuntimeHost-&gt;ExecuteInDefaultAppDomain(pszAssemblyName, pszClassName, pszMethodName, pszArgName, &amp;dwLengthRet);\u003Cbr>\tif (FAILED(hr))\u003Cbr>\t{\u003Cbr>\t\twprintf(L\"[!]Failed to call %s w\u002Fhr 0x%08lx\\n\", pszMethodName, hr);\u003Cbr>\t\tgoto Cleanup;\u003Cbr>\t}\u003Cbr>\t\u002F\u002F Print the call result of the static method.\u003Cbr>\twprintf(L\"[+]Call %s.%s(\\\"%s\\\") =&gt; %d\\n\", pszClassName, pszMethodName, pszArgName, dwLengthRet);\u003Cbr>\u003Cbr>Cleanup:\u003Cbr>\tif (pMetaHost)\u003Cbr>\t{\u003Cbr>\t\tpMetaHost-&gt;Release();\u003Cbr>\t\tpMetaHost = NULL;\u003Cbr>\t}\u003Cbr>\tif (pRuntimeInfo)\u003Cbr>\t{\u003Cbr>\t\tpRuntimeInfo-&gt;Release();\u003Cbr>\t\tpRuntimeInfo = NULL;\u003Cbr>\t}\u003Cbr>\tif (pClrRuntimeHost)\u003Cbr>\t{\u003Cbr>\t\t\u002F\u002F Please note that after a call to Stop, the CLR cannot be \u003Cbr>\t\t\u002F\u002F reinitialized into the same process. This step is usually not \u003Cbr>\t\t\u002F\u002F necessary. You can leave the .NET runtime loaded in your process.\u003Cbr>\t\t\u002F\u002Fwprintf(L\"Stop the .NET runtime\\n\");\u003Cbr>\t\t\u002F\u002FpClrRuntimeHost-&gt;Stop();\u003Cbr>\t\tpClrRuntimeHost-&gt;Release();\u003Cbr>\t\tpClrRuntimeHost = NULL;\u003Cbr>\t}\u003Cbr>\treturn hr;\u003Cbr>}\u003Cbr>\u003Cbr>int main()\u003Cbr>{\u003Cbr>\tRuntimeHost_GetRuntime_ICLRRuntimeInfo(L\"v4.0.30319\", L\"ClassLibrary1.dll\", L\"ClassLibrary1.Class1\", L\"TestMethod\", L\"argstring\");\u003Cbr>\treturn 0;\u003Cbr>}\u003C\u002Fwindows.h>\u003C\u002Fmetahost.h>\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>The code will load ClassLibrary1.dll (developed with .NET 4.0) from the same directory, with class name Class1, method TestMethod, and passed parameter argstring.\u003C\u002Fp>\u003Cp>The code for ClassLibrary1.dll is as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>using System;\u003Cbr>using System.Collections.Generic;\u003Cbr>using System.Linq;\u003Cbr>using System.Text;\u003Cbr>using System.Threading.Tasks;\u003Cbr>\u003Cbr>namespace ClassLibrary1\u003Cbr>{\u003Cbr>    public class Class1\u003Cbr>    {\u003Cbr>        public static int TestMethod(string str)\u003Cbr>        {\u003Cbr>            System.Diagnostics.Process p = new System.Diagnostics.Process();\u003Cbr>            p.StartInfo.FileName = \"c:\\\\windows\\\\system32\\\\calc.exe\";\u003Cbr>            p.Start();\u003Cbr>            return 0;\u003Cbr>        }\u003Cbr>    }\u003Cbr>}\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Ch2>0x04 Open Source Exploit Code Analysis\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Ch3>1. Unmanaged CLR Hosting Assembly Loader\u003C\u002Fh3>\u003Cp>https:\u002F\u002Fgithub.com\u002Fcaseysmithrc\u002FAssemblyLoader\u003C\u002Fp>\u003Cp>Utilizes CLR to read shellcode from a predefined array in the code, loads it into memory, and executes it\u003C\u002Fp>\u003Cp>Implementation method is as follows:\u003C\u002Fp>\u003Ch4>1. Load CLR into the process\u003C\u002Fh4>\u003Cp>(1) Call the CLRCreateInstance function to obtain the ICLRMetaHost or ICLRMetaHostPolicy interface\u003C\u002Fp>\u003Cp>(2) Call the ICLRMetaHost::GetRuntime method to obtain a valid ICLRRuntimeInfo pointer\u003C\u002Fp>\u003Cp>(3) Use ICorRuntimeHost\u003C\u002Fp>\u003Cp>\u003Cstrong>Note:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>When using ICorRuntimeHost, a reference to mscorlib.tlb needs to be added. The C++ code is as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>\u002F\u002F Import mscorlib.tlb (Microsoft Common Language Runtime Class Library).\u003Cbr>#import \"mscorlib.tlb\" raw_interfaces_only\t\t\t\t\\\u003Cbr>    high_property_prefixes(\"_get\",\"_put\",\"_putref\")\t\t\\\u003Cbr>    rename(\"ReportEvent\", \"InteropServices_ReportEvent\")\u003Cbr>using namespace mscorlib;\u003Cbr>#pragma endregion\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>In ICorRuntimeHost, the method for reading and loading .NET assemblies from files is defined as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>  virtual HRESULT __stdcall Load_2 (\u003Cbr>    \u002F*[in]*\u002F BSTR assemblyString,\u003Cbr>    \u002F*[out,retval]*\u002F struct _Assembly * * pRetVal ) = 0;\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>The method for reading and loading .NET assemblies from memory is defined as follows:\u003C\u002Fp>\u003Ctable>\u003Ctbody>\u003Ctr>\u003Ctd>\u003Cp>  virtual HRESULT __stdcall Load_3 (\u003Cbr>    \u002F*[in]*\u002F SAFEARRAY * rawAssembly,\u003Cbr>    \u002F*[out,retval]*\u002F struct _Assembly * * pRetVal ) = 0;\u003C\u002Fp>\u003C\u002Ftd>\u003C\u002Ftr>\u003C\u002Ftbody>\u003C\u002Ftable>\u003Cp>\u003Cstrong>Note:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cp>Method definitions are from mscorlib.tlh\u003C\u002Fp>\u003Cp>Here Load_3(...) is used, first reading shellcode from the array, then loading the .NET assembly\u003C\u002Fp>\u003Ch4>2. Load .NET assembly and invoke static method\u003C\u002Fh4>\u003Ch4>3. Clean CLR\u003C\u002Fh4>\u003Ch3>2. Executing a .NET Assembly from C++ in Memory (CLR Hosting)\u003C\u002Fh3>\u003Cp>https:\u002F\u002Fgithub.com\u002Fetormadiv\u002FHostingCLR\u003C\u002Fp>\u003Cp>The method is essentially the same as caseysmith's: both call the ICLRMetaHost::GetRuntime method to obtain a valid ICLRRuntimeInfo pointer, use the ICorRuntimeHost interface, and use Load_3(...) to read and load the .NET assembly from memory.\u003C\u002Fp>\u003Ch3>3. CLR via native code\u003C\u002Fh3>\u003Cp>https:\u002F\u002Fgist.githubusercontent.com\u002Fxpn\u002Fe95a62c6afcf06ede52568fcd8187cc2\u002Fraw\u002Ff3498245c8309d44af38502a2cc7090c318e8adf\u002Fclr_via_native.c\u003C\u002Fp>\u003Cp>It is noteworthy that here, ICLRMetaHost::EnumerateInstalledRuntimes is called to obtain a valid ICLRRuntimeInfo pointer.\u003C\u002Fp>\u003Cp>Then, ICLRRuntimeHost is used to load the .NET assembly from a file and call static methods.\u003C\u002Fp>\u003Ch3>4. metasploit-execute-assembly\u003C\u002Fh3>\u003Cp>https:\u002F\u002Fgithub.com\u002Fb4rtik\u002Fmetasploit-execute-assembly\u003C\u002Fp>\u003Cp>First, create a notepad.exe process, then inject HostingCLRx64.dll into notepad.exe. HostingCLRx64.dll implements in-memory loading of .NET assemblies.\u003C\u002Fp>\u003Cp>Here, we only focus on the details of in-memory loading of .NET assemblies. Code location:\u003C\u002Fp>\u003Cp>https:\u002F\u002Fgithub.com\u002Fb4rtik\u002Fmetasploit-execute-assembly\u002Fblob\u002Fmaster\u002FHostingCLR_inject\u002FHostingCLR\u002FHostingCLR.cpp\u003C\u002Fp>\u003Cp>Details are as follows:\u003C\u002Fp>\u003Cul>\u003Cli>Using .Net v4.0.30319\u003C\u002Fli>\u003Cli>Call the ICLRMetaHost::GetRuntime method to obtain a valid ICLRRuntimeInfo pointer\u003C\u002Fli>\u003Cli>Use the ICorRuntimeHost interface\u003C\u002Fli>\u003Cli>Use Load_3(...) to read and load a .NET assembly from memory\u003C\u002Fli>\u003C\u002Ful>\u003Cp>Essentially the same as 1 and 2\u003C\u002Fp>\u003Ch2>0x05 Exploitation Approach\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>Based on the open-source code in 0x04, execute-assembly typically has the following two exploitation approaches:\u003C\u002Fp>\u003Ch4>1. Read shellcode from memory and load a .NET assembly\u003C\u002Fh4>\u003Cul>\u003Cli>Call the ICLRMetaHost::EnumerateInstalledRuntimes, ICLRMetaHost::GetRuntime, or ICLRMetaHostPolicy::GetRequestedRuntime method to obtain a valid ICLRRuntimeInfo pointer\u003C\u002Fli>\u003Cli>Use the ICorRuntimeHost interface\u003C\u002Fli>\u003Cli>Use Load_3(...) to read and load a .NET assembly from memory\u003C\u002Fli>\u003Cli>Call static methods\u003C\u002Fli>\u003C\u002Ful>\u003Ch4>2. Read and load a .NET assembly from disk\u003C\u002Fh4>\u003Cul>\u003Cli>Call the ICLRMetaHost::EnumerateInstalledRuntimes, ICLRMetaHost::GetRuntime, or ICLRMetaHostPolicy::GetRequestedRuntime method to obtain a valid ICLRRuntimeInfo pointer\u003C\u002Fli>\u003Cli>Use the ICorRuntimeHost (using Load_2(...)) or ICLRRuntimeHost interface\u003C\u002Fli>\u003Cli>Load .NET assembly and invoke static methods\u003C\u002Fli>\u003C\u002Ful>\u003Cp>The first exploitation approach is superior to the second; the complete exploitation process is as follows:\u003C\u002Fp>\u003Col>\u003Cli>Create a normal process\u003C\u002Fli>\u003Cli>Inject DLL into the process via DLL reflection\u003C\u002Fli>\u003Cli>The DLL implements reading shellcode from memory and loading the final .NET assembly\u003C\u002Fli>\u003C\u002Fol>\u003Cp>Advantages are as follows:\u003C\u002Fp>\u003Cul>\u003Cli>The entire process executes in memory without writing to the file system\u003C\u002Fli>\u003Cli>The payload exists as a DLL, avoiding suspicious processes\u003C\u002Fli>\u003Cli>The final payload is a C# program, making conversion from existing PowerShell exploitation scripts to C# code convenient\u003C\u002Fli>\u003C\u002Ful>\u003Ch2>0x06 Defense Recommendations\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>The entire exploitation process requires DLL injection; common DLL injection methods (especially DLL reflection) can be intercepted\u003C\u002Fp>\u003Cp>Regarding the DLL itself, when using CLR, system DLLs such as the following will be loaded:\u003C\u002Fp>\u003Cul>\u003Cli>mscoree.dll\u003C\u002Fli>\u003Cli>mscoreei.dll\u003C\u002Fli>\u003Cli>mscorlib.dll\u003C\u002Fli>\u003C\u002Ful>\u003Cp>This can be monitored\u003C\u002Fp>\u003Ch2>0x07 Summary\u003C\u002Fh2>\u003Cp>---\u003C\u002Fp>\u003Cp>This article combines multiple open-source codes to summarize the implementation methods and exploitation ideas of \"execute-assembly\", analyzes its advantages, and finally provides defense recommendations\u003C\u002Fp>\u003C\u002Fbody>\u003C\u002Fhtml>","text","ltr",null,"Onedaysec",7,"published","2026-02-02T08:20:05.022Z",{"title":36,"description":14,"keywords":37,"ogImage":30,"canonicalUrl":30,"noIndex":38},"Exploiting .NET Assembly Loading from Memory (execute-assembly)",".NET assembly loading, execute-assembly exploitation, memory execution, Cobalt Strike, CLR hosting, ICLRRuntimeHost, ICorRuntimeHost, .NET security, defense recommendations",false,[],{"docs":41,"hasNextPage":38},[42,43,44,4],107,106,105,{"title":30,"description":30,"image":30},"2026-07-24T02:07:29.698Z","2026-07-23T16:01:01.033Z","draft","2026-07-23T16:03:38.026Z"]