今天繼續分享內核列舉系列知識,這次我們來學習如何通過代碼的方式列舉內核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定時器的列舉就介紹完了,在教程中你已經學會了使用特征碼定位這門技術,相信你完全可以輸出內核中想要得到的任何結構體,
文章作者:lyshark (王瑞)文章出處:https://www.cnblogs.com/LyShark/p/16790834.html
著作權宣告:本博客文章與代碼均為學習時整理的筆記,文章 [均為原創] 作品,轉載請 [添加出處] ,您添加出處是我創作的動力!
轉載文章請遵守《中華人民共和國著作權法》相關法律規定或遵守《署名CC BY-ND 4.0國際》規范,合理合規攜帶原創出處轉載!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/514231.html
標籤:其他
上一篇:SpringBoot(三) - Slf4j+logback 日志,異步請求,定時任務
下一篇:Go入坑 io讀寫
