作者:lu4nx@知道創宇404積極防御實驗室
日期:2021年1月25日

加載 PDB 符號文件

沒有加載符號文件,很多函數是顯示不出函數名的。如果本地有符號文件(比如用 WinDbg 時已經下載),那直接在"File"菜單選擇"Load PDB File",瀏覽目錄找到 .pdb 或 .xml 文件即可。

如果本地沒有,Ghidra 也支持直接從微軟服務器下載:

  1. 點"File"菜單,選擇"Download PDB File...";

  2. 彈框確認下載格式是 PDB 還是 XML,選擇 PDB;

  3. 選擇本地保存路徑;

  4. 在配置"Symbol Server URL"時,點"Choose from known URLs"按鈕,選擇微軟官方服務器;

  5. 點"Download from URL"即可。

配置 Data Type

Ghidra 沒有內置 WDK 的數據類型,在轉換成 C 代碼時無法更改變量類型,對分析驅動文件來說非常不方便。這個問題很早前就有網友在官方倉庫提過 Issue,但是官方至今未支持(參考:https://github.com/NationalSecurityAgency/ghidra/issues/184)。 好在有其他網友提供了 WDK 的數據類型文件,下載地址:https://github.com/0x6d696368/ghidra-data/tree/master/typeinfo

.gdt 是 Data Type 的數據文件,對于分析內核驅動只用根據處理器位數下載 ntddk_32.gdt 或 ntddk_64.gdt 即可。

以 ntddk_64.gdt 為例,下載以后,在"Data Type"窗口點右上角的"▼"按鈕,選擇"Open File Archive..",找到并確認 ntddk_64.gdt 文件,如下圖:

然后在列表中選中"ntddk_64",點右鍵,選擇"Apply Function Data Types"即可。

反匯編驅動文件

在逆向驅動時,如果驅動文件本身沒有符號文件,就在"Symbol Tree"窗口的"Functions"中找到,entry 函數,這就是驅動入口點。Windows 驅動的入口函數定義如下:

NTSTATUS DriverEntry(
    IN PDRIVER_OBJECT DriverObject,
    IN PUNICODE_STRING RegistryPath
    ){
}

入口函數有兩個參數,不過有時候因為編譯器的優化,Ghidra 反編譯出的入口代碼可能長這個樣子:

void entry(longlong param_1,longlong param_2)

{
  FUN_14000502c();
  FUN_140001000(param_1,param_2);
  return;
}

上面真正的入口函數應該是 FUN_140001000,進入 FUN_140001000,示例代碼如下:

undefined8 FUN_140001000(longlong param_1,longlong param_2)

{
  DbgPrint("hello world\n");
  if (param_2 != 0) {
    DbgPrint("hello world, RegistryPaht:%wZ\n",param_2);
  }
  if (param_1 != 0) {
    *(code **)(param_1 + 0x68) = FUN_140001060;
  }
  return 0;
}

現在就可以根據函數原型定義來修改參數類型了,比如在變量 param_1 上點右鍵,選擇"Retype Variable",然后更改為 PDRIVER_OBJECT 類型,按這個方法,依次修改兩個參數的參數類型和變量名,再將函數名改為"DriverEntry"、返回值改為 NTSTATUS,最終修改后如下:

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject,PUNICODE_STRING RegistryPath)

{
  DbgPrint("hello world\n");
  if (RegistryPath != (PUNICODE_STRING)0x0) {
    DbgPrint("hello world, RegistryPaht:%wZ\n",RegistryPath);
  }
  if (DriverObject != (PDRIVER_OBJECT)0x0) {
    DriverObject->DriverUnload = FUN_140001060;
  }
  return 0;
}

這里可以注意一個細節,修改第一個參數的類型以前,這句代碼長這樣:

*(code **)(param_1 + 0x68) = FUN_140001060;

修改以后:

DriverObject->DriverUnload = FUN_140001060;

Ghidra 根據類型將后面的成員變量給自動修正了。

如果結構體成員指向的是另一個結構體時,Ghidra 不會遞歸修正,比如:

puVar11 = CdpFindEaBufferItem(*(uint **)((longlong)&irp->AssociatedIrp + 4),"attach")

第一個參數的值在 IRP->AssociatedIrp 偏移 4 的位置,這時我們可以借助 WinDbg 來搞清楚,比如這個例子中 irp 變量對應的是 IRP 結構,用 WinDbg 查看 IRP 結構:

1: kd> dt nt!_irp /r
   +0x000 Type             : Int2B
   +0x002 Size             : Uint2B
   ...省略...
   +0x018 AssociatedIrp    : <anonymous-tag>
      +0x000 MasterIrp        : Ptr64 _IRP
         +0x000 Type             : Int2B
         +0x002 Size             : Uint2B
         +0x004 AllocationProcessorNumber : Uint2B
         ...省略...

注意 dt 命令要加 /r 參數,才能遞歸列出每個成員。

在這里可以看到 IRP->AssociatedIrp 其實是另外一個 PIRP 類型,+ 4 對應的是 IRP 結構體的 MasterIrp 成員變量。

參考

《Methodology for Static Reverse Engineering of Windows Kernel Drivers》
https://posts.specterops.io/methodology-for-static-reverse-engineering-of-windows-kernel-drivers-3115b2efed83


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1468/