6.3 Windows驱动开发:内核枚举IoTimer定时器

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

今天继续分享内核枚举系列知识,这次我们来学习如何通过代码的方式枚举内核IoTimer定时器,内核定时器其实就是在内核中实现的时钟,该定时器的枚举非常简单,因为在IoInitializeTimer初始化部分就可以找到IopTimerQueueHead地址,该变量内存储的就是定时器的链表头部。


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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/193307.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JVM——垃圾回收器(Serial,SerialOld,ParNew,CMS,Parallel Scavenge,Parallel Old)

目录 1.垃圾回收器的组合关系1.年轻代-Serial垃圾回收器2.老年代-SerialOld垃圾回收器3.年轻代-ParNew垃圾回收器4.老年代- CMS(Concurrent Mark Sweep)垃圾回收器CMS执行步骤&#xff1a;CMS垃圾回收器存在的问题缺点&#xff1a;CMS垃圾回收器存在的问题 – 线程资源争抢问题…

【教学类-06-12】20231126 (二)三位数 如何让加减乘除题目从小到大排序(以0-110之间加法为例,做正序排列用)

结果展示 背景需求&#xff1a; 二位数&#xff1a;去0 三位数&#xff08;需要排除很多0&#xff09; 解决思路 一、把数字改成三位数 二、对数组内的题目&#xff0c;8种可能性进行去“0”处理 1、十位数&#xff08;去百位数0&#xff09;十位数&#xff08;去百位数0&am…

AMD ROCm软件栈组件介绍

AMD ROCm™ Platform 1.1 ROCm简介 参考&#xff1a;https://github.com/RadeonOpenCompute/ROCm ROCm&#xff08;Radeon Open Compute&#xff09;开源软件栈。 在NVIDIA GPU上&#xff0c;术语“CUDA”通常是指GPU编程编译器、API和运行时库&#xff0c;但ROCm不那么单一…

【云备份】数据管理模块

文章目录 1. 数据管理模块要管理什么数据&#xff1f;2. 数据管理模块如何管理数据&#xff1f;3. 数据管理模块的具体实现BackupInfo 数据信息类NewBackupInfo —— 获取各项属性信息 DataManager 数据管理类构造函数析构函数insert —— 新增update —— 修改GetOneByURL——…

C语言--每日选择题--Day24

第一题 1. 在C语言中&#xff0c;非法的八进制是&#xff08; &#xff09; A&#xff1a;018 B&#xff1a;016 C&#xff1a;017 D&#xff1a;0257 答案及解析 A 八进制是0&#xff5e;7的数字&#xff0c;所以A错误 第二题 2. fun((exp1,exp2),(exp3,exp4,exp5))有几…

网络通信(架构与三要素)

一&#xff0c;通信架构 二&#xff0c;三要素 三&#xff0c;IP地址 四&#xff0c;域名 五&#xff0c;InetAddress&#xff08;操作IP地址的工具类&#xff09; 六&#xff0c;案例 import java.net.InetAddress; import java.net.UnknownHostException;public class IPInet…

华为的数字化转型(9)——企业架构4A集成模型

企业架构&#xff08;Enterprise Architecture&#xff0c;EA&#xff09;是衔接战略与项目实施的桥梁&#xff0c;引入企业架构方法&#xff0c;可以对数字化转型愿景进行系统性的、分层分级的梳理和解释&#xff0c;以便企业上下在同一张蓝图上统一认识。 企业架构提供了整体…

时间序列预测 — LSTM实现多变量多步负荷预测(Keras)

目录 1 数据处理 1.1 数据集简介 1.2 数据集处理 2 模型训练与预测 2.1 模型训练 2.2 模型多步预测 2.3 结果可视化 1 数据处理 1.1 数据集简介 实验数据集采用数据集6&#xff1a;澳大利亚电力负荷与价格预测数据&#xff08;下载链接&#xff09;&#xff0c;包括数…

嵌入式FPGA IP正在发现更广阔的用武之地

作者&#xff1a;郭道正, Achronix Semiconductor中国区总经理 在日前落幕的“中国集成电路设计业2023年会暨广州集成电路产业创新发展高峰论坛&#xff08;ICCAD 2023&#xff09;”上&#xff0c;Achronix的Speedcore™嵌入式FPGA硅知识产权&#xff08;eFPGA IP&#xff09…

5. 链表

内存空间是所有程序的公共资源&#xff0c;在一个复杂的系统运行环境下&#xff0c;空闲的内存空间可能散落在内存各处。我们知道&#xff0c;存储数组的内存空间必须是连续的&#xff0c;而当数组非常大时&#xff0c;内存可能无法提供如此大的连续空间。此时链表的灵活性优势…

计算机组成原理-虚拟存储器

文章目录 虚拟存储系统页式虚拟存储器存储器的层次化结构段式虚拟存储器段页式虚拟存储器 虚拟存储系统 将辅存中程序部分调入内存&#xff0c;程序其他待分待需要再调入内存 页式虚拟存储器 将辅存中的程序分页&#xff0c;将当前用得到的程序的页调入到主存中。 外存块号…

文献速递:超声影像人工智能专题文献分享

文献速递&#xff1a;超声影像人工智能专题文献分享 01 文献速递介绍 本文综述了超声影像组学在甲状腺疾病研究中的应用及其局限性。近年来&#xff0c;甲状腺疾病的发病率逐渐增加&#xff0c;传统超声是最关键的甲状腺成像方法之一&#xff0c;但仍存在一定局限性。超声影…

【青蛙跳台阶问题 —— (三种算法)】

青蛙跳台阶问题 —— (三种算法&#xff09; 一.题目介绍1.1.题目1.2.图示 二.解题思路三.题解及其相关算法3.1.递归分治法3.2.动态规划算法&#xff08;Dynamic Programming&#xff09;3.3.斐波那契数列法 四.注意细节 一.题目介绍 1.1.题目 一只青蛙一次可以跳上1级台阶&am…

pinia从入门到使用

pinia: 比vuex更适合vue3的状态管理工具&#xff0c;只保留了vuex 原有的 state, getters&#xff0c;actions 作用等同于 data computed methods&#xff0c;可以有多个 state 1.安装创建导入 安装&#xff1a;npm install pinia 或 yarn add pinia 创建stores/index.js inde…

亚马逊云科技实现了奇瑞捷豹路虎SAP系统的上云目标并保持成本优化

11月23日&#xff0c;“2023第八届IDC中国数字化转型年度盛典”正式开启并揭晓“2023 IDC中国未来企业大奖-卓越奖”获奖企业&#xff0c;奇瑞捷豹路虎汽车有限公司&#xff08;以下简称“奇瑞捷豹路虎”&#xff09;凭借“基于云原生的智慧化运营平台”项目&#xff0c;继获得…

HTTP协议发展

HTTP 1.0 -> HTTP 1.1 -> HTTP 2.0 -> HTTP 3.0 (QUIC) 每一代HTTP解决了什么问题&#xff1f; 下图说明了主要功能。 HTTP 1.0 于 1996 年最终确定并完整记录。对同一服务器的每个请求都需要单独的 TCP 连接。 HTTP 1.1 于 1997 年发布。TCP 连接可以保持打开状态…

基于Springboot的墙绘产品展示交易平台(有报告),Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的墙绘产品展示交易平台&#xff08;有报告&#xff09;&#xff0c;Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff0…

在Windows上配置MySql开发java,导入JDBC的jar包后连接SQL Server数据库结合Java和MySql的一些简单实践

在Windows上配置MySql 我们先进入MySql官网 在官网中选择MySQL Installer for Windows 进入后选择第一个下载 接下来安装即可&#xff0c;在安装时&#xff0c;可以只安装MySql Server&#xff08;默认选项&#xff09;,选择Full也可&#xff0c;这样会同时安装workbench以及…

IDEA中Tomcat启动web项目

1.首先【Run】-->【Edit Configurations】&#xff0c;进入对应功能界面 2.点击左上角【】&#xff0c;选择Tomcat Server -->Local 3.Name输入自己中意的&#xff0c;下面两个port&#xff0c;保证没被占用就行 4.切到【Deployment】页签&#xff0c;点击【】&#xff…

Python入门04字符串

目录 1 字符串的定义2 转义字符3 字符串的常见方法4 分割字符串5 字符串反转6 字符串的链式调用7 格式化字符串8 多行字符串总结 1 字符串的定义 在Python中&#xff0c;字符串表示一个字符的序列&#xff0c;比如 str "hello,world"这里我们定义了一个字符串&…