免杀断链
免杀断链
前言
在这篇文章中我们介绍PEB断链(R3层和R0层)
R3层隐藏模块
首先我们介绍一下PEB断链,相信各位读者大大对PEB这个结构体可以说相当熟悉了,这里我们简单描述一下,做一个介绍
在开始使用TEB/PEB 获取进程的模块信息之前,我想有必要解释一下这两个名词:PEB 指的是进程环境块(Process Environment Block),用于存储进程状态信息和进程所需的各种数据。每个进程都有一个对应的 PEB 结构体。TEB 指的是线程环境块(Thread Environment Block),用于存储线程状态信息和线程所需的各种数据。每个线程同样都有一个对应的 TEB 结构体。
PEB 中包含了进程的代码、数据段指针、进程的环境变量、进程启动参数信息以及加载的模块信息等。在 x86-32 体系下,FS 段寄存器偏移 0x30 处存放了索引,索引查找的指针指向当前进程的 PEB 结构体,在 x86-64 下该指针位于 FS 段寄存器偏移 0x60 处。其他进程可以通过访问自己的 PEB 结构体来获取自己的状态和信息。
TEB 中包含了线程的堆栈指针、TLS(线程本地存储)指针、异常处理链表指针、用户模式分页表指针等信息。在 x86-32体系下,FS 段寄存器偏移 0x18 处通常为指向 TEB 结构体的指针,在 x86-64 下该指针位于 FS 段寄存器偏移 0x30 处。其他线程可以通过访问自己的 TEB 结构体来获取自己的状态和信息。
而对于 PEB 结构体,微软是没有公开文档的,需要自己进行重定义(原始结构体定义中缺少我们需要的部分),经查阅逆向文献,得到如下的结构体定义(部分不需要用到的成员已经被截断):
1 |
|
PEB_LDR_DATA结构体
以 x86-32 为例:PEB 结构体中偏移为 0xC 的成员变量是 Ldr 该变量是一个指向 PEB_LDR_DATA 结构体的指针。
该结构体的第一个变量 Length 表示链表长度信息,大小是结点数乘以当前范围的大小(x32 是0x4,x64 是 0x8),最后再减去 1。
然后从偏移 0xC 开始,就是三个 LIST_ENTRY 链表的头结点,链表中结点数据类型都是 LIST_ENTRY,只是链表的排序模式不同。
LIST_ENTRY结构体
LIST_ENTRY 结构是模块链表结构里的结点数据结构,它包含两个成员指针, Flink 指向下一个链表结点,Blink 指向前一个链表结点。模块链表属于一种双向链表的数据结构。
LDR_DATA_TABLE_ENTRY结构体
对于每一个指针,它实际指向的数据结构并不是 LIST_ENTRY 结构体,而是 LDR_DATA_TABLE_ENTRY结构体,这是每一个结点指向的模块信息数据结构。
对于该结构中的 DllBase 是当前模块的基址,EntryPoint 是模块的入口地址,SizeOfImage 是映像大小,FullDllName 是模块完整路径字符串。
可以通过遍历链表结点的方式获取所有模块的信息:LDR_DATA_TABLE_ENTRY 结构中的 LIST_ENTRY 结构对应下一个 LDR_DATA_TABLE_ENTRY 结点中的 LIST_ENTRY 结构。
这些信息引用于其他大佬的文章下面是原文链接,这里只复制了我们需要的信息,想要了解的更全面,就可以阅读那篇文章
原文链接:https://blog.csdn.net/qq_59075481/article/details/135281393
这里我们了解到每一个结构体都有两个指针,分别指向其前后的结构体,并且每个结构体都带有该模块的信息,所以我们通过获取到需要的信息再进行判读,如果满足条件,就执行模块隐藏操作(让其前后指针都指向自己)。
实现的效果:这里我们可以看到已经隐藏成功。这是效果图
R0层隐藏进程****注:基于R0层进行隐藏进程,需要使用驱动,但是有个问题就是加载驱动需要带有有效签名,如果没有签名windows是不会将驱动加载起来的。获取进程模块的步骤为:获取 _KPCR -> _KPRCB -> KTHREAD -> _KAPC_STATE -> _KPROCESS -> _EPROCESS(1)KPCR 的获取方法为:fs:[0](2)KPCR 结构体中偏移0x120处有一个成员 _KPRCB 结构体,该结构体中偏移0x4处(即 fs:[0x124])有一个 CurrentThread 指针,指向 _KTHREAD 结构体(3)_KTHREAD 结构体偏移0x34处有一个 ApcState 结构体,该结构体偏移0x10处有一个 Process 指针,指向 _KPROCESS(4)_EPROCESS 结构体的第一个成员是 Pcb,是一个 _KPROCESS 结构体(5)(3)中的 Process 指针指向的就是 _EPROCESS 中的一个成员 _KPROCESS 结构体,也是 _EPROCESS 结构体的首地址。这一点和PEB断链中,ldr->Flink 与 _LDR_DATA_TABLE_ENTRY 首地址相等是一样的(6)_EPROCESS 结构体中偏移0x88有一个 _LIST_ENTRY 结构体,该结构体保存了当前进程在进程链表中的位置信息(7)节点指针指向的是 _LIST_ENTRY 结构体,该结构体存储了模块在链表中的位置信息(8)通过 _EPROCESS 结构体中偏移0x174的 ImageFileName 确定所属进程, 并通过该结构体中的 _LIST_ENTRY结构体删除该节点在链表中的位置信息(双向循环链表删除节点),即可达到0环下的进程隐藏。
PPID Spoofing**
PPID spoofing是一种攻击技术,用于欺骗系统和安全工具,使其认为恶意进程的父进程ID(PPID)是合法的。通过伪装父进程ID,攻击者可以隐藏恶意进程的真实来源,绕过安全监控和检测。这种技术可能被用于实施恶意活动,如隐藏恶意进程、绕过安全防护、进行横向移动等。在安全领域中,需要采取相应的防御措施来防范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 是通过在 STARTUPINFOEXW 结构体中的 PPROC_THREAD_ATTRIBUTE_LIST lpAttributeList 成员中,使用 PROC_THREAD_ATTRIBUTE_PARENT_PROCESS 来告诉最终调用调用的 CreateProcess 函数,将即将创建的进程,归入到指定的父进程之下。PPROC_THREAD_ATTRIBUTE_LIST lpAttributeList 成员是通过 InitializeProcThreadAttributeList分配内存,并由 UpdateProcThreadAttribute 函数设置其属性(设置成 PROC_THREAD_ATTRIBUTE_PARENT_PROCESS),来达到偷换父进程的目的。
InitializeProcThreadAttributeList 函数
初始化用于创建进程和线程的指定属性列表。BOOL InitializeProcThreadAttributeList( [out, optional] LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList, [in] DWORD dwAttributeCount, DWORD dwFlags, [in, out] PSIZE_T lpSize );[out, optional] lpAttributeList属性列表。此参数可以为 NULL,以确定支持指定数量的属性所需的缓冲区大小。[in] dwAttributeCount要添加到列表的属性计数。//这里我们只需要一个就行了,那就是PROC_THREAD_ATTRIBUTE_PARENT_PROCESS属性,用于创建指定的父进程。dwFlags此参数是保留的,必须为零。[in, out] lpSize如果 lpAttributeList 不为 NULL,则此参数指定输入时 lpAttributeList 缓冲区的大小(以字节为单位)。输出时,此参数接收初始化的属性列表的大小(以字节为单位)。如果 lpAttributeList 为 NULL,则此参数接收所需的缓冲区大小(以字节为单位)。UpdateProcThreadAttribute函数BOOL UpdateProcThreadAttribute( [in, out] LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList, [in] DWORD dwFlags, [in] DWORD_PTR Attribute, [in] PVOID lpValue, [in] SIZE_T cbSize, [out, optional] PVOID lpPreviousValue, [in, optional] PSIZE_T lpReturnSize );这里我们主要注意第三个参数,给定一个属性,这里有很多我们只使用其中的一个。PROC_THREAD_ATTRIBUTE_PARENT_PROCESS:**lpValue参数是指向要使用的进程的句柄的指针,而不是作为所创建进程的父进程的调用进程。要使用的进程必须具有PROCESS_CREATE_PROCESS访问权限。从指定进程继承的属性包括句柄、设备映射、处理器相关性、优先级、配额、进程令牌和作业对象。(请注意,某些属性(如调试端口)将来自创建进程,而不是此 handle 指定的进程。)那么思路明确,我们先声明结构体,然后对结构体第二个值进行大小以及属性的初始化。
接下来上代码: