17 内存取证:Rootkit
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
IAT(Import Address Table)
PE文件的导入表存储了进程在运行时使 用的API函数的信息。特别是,它存储 了API函数的名字(或指针),API函数的 地址,包含这些API函数的DLL的名字 存储所有API函数地址的表称为导入地 址表(IAT)
IAT钩子
要拦截IAT中的API函数,恶意软件通常 会向目标进程注入一个DLL。注入的 DLL会解析进程的PE头,在IAT中找到 存储有将被拦截的API函数的指针的位 置。接下来,它改写该位置的指针值, 使其指向恶意软件提供的函数,这会强 制进程调用恶意软件提供的函数
检测驱动程序的IRP钩子
Volatility的driverscan命令能够扫描 _DRIVER_OBJECT结构并显示驱动程序对象 Volatility的driverirp命令扩展了driverscan命令的 功能,它能够打印每个驱动程序的主函数表 (IRP表) 注意:拦截IRP函数(主函数表中的指针指向的 函数)未必是非法的。所以,即使发现主函数 表中某个指针指向其他驱动程序,也并不意味 着该拦截(钩子)是恶意的
系统描述符表SSDT包含了指向内核模式 函数的指针
◦ 在不同的Windows系统上,SSDT中函数的次序 和个数并不相同
有4个SSDT。第一个也是最有名的SSDT存 储了由内核执行模块(即ntoskrnl.exe或 ntkrnlpa.exe)提供的原生(native)函数;第 二个SSDT也称为shadow SSDT,存储了由 win32k.sys提供的GUI函数;其他两个 SSDT子,一个重要的原因是, 在EAT钩子安装之前,任何已经加载的可执行文件或 DLL的IAT中都会包含合法函数的地址,当rootkit改变 EAT中导出函数的RVA值时,并不会自动更新相应的IAT 项。因此,EAT钩子只对其后加载的模块有效,或者之 前加载的模块利用GetProcAddress函数来定位被拦截的 函数时有效
如果没有使用LoadLibrary,则PEB中的DLL列表 不会更新,从VAD中也得不到可用的内存映像文 件名字
IDT(Interrupt Descriptor Table)
中断描述符表(IDT)是一个存储处理中 断和处理器异常的函数的地址的数据结 构 IDT可存储256个函数地址,第0x2E个 地址指向函数KiSystemService。 KiSystemService是ntoskrnl.exe中的一个 函数,它是所有系统调用的入口
检测驱动程序的IRP钩子
通过改写驱动程序的主函数表中相应函 数的指针,使其指向rootkit自己的代码 ,rootkit能够拦截驱动程序的特定操作 检测驱动程序的IRP钩子的方法:
◦ 在内存中搜索_DRIVER_OBJECT结构,读 取MajorFunction数组中的28个地址。根据 驱动程序的基址和大小,判断每一个地址 是否位于驱动程序占有的内存范围内。如 果为否,则对应的函数被拦截了
◦ 同样的,即使某驱动程序的主函数表中的指针全 部指向该驱动程序自身,也并不意味着没有IRP钩 子(如TDL3 rootkit所采用的技术,见下页)
检测驱动程序的IRP钩子
TDL3 rootkit逃避IRP钩子检测的方法
SSDT(System Service Descriptor Table)
检测IDT钩子
rootkit会改写IDT中的第0x2E个地址,使 其指向rootkit自己的代码,从而能够在出 现调用内核模式API函数请求的任何时候 获得控制权
◦ 从XP开始,Windows不再使用IDT来定位 KiSystemService函数,而使用MSR(modelspecific registers)来定位。因此,目前拦截Int 2E的rootkit已经比较少见了
◦ 每个驱动程序都有一个包含28个函数指针的表, 用于注册处理不同操作的函数。加载之后,驱动 程序通常会在入口点例程中配置该表。在 _DRIVER_OBJECT结构中,MajorFunction成员用于 存放这个表,所以称之为主函数表(或IRP表)
驱动程序并不需要处理所有类型的操作。对于不处理的操 作,正确的做法是让主函数表中的对应项指向 nt!IopInvalidDeviceRequest(一个哑函数)
◦ 注意:仅搜索JMP和CALL指令无法检测出所有的钩子,因为 还有其他方法实现跳转
使用Volatility检测API钩子
Volatility的apihooks命令能够检测API钩 子,包括IAT、EAT及内联钩子
◦ 在命令的输出结果中,如果钩子模块 (Hooking Module)的名字是UNKNOWN, 则表明恶意程序没有使用LoadLibrary函数 ,而是采用其他方法把rootkit代码注入到 目标进程中
◦ 一些基于内联钩子的库如Microsoft Detours, Mhook, EasyHook, 和 madCodeHook ◦ 内联钩子并不像IAT、EAT钩子那样改写单个指针 值,它需要反汇编指令并写入进程内存的几个不 同的地方
内联钩子示意图
在内存转储中检测内联钩子的步骤
1. 2.
3.
4.
SSDT与系统调用
无论使用Int 2E指令还是使用 SYSENTER(MSR)指令来跨越用户-内核 边界,都会引向KiSystemService函数, 该函数负责在SSDT中查找请求的内核 函数的地址
SSDT与系统调用
检测SSDT钩子
恶意软件要拦截SSDT中的函数,需要两个关键信 息:
◦ 函数表(function table)在内核内存中的基址(存储在 ServiceTable中,如前图) ◦ 将要拦截的函数的索引
5.
6.
遍历EPROCESS结构的列表,列出系统中的活动进程 检查PEB或VAD,列出每个进程加载的DLL。记录下每 个DLL名字、基址和大小。从而知晓DLL占有的内存范 围 转储并重建所有已加载的DLL,从而能够解析PE头并定 位EAT 对每一个DLL,解析其EAT,(按序)计算导出函数的VA (RVA+DLL的基址) 由VA定位到导出函数,反汇编该导出函数的第一条指 令。如果它是JMP或CALL指令,则进行第6步。否则, 继续计算EAT中的下一个导出函数的VA,执行相同的操 作,直到检查完所有DLL的所有导出函数 确定JMP或CALL指令的目标地址。如果该目标地址不在 导出函数的DLL占有的内存范围内,那么,该导出函数 被拦截了
IAT钩子示意图
在内存转储中检测IAT钩子的步骤
遍历EPROCESS结构的列表,列出系统中的 活动进程 2. 检查PEB或VAD,列出每个进程加载的DLL 。记录下每个DLL名字、基址和大小。从而 知晓DLL占有的内存范围 3. 转储并重建进程的可执行文件和所有已加 载的DLL,从而能够解析PE头并定位IAT
恶意软件分析
第17章
内存取证:Rootkit
本章内容
为了隐身,rootkit常常试图隐藏文件、 进程、注册表项和端口等资源。API钩 子是一种rootkit使用的古老而又容易的 隐身方法。它们能让操作系统报告错误 的、或者不准确的系统状态 本章将讨论最常见的钩子类型,并说明 如何在内存转储中检测它们。本章还会 讨论rootkit使用的其他隐身方法
EAT钩子示意图
在内存转储中检测EAT钩子的步骤
遍历EPROCESS结构的列表,列出系统中的 活动进程 2. 检查PEB或VAD,列出每个进程加载的DLL 。记录下每个DLL名字、基址和大小。从而 知晓DLL占有的内存范围 3. 转储并重建所有已加载的DLL,从而能够解 析PE头并定位EAT 4. 对于每一个导出函数,将其RVA与导出它的 DLL的基址相加,得到的VA地址如果不在 该DLL的内存范围内,那么,该导出函数被 拦截了
Volatility的idt命令能够显示IDT
◦ 通过查看IDT信息,特别是第0x2E个地址,能 够检测IDT钩子
IRP(I/O Request Packets)
Windows程序通过发送I/O请求包(IRP)与驱动 程序进行通信。 IRP是一个数据结构,包含了标识所需操作(创 建、读、写等)的代码,及驱动程序读写数据 的缓冲区
◦ 在Process Explorer或Process Hacker中查看System 进程的线程,会发现这些线程的启动地址中并不 包含模块的名字(与驱动程序分离了) ◦ 尽管System进程运行在用户模式下,但它的线程都 运行在内核模式下
服务进程
Windows上的服务进程通常都是非交互的 ,在后台持续运行,并且其优先级常常高 于用户启动的大多数程序 services.exe进程是服务控制管理器(SCM) ,它负责按照依赖关系的次序加载注册的 服务,维护系统中服务有关信息
1.
内联钩子
和IAT、EAT钩子相比,恶意软件更喜欢使用内 联(Inline)API钩子。内联钩子也被称为蹦床钩子 (trampoline)或迂回(detours)钩子,常常利用 JMP和CALL指令实现跳转 虽然内联钩子需要更多的编程工作,但并不难 实现,因为存在许多开源库会告诉你如何准确 的完成内联钩子
1.
◦ 不仅要检查进程的可执行文件的IAT,还要检查所 有DLL文件的IAT
4.
对于每一个导入函数,检查其在IAT中的地 址是否位于包含该函数的DLL占有的内存范 围内。如果为否,则该函数被拦截了
EAT及其钩子
导出地址表(EAT,Export Address Table)存储了DLL 文件导出的函数的名字和相对虚拟地址(RVA)。 RVA是相对于DLL在内存中的基址的相对地址。根 据RVA可以找到对应的导出函数 如前所述,IAT钩子是通过改写IAT中的导入函数的 指针值实现的,与此相似,EAT钩子是通过改写 EAT中的导出函数的RVA值实现的
有多种方法可以找到函数表。恶意软件通常会调 用MmGetSystemRoutineAddress (GetProcAddress的 内核版本),然后定位由ntoskrnl.exe导出的 KeServiceDescriptorTable符号,进而找到函数表 原生函数表中的所有地址应该指向内核执行模块 内部;GUI函数表中的所有地址应该指向 win32k.sys内部。因此检测SSDT钩子非常简单—— 检查每一个地址是否指向正确的位置
◦ SCM维护了一个双链表结构,其中保存了服务 的有关信息。如服务名称、服务的驱动程序对 象名字或服务的可执行文件路径、服务类型、 服务的当然状态(如暂停、运行、停止等)。特 别的,在该结构的固定偏移处包含了一个值为 sErv的成员
恶意软件滥用服务的方式
1.
停止运行的服务。包括如下方法
◦ 使用ControlService函数 ◦ 使用包含net stop SERVICENAME命令的批处理文 件 ◦ 使用TerminateProcess
◦ 通过查看SSDT列表中是否有不属于内核执行模块( 即ntoskrnl.exe或ntkrnlpa.exe)或win32k.sys的函数, 能够迅速隔离出被拦截的函数
其他
分离的内核线程
当内核模块使用PsCreateSystemThread创建新 的线程时,System进程就成为新线程的所有者 。换句话说,System进程是从内核模式启动的 线程的默认宿主 某些rootkit会调用PsCreateSystemThread创建 新的内核线程。然后从已加载模块的双链表中 解除rootkit的驱动程序的节点,或者完全卸载 rootkit的驱动程序。这样的话,新建的内核线 程将会在没有标记的内存池中运行
检测SSDT钩子
SSDT是在每一个线程的基础上分配的。这意味 着,依赖于线程的ETHREAD.Tcb.ServiceTable 成员的值,每个线程会看到不同的SSDT。因此 ,如果只检查原始的函数表,而不检查所有线 程看到的函数表,则无法发现SSDT钩子 Volatility的ssdt命令列举所有的线程,并根据所 有线程的ETHREAD.Tcb.ServiceTable成员值建 立SSDT列表