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

  • 相关阅读:
    JAVA最简单常识
    BREW的资源文件概述及问题
    c语言 512
    c语言510 求矩阵的乘积
    c语言 511
    c语言57
    c语言 59
    c语言55 在应用对象式宏的数组中对数组元素进行倒序排列
    c语言 511
    c语言 510 求4行3列矩阵和3行4列矩阵的乘积。各构成元素的值从键盘输入。
  • 原文地址:https://www.cnblogs.com/LyShark/p/16790834.html
Copyright © 2020-2023  润新知