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

内核I/O定时器(Kernel I/O Timer)是Windows内核中的一个对象,它允许内核或驱动程序设置一个定时器,以便在指定的时间间隔内调用一个回调函数。通常,内核I/O定时器用于周期性地执行某个任务,例如检查驱动程序的状态、收集性能数据等。

内核I/O定时器通常由内核或驱动程序创建,使用KeInitializeTimerEx函数进行初始化。然后,使用KeSetTimerEx函数启动定时器,以指定间隔和回调函数。每次定时器超时时,回调函数都会被调用,然后定时器重新启动以等待下一个超时。

内核I/O定时器是内核中常见的机制之一,它允许内核和驱动程序实现各种功能,如性能监视、定时执行任务等。但是,使用内核I/O定时器必须小心谨慎,因为它们可能会影响系统的性能和稳定性,特别是当存在大量定时器时。

枚举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属性。

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定时器的所需结构体。

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基址
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 \n")); // 得到基址
PUCHAR IoInitializeTimer = GetIoInitializeTimerAddress();
DbgPrint("IoInitializeTimer Address = %p \n", IoInitializeTimer); Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}

运行这个驱动程序,然后对比下是否一致:

接着我们在反汇编代码中寻找IoTimerQueueHead,此处在LyShark系统内这个偏移位置是nt!IoInitializeTimer+0x5d 具体输出位置如下。

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基址
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 \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) 得到结构体,循环输出即可。
#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 \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定时器的枚举就介绍完了,在教程中你已经学会了使用特征码定位这门技术,相信你完全可以输出内核中想要得到的任何结构体。

6.3 Windows驱动开发:内核枚举IoTimer定时器的更多相关文章

  1. 驱动开发:内核枚举IoTimer定时器

    今天继续分享内核枚举系列知识,这次我们来学习如何通过代码的方式枚举内核IoTimer定时器,内核定时器其实就是在内核中实现的时钟,该定时器的枚举非常简单,因为在IoInitializeTimer初始化 ...

  2. Windows驱动开发-内核常用内存函数

    搞内存常用函数 C语言 内核 malloc ExAllocatePool memset RtlFillMemory memcpy RtlMoveMemory free ExFreePool

  3. 驱动开发:内核枚举DpcTimer定时器

    在笔者上一篇文章<驱动开发:内核枚举IoTimer定时器>中我们通过IoInitializeTimer这个API函数为跳板,向下扫描特征码获取到了IopTimerQueueHead也就是I ...

  4. Windows驱动开发(中间层)

    Windows驱动开发 一.前言 依据<Windows内核安全与驱动开发>及MSDN等网络质料进行学习开发. 二.初步环境 1.下载安装WDK7.1.0(WinDDK\7600.16385 ...

  5. [Windows驱动开发](一)序言

    笔者学习驱动编程是从两本书入门的.它们分别是<寒江独钓——内核安全编程>和<Windows驱动开发技术详解>.两本书分别从不同的角度介绍了驱动程序的制作方法. 在我理解,驱动程 ...

  6. windows驱动开发推荐书籍

    [作者] 猪头三 个人网站 :http://www.x86asm.com/ [序言] 很多人都对驱动开发有兴趣,但往往找不到正确的学习方式.当然这跟驱动开发的本土化资料少有关系.大多学的驱动开发资料都 ...

  7. windows 驱动开发入门——驱动中的数据结构

    最近在学习驱动编程方面的内容,在这将自己的一些心得分享出来,供大家参考,与大家共同进步,本人学习驱动主要是通过两本书--<独钓寒江 windows安全编程> 和 <windows驱动 ...

  8. Windows驱动——读书笔记《Windows驱动开发技术详解》

    =================================版权声明================================= 版权声明:原创文章 谢绝转载  请通过右侧公告中的“联系邮 ...

  9. Windows驱动开发-IRP的完成例程

    <Windows驱动开发技术详解 >331页, 在将IRP发送给底层驱动或其他驱动之前,可以对IRP设置一个完成例程,一旦底层驱动将IRP完成后,IRP完成例程立刻被处罚,通过设置完成例程 ...

  10. C++第三十八篇 -- 研究一下Windows驱动开发(二)--WDM式驱动的加载

    基于Windows驱动开发技术详解这本书 一.简单的INF文件剖析 INF文件是一个文本文件,由若干个节(Section)组成.每个节的名称用一个方括号指示,紧接着方括号后面的就是节内容.每一行就是一 ...

随机推荐

  1. 叫板GPT-4的Gemini,我做了一个聊天网页,可图片输入,附教程

    先看效果: 简介 Gemini 是谷歌研发的最新一代大语言模型,目前有三个版本,被称为中杯.大杯.超大杯,Gemini Ultra 号称可与GPT-4一较高低: Gemini Nano(预览访问) 为 ...

  2. OpenShift 与 OpenStack:让云变得更简单

    OpenShift 与 OpenStack 都是在 2010.2011 年左右创建的,用于构建可扩展云平台的开源技术,两者都用于在混合云环境中构建可扩展系统.从历史来看,OpenStack 的存在时间 ...

  3. mysql--read only

    问题背景: 1.在进行数据迁移和从库只读状态设置时,都会涉及到只读状态和Master-Slave主从关系设置 2.数据库参数文件默认是只读,重启数据库服务时 解决方法: 1.在my.cnf配置文件中添 ...

  4. Python报错:TypeError: 'dict_keys' object does not support indexing(机器学习实战treePlotter代码)解决方案

    错误信息: 学习<机器学习实战>这本书时,按照书上的代码运行,产生了错误,但是在代码中没有错误提示,产生错误的代码如下: firstStr = myTree.keys()[0] print ...

  5. LocalDateTime、LocalDate、Date的相互转换

    1==LocalDateTime 转 LocalDate: 直接调用 toLocalDate() 方法: LocalDateTime localDateTime = LocalDateTime.now ...

  6. LightOJ 1030 数学期望

    Time Limit:2000MS     Memory Limit:32768KB     64bit IO Format:%lld & %llu Submit Status Practic ...

  7. 每天学五分钟 Liunx 0011 | 服务篇:进程

    1. 进程 程序放在硬盘中,在运行它的时候加载到内存,在内存里程序以进程的方式运行,进程有唯一的 ID ,叫 PID.   写个简单的 Hellow world 程序,让它产生 PID: [root@ ...

  8. maven总结二: 常用标签及属性

    本文为博主原创,未经允许不得转载  目录: 1. maven 依赖属性:groupId.artifactId.version 2.插件执行: execution,id ,phase,goals,con ...

  9. Oracle ORA-01861: 文字与格式字符串不匹配(日期格式导致的问题)

    1.问题 如图所示,Oracle ORA-01861: 文字与格式字符串不匹配.这里的日期格式出现问题,导致了ORA-01861错误. 2.解决方式 原因: 如果直接按照字符串方式,或者直接使用to_ ...

  10. 【VSCode】秒下vscode

    有时从vscode官网下载速度奇慢甚至失败,介绍一种方法可以秒下 进入官网选择要下载的版本 像我的电脑,下载网址根本打不开 修改下载网址,替换下载地址中红框字符串:vscode.cdn.azure.c ...