Malware Dev 01 – 免杀之 PPID Spoofing 原理解析

渗透技巧 1年前 (2023) admin
371 0 0

写在最前
如果你是信息安全爱好者,如果你想考一些证书来提升自己的能力,那么欢迎大家来我的 Discord 频道 Northern Bay。邀请链接在这里:

https://discord.gg/9XvvuFq9Wb
我会提供备考过程中尽可能多的帮助,并分享学习和实践过程中的资源和心得,大家一起进步,一起 NB~

背景
把免杀主题放在 Malware Dev 里面有点不恰当,但是真的不想分太细了。我目前就两个方向,Active Directory,和 Malware Dev(包括 shellcode 编写,免杀,C2,Windows Kernel/Driver Exploit)。我也不知道自己顾不顾得过来,但是我相信有些东西是通的,越到后面学习曲线越平滑。呵呵呵~

今天先来看一下进程免杀的技巧第一篇,PPID Spoofing。

PPID Spoofing
PPID Spoofing,全称 Parent PID Spoofing。整个过程就是利用 OpenProcess,InitializeProcThreadAttributeList, UpdateProcThreadAttribute, 以及 CreateProcess 这些 API,配合 STARTUPINFOEX 结构在创建进程的时候,做到父进程的切换。

该技术通常用于 Cobalt Strike Beacon 的免杀。通常如 shell, run, execute-assembly, shspawn 等 post-ex 命令默认会创建在 Beacon 进程下。例如,如果 Beacon 是通过 Powershell 拿到的,那么这些命令的进程就会被 fork 在 Powershell 进程之下。

在企业这样的注重安全的环境中,进程创建事件会被密切监控(如 Sysmon)。如果一个进程总是在创建非常规进程,那么 Beacon 就会大概率被查杀。例如我们通过 Powershell 已经拿到了 shell,Cobalt Strike 的 powerpick 命令默认使用 rundll32 进程。而通常情况下 Powershell 进程是不会生成 rundll32 进程的,造成 Beacon 被查杀(当然这有其他办法可以解决,今后有机会在 C2 部分细说)。

因此,PPID Spoofing 技术就是用来改变恶意进程的父进程,至少在某种程度上,混淆视听,增加防御或是溯源的难度。

接下来我们就来看一下 PPID Spoofing 的基本原理。

PPID Spoofing 原理概述
PPID Spoofing 是通过在 STARTUPINFOEXW 结构体中的 PPROC_THREAD_ATTRIBUTE_LIST lpAttributeList 成员中,使用 PROC_THREAD_ATTRIBUTE_PARENT_PROCESS 来告诉最终调用调用的 CreateProcess 函数,将即将创建的进程,归入到指定的父进程之下。

PPROC_THREAD_ATTRIBUTE_LIST lpAttributeList 成员是通过 InitializeProcThreadAttributeList 分配内存,并由 UpdateProcThreadAttribute 函数设置其属性(设置成 PROC_THREAD_ATTRIBUTE_PARENT_PROCESS),来达到偷换父进程的目的。

PPID Spoofing 原理详解
我们开始拆解 PPID Spoofing 的整个原理,一步一步实践一个 PPID Spoofing。

初始化 STARTUPINFOEXW 结构
一切从 STARTUPINFOEXW 结构体说起。

STARTUPINFOEXW struct:

typedef struct _STARTUPINFOEXW {
STARTUPINFOW StartupInfo;
LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList;
} STARTUPINFOEXW, *LPSTARTUPINFOEXW;
这个结构体包含了两个成员,一个是 STARTUPINFOW,一个是 LPPROC_THREAD_ATTRIBUTE_LIST。我们要关注的是 LPPROC_THREAD_ATTRIBUTE_LIST 这个成员。

首先,我们初始化一个 STARTUPINFOEXW 结构。

STARTUPINFOEX sie = { sizeof(sie) };
初始化 STARTUPINFOEXW 结构中的 lpAttributeList 成员
我们必须先为 lpAttributeList 成员分配一个内存空间。但是这个空间的大小怎么确定呢?

根据官方文档,这个成员由 InitializeProcThreadAttributeList 函数生成。

 

InitializeProcThreadAttributeList function:

BOOL InitializeProcThreadAttributeList(
[out, optional] LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList,
[in] DWORD dwAttributeCount,
DWORD dwFlags,
[in, out] PSIZE_T lpSize
);

这个函数有两个 OUT Parameter,lpAttributeList 和 lpSize。lpSize 是 dwAttributeCount 个 lpAttributeList 中的 flag 的总大小,也就是我们要找的 lpAttributeList 的内存空间大小。在概述中,我们知道这里只需要关心 PROC_THREAD_ATTRIBUTE_PARENT_PROCESS 这一个 flag,因此,我们可以这样来获取 lpSize 参数的值。

