驱动开发:内核枚举IoTimer定时器
今天继续分享内核枚举系列知识,这次我们来学习如何通过代码的方式枚举内核IoTimer定时器,内核定时器其实就是在内核中实现的时钟,该定时器的枚举非常简单,因为在IoInitializeTimer初始化部分就可以找到IopTimerQueueHead地址,该变量内存储的就是定时器的链表头部。枚举IO定时器的案例并不多见,即便有也是无法使用过时的,此教程学到肯定就是赚到了。

枚举Io定时器过程是这样的:
- 1.找到
IoInitializeTimer函数,该函数可以通过MmGetSystemRoutineAddress得到。 - 2.找到地址以后,我们向下增加
0xFF偏移量,并搜索特征定位到IopTimerQueueHead链表头。 - 3.将链表头转换为
IO_TIMER结构体,并循环链表头输出。
这里解释一下为什么要找IoInitializeTimer这个函数他是一个初始化函数,既然是初始化里面一定会涉及到链表的存储问题,找到他就能找到定时器链表基址,该函数的定义如下。
NTSTATUS
IoInitializeTimer(
IN PDEVICE_OBJECT DeviceObject, // 设备对象指针
IN PIO_TIMER_ROUTINE TimerRoutine, // 定时器例程
IN PVOID Context // 传给定时器例程的函数
);
接着我们需要得到IO定时器的结构定义,在DEVICE_OBJECT设备对象指针中存在一个Timer属性。
lyshark.com: kd> dt _DEVICE_OBJECT
ntdll!_DEVICE_OBJECT
+0x000 Type : Int2B
+0x002 Size : Uint2B
+0x004 ReferenceCount : Int4B
+0x008 DriverObject : Ptr64 _DRIVER_OBJECT
+0x010 NextDevice : Ptr64 _DEVICE_OBJECT
+0x018 AttachedDevice : Ptr64 _DEVICE_OBJECT
+0x020 CurrentIrp : Ptr64 _IRP
+0x028 Timer : Ptr64 _IO_TIMER
+0x030 Flags : Uint4B
+0x034 Characteristics : Uint4B
+0x038 Vpb : Ptr64 _VPB
+0x040 DeviceExtension : Ptr64 Void
+0x048 DeviceType : Uint4B
+0x04c StackSize : Char
+0x050 Queue : <anonymous-tag>
+0x098 AlignmentRequirement : Uint4B
+0x0a0 DeviceQueue : _KDEVICE_QUEUE
+0x0c8 Dpc : _KDPC
+0x108 ActiveThreadCount : Uint4B
+0x110 SecurityDescriptor : Ptr64 Void
+0x118 DeviceLock : _KEVENT
+0x130 SectorSize : Uint2B
+0x132 Spare1 : Uint2B
+0x138 DeviceObjectExtension : Ptr64 _DEVOBJ_EXTENSION
+0x140 Reserved : Ptr64 Void

这里的这个+0x028 Timer定时器是一个结构体_IO_TIMER其就是IO定时器的所需结构体。
lyshark.com: kd> dt _IO_TIMER
ntdll!_IO_TIMER
+0x000 Type : Int2B
+0x002 TimerFlag : Int2B
+0x008 TimerList : _LIST_ENTRY
+0x018 TimerRoutine : Ptr64 void
+0x020 Context : Ptr64 Void
+0x028 DeviceObject : Ptr64 _DEVICE_OBJECT

如上方的基础知识有了也就够了,接着就是实际开发部分,首先我们需要编写一个GetIoInitializeTimerAddress()函数,让该函数可以定位到IoInitializeTimer所在内核中的基地址上面,具体实现调用代码如下所示。
#include <ntifs.h>
// 得到IoInitializeTimer基址
// By: LyShark 内核开发系列教程
PVOID GetIoInitializeTimerAddress()
{
PVOID VariableAddress = 0;
UNICODE_STRING uioiTime = { 0 };
RtlInitUnicodeString(&uioiTime, L"IoInitializeTimer");
VariableAddress = (PVOID)MmGetSystemRoutineAddress(&uioiTime);
if (VariableAddress != 0)
{
return VariableAddress;
}
return 0;
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint(("Uninstall Driver Is OK \n"));
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint(("hello lyshark.com \n"));
// 得到基址
PUCHAR IoInitializeTimer = GetIoInitializeTimerAddress();
DbgPrint("IoInitializeTimer Address = %p \n", IoInitializeTimer);
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
运行这个驱动程序,然后对比下是否一致:

接着我们在反汇编代码中寻找IoTimerQueueHead,此处在LyShark系统内这个偏移位置是nt!IoInitializeTimer+0x5d 具体输出位置如下。
lyshark.com: kd> uf IoInitializeTimer
nt!IoInitializeTimer+0x5d:
fffff805`74b85bed 488d5008 lea rdx,[rax+8]
fffff805`74b85bf1 48897018 mov qword ptr [rax+18h],rsi
fffff805`74b85bf5 4c8d054475e0ff lea r8,[nt!IopTimerLock (fffff805`7498d140)]
fffff805`74b85bfc 48897820 mov qword ptr [rax+20h],rdi
fffff805`74b85c00 488d0dd9ddcdff lea rcx,[nt!IopTimerQueueHead (fffff805`748639e0)]
fffff805`74b85c07 e8141e98ff call nt!ExInterlockedInsertTailList (fffff805`74507a20)
fffff805`74b85c0c 33c0 xor eax,eax
在WinDBG中标注出颜色lea rcx,[nt!IopTimerQueueHead (fffff805748639e0)]`更容易看到。

接着就是通过代码实现对此处的定位,定位我们就采用特征码搜索的方式,如下代码是特征搜索部分。
- StartSearchAddress 代表开始位置
- EndSearchAddress 代表结束位置,粗略计算0xff就可以定位到了。
#include <ntifs.h>
// 得到IoInitializeTimer基址
// By: LyShark 内核开发系列教程
PVOID GetIoInitializeTimerAddress()
{
PVOID VariableAddress = 0;
UNICODE_STRING uioiTime = { 0 };
RtlInitUnicodeString(&uioiTime, L"IoInitializeTimer");
VariableAddress = (PVOID)MmGetSystemRoutineAddress(&uioiTime);
if (VariableAddress != 0)
{
return VariableAddress;
}
return 0;
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint(("Uninstall Driver Is OK \n"));
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint(("hello lyshark.com \n"));
// 得到基址
PUCHAR IoInitializeTimer = GetIoInitializeTimerAddress();
DbgPrint("IoInitializeTimer Address = %p \n", IoInitializeTimer);
INT32 iOffset = 0;
PLIST_ENTRY IoTimerQueueHead = NULL;
PUCHAR StartSearchAddress = IoInitializeTimer;
PUCHAR EndSearchAddress = IoInitializeTimer + 0xFF;
UCHAR v1 = 0, v2 = 0, v3 = 0;
for (PUCHAR i = StartSearchAddress; i < EndSearchAddress; i++)
{
if (MmIsAddressValid(i) && MmIsAddressValid(i + 1) && MmIsAddressValid(i + 2))
{
v1 = *i;
v2 = *(i + 1);
v3 = *(i + 2);
// 三个特征码
if (v1 == 0x48 && v2 == 0x8d && v3 == 0x0d)
{
memcpy(&iOffset, i + 3, 4);
IoTimerQueueHead = (PLIST_ENTRY)(iOffset + (ULONG64)i + 7);
DbgPrint("IoTimerQueueHead = %p \n", IoTimerQueueHead);
break;
}
}
}
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
搜索三个特征码v1 == 0x48 && v2 == 0x8d && v3 == 0x0d从而得到内存位置,运行驱动对比下。
- 运行代码会取出
lea指令后面的操作数,而不是取出lea指令的内存地址。

最后一步就是枚举部分,我们需要前面提到的IO_TIMER结构体定义。
- PIO_TIMER Timer = CONTAINING_RECORD(NextEntry, IO_TIMER, TimerList) 得到结构体,循环输出即可。
// By: LyShark 内核开发系列教程
// https://www.cnblogs.com/LyShark/articles/16784393.html
#include <ntddk.h>
#include <ntstrsafe.h>
typedef struct _IO_TIMER
{
INT16 Type;
INT16 TimerFlag;
LONG32 Unknown;
LIST_ENTRY TimerList;
PVOID TimerRoutine;
PVOID Context;
PVOID DeviceObject;
}IO_TIMER, *PIO_TIMER;
// 得到IoInitializeTimer基址
PVOID GetIoInitializeTimerAddress()
{
PVOID VariableAddress = 0;
UNICODE_STRING uioiTime = { 0 };
RtlInitUnicodeString(&uioiTime, L"IoInitializeTimer");
VariableAddress = (PVOID)MmGetSystemRoutineAddress(&uioiTime);
if (VariableAddress != 0)
{
return VariableAddress;
}
return 0;
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint("卸载完成... \n");
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint(("hello lyshark.com \n"));
// 得到基址
PUCHAR IoInitializeTimer = GetIoInitializeTimerAddress();
DbgPrint("IoInitializeTimer Address = %p \n", IoInitializeTimer);
// 搜索IoTimerQueueHead地址
/*
nt!IoInitializeTimer+0x5d:
fffff806`349963cd 488d5008 lea rdx,[rax+8]
fffff806`349963d1 48897018 mov qword ptr [rax+18h],rsi
fffff806`349963d5 4c8d05648de0ff lea r8,[nt!IopTimerLock (fffff806`3479f140)]
fffff806`349963dc 48897820 mov qword ptr [rax+20h],rdi
fffff806`349963e0 488d0d99f6cdff lea rcx,[nt!IopTimerQueueHead (fffff806`34675a80)]
fffff806`349963e7 e8c43598ff call nt!ExInterlockedInsertTailList (fffff806`343199b0)
fffff806`349963ec 33c0 xor eax,eax
*/
INT32 iOffset = 0;
PLIST_ENTRY IoTimerQueueHead = NULL;
PUCHAR StartSearchAddress = IoInitializeTimer;
PUCHAR EndSearchAddress = IoInitializeTimer + 0xFF;
UCHAR v1 = 0, v2 = 0, v3 = 0;
for (PUCHAR i = StartSearchAddress; i < EndSearchAddress; i++)
{
if (MmIsAddressValid(i) && MmIsAddressValid(i + 1) && MmIsAddressValid(i + 2))
{
v1 = *i;
v2 = *(i + 1);
v3 = *(i + 2);
// fffff806`349963e0 48 8d 0d 99 f6 cd ff lea rcx,[nt!IopTimerQueueHead (fffff806`34675a80)]
if (v1 == 0x48 && v2 == 0x8d && v3 == 0x0d)
{
memcpy(&iOffset, i + 3, 4);
IoTimerQueueHead = (PLIST_ENTRY)(iOffset + (ULONG64)i + 7);
DbgPrint("IoTimerQueueHead = %p \n", IoTimerQueueHead);
break;
}
}
}
// 枚举列表
KIRQL OldIrql;
// 获得特权级
OldIrql = KeRaiseIrqlToDpcLevel();
if (IoTimerQueueHead && MmIsAddressValid((PVOID)IoTimerQueueHead))
{
PLIST_ENTRY NextEntry = IoTimerQueueHead->Flink;
while (MmIsAddressValid(NextEntry) && NextEntry != (PLIST_ENTRY)IoTimerQueueHead)
{
PIO_TIMER Timer = CONTAINING_RECORD(NextEntry, IO_TIMER, TimerList);
if (Timer && MmIsAddressValid(Timer))
{
DbgPrint("IO对象地址: %p \n", Timer);
}
NextEntry = NextEntry->Flink;
}
}
// 恢复特权级
KeLowerIrql(OldIrql);
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
运行这段源代码,并可得到以下输出,由于没有IO定时器所以输出结果是空的:

至此IO定时器的枚举就介绍完了,在教程中你已经学会了使用特征码定位这门技术,相信你完全可以输出内核中想要得到的任何结构体。
驱动开发:内核枚举IoTimer定时器的更多相关文章
- 驱动开发:内核枚举DpcTimer定时器
在笔者上一篇文章<驱动开发:内核枚举IoTimer定时器>中我们通过IoInitializeTimer这个API函数为跳板,向下扫描特征码获取到了IopTimerQueueHead也就是I ...
- Windows驱动开发-内核常用内存函数
搞内存常用函数 C语言 内核 malloc ExAllocatePool memset RtlFillMemory memcpy RtlMoveMemory free ExFreePool
- 驱动开发:Win10内核枚举SSDT表基址
三年前面朝黄土背朝天的我,写了一篇如何在Windows 7系统下枚举内核SSDT表的文章<驱动开发:内核读取SSDT表基址>三年过去了我还是个单身狗,开个玩笑,微软的Windows 10系 ...
- 驱动开发:内核枚举PspCidTable句柄表
在上一篇文章<驱动开发:内核枚举DpcTimer定时器>中我们通过枚举特征码的方式找到了DPC定时器基址并输出了内核中存在的定时器列表,本章将学习如何通过特征码定位的方式寻找Windows ...
- 《Windows内核安全与驱动开发》4.3 时间与定时器
<Windows内核安全与驱动开发>阅读笔记 -- 索引目录 <Windows内核安全与驱动开发>4.3 时间与定时器 一.获取自系统启动以来的毫秒数 /* 函数作用:求自操 ...
- 驱动开发:内核枚举ShadowSSDT基址
在笔者上一篇文章<驱动开发:Win10枚举完整SSDT地址表>实现了针对SSDT表的枚举功能,本章继续实现对SSSDT表的枚举,ShadowSSDT中文名影子系统服务描述表,SSSDT其主 ...
- 驱动开发:内核枚举LoadImage映像回调
在笔者之前的文章<驱动开发:内核特征码搜索函数封装>中我们封装实现了特征码定位功能,本章将继续使用该功能,本次我们需要枚举内核LoadImage映像回调,在Win64环境下我们可以设置一个 ...
- 驱动开发:内核枚举Registry注册表回调
在笔者上一篇文章<驱动开发:内核枚举LoadImage映像回调>中LyShark教大家实现了枚举系统回调中的LoadImage通知消息,本章将实现对Registry注册表通知消息的枚举,与 ...
- Windows内核安全与驱动开发
这篇是计算机中Windows Mobile/Symbian类的优质预售推荐<Windows内核安全与驱动开发>. 编辑推荐 本书适合计算机安全软件从业人员.计算机相关专业院校学生以及有一定 ...
随机推荐
- CF Global Round 21 题解 (CDEG)
C 把 \(a,b\) 全拆开然后比较即可(因为分裂和合并是互逆的) 注意开 long long . using namespace std; typedef long long ll; typede ...
- spring boot实现不同生产环境下的文件配置
配置不同生产环境 本文适用于开发环境下需要打包项目至生产环境,避免开发环境的配置文件泄露. 设置maven 作用:1. 手动调节运行时的不同环境 2. 打包时可以不会有其它环境的文件 注:每次换环境前 ...
- EPLAN部件库共享方法
EPLAN部件库共享方法 关键词: EPLAN部件库共享 EPLAN 摘要:在使用EPLAN时经常会碰到自己电脑里的部件库和公司里其他同事的部件库存在差异,如果不是很平凡的同步所有使用的部 ...
- Dolphin Scheduler 1.2.0 部署参数分析
本文章经授权转载 1 组件介绍 Apache Dolphin Scheduler是一个分布式易扩展的可视化DAG工作流任务调度系统.致力于解决数据处理流程中错综复杂的依赖关系,使调度系统在数据处理流程 ...
- 元数据治理利器 - Apache Atlas
一.功能简介 Atlas 是一组可扩展的核心基础治理服务,使企业能够高效地满足其在 Hadoop 中的合规性要求,并允许与整个企业数据生态系统集成.Apache Atlas 为组织提供开放的元数据管理 ...
- 国家都给NISP证书的补贴了!关于NISP考试的政策有哪些?
NISP证书由中国信息安全测评中心依据中编办赋予"信息安全服务和信息安全专业人员的能力评估与资质审核"的职能而推出的证书,是中国信息安全测评中心代表国家实施的信息安全人员能力评定证 ...
- 记一次twikoo引发的站点重大事故
今天我测试私人博客的时候发现twikoo评论发生了错误,显示评论失败:0,我怀疑是我设置的twikoo安全域名有问题,所以我看了整个lssues,找到了我的解决方法! 1.关于配置安全域名后评论消失的 ...
- mac M1通过homebrew安装python3报错Error: Command failed with exit 128: git
fatal: not in a git directoryError: Command failed with exit 128: git 只需要运行 git config --global --ad ...
- 【java】学习路径16-重写Object方法(equals()等)
在平时开发中,想要比较自定义类对象中的特定成员时,我们需要逐一手动比较,非常不方便. 举个栗子,我们有两个cafe对象,我们想比较两杯咖啡的价格是否一样,一般来说我们使用getter()来比较,但是这 ...
- Python入门系列(四)别再傻傻分不清:列表、元组、字典、集合的区别
总结分析列表.元组.字典.集合的相同与区别之处,只有彻底分清之后,就会在应用的时候,得心应手. 四句话总结 列表是一个有序且可更改的集合,允许重复成员. 元组是一个有序且不可更改的集合,允许重复成员. ...