优势:高稳定性、开发难度降低、系统大版本升级兼容
劣势:自由度低、只知其然不知其所以然、框架中未提供功能束手无策
相关框架 例如:
文件系统相关:miniFilter、SFilter
进程、注册表相关:ObRegisterCallbacks注册回调
网络:TDI、WFP
优势:自由度高、没有做不到只有想不到,更接近底层
劣势:难度高 需要对处理的功能前后都了解、错误或不完善的处理会引发未知的错误
相关框架 例如:
InfinityHook、SSDTHook、IDTHook 等
如果选择微软规范以外的框架,win7 64位系统以后 我们碰到了一只拦路虎——PG (PatchGuard)
我们有两个选择 干掉PG 或者 绕过PG
干掉PG
需要找到所有检测点,每个监测点触发的条件和时间都不定,很难过掉所有的检测
绕过PG
InfinityHook 利用Windows的事件跟踪机制,可以与 PatchGuard 同时稳定运行
开启 Event Tracing 后,当用户层程序进行系统调用时,首先会调用PerfInfoLogSysCallEntry函数来对此次系统调用进行记录。然后才调用系统调用函数。
PerfInfoLogSysCallEntry内部又会调用驱动注册的WMI_LOGGER_CONTEXT结构中的GetCpuClock指向的函数。
这样就可以通过修改GetCpuClock指针为自己的代理函数,而且此时系统调用函数被放置在栈上,因此在该函数中能从栈中获取系统调用函数的值并进行修改为自己的hook函数。
这里我用 ZwQueryInformationProcess 举例
定义函数类型
声明函数指针
获取函数地址
请注意! 调用未导出函数,可能会导致驱动不能兼容高版本;
调用导出函数,也可能会导致驱动不能兼容高版本。
例如:
KeAttachProcess 已过时,Win10.19041 开始不再支持
改用 KeStackAttachProcess
应用程序调用WinAPI,GUI无关的对应到 ntdll.dll 中的 NtXXX函数,GUI相关的对应到 win32u.dll 中的 NtXXX函数
NtXXX函数中 通过 mov eax,0x***
将系统服务号放入EAX(win32u.dll 中真实系统服务号为0x*** - 0x1000
)
服务分发函数 KiSystemService 根据传入的系统服务号,通过SSDT表 找到内核中的地址。
SSDT 系统服务描述符表 (System Services Descriptor Table)
SSDT 对应 ntoskrnl.exe 中的服务函数,这些函数实现了如文件管理、进程管理、设备管理等等相关的功能。
ShadowSSDT 对应 win32k.sys 中的服务函数,重点实现创建窗口、查找窗口、窗口绘图等 Gdi 与用户交互相关的功能。
Win7 32 位系统中,SSDT 在内核 Ntoskrnl.exe 中导出,直接获取导出符号 KeServiceDescriptorTable。
而在 64 位系统中,SSDT 表并没有在内核 Ntoskrnl.exe 中导出,我们不能像 32 位系统中那样直接获取导出符号 KeServiceDescriptorTable。
Win7 x64 与 Win10 64(Win10低版本)中 通过 __readmsr(0xC0000082)
获取内核函数 KiSystemCall64 的地址
KiSystemCall64 中调用了 KeServiceDescriptorTable 和 KeServiceDescriptorTableShadow
IDA查看 KiSystemCall64 的反汇编
KeServiceDescriptorTable 特征码 4c8d15
KeServiceDescriptorTableShadow 特征码 4c8d1d
win10 高版本中 __readmsr(0xC0000082) 返回 KiSystemCall64Shadow 函数
而 KiSystemCall64Shadow 无法直接搜索到 KeServiceDescriptorTable,IDA查看 KiSystemCall64Shadow 的反汇编
最终获取到的函数地址由 功能代码 变为 jmp跳转
代码从 win32k!NtXXX 跳转到了 win32kfull!NtXXX
win32k.sys 不再直接处理来自用户层的系统服务调用,而真正去处理用户的系统服务调用的函数,实际上是 win32kfull.sys 中的同名函数
这里贴的代码是获取 ShadowSSDT 地址,如果需要获取SSDT 地址,简单修改即可
定义服务描述符表
用法举例
GUI无关的 使用IDA 查看 ntdll.dll,找到 NtXXX 函数,查看反汇编
找到 mov eax,0x***
,0x***
就是系统服务号
GUI相关的 使用IDA 查看 win32u.dll,找到 NtXXX 函数,查看反汇编
找到 mov eax,0x***
,0x*** - 0x1000
就是系统服务号
注意! 有一部分内核函数的系统服务号,会随着系统版本、补丁发生改变,导致驱动不兼容多版本,建议采用代码自动获取的方式。
手动获取的系统服务号不能兼容多个版本,所以还需要获取并判断系统版本
枚举windows版本
获取并判断系统版本
用法举例
代码自动获取系统服务号相当于是用代码模拟了手动获取的方式
下面贴出代码
只有在有 GUI 的线程当中,win32k.sys 的内存才可以被访问。
调用 KeAttachProcess 将当前线程附加到GUI进程的地址空间
KeAttachProcess 需要用到被附加进程的PEPROCESS
目前最好用的是,遍历PID 对比进程名
其他方式在高版本可能不再兼容
这个直接贴代码
用法示例
如果为内核中导出的函数,MSDN可以查到函数原型
例如:ZwQueryInformationProcess
如果为内核未导出的函数
测试代码,测试R3 APIEnumDisplaySettingsW
R0 APINtUserEnumDisplaySettings
X64dbg 执行NtXXX 函数前
可以看到有四个参数,参数三是 RtlAllocateHeap 申请的堆地址
X64dbg 执行NtXXX 函数后,分析返回的信息,提取有用的部分
思路:
通过 进程句柄 调用 ZwQueryInformationProcess 获取 进程名
实现代码
用法示例
例如:
系统设置是如何获取的显示分辨率列表
系统设置->显示设置->显示分辨率
设置 是通过,调用 EnumDisplaySettingsW,
参数二 从0开始,每次调用+1,每次获取一条显示信息,直到返回值为0,枚举出了所有显示信息;
而内核中的处理是,当参数二为0时,操作系统将初始化并缓存有关显示设备的信息。当您将参数二设置为非零值调用EnumDisplaySettings时,该函数将返回上次在参数二为0的情况下调用该函数时缓存的信息。
内核NtAPI 返回成功 STATUS_SUCCESS 返回值 rax = 0
而当最后一个显示信息被返回后,再次参数二+1调用,内核会返回 STATUS_INVALID_PARAMETER_2 rax = 0xC00000F0L
当系统枚举显示信息时,我们如果在挂钩的函数中,直接返回 STATUS_INVALID_PARAMETER_2,系统将停止枚举分辨率。
还是用 NtUserEnumDisplaySettings 举例
NtUserEnumDisplaySettings 使用第三个参数,也是R3 API 申请的堆来保存要返回的显示信息
如果我们要修改R3 EnumDisplaySettingsW 拿到的显示信息,可以在挂钩的内核函数中 直接修改(当前线程与申请堆的线程所属进程一致时,可以直接修改)
第一次调用返回我们设置好的显示信息,第二次调用返回 STATUS_INVALID_PARAMETER_2,系统的设置就只能选择我们设置的分辨率,而没有其他选项。
驱动与R3通讯,比较简单 驱动中 只需创建设备、将设备对象和符号链接绑定;R3的应用程序打开设备调用DeviceIoControl与驱动通讯。
需要注意的是: 我们只用到了DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]
,但是其他的功能函数也需要填充,否则R3的应用程序打开设备时将报错。
用法示例 (在 DriverEntry 中调用)
通讯示例
高IRQL的代码,可以中断(抢占)低IRQL的代码的执行过程,从而得到执行机会
常见的IRQL:
IRQLd等级 | 介绍
-------- | -----
PASSIVE_LEVEL | 应用层线程以及大部分内核函数处于该IRQL,可以无限制使用所有内核API,可以访问分页及非分页内存
APC_LEVEL | 异步方法调用(APC)或页错误时处于该IRQL,可以使用大部分内核API,可以访问分页及非分页内存
DISPATCH_LEVEL | 延迟方法调用(DPC)时处于该IRQL,可以使用特定的内核API,只能访问非分页内存
注意! 在调用任何一个内核API前,必须查看WDK文档,了解这个内核API的中断要求!
获取当前IRQL
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2020-7-29 10:06
被随风行编辑
,原因: