在筆者上一篇文章《驅動開發:核心列舉IoTimer定時器》
中我們通過IoInitializeTimer
這個API函數為跳板,向下掃描特徵碼獲取到了IopTimerQueueHead
也就是IO定時器的佇列頭,本章學習的列舉DPC定時器依然使用特徵碼掃描,唯一不同的是在新版系統中DPC是被互斥或加密的,想要找到正確的地址,只是需要在找到DPC表頭時進行解密操作即可。
DPC定時器的作用: 在核心中可以使用DPC定時器設定任意定時任務,當到達某個節點時自動觸發定時回撥,定時器的內部使用KTIMER
物件,當設定任務時會自動插入到DPC
佇列,由作業系統迴圈讀取DPC
佇列並執行任務,列舉DPC
定時器可得知系統中存在的DPC
任務。
要想在新版系統中得到DPC定時器則需要執行以下步驟
KiProcessorBlock
地址並解析成_KPRCB
結構_KPRCB
結構中得到_KTIMER_TABLE
偏移_KTIMER_TABLE_ENTRY
得到加密後的雙向連結串列首先_KPRCB
這個結構體與CPU核心對應,獲取方式可通過一個未匯出的變數nt!KiProcessorBlock
來得到,如下雙核電腦,結構體存在兩個與之對應的結構地址。
lyshark.com 0: kd> dq nt!KiProcessorBlock
fffff807`70a32cc0 fffff807`6f77c180 ffffbe81`3cee0180
fffff807`70a32cd0 00000000`00000000 00000000`00000000
fffff807`70a32ce0 00000000`00000000 00000000`00000000
此KiProcessorBlock
是一個陣列,其第一個結構體TimerTable
則是結構體的偏移。
lyshark.com 0: kd> dt _KPRCB fffff807`6f77c180
ntdll!_KPRCB
+0x000 MxCsr : 0x1f80
+0x3680 TimerTable : _KTIMER_TABLE (此處)
+0x5880 DpcGate : _KGATE
接下來是把所有的KTIMER
都列舉出來,KTIMER在TimerTable
中的儲存方式是陣列+雙向連結串列。
lyshark.com 0: kd> dt _KTIMER_TABLE
ntdll!_KTIMER_TABLE
+0x000 TimerExpiry : [64] Ptr64 _KTIMER
+0x200 TimerEntries : [256] _KTIMER_TABLE_ENTRY (此處)
到了_KTIMER_TABLE_ENTRY
這裡,Entry
開始的雙向連結串列,每一個元素都對應一個Timer
也就是說我們已經可以遍歷所有未解密的Time
變數了。
lyshark.com 0: kd> dt _KTIMER_TABLE_ENTRY 0xfffff807`6f77c180 + 0x3680
ntdll!_KTIMER_TABLE_ENTRY
+0x000 Lock : 0
+0x008 Entry : _LIST_ENTRY [ 0x00000000`00000000 - 0x00000000`00000000 ]
+0x018 Time : _ULARGE_INTEGER 0x0
lyshark.com 0: kd> dt _KTIMER_TABLE_ENTRY 0xfffff807`6f77c180 + 0x3680 + 0x200
ntdll!_KTIMER_TABLE_ENTRY
+0x000 Lock : 0
+0x008 Entry : _LIST_ENTRY [ 0xffffa707`a0d3e1a0 - 0xffffa707`a0d3e1a0 ]
+0x018 Time : _ULARGE_INTEGER 0x00000001`a8030353
至於如何解密,我們需要得到加密位置,如下通過KeSetTimer
找到KeSetTimerEx
從中得到DCP
加密流程。
lyshark.com 0: kd> u nt!KeSetTimer
nt!KeSetTimer:
fffff803`0fc63a40 4883ec38 sub rsp,38h
fffff803`0fc63a44 4c89442420 mov qword ptr [rsp+20h],r8
fffff803`0fc63a49 4533c9 xor r9d,r9d
fffff803`0fc63a4c 4533c0 xor r8d,r8d
fffff803`0fc63a4f e80c000000 call nt!KiSetTimerEx (fffff803`0fc63a60)
fffff803`0fc63a54 4883c438 add rsp,38h
fffff803`0fc63a58 c3 ret
fffff803`0fc63a59 cc int 3
0: kd> u nt!KiSetTimerEx l50
nt!KiSetTimerEx:
fffff803`0fc63a60 48895c2408 mov qword ptr [rsp+8],rbx
fffff803`0fc63a65 48896c2410 mov qword ptr [rsp+10h],rbp
fffff803`0fc63a6a 4889742418 mov qword ptr [rsp+18h],rsi
fffff803`0fc63a6f 57 push rdi
fffff803`0fc63a70 4154 push r12
fffff803`0fc63a72 4155 push r13
fffff803`0fc63a74 4156 push r14
fffff803`0fc63a76 4157 push r15
fffff803`0fc63a78 4883ec50 sub rsp,50h
fffff803`0fc63a7c 488b057d0c5100 mov rax,qword ptr [nt!KiWaitNever (fffff803`10174700)]
fffff803`0fc63a83 488bf9 mov rdi,rcx
fffff803`0fc63a86 488b35630e5100 mov rsi,qword ptr [nt!KiWaitAlways (fffff803`101748f0)]
fffff803`0fc63a8d 410fb6e9 movzx ebp,r9b
fffff803`0fc63a91 4c8bac24a0000000 mov r13,qword ptr [rsp+0A0h]
fffff803`0fc63a99 458bf8 mov r15d,r8d
fffff803`0fc63a9c 4933f5 xor rsi,r13
fffff803`0fc63a9f 488bda mov rbx,rdx
fffff803`0fc63aa2 480fce bswap rsi
fffff803`0fc63aa5 4833f1 xor rsi,rcx
fffff803`0fc63aa8 8bc8 mov ecx,eax
fffff803`0fc63aaa 48d3ce ror rsi,cl
fffff803`0fc63aad 4833f0 xor rsi,rax
fffff803`0fc63ab0 440f20c1 mov rcx,cr8
fffff803`0fc63ab4 48898c24a0000000 mov qword ptr [rsp+0A0h],rcx
fffff803`0fc63abc b802000000 mov eax,2
fffff803`0fc63ac1 440f22c0 mov cr8,rax
fffff803`0fc63ac5 8b05dd0a5100 mov eax,dword ptr [nt!KiIrqlFlags (fffff803`101745a8)]
fffff803`0fc63acb 85c0 test eax,eax
fffff803`0fc63acd 0f85b72d1a00 jne nt!KiSetTimerEx+0x1a2e2a (fffff803`0fe0688a)
fffff803`0fc63ad3 654c8b342520000000 mov r14,qword ptr gs:[20h]
fffff803`0fc63adc 33d2 xor edx,edx
fffff803`0fc63ade 488bcf mov rcx,rdi
fffff803`0fc63ae1 e86aa2fdff call nt!KiCancelTimer (fffff803`0fc3dd50)
fffff803`0fc63ae6 440fb6e0 movzx r12d,al
fffff803`0fc63aea 48897730 mov qword ptr [rdi+30h],rsi
fffff803`0fc63aee 33c0 xor eax,eax
fffff803`0fc63af0 44897f3c mov dword ptr [rdi+3Ch],r15d
fffff803`0fc63af4 8b0f mov ecx,dword ptr [rdi]
fffff803`0fc63af6 4889442430 mov qword ptr [rsp+30h],rax
fffff803`0fc63afb 894c2430 mov dword ptr [rsp+30h],ecx
fffff803`0fc63aff 488bcb mov rcx,rbx
fffff803`0fc63b02 48c1e920 shr rcx,20h
fffff803`0fc63b06 4889442438 mov qword ptr [rsp+38h],rax
fffff803`0fc63b0b 4889442440 mov qword ptr [rsp+40h],rax
fffff803`0fc63b10 40886c2431 mov byte ptr [rsp+31h],bpl
fffff803`0fc63b15 85c9 test ecx,ecx
fffff803`0fc63b17 0f89c0000000 jns nt!KiSetTimerEx+0x17d (fffff803`0fc63bdd)
fffff803`0fc63b1d 33c9 xor ecx,ecx
fffff803`0fc63b1f 8bd1 mov edx,ecx
fffff803`0fc63b21 40f6c5fc test bpl,0FCh
fffff803`0fc63b25 0f85a3000000 jne nt!KiSetTimerEx+0x16e (fffff803`0fc63bce)
fffff803`0fc63b2b 48894c2420 mov qword ptr [rsp+20h],rcx
fffff803`0fc63b30 48b80800000080f7ffff mov rax,0FFFFF78000000008h
fffff803`0fc63b3a 4d8bc5 mov r8,r13
fffff803`0fc63b3d 488b00 mov rax,qword ptr [rax]
fffff803`0fc63b40 804c243340 or byte ptr [rsp+33h],40h
fffff803`0fc63b45 482bc3 sub rax,rbx
fffff803`0fc63b48 48894718 mov qword ptr [rdi+18h],rax
fffff803`0fc63b4c 4803c2 add rax,rdx
fffff803`0fc63b4f 48c1e812 shr rax,12h
fffff803`0fc63b53 488bd7 mov rdx,rdi
fffff803`0fc63b56 440fb6c8 movzx r9d,al
fffff803`0fc63b5a 44884c2432 mov byte ptr [rsp+32h],r9b
fffff803`0fc63b5f 8b442430 mov eax,dword ptr [rsp+30h]
fffff803`0fc63b63 8907 mov dword ptr [rdi],eax
fffff803`0fc63b65 894f04 mov dword ptr [rdi+4],ecx
fffff803`0fc63b68 498bce mov rcx,r14
fffff803`0fc63b6b e8209ffdff call nt!KiInsertTimerTable (fffff803`0fc3da90)
fffff803`0fc63b70 84c0 test al,al
fffff803`0fc63b72 0f8495000000 je nt!KiSetTimerEx+0x1ad (fffff803`0fc63c0d)
fffff803`0fc63b78 f7058608510000000200 test dword ptr [nt!PerfGlobalGroupMask+0x8 (fffff803`10174408)],20000h
fffff803`0fc63b82 0f852f2d1a00 jne nt!KiSetTimerEx+0x1a2e57 (fffff803`0fe068b7)
fffff803`0fc63b88 f081277fffffff lock and dword ptr [rdi],0FFFFFF7Fh
fffff803`0fc63b8f 488b8424a0000000 mov rax,qword ptr [rsp+0A0h]
fffff803`0fc63b97 4533c9 xor r9d,r9d
fffff803`0fc63b9a 33d2 xor edx,edx
fffff803`0fc63b9c 88442420 mov byte ptr [rsp+20h],al
fffff803`0fc63ba0 498bce mov rcx,r14
fffff803`0fc63ba3 458d4101 lea r8d,[r9+1]
fffff803`0fc63ba7 e8044efeff call nt!KiExitDispatcher (fffff803`0fc489b0)
如上組合程式碼KiSetTimerEx
中就是DPC加密細節,如果需要解密只需要逆操作即可,此處我就具體分析下加密細節,分析這個東西我建議你使用記事本帶著色的。
分析思路是這樣的,首先這裡要傳入待加密的DPC資料,然後經過KiWaitNever
和KiWaitAlways
對資料進行xor,ror,bswap
等操作。
將解密流程通過程式碼的方式實現。
#include <ntddk.h>
#include <ntstrsafe.h>
// 解密DPC
void DPC_Print(PKTIMER ptrTimer)
{
ULONG_PTR ptrDpc = (ULONG_PTR)ptrTimer->Dpc;
KDPC* DecDpc = NULL;
DWORD nShift = (p2dq(ptrKiWaitNever) & 0xFF);
// _RSI->Dpc = (_KDPC *)v19;
// _RSI = Timer;
ptrDpc ^= p2dq(ptrKiWaitNever); // v19 = KiWaitNever ^ v18;
ptrDpc = _rotl64(ptrDpc, nShift); // v18 = __ROR8__((unsigned __int64)Timer ^ _RBX, KiWaitNever);
ptrDpc ^= (ULONG_PTR)ptrTimer;
ptrDpc = _byteswap_uint64(ptrDpc); // __asm { bswap rbx }
ptrDpc ^= p2dq(ptrKiWaitAlways); // _RBX = (unsigned __int64)DPC ^ KiWaitAlways;
// real DPC
if (MmIsAddressValid((PVOID)ptrDpc))
{
DecDpc = (KDPC*)ptrDpc;
DbgPrint("DPC = %p | routine = %p \n", DecDpc, DecDpc->DeferredRoutine);
}
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint("解除安裝完成... \n");
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint("hello lyshark.com");
PKTIMER ptrTimer = NULL;
DPC_Print(ptrTimer);
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
接著將這些功能通過程式碼實現,首先得到我們需要的函數地址,這些地址包括。
ULONG_PTR ptrKiProcessorBlock = 0xfffff80770a32cc0;
ULONG_PTR ptrOffsetKTimerTable = 0x3680;
ULONG_PTR ptrKiWaitNever = 0xfffff80770a316f8;
ULONG_PTR ptrKiWaitAlways = 0xfffff80770a318e8;
此處我把它分為三步走,第一步找到KiProcessorBlock
函數地址,第二步找到KeSetTimer
並從裡面尋找KeSetTimerEx
,第三步根據KiSetTimerEx
地址,搜尋到KiWaitNever(),KiWaitAlways()
這兩個函數記憶體地址,最終迴圈連結串列並解密DPC佇列。
第一步: 找到KiProcessorBlock
函數地址,該地址可通過__readmsr()
暫存器相加偏移得到。
在WinDBG中可以輸入rdmsr c0000082
得到MSR地址。
MSR暫存器
使用程式碼獲取
也是很容易,只要找到MSR地址在加上0x20
即可得到KiProcessorBlock
的地址了。
/*
lyshark.com 0: kd> dp !KiProcessorBlock
fffff807`70a32cc0 fffff807`6f77c180 ffffbe81`3cee0180
fffff807`70a32cd0 00000000`00000000 00000000`00000000
fffff807`70a32ce0 00000000`00000000 00000000`00000000
fffff807`70a32cf0 00000000`00000000 00000000`00000000
fffff807`70a32d00 00000000`00000000 00000000`00000000
fffff807`70a32d10 00000000`00000000 00000000`00000000
fffff807`70a32d20 00000000`00000000 00000000`00000000
fffff807`70a32d30 00000000`00000000 00000000`00000000
*/
#include <ntddk.h>
#include <ntstrsafe.h>
// 得到KiProcessorBlock地址
ULONG64 GetKiProcessorBlock()
{
ULONG64 PrcbAddress = 0;
PrcbAddress = (ULONG64)__readmsr(0xC0000101) + 0x20;
if (PrcbAddress != 0)
{
// PrcbAddress 是一個地址 這個地址存放了某個 CPU 的 _KPRCB 的地址
return *(ULONG_PTR*)PrcbAddress;
}
return 0;
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint("解除安裝完成... \n");
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint("hello lyshark.com \n");
ULONG64 address = GetKiProcessorBlock();
if (address != 0)
{
DbgPrint("KiProcessorBlock = %p \n", address);
}
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
執行後即可得到輸出效果如下:
第二步: 找到KeSetTimer
從裡面搜尋特徵得到call KeSetTimerEx
函數地址,還記得《驅動開發:核心列舉IoTimer定時器》
中我們採用的特徵碼定位方式嗎,沒錯本次還要使用這個方法,我們此處需要搜尋到e80c000000
這段特徵。
/*
lyshark.com 0: kd> uf KeSetTimer
nt!KeSetTimer:
fffff807`70520a30 4883ec38 sub rsp,38h
fffff807`70520a34 4c89442420 mov qword ptr [rsp+20h],r8
fffff807`70520a39 4533c9 xor r9d,r9d
fffff807`70520a3c 4533c0 xor r8d,r8d
fffff807`70520a3f e80c000000 call nt!KiSetTimerEx (fffff807`70520a50)
fffff807`70520a44 4883c438 add rsp,38h
fffff807`70520a48 c3 ret
*/
#include <ntddk.h>
#include <ntstrsafe.h>
// 得到KiProcessorBlock地址
ULONG64 GetKeSetTimerEx()
{
// 獲取 KeSetTimer 地址
ULONG64 ul_KeSetTimer = 0;
UNICODE_STRING uc_KeSetTimer = { 0 };
RtlInitUnicodeString(&uc_KeSetTimer, L"KeSetTimer");
ul_KeSetTimer = (ULONG64)MmGetSystemRoutineAddress(&uc_KeSetTimer);
if (ul_KeSetTimer == 0)
{
return 0;
}
// 前 30 位元組找 call 指令
BOOLEAN b_e8 = FALSE;
ULONG64 ul_e8Addr = 0;
for (INT i = 0; i < 30; i++)
{
// 驗證地址是否可讀寫
if (!MmIsAddressValid((PVOID64)ul_KeSetTimer))
{
continue;
}
// e8 0c 00 00 00 call nt!KiSetTimerEx (fffff807`70520a50)
if (*(PUCHAR)(ul_KeSetTimer + i) == 0xe8)
{
b_e8 = TRUE;
ul_e8Addr = ul_KeSetTimer + i;
break;
}
}
// 找到 call 則解析目的地址
if (b_e8 == TRUE)
{
if (!MmIsAddressValid((PVOID64)ul_e8Addr))
{
return 0;
}
INT ul_callCode = *(INT*)(ul_e8Addr + 1);
ULONG64 ul_KiSetTimerEx = ul_e8Addr + ul_callCode + 5;
return ul_KiSetTimerEx;
}
return 0;
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint("解除安裝完成... \n");
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint("hello lyshark.com \n");
ULONG64 address = GetKeSetTimerEx();
if (address != 0)
{
DbgPrint("KeSetTimerEx = %p \n", address);
}
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
輸出尋找CALL地址效果圖如下:
第三步: 也是最重要的一步,在KiSetTimerEx
裡面,搜尋特徵,拿到裡面的KiWaitNever(),KiWaitAlways()
這兩個函數地址。
這個過程需要重複搜尋,所以要把第一步和第二部過程歸納起來,具體程式碼如下所示。
/*
0: kd> uf KiSetTimerEx
nt!KiSetTimerEx:
fffff807`70520a50 48895c2408 mov qword ptr [rsp+8],rbx
fffff807`70520a55 48896c2410 mov qword ptr [rsp+10h],rbp
fffff807`70520a5a 4889742418 mov qword ptr [rsp+18h],rsi
fffff807`70520a5f 57 push rdi
fffff807`70520a60 4154 push r12
fffff807`70520a62 4155 push r13
fffff807`70520a64 4156 push r14
fffff807`70520a66 4157 push r15
fffff807`70520a68 4883ec50 sub rsp,50h
fffff807`70520a6c 488b05850c5100 mov rax,qword ptr [nt!KiWaitNever (fffff807`70a316f8)]
fffff807`70520a73 488bf9 mov rdi,rcx
fffff807`70520a76 488b356b0e5100 mov rsi,qword ptr [nt!KiWaitAlways (fffff807`70a318e8)]
fffff807`70520a7d 410fb6e9 movzx ebp,r9b
*/
#include <ntddk.h>
#include <ntstrsafe.h>
// 得到KiProcessorBlock地址
ULONG64 GetKeSetTimerEx()
{
// 獲取 KeSetTimer 地址
ULONG64 ul_KeSetTimer = 0;
UNICODE_STRING uc_KeSetTimer = { 0 };
RtlInitUnicodeString(&uc_KeSetTimer, L"KeSetTimer");
ul_KeSetTimer = (ULONG64)MmGetSystemRoutineAddress(&uc_KeSetTimer);
if (ul_KeSetTimer == 0)
{
return 0;
}
// 前 30 位元組找 call 指令
BOOLEAN b_e8 = FALSE;
ULONG64 ul_e8Addr = 0;
for (INT i = 0; i < 30; i++)
{
// 驗證地址是否可讀寫
if (!MmIsAddressValid((PVOID64)ul_KeSetTimer))
{
continue;
}
// e8 0c 00 00 00 call nt!KiSetTimerEx (fffff807`70520a50)
if (*(PUCHAR)(ul_KeSetTimer + i) == 0xe8)
{
b_e8 = TRUE;
ul_e8Addr = ul_KeSetTimer + i;
break;
}
}
// 找到 call 則解析目的地址
if (b_e8 == TRUE)
{
if (!MmIsAddressValid((PVOID64)ul_e8Addr))
{
return 0;
}
INT ul_callCode = *(INT*)(ul_e8Addr + 1);
ULONG64 ul_KiSetTimerEx = ul_e8Addr + ul_callCode + 5;
return ul_KiSetTimerEx;
}
return 0;
}
// 得到KiWaitNever地址
ULONG64 GetKiWaitNever(ULONG64 address)
{
// 驗證地址是否可讀寫
if (!MmIsAddressValid((PVOID64)address))
{
return 0;
}
// 前 100 位元組找 找 KiWaitNever
for (INT i = 0; i < 100; i++)
{
// 48 8b 05 85 0c 51 00 | mov rax, qword ptr[nt!KiWaitNever(fffff807`70a316f8)]
if (*(PUCHAR)(address + i) == 0x48 && *(PUCHAR)(address + i + 1) == 0x8b && *(PUCHAR)(address + i + 2) == 0x05)
{
ULONG64 ul_movCode = *(UINT32*)(address + i + 3);
ULONG64 ul_movAddr = address + i + ul_movCode + 7;
// DbgPrint("找到KiWaitNever地址: %p \n", ul_movAddr);
return ul_movAddr;
}
}
return 0;
}
// 得到KiWaitAlways地址
ULONG64 GetKiWaitAlways(ULONG64 address)
{
// 驗證地址是否可讀寫
if (!MmIsAddressValid((PVOID64)address))
{
return 0;
}
// 前 100 位元組找 找 KiWaitNever
for (INT i = 0; i < 100; i++)
{
// 48 8b 35 6b 0e 51 00 | mov rsi,qword ptr [nt!KiWaitAlways (fffff807`70a318e8)]
if (*(PUCHAR)(address + i) == 0x48 && *(PUCHAR)(address + i + 1) == 0x8b && *(PUCHAR)(address + i + 2) == 0x35)
{
ULONG64 ul_movCode = *(UINT32*)(address + i + 3);
ULONG64 ul_movAddr = address + i + ul_movCode + 7;
return ul_movAddr;
}
}
return 0;
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint("解除安裝完成... \n");
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint("hello lyshark.com \n");
ULONG64 address = GetKeSetTimerEx();
if (address != 0)
{
ULONG64 KiWaitNeverAddress = GetKiWaitNever(address);
DbgPrint("KiWaitNeverAddress = %p \n", KiWaitNeverAddress);
ULONG64 KiWaitAlwaysAddress = GetKiWaitAlways(address);
DbgPrint("KiWaitAlwaysAddress = %p \n", KiWaitAlwaysAddress);
}
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
執行這個程式,我們看下尋找到的地址是否與WinDBG中找到的地址一致。
功能實現部分: 最後將這些功能整合在一起,迴圈輸出連結串列元素,並解密元素即可實現列舉當前系統DPC定時器。
程式碼核心API分析:
解密部分提取出KiWaitNever
和KiWaitAlways
用於解密計算,轉換PKDPC
物件結構,並輸出即可。
#include <Fltkernel.h>
#include <ntddk.h>
#include <intrin.h>
typedef struct _KTIMER_TABLE_ENTRY
{
ULONG_PTR Lock;
LIST_ENTRY Entry;
ULONG_PTR Time;
}KTIMER_TABLE_ENTRY, *PKTIMER_TABLE_ENTRY;
typedef struct _KTIMER_TABLE
{
ULONG_PTR TimerExpiry[64];
KTIMER_TABLE_ENTRY TimerEntries[256];
}KTIMER_TABLE, *PKTIMER_TABLE;
BOOLEAN get_KiWait(PULONG64 never, PULONG64 always)
{
// 獲取 KeSetTimer 地址
ULONG64 ul_KeSetTimer = 0;
UNICODE_STRING uc_KeSetTimer = { 0 };
RtlInitUnicodeString(&uc_KeSetTimer, L"KeSetTimer");
ul_KeSetTimer = (ULONG64)MmGetSystemRoutineAddress(&uc_KeSetTimer);
if (ul_KeSetTimer == NULL)
{
return FALSE;
}
// 前 30 位元組找 KeSetTimer
BOOLEAN b_e8 = FALSE;
ULONG64 ul_e8Addr = 0;
for (INT i = 0; i < 30; i++)
{
if (!MmIsAddressValid((PVOID64)ul_KeSetTimer))
{
continue;
}
/*
0: kd> u nt!KeSetTimer
nt!KeSetTimer:
fffff803`0fc63a40 4883ec38 sub rsp,38h
fffff803`0fc63a44 4c89442420 mov qword ptr [rsp+20h],r8
fffff803`0fc63a49 4533c9 xor r9d,r9d
fffff803`0fc63a4c 4533c0 xor r8d,r8d
fffff803`0fc63a4f e80c000000 call nt!KiSetTimerEx (fffff803`0fc63a60)
fffff803`0fc63a54 4883c438 add rsp,38h
fffff803`0fc63a58 c3 ret
fffff803`0fc63a59 cc int 3
*/
// fffff803`0fc63a4f e8 0c 00 00 00 call nt!KiSetTimerEx (fffff803`0fc63a60)
if (*(PUCHAR)(ul_KeSetTimer + i) == 0xe8)
{
b_e8 = TRUE;
ul_e8Addr = ul_KeSetTimer + i;
break;
}
}
// 找到 call 則解析目的地址
/*
0: kd> u nt!KiSetTimerEx l20
nt!KiSetTimerEx:
fffff803`0fc63a60 48895c2408 mov qword ptr [rsp+8],rbx
fffff803`0fc63a65 48896c2410 mov qword ptr [rsp+10h],rbp
fffff803`0fc63a6a 4889742418 mov qword ptr [rsp+18h],rsi
fffff803`0fc63a6f 57 push rdi
fffff803`0fc63a70 4154 push r12
fffff803`0fc63a72 4155 push r13
fffff803`0fc63a74 4156 push r14
fffff803`0fc63a76 4157 push r15
fffff803`0fc63a78 4883ec50 sub rsp,50h
fffff803`0fc63a7c 488b057d0c5100 mov rax,qword ptr [nt!KiWaitNever (fffff803`10174700)]
fffff803`0fc63a83 488bf9 mov rdi,rcx
*/
ULONG64 ul_KiSetTimerEx = 0;
if (b_e8 == TRUE)
{
if (!MmIsAddressValid((PVOID64)ul_e8Addr))
{
return FALSE;
}
INT ul_callCode = *(INT*)(ul_e8Addr + 1);
ULONG64 ul_callAddr = ul_e8Addr + ul_callCode + 5;
ul_KiSetTimerEx = ul_callAddr;
}
// 沒有 call 則直接在當前函數找
else
{
ul_KiSetTimerEx = ul_KeSetTimer;
}
// 前 50 位元組找 找 KiWaitNever 和 KiWaitAlways
/*
0: kd> u nt!KiSetTimerEx l20
nt!KiSetTimerEx:
fffff803`0fc63a60 48895c2408 mov qword ptr [rsp+8],rbx
fffff803`0fc63a65 48896c2410 mov qword ptr [rsp+10h],rbp
fffff803`0fc63a6a 4889742418 mov qword ptr [rsp+18h],rsi
fffff803`0fc63a6f 57 push rdi
fffff803`0fc63a70 4154 push r12
fffff803`0fc63a72 4155 push r13
fffff803`0fc63a74 4156 push r14
fffff803`0fc63a76 4157 push r15
fffff803`0fc63a78 4883ec50 sub rsp,50h
fffff803`0fc63a7c 488b057d0c5100 mov rax,qword ptr [nt!KiWaitNever (fffff803`10174700)]
fffff803`0fc63a83 488bf9 mov rdi,rcx
fffff803`0fc63a86 488b35630e5100 mov rsi,qword ptr [nt!KiWaitAlways (fffff803`101748f0)]
*/
if (!MmIsAddressValid((PVOID64)ul_KiSetTimerEx))
{
return FALSE;
}
// 存放 KiWaitNever 和 KiWaitAlways 的地址
ULONG64 ul_arr_ret[2];
// 對應 ul_arr_ret 的下標
INT i_sub = 0;
for (INT i = 0; i < 50; i++)
{
// fffff803`0fc63a7c 488b057d0c5100 mov rax,qword ptr [nt!KiWaitNever (fffff803`10174700)]
if (*(PUCHAR)(ul_KiSetTimerEx + i) == 0x48 && *(PUCHAR)(ul_KiSetTimerEx + i + 1) == 0x8b && *(PUCHAR)(ul_KiSetTimerEx + i + 6) == 0x00)
{
ULONG64 ul_movCode = *(UINT32*)(ul_KiSetTimerEx + i + 3);
ULONG64 ul_movAddr = ul_KiSetTimerEx + i + ul_movCode + 7;
// 只拿符合條件的前兩個值
if (i_sub < 2)
{
ul_arr_ret[i_sub++] = ul_movAddr;
}
}
}
*never = ul_arr_ret[0];
*always = ul_arr_ret[1];
return TRUE;
}
NTSTATUS DriverUnload(IN PDRIVER_OBJECT DriverObject)
{
return STATUS_SUCCESS;
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,IN PUNICODE_STRING RegistryPath)
{
DbgPrint("hello lyshark.com \n");
// 獲取 CPU 核心數
INT i_cpuNum = KeNumberProcessors;
DbgPrint("CPU核心數: %d \n", i_cpuNum);
for (KAFFINITY i = 0; i < i_cpuNum; i++)
{
// 執行緒繫結特定 CPU
KeSetSystemAffinityThread(i + 1);
// 獲得 KPRCB 的地址
ULONG64 p_PRCB = (ULONG64)__readmsr(0xC0000101) + 0x20;
if (!MmIsAddressValid((PVOID64)p_PRCB))
{
return FALSE;
}
// 取消繫結 CPU
KeRevertToUserAffinityThread();
// 判斷作業系統版本
RTL_OSVERSIONINFOEXW OSVersion = { 0 };
OSVersion.dwOSVersionInfoSize = sizeof(RTL_OSVERSIONINFOEXW);
RtlGetVersion((PRTL_OSVERSIONINFOW)&OSVersion);
// 計算 TimerTable 在 _KPRCB 結構中的偏移
PKTIMER_TABLE p_TimeTable = NULL;
if (OSVersion.dwMajorVersion == 10 && OSVersion.dwMinorVersion == 0)
{
p_TimeTable = (PKTIMER_TABLE)(*(PULONG64)p_PRCB + 0x3680);
}
else if (OSVersion.dwMajorVersion == 6 && OSVersion.dwMinorVersion == 1)
{
p_TimeTable = (PKTIMER_TABLE)(*(PULONG64)p_PRCB + 0x2200);
}
else
{
return FALSE;
}
// 遍歷 TimerEntries[] 陣列(大小 256)
for (INT j = 0; j < 256; j++)
{
// 獲取 Entry 雙向連結串列地址
if (!MmIsAddressValid((PVOID64)p_TimeTable))
{
continue;
}
PLIST_ENTRY p_ListEntryHead = &(p_TimeTable->TimerEntries[j].Entry);
// 遍歷 Entry 雙向連結串列
for (PLIST_ENTRY p_ListEntry = p_ListEntryHead->Flink; p_ListEntry != p_ListEntryHead; p_ListEntry = p_ListEntry->Flink)
{
// 根據 Entry 取 _KTIMER 物件地址
if (!MmIsAddressValid((PVOID64)p_ListEntry))
{
continue;
}
PKTIMER p_Timer = CONTAINING_RECORD(p_ListEntry, KTIMER, TimerListEntry);
// 寫死取 KiWaitNever 和 KiWaitAlways
ULONG64 never = 0, always = 0;
if (get_KiWait(&never, &always) == FALSE)
{
return FALSE;
}
// 獲取解密前的 Dpc 物件
if (!MmIsAddressValid((PVOID64)p_Timer))
{
continue;
}
ULONG64 ul_Dpc = (ULONG64)p_Timer->Dpc;
INT i_Shift = (*((PULONG64)never) & 0xFF);
// 解密 Dpc 物件
ul_Dpc ^= *((ULONG_PTR*)never); // 互斥或
ul_Dpc = _rotl64(ul_Dpc, i_Shift); // 迴圈左移
ul_Dpc ^= (ULONG_PTR)p_Timer; // 互斥或
ul_Dpc = _byteswap_uint64(ul_Dpc); // 顛倒順序
ul_Dpc ^= *((ULONG_PTR*)always); // 互斥或
// 物件型別轉換
PKDPC p_Dpc = (PKDPC)ul_Dpc;
// 列印驗證
if (!MmIsAddressValid((PVOID64)p_Dpc))
{
continue;
}
DbgPrint("[LyShark] 定時器物件:0x%p | 函數入口:0x%p | 觸發週期: %d \n ", p_Timer, p_Dpc->DeferredRoutine);
}
}
}
return STATUS_SUCCESS;
}
最終執行列舉程式,你將會看到系統中所有的定時器,與ARK工具對比是一致的。