学习Windows10基础下的一些关于程序隐藏的技术,程序隐藏可以增加分析人员的逆向成本,同时可以将入门分析者挡在门外,甚至对网络安全从业者实施攻击,造成更大的危害。
windows系统分层
x86架构的处理器有四层,ring0 - ring3,Windows系统使用其中的两层ring0(内核)和ring3(用户)。用户模式下程序最终还是通过ntdll.dll去调用内核。如下图所示
1、内核主要组件
- Windows 执行体:Windows 操作系统内核模块偏上 层的部分,包括了许多基本的操作系统服务和系统组件,如进程与线程管理器内存管理器,IO 管理器等,主要负责整个操 作系统的对外功能,并提供应用程序需要的内核服务。
- Windows 内核:提供了操作系统最基本的服务,如多处理器同步,线程调度,中断分发等,执行体通常利用 Windows 内核提供的例程和对象实现更加复杂的功能。
- Windows 驱动程序:包括设备驱动程序和软件驱动程序,主要负责将 IO 管理器的请求转化为特定的硬件设备请求。更详细的内 容会在下面介绍。
- 硬件抽象层:主要目的是将不同硬件之间的差异隐藏起来, 为上层操作系统提供抽象且一致的硬件接口,提高 Windows 操作系统的兼容性和可移植性,使 Windows 操作系统在面对不同厂商制造的不同硬件设备时依然能够用统一的方式处理。
- 窗口管理程序与图形驱动:主要用于处理图形界面绘制,窗体控制等功能,在视窗操作系统中非常重要。
驱动程序
Windows作为闭源的操作系统,他不允许一般的程序去访问内核,但是他为了各种硬件的拓展性,所以提供了驱动。
驱动一开始是只有硬件驱动,将程序变成电信号去控制硬件。后来添加了软件驱动。
2、系统调用流程
这里提到的系统调用并不是syscall
,而是通过ntdll.dll从r0 - r3的一个过程。
CPU 从 Ring3 进入 Ring0 的手段主要包括中断,异常和自陷三种。
- 中断:自外部设备的中断请求
- 异常:指令执行失败之后在系统空间对异常进行处理
- 自陷: CPU 通过自陷指令主动进入 Ring0。
Windows 系统调用函数一般依靠自陷指令实现。
方式
syscall:新款的系统调用,32位是sysenter
INT 2E:老式的系统调用(奔腾 2 之前的处理器)
下图阐述了系统调用的基本流程:
WriteFile()函数为例
1、转到 ntdll.dll 中的 NtWriteFile()
函数
2、系统会调用 ntdll.dll 中的 KiFastSystemCall()
函数,设置 MSR
寄存器的值之后进入系统内核
3、操作系统通过 nt!KiFastCallEntry()
函数分发系统调用, 调用内核空间中的同名异前缀函数 nt!NtWriteFile()
4、IO 管理器将 IO 请求封装成输入输出请求包(IO Request Package , IRP)发送给最上层驱动程序
5、IRP 沿着驱动程序栈一路向下传递,最后传递给硬件抽象层,由硬件完成指定功能
win10安全机制
1、Secure Boot
传统的BIOS启动会导致Bootkit
问题
Bootkit
:早期计算机在通电之后会首先进入 BIOS,通过硬件自检之后调用主引导记录(Main Boot Record , MBR)引导 Windows 操作系统启动,在这个基础上,
Bootkit
通过修改主引导记录 MBR,使其自身加载甚至早于 Windows 操作系统加载,实现隐身加载,并且绕过安全检测,Bootkit
的加载都是完全不可见的。因为他是最先启动执行的。
Windows通过 Secure Boot 技术来规避Bootkit
攻击。
预启动
检查计算机是否安装并启用了
- 统一可扩展固件接口(The Unified Extensible Firmware Interface , UEFI)
- 可信平台模块(Trusted Platform Module , TPM)
计算机的 UEFI 固件会将【固件,引导加载程序,引导驱动程序等】在操作系统加载之前加载的所有内容的哈希值作为数字签名存储在 TPM 中,在存储内容变更时会对其进行验证, 验证不通过就不允许修改。
其实就是对固件的hash和数字签名进行检查。
后续
验证完成后,Windows 10 内核依次验证 Windows 操作系统启动过程中的其他组件。如果存在 Windows 内核或者其他组件被篡改的情况,引导加载程序会拒绝加载 Windows 系统。
Windows 10 通常会对这些组件进行备份,从而保证 Windows 操作系统的完整性。
2、Patch Guard
为了防止第三方程序或者恶意软件任意修改系统内核,在 Windows 64 位 系统中,微软引入了 Patch Guard 机制
能够有效防止内核驱动改动或替换Windows内核的任何内容,第三方软件将无法再给Windows Vista内核添加任何“补丁”。
Patch Guard 会定期检查系统内核关键部分,以确保内核中受保护的系统 结构未被修改,如果检测到修改,则 Windows 操作系统将启动错误检查 0x109, 并中断操作系统正常运行。
保护对象
Patch Guard 所保护的对象范围十分广泛,主要包括:
- 系统服务描述符表 SSDT
- 全局描述符表 GDT
- 中断描述符表 IDT
- 关键 MSR 寄存器
- 关键内核模块
除此之外,还有许多内核对象和内存区域也在 Patch Guard 的保护范围内,通过在 WinDBG 中!analyze -show 109
查看。
3、DSE
强制驱动签名技术,这里区别一下DEP:堆栈不可执行。
之前
Windows 驱动程序可以直接加载进入系统内核。 驱动程序在加载进入 Windows 操作系统内核之后,可以和 Windows 操作系统内核其他组件共享同一片内存空间。
之后
在驱动程序加载之前,Windows 操作系统会对其数字签名进行验证,如果驱动程序没有合法签名,那么即使是取得计算机管理员权限, 也无法将驱动加载进入 Windows 系统内核。
4、虚拟化安全
基于虚拟化的安全:Virtual Based Security , VBS;Windows 10 1607 版本推出的又一种安全机制。通常称为内核隔离。使用硬件虚拟化在内存中创建安全区域,为其他安全功能提供了一个安全平台。
VBS 使用处理器提供的硬件虚拟化功能创建系统安全区域,并且将其与 正常操作系统相隔离,借此保护重要的操作系统组件以及用户资产,即使恶意 软件成功进入 Windows 内核模式运行,也无法访问到 VBS 保护的资源。
巨硬利用这种技术实现了两个及其影响游戏性能的功能
代码完整性保护 (Hypervisor-Protected Code Integrity , HVCI)
基于虚拟化的操作系统修改保护机制 Hyper Guard
HVCI
通常称为内存完整性, 这个东西在win11中是默认开启的,但是会影响到游戏的帧率,所以一般选择关掉
Hyper Guard
扩展了 Patch Guard 的功能。Hyper Guard 使内核模式下运行的恶意软件不再和防护机制处于同一个安全等级。
不同于 Patch Guard 的定期检查, Hyper Guard 可以即时检查到篡改行为,使过去恶意软件通过精确控制时间恢复被破坏或者篡改的系统内核组件的绕过方式变得无法使用。
程序隐藏
分为四种基本隐藏形式;
- 驱动隐藏 》DKMO
- 文件隐藏 》硬件虚拟化
- 进程隐藏 》DKMO
- 通信隐藏 》WFP
1、驱动隐藏
内核级程序隐藏功能都需要通过将特定驱动程序加载进入的方式实现,同时,专业的内核防护工具也会对 Windows 操作系统已加载的内核模块进行检查。所以我们需要对特定驱动模块进行隐藏来规避检测。
过去式 XP & 7
Windows XP 和 Windows7 中
- 在内核内存中定位记录驱动模块信息的双向链表
InLoadOrderLinks
,然后从该双向链表中摘除目标驱动模块信息对应的结构。 - 在内核内存中定位目标驱动程序的
DriverObject
对象,该对象 的 Section 节区记录了驱动程序的相关信息,直接将该字段设为 NULL。
上述两种方式在win10 64bit中会触发Patch Guard保护机制导致蓝屏。而且不支持SEH。、
流程
每一个 Windows 驱动程序都对应这一个
DriverObject
对象。当驱动程序被加载进入内核之后,OS首先为驱动程序分配内存,创建
DriverObject
对象,进行初始化操作将
DriverObject
对象中的成员对象 Section 加入双向链表InLoadOrderLinks
调用驱动入口函数
DriverEntry()
对驱动进行初始化。隐藏方式
在 DriverEntry()
函数中将 Section 对象从双向链表中摘除,并抹去部分驱动特征, 就可以达到驱动隐藏的目的
MiProcessLoaderEntry()
上述方式在老版本的Windows中可以得到运用,但是在现代系统中会触发Patch Guard保护机制,所以需要使用MiProcessLoaderEntry()
函数将目标驱动程序的 Section 节区从双向链表 InLoadOrderLinks
中摘除。
该函数会对 Patch Guard 监控的全局变量进行处理,这样在摘链时就不会触发 Patch Guard 导致蓝屏。
MiProcessLoaderEntry(pDriverObject->DriverSection, 0);// 移除
MiProcessLoaderEntry(pDriverObject->DriverSection, 1);// 添加
摘除驱动模块
1、DriverObject 对象的 DriverExtension 成员指向的是驱动程序映像的内 存区对象,该结构体中的第一个成员 InLoadOrderLinks 是一个 LIST_ENTRY 的结构。Windows 操作系统内核加载的每个驱动模块都会被加入到这个双向链 表中,只要遍历这个双向链表,就能枚举出所有的驱动模块。这里类似PEB结构体里的LDR
2、在 Windows 操作系统中的\Driver 和\FileSystem 目录对象中都存有 Windows 操作系统内核加载的驱动对象。只需要遍历这两个系统目录,就可以枚举出所有的驱动模块
3、为了实现驱动隐藏,不仅需要在驱动入口函数 DriverEntry()中将目标驱动 节对象 DriverSection 从节对象链表中摘除,还需要将目标驱动从\Driver 目录 中删除,并抹去目标驱动 DriverObject 对象中的部分信息。
隐藏过程
通过将目标驱动对象的 DriverSection 对象通过 MiProcessLoaderEntry()函数从双向链表 InLoadOrderLinks 中摘除,就可以达到隐藏的目的。
摘除目标驱动模块的 DriverSection 对象之后,只需再将目标驱动从 Windows 操作系统中的\Driver 目录中删除,并抹去部分对象特征,即可实现驱动隐藏。
总结
关键就是利用 MiProcessLoaderEntry() 这个函数,将目标驱动在双向列表中拿出来,同时防止触发PG保护。难点在于程序编写上,因为这个函数是未公开的函数,可以拿到的信息很少,而且涉及到驱动编程,但是我不会驱动编程。
2、文件隐藏
攻击中的样本往往不希望被狩猎到,所以都会采用各种方式来隐藏自己,包括多级目录,文件改名,文件不落地,依附其他进程等形式。
传统方式
传统的文件隐藏实现方式主要包括两种:
(1) 通过直接挂钩 SSDT 表中的相应系统服务分发例程,对文件操作的返回值进行过滤,从而达到文件隐藏的目的。
- 缺点:HOOK SSDT 表的方式在 Windows 10 64 位操作系统环境下会触发 Patch Guard 机制
(2) 通过文件过滤驱动对文件操作相关 IRP 进行过滤,拦截或修改返回的 IRP,从而达到文件隐藏的目的。
- 缺点:文件过滤驱动容易被设备遍历的方式检测到
一种新方式
- 参考[Windows 10环境下的程序隐藏技术研究]
可以看作是在r1层加了一个过滤装置
因为VMM 可以 完全控制计算机硬件资源,如 CPU,IO,物理内存和中断等,所以运行在上面的软件无法判断自己是不是运行在虚拟机上。和vmware不同的是,这种虚拟技术不是完全的模拟,而是通常情况下会将各种操作放行,直接给到CPU,只有检测到特殊的指令或者异常的时候才会进行拦截。
交互过程
Guest OS 与 VMM 之间的交互利用 Intel 提供的虚拟机拓展指令 VMX(Virtual Machine Extensions)、虚拟机控制结构(VMCS , Virtual Machine Control Structure)来实现。
- VM Exit:Guest OS 触发特定的事 件或者异常,从而把计算机控制器转交给 VMM 的过程
- VM Entry:从 VMM 进入 Guest OS 的过程
为了实现文件隐藏,VMM 主要需要处理 EPT 内存访问引发的 VM Exit。
EPT HOOK
Extend Page Table,拓展页表,是 Intel 为了实现内存虚拟化引入的新特性。
在引入EPT之前:
Guest OS 对物理内存的访问被认为是敏感指令,会引发 VM Exit 事件,由 VMM 对这个过程进行处理,将 Guest OS 的物理地址转换为主机真实物理地 址返回给 Guest OS 使用。CPU 访问物理内存非常频繁,会导致不断触发模式 切换,开销很大,且通过 VMM 进行内存地址转换,效率很低
HOOK 原理
将目标函数的内存页复制为 A 和 B 两份,A 为原函数页,B 为修改之后的函数页,A 的权限设为可读写不可执行,B 的权限设为不可读写可执行。这样一来,每当执行原函数,由于权限问题,都会触发 EPT violation,VMM 会将原函数所在页物理地址定向至 B 的物理地址,实际上执行的是被修改后的函数页 B。过程如下图所示
修改 Guest OS 物理地址到真实主机物理地址的映射,没有修改 Guest OS 中的任何内 核对象或者重要系统表项,达到不触发 Patch Guard 的情况下对文件查看相关 系统调用例程进行 HOOK的目的。
3、进程隐藏
隐藏虚拟机环 境中 Vmtools 进程,避免恶意软件检测到自身处于虚拟化环境,或者隐藏蜜罐 中的监控进程而不被入侵者觉察等。
隐藏
传统方式
(1) HOOK系统服务函数 ZwQuerySystemInformation()
,过滤该函数返回值,实现对用户层进程隐藏。
(2) 使用 DKOM 技术,定位内核模式内存中的进程 EPROCESS 链表,将目标进程的 EPROCESS 结构从该链表中摘除
- 缺点: 马上触发Patch Guard 机制、交叉视图检测,直接失效。
遍历进程的方式
- 调用系统函数 CreateToolhelp32Snapshot(),照快照直接遍历
- 在内核模式下遍历该双向链表,遍历进程 EPROCESS 结构所在的双向链表
ActiveProcessLinks
- 对内核模式下的内存进行暴力搜索,以 EPROCESS 结构中的成员 ObjectType 为特征码,搜索所有当前内存中存在的 EPROCESS 结构
- 全局句柄表
PspCidTable
,存有所有系统进程和线程对象的句柄。通过解析该句柄表,遍历所有进程线程句柄
解决办法
利用函数 MiProcessLoaderEntry()将进程的 EPROCESS 结构从双向链表 ActiveProcessLinks 上摘除。
解析 PspCidTable 句柄表,将目标隐藏进程的句柄抹去。
最后对目标进程的所有线程进行处理, 将所有线程的父 ID 修改为其他进程的 ID,并修改其指向的进程。
伪装
除了基本的改名,改PEB结构体,进程注入之外,还可以通过在内核模式下通过修改进程 EPROCESS 结构中的信息实现进程伪装。
原理
为了方便起见,进程伪装的目标进程路径必须比 svchost.exe 的路径长度长,否则无法腾出额 外空间修改路径。
总结
进程隐藏的本质还是去对抗PG保护,然后修改双向链表,把目标进程那结构体拿出去,并通过PG保护检测的这么一个过程。
4、通信隐藏
作为一些RAT攻击的最后一步,将信息回传回去,肯定不希望被轻易的抓包获得。所以可以通过隐藏通信的方式达到目的
WFP
Windows 文件保护 (WFP,Windows file project) 可防止程序替换重要的 Windows 系统文件。程序绝不能覆盖这些文件,因为操作系统及其他程序都要使用它们。通过保护这些文件,可以防止程序和操作系统出现问题。
使用 Windows 过滤平台 WFP 对控制端发送的网络数据包的源 IP 和源端口进行伪 装,在受控端数据链路层对其进行截取和还原,从而达到隐蔽通信的目的。
方法
基本思路就是在网络流量沿网卡和驱动栈上行或者下行的过程中插入一个过滤子层,以拦截相关网络流量。
Windows 过滤平台 WFP;位于 TCP/IP 协议驱动上层,只能拦截经过 TCP/IP 协议驱动的网络流量包。
网络驱动接口规范 NDIS 过滤驱动技术;位于协议驱动和小端口驱动之间,主要用于拦截网卡流量。
WFP 的目的,就是替代过去的传输层驱动接口(Transport layer Device Interface , TDI)。利用 WFP 框架,可以较为轻松地实现网络流量过滤和处理。所以通信隐藏主要还是利用WFP来进行的。
基本模型
核心:过滤引擎
过滤引擎可以与 OSI 7 层模型中的任意一层进行交互,实现不同的网络数据包处理功能。
网络数据包通过网卡沿网络协议栈向上传输的过程中,会被过滤引擎进行过滤,并且按照规则进行拦截;
图中的 Callout 是预设的对网络数据包的处理动作,包含过滤规则,处理方法等,就类似于键盘钩子的处理函数。
为了避免原始数据包被网络抓包软件嗅探捕获,需要在网络数据包到达抓包软件所在网络层次之后对其进行还原。
TCP / IP
Windows下利用tcpip.sys这个驱动来处理tcp ip协议。包括 TCP 连接的建立和释放,超时重传,拥塞处理,差错校验等机制,以及 IP 协议的 IP 维护,ARP 表维护,校验和等机制。
这个驱动他并没有给应用程序一个接口,但是给了WFP一些接口来进行提高执行效率,我们就可以利用这个特性来拦截,修改、重定向等流量。
总结
实际上针对WFP来进行通信隐藏的方式并不是让你看不到这个流量包,还是可以通过网卡流量来进行捕获的。这种方式仅仅可以对流量包的源ip和目标ip进行还有端口等信息进行隐藏。通信的主机之间可以识别伪造的地址,并使用预置的地址映射表进行地址变换,从而使得链路上/主机上捕获到的网络流量数据是经过伪造后的数据。
总结
基于Windows下程序隐藏的技术都涉及到内核层面的操作,所以我先去学习一下Windows内核开发的一些知识,等我学完再继续。