防御规避技术
父进程伪造
#include <windows.h>
#include <TlHelp32.h>
#include <iostream>
DWORD FindExplorerProcessId()
{
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot)
{
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(PROCESSENTRY32);
if (Process32First(hSnapshot, &pe32))
{
do
{
if (_wcsicmp(pe32.szExeFile, L"explorer.exe") == 0)
{
CloseHandle(hSnapshot);
return pe32.th32ProcessID; // Returns the first instance's PID
}
} while (Process32Next(hSnapshot, &pe32));
}
CloseHandle(hSnapshot);
}
return 0;
}
int main()
{
DWORD pid = FindExplorerProcessId();
if (pid != 0)
{
printf("The PID of the first instance of explorer.exe: %lu\n", pid);
}
else
{
printf("explorer.exe is not running.\n");
}
STARTUPINFOEXA si;
PROCESS_INFORMATION pi;
SIZE_T attributeSize;
ZeroMemory(&si, sizeof(STARTUPINFOEXA));
HANDLE parentProcessHandle = OpenProcess(MAXIMUM_ALLOWED, false, pid);
InitializeProcThreadAttributeList(NULL, 1, 0, &attributeSize);
si.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, attributeSize);
InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &attributeSize);
UpdateProcThreadAttribute(si.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &parentProcessHandle, sizeof(HANDLE), NULL, NULL);
si.StartupInfo.cb = sizeof(STARTUPINFOEXA);
CreateProcessA(NULL, (LPSTR)"notepad", NULL, NULL, FALSE, EXTENDED_STARTUPINFO_PRESENT, NULL, NULL, &si.StartupInfo, &pi);
return 0;
}
查看当前的 explorer.exe 进程,有着诸多子进程,包括即将运行该程序的 cmd.exe。
如果没有父进程伪造,那么进程树的关系应该是 explorer.exe -> cmd.exe -> ppid_spoofing.exe -> mspaint.exe。
我们看到,程序得以正确运行,mspaint.exe 成了 explorer.exe 的直接子进程。
命令行参数伪造
创建一个暂停状态的 powershell 进程:
STARTUPINFOEXA si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(STARTUPINFOEXA));
si.StartupInfo.cb = sizeof(STARTUPINFOEXA);
LPSTR app = (LPSTR)"C:\\Windows\\System32\\WindowsPowershell\\v1.0\\powershell.exe";
LPSTR fakearg= (LPSTR)"-c mspaint.exe";
CreateProcessA(app, fakearg, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si.StartupInfo, &pi);
printf("Created process ID: %d\n", pi.dwProcessId);
通过 NtQueryInformationProcess 函数请求 PROCESS_BASIC_INFORMATION 数据,函数调用成功后 ProcessInformation 参数被填充,该参数是被请求信息的缓冲区指针。
其中,NtQueryInformationProcess 的函数原型如下:
__kernel_entry NTSTATUS NtQueryInformationProcess(
[in] HANDLE ProcessHandle,
[in] PROCESSINFOCLASS ProcessInformationClass,
[out] PVOID ProcessInformation,
[in] ULONG ProcessInformationLength,
[out, optional] PULONG ReturnLength
);
PROCESS_BASIC_INFORMATION 结构体如下:
typedef struct _PROCESS_BASIC_INFORMATION {
NTSTATUS ExitStatus;
PPEB PebBaseAddress;
ULONG_PTR AffinityMask;
KPRIORITY BasePriority;
ULONG_PTR UniqueProcessId;
ULONG_PTR InheritedFromUniqueProcessId;
} PROCESS_BASIC_INFORMATION;
这样,我们获得了 PEB 的地址,从而使用 ReadProcessMemory 函数将 PEB 结构体中的数据拷贝到 peb 变量中:
PPEB peb = new PEB();
SIZE_T bytesRead = 0;
ReadProcessMemory(pi.hProcess, pbi.PebBaseAddress, peb, sizeof(PEB), &bytesRead);
其中,ReadProcessMemory 函数原型如下:
BOOL ReadProcessMemory(
[in] HANDLE hProcess,
[in] LPCVOID lpBaseAddress,
[out] LPVOID lpBuffer,
[in] SIZE_T nSize,
[out] SIZE_T *lpNumberOfBytesRead
);
回顾一下 PEB 的数据结构:
typedef struct _PEB {
BYTE Reserved1[2];
BYTE BeingDebugged;
BYTE Reserved2[1];
PVOID Reserved3[2];
PPEB_LDR_DATA Ldr;
PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
PVOID Reserved4[3];
PVOID AtlThunkSListPtr;
PVOID Reserved5;
ULONG Reserved6;
PVOID Reserved7;
ULONG Reserved8;
ULONG AtlThunkSListPtr32;
PVOID Reserved9[45];
BYTE Reserved10[96];
PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
BYTE Reserved11[128];
PVOID Reserved12[1];
ULONG SessionId;
} PEB, *PPEB;
其中 PEB 中的 ProcessParameters 是 RTL_USER_PROCESS_PARAMETERS 结构体的指针:
typedef struct _RTL_USER_PROCESS_PARAMETERS {
BYTE Reserved1[16];
PVOID Reserved2[10];
UNICODE_STRING ImagePathName;
UNICODE_STRING CommandLine;
} RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS;
ImagePathName 与 CommandLine 都是 UNICODE_STRING 结构体。用 ReadProcessMemory 函数将 ProcessParameters 结构体中的数据拷贝到 parameters 变量里。
PRTL_USER_PROCESS_PARAMETERS parameters = new RTL_USER_PROCESS_PARAMETERS();
ReadProcessMemory(pi.hProcess, pbi.PebBaseAddress, parameters, sizeof(RTL_USER_PROCESS_PARAMETERS), &bytesRead);
然后,我们就可以通过 WriteProcessMemory 函数来补丁 CommandLine.Buffer 了。
BOOL WriteProcessMemory(
[in] HANDLE hProcess,
[in] LPVOID lpBaseAddress,
[in] LPCVOID lpBuffer,
[in] SIZE_T nSize,
[out] SIZE_T *lpNumberOfBytesWritten
);
#include <windows.h>
#include <TlHelp32.h>
#include <iostream>
#include <winternl.h>
#include <vector>
int main()
{
STARTUPINFOWLPSTARTUPINFOW si;si si.= new STARTUPINFOW();
si->cb = sizeof(STARTUPINFOW);
PROCESS_INFORMATIONPPROCESS_INFORMATION pi;pi = new PROCESS_INFORMATION();
LPCWSTR app = L"C:\\Windows\\System32\\WindowsPowerShell\WindowsPowershell\\v1.0\\powershell.exe\0";
wchar_t fakeargs[fakearg[] = L"powershell -c \"Get-Processget-process | findstr explorer.exe\0";
CreateProcess(app, fakeargs,fakearg, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi);
PROCESS_BASIC_INFORMATIONprintf("Created pbi;process NtQueryInformationProcess(pi.ID: %d\n", pi->dwProcessId);
PPROCESS_BASIC_INFORMATION pbi=new PROCESS_BASIC_INFORMATION();
typedef NTSTATUS(NTAPI* DynamicNtQueryInformationProcess)(
IN HANDLE ProcessHandle,
IN PROCESSINFOCLASS ProcessInformationClass,
OUT PVOID ProcessInformation,
IN ULONG ProcessInformationLength,
OUT PULONG ReturnLength OPTIONAL);
DynamicNtQueryInformationProcess pDynamicNtQueryInformationProcess = (DynamicNtQueryInformationProcess)GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtQueryInformationProcess");
pDynamicNtQueryInformationProcess(pi->hProcess, ProcessBasicInformation, &pbi, sizeof(PROCESS_BASIC_INFORMATION), NULL);
PPEB peb = new PEB();
SIZE_T bytesRead = 0;
ReadProcessMemory(pi.pi->hProcess, pbi.pbi->PebBaseAddress, peb, sizeof(PEB), &bytesRead);
PRTL_USER_PROCESS_PARAMETERS parameters = new RTL_USER_PROCESS_PARAMETERS();
ReadProcessMemory(pi.pi->hProcess, pbi.PebBaseAddress,peb->ProcessParameters, parameters, sizeof(RTL_USER_PROCESS_PARAMETERS), &bytesRead);
std::vector<BYTE> vector(sizeof(fakeargs)fakearg));
RtlZeroMemory(&vector[0], sizeof(fakeargs)fakearg));
WriteProcessMemory(pi.pi->hProcess, parameters->CommandLine.Buffer, &vector[0], sizeof(fakeargs)fakearg), NULL);
wchar_t realargs[] = L"powershell -c \"Write-host real args\"mspaint.exe\0";
WriteProcessMemory(pi.pi->hProcess, parameters->CommandLine.Buffer, &realargs, sizeof(realargs), NULL);
ResumeThread(pi.pi->hThread);
CloseHandle(pi.pi->hThread);
CloseHandle(pi.pi->hProcess);
return 0;
}