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

news/2024/4/27 13:07:49/文章来源:https://blog.csdn.net/heisejiuhuche/article/details/129268901

写在最前

如果你是信息安全爱好者,如果你想考一些证书来提升自己的能力,那么欢迎大家来我的 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。整个过程就是利用 OpenProcessInitializeProcThreadAttributeList, 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,										// 文档强制为 0PROC_THREAD_ATTRIBUTE_PARENT_PROCESS,	// 使用 PROC_THREAD_ATTRIBUTE_PARENT_PROCESS 来创建新的进程&hParentProcess, 						// 新进程的父进程sizeof(HANDLE),							// HANDLE 的 sizeNULL,									// 文档强制为 NULLNULL									// 文档强制为 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 中的 StartupInfoNULL,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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_75528.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Active Directory管理帮助台

随着组织规模扩大,需要大幅增加Active Directory帮助台指派。随着组织开始在新地点开设办事处,管理员管理所有地点的用户变得极为繁琐。在这样的情况下,帮助台指派需要横跨不同的域以方便多域管理。尝试使用本机AD工具或PowerShell执行帮助台…

守护进程与TCP通讯

目录 一.守护进程 1.1进程组与会画 1.2守护进程 二.创建守护进程 setsid函数: 三. TCP通讯流程 3.1三次握手: 3.2 数据传输的过程 3.3四次挥手 一.守护进程 1.1进程组与会画 进程组:进程组由一个进程或者多个进程组成,每…

springcloud3 Nacos中namespace和group,dataId的联系

一 Namespance和group和dataId的联系 1.1 3者之间的联系 话不多说,上答案,如下图: namespance用于区分部署环境,group和dataId用于逻辑上区分两个目标对象。 二 案例:实现读取注册中心的不同环境下的配置文件 …

如何顺利渡过三月“大考”?ScanV为您献上“通关秘籍”

随着网络安全形势日益复杂、严峻,在重大安全保障事件期间,重要业务系统,尤其是党政机关、国企央企、能源、金融等重要的关基单位更应重视网站及业务系统安全。 临近三月重保季,知道创宇推出“御黑行动-典型案例篇”,以…

王道计算机网络课代表 - 考研计算机 第一章 计算机网络体系结构 究极精华总结笔记

本篇博客是考研期间学习王道课程 传送门 的笔记,以及一整年里对 计算机网络 知识点的理解的总结。希望对新一届的计算机考研人提供帮助!!! 关于对 “计算机网络体系结构” 章节知识点总结的十分全面,涵括了《计算机网络…

【大话面试】- Redis 篇-第一篇

【大话面试】- Redis 篇-第一篇 认识 NoSQL SQL VS NoSQL 1️⃣ 结构化(Structured) SQL 的存储格式 NoSQL 从其存储的结构上来看,对于 SQL 数据库而言,我们可以给每一个表的属性添加不同的约束(主键唯一&#xff…

分布式任务处理

分布式任务处理 1. 什么是分布式任务调度 视频上传成功需要对视频的格式进行处理,如何用Java程序对视频进行处理呢?这里有一个关键的需求就是当视频比较多的时候我们如何可以高效处理。 如何去高效处理一批任务呢? 1、多线程 多线程是充…

java易错题锦集系列五

接口中不能有构造方法,抽象类中可以有。抽象类中构造方法作用:初始化抽象类的成员;为继承它的子类使用 定义在同一个包(package)内的类是可以不经过import而直接相互使用 final修饰的方法可以被重载 但不能被重写 从…

线性插值基本原理推导

线性插值基本原理1. 为什么使用线性插值?2. 单线性插值2.1 单线性插值推导3.多线性插值3.1 多线性插值推导注意事项3.2 多线性插值推导3.3 插入坐标越远权重越大1. 为什么使用线性插值? 在深度学习对图片进行上采样和下采样的时候会应用到线性插值 对图…

TypeScript 使用 ES6 解构骚操作

TypeScript 使用 ES6 解构骚操作 文章目录TypeScript 使用 ES6 解构骚操作一、TypeScript 对象解构二、TypeScript 函数参数解构四、参考资料💘五、推荐博文🍗一、TypeScript 对象解构 我们都知道 ES6 的数据解构功能很强大,一行命令就能够声…

TypeScript 常用知识

「 推荐一个学习 ts 基础的专栏,满满的干货:typeScript 」 1、为什么推荐使用 TypeScript 【】ts 是 js 的超集,包含 js 的所有元素 【】ts 通过对代码进行类型检查,可以帮助我们避免在编写 js 时经常遇到令人痛苦的错误 【】强…

云端IDE系列教程7:解决 WeTTY 在 Ubuntu 非 root 用户不能运行的问题

原文作者:行云创新技术总监 邓冰寒 概述 上一期在使用官方容器镜像快速成功地在 TitanIDE 运行起来了 WeTTY,但是不适合开发人员使用,而我自己编译构建出来的容器镜像无法直接运行指定的应用(/bin/bash 或 /bin/zsh)&…

GO中sync 包的 RWMutex 读写互斥锁

文章目录背景RWMutex 简介代码验证多个协程请求读锁 RLock() 和 RLock()读写交错 RLock() 和 Lock()写入的时候读取读取的时候写入请求多个写Lock() 和 Lock()背景 Mutex 互斥锁是严格锁定读和写,如果我们需要单独对读或者写添加锁需要使用 sync包的RWMutex 针对读…

PHP使用chilkat入门教程

前言: 我们需要先确认自己的版本,在PHP中,可以利用phpinfo()函数来查看php是ts版本还是nts版本,该方法可以展示出当前phpinfo信息,若“Thread Safety”项的信息是“enabled”,一般来说就表示ts版本&#xf…

备战英语6级——记录复习进度

开始记录—— 学习:如何记录笔记? 1:首先我认为:电脑打字比较适合我! 2:先记笔记,再“填笔记”! 记笔记就是一个框架,记录一个大概的东西。后面需要在笔记中&#xff0…

实例10:四足机器人运动学逆解可视化与实践

实例10: 四足机器人运动学逆解单腿可视化 实验目的 了解逆运动学的有无解、有无多解情况。了解运动学逆解的求解。熟悉逆运动学中求解的几何法和代数法。熟悉单腿舵机的简单校准。掌握可视化逆向运动学计算结果的方法。 实验要求 拼装一条mini pupper的腿部。运…

Qt 事件机制

【1】事件 事件是可以被控件识别的操作。如按下确定按钮、选择某个单选按钮或复选框。 每种控件有自己可识别的事件,如窗体的加载、单击、双击等事件,编辑框(文本框)的文本改变事件等等。 事件就是用户对窗口上各种组件的操作。…

LibAFL的安装及基本使用

本教程安装LibAFL使用的是Ubuntu 22.04 操作系统 1. 安装 1.1 Rust 安装 Rust的安装,参照Rust官网:https://www.rust-lang.org/tools/install curl --proto https --tlsv1.2 -sSf https://sh.rustup.rs | sh1.2 LLVM安装 直接apt安装,安…

表格形式的Sarsa与Q_learning算法

环境如下: 这是一个简单的环境,绿色方块代表终点,白色方块代表可行点,灰色方块代表陷阱 用Sarsa算法和Q_learning算法训练得到value表格 代码如下: (jupyter notebook上的代码,所以顺序看起来有点儿奇怪) …

Java8 新特性强大的Stream API

一、Stream API 说明 Java8中有两大最为重要的改变。第一个是 Lambda 表达式;另外一个则是 Stream API。 Stream API ( java.util.stream) 把真正的函数式编程风格引入到Java中。这是目前为止对Java类库最好的补充,因为Stream API可以极大提供Ja…