驅動開發:核心列舉DpcTimer定時器

2022-10-16 09:00:33

在筆者上一篇文章《驅動開發:核心列舉IoTimer定時器》中我們通過IoInitializeTimer這個API函數為跳板,向下掃描特徵碼獲取到了IopTimerQueueHead也就是IO定時器的佇列頭,本章學習的列舉DPC定時器依然使用特徵碼掃描,唯一不同的是在新版系統中DPC是被互斥或加密的,想要找到正確的地址,只是需要在找到DPC表頭時進行解密操作即可。

DPC定時器的作用: 在核心中可以使用DPC定時器設定任意定時任務,當到達某個節點時自動觸發定時回撥,定時器的內部使用KTIMER物件,當設定任務時會自動插入到DPC佇列,由作業系統迴圈讀取DPC佇列並執行任務,列舉DPC定時器可得知系統中存在的DPC任務。

要想在新版系統中得到DPC定時器則需要執行以下步驟

  • 1.找到KiProcessorBlock地址並解析成_KPRCB結構
  • 2.在_KPRCB結構中得到_KTIMER_TABLE偏移
  • 3.解析_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資料,然後經過KiWaitNeverKiWaitAlways對資料進行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()這兩個函數地址。

  • 488b05850c5100 KiWaitNever
  • 488b356b0e5100 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分析:

  • KeNumberProcessors 得到CPU數量(核心常數)
  • KeSetSystemAffinityThread 執行緒繫結到特定CPU上
  • GetKiProcessorBlock 獲得KPRCB的地址
  • KeRevertToUserAffinityThread 取消繫結CPU

解密部分提取出KiWaitNeverKiWaitAlways用於解密計算,轉換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工具對比是一致的。