PPID的攻与防
PPID Spoofing(父进程欺骗)
主要参考文章: 奇安信攻防社区-Parent Process ID (PPID) Spoofing
PPID Spoofing介绍
PPID Spoofing(父进程ID欺骗)的原理是通过操控新进程的父进程ID,使其看起来是由另一个指定的进程创建的,而不是实际创建它的进程。这种技术可以用来隐藏恶意行为或混淆分析,避免安全软件的检测,因为某些安全工具可能通过监控父子进程关系来检测恶意软件。
PPID Spoofing 的操作方式: 在PPID欺骗中,通过使用扩展启动信息(STARTUPINFOEXA
)结构,并修改其属性列表(ProcThreadAttributeList
),可以在进程创建时指定一个自定义的父进程,而不是默认的创建进程。这通过在创建新进程时,将另一个合法进程的句柄传递给CreateProcess
函数来实现。
PPID Spoofing实现代码
先看一个代码:
这个代码的主要功能是在一个指定的父进程下创建一个notepad
子进程,通过CreateProcessA
来实现,并使用扩展启动信息STARTUPINFOEXA
来指定父进程。
1 | //PPID Spoofing(父进程id欺骗)。 |
这里解释下这些代码的含义:
1 | InitializeProcThreadAttributeList(NULL, 1, 0, &attributeSize); |
这是调用InitializeProcThreadAttributeList
函数的第一步,目的是计算所需的属性列表大小
参数解释:
NULL
:因为这次调用是为了获取需要的内存大小,而不是初始化实际的列表,所以传递NULL
。1
:指定列表中的属性数量。在这个例子中,我们只需要一个属性(父进程属性),因此传递1
。0
:这个标志(dwFlags
)一般设置为0
,表示没有特殊要求。&attributeSize
:用于存储计算出来的属性列表所需的大小。在第一次调用时,InitializeProcThreadAttributeList
不会创建列表,而是通过这个参数告诉你需要多大的内存来存储属性列表。
1 | si.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, attributeSize); |
功能:这行代码为属性列表分配内存,大小为上一步中得到的attributeSize
。
参数解释:
GetProcessHeap()
:获取当前进程的默认堆句柄,用于分配内存。0
:分配内存的标志,一般设置为0
,表示没有特殊的分配要求。attributeSize
:上一步计算出来的内存大小,这是属性列表所需的大小。
最终,HeapAlloc
函数为属性列表分配一块堆内存,并将其赋值给si.lpAttributeList
,这是一个指向属性列表的指针。
1 | InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &attributeSize); |
功能:在分配了合适大小的内存之后,真正初始化属性列表。
参数解释:
si.lpAttributeList
:这是前面分配好的内存指针,InitializeProcThreadAttributeList
将在此指针指向的内存块中初始化属性列表。1
:指定属性列表中包含的属性数量。此处为1,因为我们只需要一个属性(父进程属性)。0
:这个标志(dwFlags
)一般设置为0
,表示没有特殊要求。&attributeSize
:在第一次调用时,它用于计算大小。第二次调用时,它已经被分配了内存并传递给该函数。
1 | UpdateProcThreadAttribute(si.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &parentProcessHandle, sizeof(HANDLE), NULL, NULL); |
- 功能:这行代码将父进程属性更新到启动信息的属性列表中。换句话说,它将告诉
CreateProcessA
函数,在启动新的进程时要将parentProcessHandle
作为新进程的父进程。 - 参数解释:
si.lpAttributeList
:指向我们之前通过HeapAlloc
分配并初始化好的属性列表,这里更新父进程信息到这个列表中。0
:保留参数,通常设置为0。PROC_THREAD_ATTRIBUTE_PARENT_PROCESS
:这是一个常量,表示父进程属性。通过这个参数告诉系统,你要在新创建的进程中设置一个特定的父进程。&parentProcessHandle
:指向父进程的句柄。我们之前通过OpenProcess
函数获取了这个父进程句柄,将它传递给新进程作为其父进程。sizeof(HANDLE)
:表示传递的parentProcessHandle
的大小,HANDLE
在Windows系统中通常是一个32位或64位的值,所以需要传递它的字节大小。NULL, NULL
:这两个参数分别用于额外的输入缓冲区和返回缓冲区,这里不需要,因此传递NULL
。
总结:通过这行代码,si.lpAttributeList
这个属性列表中被更新了父进程信息。当后续使用CreateProcessA
创建进程时,这个新进程的父进程将是parentProcessHandle
指定的进程。
1 | si.StartupInfo.cb = sizeof(STARTUPINFOEXA); |
- 功能:这行代码的作用是使用扩展的启动信息结构
STARTUPINFOEXA
来创建一个新的进程,并且传递我们刚刚设置的父进程属性。具体来说,这个进程是notepad.exe
,它的父进程将是之前通过parentProcessHandle
指定的进程。
参数解析:
- **
si.StartupInfo.cb = sizeof(STARTUPINFOEXA);
**:- 设置
STARTUPINFOEXA
结构的大小(cb
字段),这是标准步骤,CreateProcessA
需要知道这个结构的大小。 - 这里是通过扩展启动信息结构
STARTUPINFOEXA
来实现父进程继承,而不仅仅是普通的STARTUPINFO
。
- 设置
CreateProcessA(NULL, (LPSTR)"notepad", NULL, NULL, FALSE, EXTENDED_STARTUPINFO_PRESENT, NULL, NULL, &si.StartupInfo, &pi);
NULL
:应用程序的路径,这里传递NULL
,表示通过命令行参数"notepad"
来启动程序。(LPSTR)"notepad"
:命令行参数,指定要启动的应用程序是notepad.exe
。NULL
:进程安全属性,表示使用默认安全属性。NULL
:线程安全属性,表示使用默认安全属性。FALSE
:不继承父进程的句柄。EXTENDED_STARTUPINFO_PRESENT
:这是关键的标志,表示你使用了扩展启动信息(即STARTUPINFOEXA
)。这个标志告诉系统要检查并应用si.lpAttributeList
中的属性(如父进程属性)。NULL
:新进程的环境变量,使用当前进程的默认环境变量。NULL
:新进程的工作目录,使用当前进程的默认工作目录。&si.StartupInfo
:传递启动信息结构,实际上是STARTUPINFOEXA
结构。&pi
:进程信息结构,CreateProcessA
调用成功后将返回新进程的句柄和ID等信息。
总结:通过CreateProcessA
函数,使用了我们之前设置的扩展启动信息(包含父进程属性),启动了notepad.exe
进程,并将其父进程设置为我们指定的进程。
效果展示:
我们指定一个Notepad.exe的父进程为一个Notepad.exe
利用ETW监控PPID
复习一下之前说的ETW
Providers(提供程序)
:可以产生事件日志的程序;Consumers
:订阅和监听 Providers 发出的事件的程序Keywords(关键字)
:Providers 提供给 Consumer 的事件类型;Tracing session(跟踪会话)
:记录来自一个或多个 Providers 的事件;Contollers
:可以启动 Tracing session 的程序。
步骤
1.选择Providers
我们可以用 Windows 自带的工具 Logman.exe启动跟踪会话
这里的 Logman.exe
对应着上面的Controllers
角色。
首先列出有哪些 Providers
1 | logman query providers |
这样一下会列出超级多的项,但是我们只需要关注一个就行
也就是Microsoft-Windows-Kernel-Process
这个providers
具体来说,有两种方式可以查询
1 | # 通过 provider 名字查询 |
查询出来长这样
可以看到,这个 Providers 有一些 Keywords(关键字),代表这个 Providers 可以提供一些进程、线程等事件。对我们的 PPID欺骗来说,只要看进程就行,所以这里选择的关键字是WINEVENT_KEYWORD_PROCESS
,对应值为0x10
如果我想要同时知道 WINEVENT_KEYWORD_PROCESS
和 WINEVENT_KEYWORD_THREAD
怎么办,那么只需要把他们相加,也就是0x10+0x20=0x30
2.创建跟踪会话并指定 Provider
选定好Providers之后,我们就需要启用Tracing session,也就是跟踪会话。这里我们指定一个名字,例如叫ppid-spoof
,同时指定Providers
为我们之前找的Microsoft-Windows-Kernel-Process
,keyword指定为0x10,也就是WINEVENT_KEYWORD_PROCESS
1 | logman create trace ppid-spoofing -p Microsoft-Windows-Kernel-Process 0x10 -ets |
指令详解
- **
logman
**:这是 Windows 的命令行工具,用于管理事件跟踪会话,能够创建、启动、停止和删除跟踪会话。 - **
create trace
**:这是指令的动作,表示创建一个新的追踪会话。 - **
ppid-spoof
**:这是新创建的追踪会话的名称。你可以自定义这个名称,以便于后续识别和管理这个会话。 - **
-p Microsoft-Windows-Kernel-Process
**:这个参数指定要注册的 ETW 提供程序(Provider)。在这里,Microsoft-Windows-Kernel-Process
是一个系统提供程序,负责跟踪与进程相关的事件,如进程的创建、终止等。 - **
0x10
**:这是一个事件关键字(Keyword),用于过滤事件类型。0x10
对应于WINEVENT_KEYWORD_PROCESS
,它指定了要监控的事件类别,即与进程活动相关的事件。具体来说,0x10
表示仅记录与进程创建相关的事件。 - **
-ets
**:这是一个选项,表示使用 ETW(事件跟踪)会话。在这种模式下,创建的追踪会话将立即开始记录事件,而不需要后续手动启动。
然后可以查一下这个跟踪会话的内容,确保它在运行。
1 | logman query ppid-spoofing -ets |
我们主要观察三个点
1.状态 2.输出位置 3.KeywordsAny
3. 分析日志
我们打开之前我们收集到的etl文件
然后打开事件查看器
选择打开保存的日志,就把我们之前生成的etl文件导入进去
这里选择否
进来之后去对比俩,一个是Execution的PID,另一个是EventData的PID
CreateProcess可以改变的是
真正的 ParentProcessID
应该是Execution
下的ProcessID
可以看到,如果是伪造的,这俩是不一样的
Execution 中的 ProcessID:
- 这是与事件的执行上下文相关的进程ID。它通常指的是正在执行该事件的线程所属的进程。换句话说,它表示触发该事件的执行上下文。
因此得出结论,Execution
下的ProcessID
和Data
下的ParentProcessID
的值要一致,否则可能是 PPID 欺骗,而且真正的 PPID 应该以Execution
下的ProcessID
为准。
4.移除会话
想要移除这个会话
1 | logman stop ppid-spoof -ets |