C語(yǔ)言驅(qū)動(dòng)開(kāi)發(fā)內(nèi)核枚舉IoTimer定時(shí)器解析
正文
今天繼續(xù)分享內(nèi)核枚舉系列知識(shí),這次我們來(lái)學(xué)習(xí)如何通過(guò)代碼的方式枚舉內(nèi)核IoTimer定時(shí)器,內(nèi)核定時(shí)器其實(shí)就是在內(nèi)核中實(shí)現(xiàn)的時(shí)鐘,該定時(shí)器的枚舉非常簡(jiǎn)單,因?yàn)樵?code>IoInitializeTimer初始化部分就可以找到IopTimerQueueHead地址,該變量?jī)?nèi)存儲(chǔ)的就是定時(shí)器的鏈表頭部。枚舉IO定時(shí)器的案例并不多見(jiàn),即便有也是無(wú)法使用過(guò)時(shí)的,此教程學(xué)到肯定就是賺到了。

枚舉Io定時(shí)器過(guò)程
- 1.找到
IoInitializeTimer函數(shù),該函數(shù)可以通過(guò)MmGetSystemRoutineAddress得到。 - 2.找到地址以后,我們向下增加
0xFF偏移量,并搜索特征定位到IopTimerQueueHead鏈表頭。 - 3.將鏈表頭轉(zhuǎn)換為
IO_TIMER結(jié)構(gòu)體,并循環(huán)鏈表頭輸出。
這里解釋一下為什么要找IoInitializeTimer這個(gè)函數(shù)他是一個(gè)初始化函數(shù),既然是初始化里面一定會(huì)涉及到鏈表的存儲(chǔ)問(wèn)題,找到他就能找到定時(shí)器鏈表基址,該函數(shù)的定義如下。
NTSTATUS
IoInitializeTimer(
IN PDEVICE_OBJECT DeviceObject, // 設(shè)備對(duì)象指針
IN PIO_TIMER_ROUTINE TimerRoutine, // 定時(shí)器例程
IN PVOID Context // 傳給定時(shí)器例程的函數(shù)
);
接著我們需要得到IO定時(shí)器的結(jié)構(gòu)定義,在DEVICE_OBJECT設(shè)備對(duì)象指針中存在一個(gè)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
這里的這個(gè)+0x028 Timer定時(shí)器是一個(gè)結(jié)構(gòu)體_IO_TIMER其就是IO定時(shí)器的所需結(jié)構(gòu)體。
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

如上方的基礎(chǔ)知識(shí)有了也就夠了,接著就是實(shí)際開(kāi)發(fā)部分,首先我們需要編寫(xiě)一個(gè)GetIoInitializeTimerAddress()函數(shù),讓該函數(shù)可以定位到IoInitializeTimer所在內(nèi)核中的基地址上面,具體實(shí)現(xiàn)調(diào)用代碼如下所示。
GetIoInitializeTimerAddress()函數(shù)
#include <ntifs.h>
// 得到IoInitializeTimer基址
// By: LyShark 內(nèi)核開(kāi)發(fā)系列教程
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;
}
運(yùn)行這個(gè)驅(qū)動(dòng)程序,然后對(duì)比下是否一致:

接著我們?cè)诜磪R編代碼中尋找IoTimerQueueHead,此處在LyShark系統(tǒng)內(nèi)這個(gè)偏移位置是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中標(biāo)注出顏色lea rcx,[nt!IopTimerQueueHead (fffff805748639e0)]更容易看到。

接著就是通過(guò)代碼實(shí)現(xiàn)對(duì)此處的定位,定位我們就采用特征碼搜索的方式,如下代碼是特征搜索部分。
特征搜索部分
- StartSearchAddress 代表開(kāi)始位置
- EndSearchAddress 代表結(jié)束位置,粗略計(jì)算0xff就可以定位到了。
#include <ntifs.h>
// 得到IoInitializeTimer基址
// By: LyShark 內(nèi)核開(kāi)發(fā)系列教程
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);
// 三個(gè)特征碼
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;
}
搜索三個(gè)特征碼v1 == 0x48 && v2 == 0x8d && v3 == 0x0d從而得到內(nèi)存位置,運(yùn)行驅(qū)動(dòng)對(duì)比下。
- 運(yùn)行代碼會(huì)取出
lea指令后面的操作數(shù),而不是取出lea指令的內(nèi)存地址。

IO_TIMER結(jié)構(gòu)體定義
最后一步就是枚舉部分,我們需要前面提到的IO_TIMER結(jié)構(gòu)體定義。
- PIO_TIMER Timer = CONTAINING_RECORD(NextEntry, IO_TIMER, TimerList) 得到結(jié)構(gòu)體,循環(huán)輸出即可。
// By: LyShark 內(nèi)核開(kāi)發(fā)系列教程
// 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;
// 獲得特權(quán)級(jí)
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對(duì)象地址: %p \n", Timer);
}
NextEntry = NextEntry->Flink;
}
}
// 恢復(fù)特權(quán)級(jí)
KeLowerIrql(OldIrql);
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
運(yùn)行這段源代碼,并可得到以下輸出,由于沒(méi)有IO定時(shí)器所以輸出結(jié)果是空的:

至此IO定時(shí)器的枚舉就介紹完了,在教程中你已經(jīng)學(xué)會(huì)了使用特征碼定位這門(mén)技術(shù),相信你完全可以輸出內(nèi)核中想要得到的任何結(jié)構(gòu)體。
以上就是C語(yǔ)言驅(qū)動(dòng)開(kāi)發(fā)內(nèi)核枚舉IoTimer定時(shí)器解析的詳細(xì)內(nèi)容,更多關(guān)于C語(yǔ)言 內(nèi)核枚舉IoTimer定時(shí)器的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C++實(shí)現(xiàn)英文句子中的單詞逆序輸出的方法
這篇文章主要介紹了C++實(shí)現(xiàn)英文句子中的單詞逆序輸出的方法,涉及C++字符串遍歷、分割、截取、輸出等相關(guān)操作技巧,需要的朋友可以參考下2018-01-01
c語(yǔ)言通過(guò)棧判斷括號(hào)匹配是否配對(duì)
前面實(shí)現(xiàn)了棧的基本數(shù)據(jù)結(jié)構(gòu),這里來(lái)做一個(gè)聯(lián)系,用棧來(lái)解決一道比較常見(jiàn)的算法題,就是括號(hào)配對(duì)是否滿足規(guī)則,文中有相關(guān)的代碼示例供大家參考,需要的朋友可以參考下2023-09-09
C++11/14 線程調(diào)用類(lèi)對(duì)象和線程傳參的方法
這篇文章主要介紹了C++11/14 線程調(diào)用類(lèi)對(duì)象和線程傳參的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-01-01
關(guān)于統(tǒng)計(jì)數(shù)字問(wèn)題的算法
本文介紹了統(tǒng)計(jì)數(shù)字問(wèn)題的算法,計(jì)算出書(shū)的全部頁(yè)碼中分別用到多少次數(shù)字0,1,2,3,.....9,并有每一步的解題思路,需要的朋友可以參考下2015-08-08
C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單的三子棋游戲
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)三子棋游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-09-09