SIZE_T lpSize;
InitializeProcThreadAttributeList
(
NULL, // lpAttributeList 先给 NULL,因为第一次调用这个函数是为了获取 lpSize 的值
1, // 我们需要往 lpAttributeList 中放存放 1 个 flag,即 PROC_THREAD_ATTRIBUTE_PARENT_PROCESS
0, // 这个参数官方文档强制为 0
&lpSize // 给出 lpSize 的地址,存放函数的返回值
);

第一次调用之后,我们拿到了 lpSize。接下来,就可以用 lpSize 为 STARTUPINFOEXW 结构中的 lpAttributeList 成员分配 lpSize 大小的内存空间。

sie.lpAttributeList = (PPROC_THREAD_ATTRIBUTE_LIST)malloc(lpSize);

lpAttributeList 在内存中有了空间,下一步就可以再次调用 InitializeProcThreadAttributeList,来初始化 lpAttributeList 成员。

if (!InitializeProcThreadAttributeList
(
sie.lpAttributeList, // lpAttributeList,将被初始化
1, // 我们只需要 1 个 flag 的大小 (PROC_THREAD_ATTRIBUTE_PARENT_PROCESS )
0, // 文档强制为 0
&lpSize // 拥有 1 个 flag 的 lpAttributeList 的大小
)
)
{
_tprintf(L”InitializeProcThreadAttributeList failed. Error code: %d.\n”, GetLastError());
return -1;
}

到这里,STARTUPINFOEXW 结构体中的 lpAttributeList 成员,就初始化完成了。

UpdateProcThreadAttribute 指定父进程
这里,我们要告诉指定的父进程,在创建新的进程的时候,以该指定的进程作为父进程。

我们调用 UpdateProcThreadAttribute 函数来完成这个任务。

if (!UpdateProcThreadAttribute(
sie.lpAttributeList, // 初始化好的 lpAttributeList 成员
0, // 文档强制为 0
PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, // 使用 PROC_THREAD_ATTRIBUTE_PARENT_PROCESS 来创建新的进程
&hParentProcess, // 新进程的父进程
sizeof(HANDLE), // HANDLE 的 size
NULL, // 文档强制为 NULL
NULL // 文档强制为 NULL
)
)
{
_tprintf(L”UpdateProcThreadAttribute failed. Error code: %d.\n”, GetLastError());
return -1;
}

经过这一步,我们可以调用 CreateProcess,配合 EXTENDED_STARTUPINFO_PRESENT flag,在指定进程下,生成新进程。

在指定进程下 CreateProcess 创建新进程
剩下的就是创建新进程了,经过以上步骤,新的进程将会以指定的进程为父进程来创建。

PROCESS_INFORMATION pi;

if (!CreateProcess(
L”C:\\Windows\\System32\\notepad.exe”,
NULL,
0,
0,
FALSE,
EXTENDED_STARTUPINFO_PRESENT, // 告诉 CreateProcess 使用 STARTUPINFOEXW 中的 StartupInfo
NULL,
L”C:\\Windows\\System32″,
&sie.StartupInfo,
&pi))
{
_tprintf(L”CreateProcess failed. Error code: %d.\n”, GetLastError());
return 0;
}

_tprintf(L”New process created with PID: %d”, pi.dwProcessId);
return 0;

最后看一下效果。我们制定 ProcessHacker.exe 为父进程,那么,notepad.exe 就会生成在 ProcessHacker.exe 下面。

 

总结
PPID Spoofing 通常结合 Beacon 使用。Cobalt Strike 中也有专门的 PPID 命令来开启 PPID Spoofing。操作系统提供的 API 也是被利用的对象。通过对 PPID Spoofing 的原理的了解,可以发散更多的 API 组合来绕过特定的防御机制。

免杀部分,我们会逐步总结更多的技巧,在本地搭建的 Lab 中逐一实践。

参考链接
https://learn.microsoft.com/en-us/windows/win32/psapi/enumerating-all-processes?redirectedfrom=MSDN
https://stackoverflow.com/questions/5202114/compare-tchar-with-string-value-in-vc
https://www.geeksforgeeks.org/command-line-arguments-in-c-cpp/
https://stackoverflow.com/questions/5669173/is-there-a-format-specifier-that-always-means-char-string-with-tprintf
https://learn.microsoft.com/en-us/cpp/text/how-to-convert-between-various-string-types?view=msvc-170

 

版权声明:admin 发表于 2023年3月1日 上午9:36。
转载请注明:Malware Dev 01 – 免杀之 PPID Spoofing 原理解析 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...