在上一篇文章《驅動開發:核心列舉DpcTimer定時器》
中我們通過列舉特徵碼的方式找到了DPC
定時器基址並輸出了核心中存在的定時器列表,本章將學習如何通過特徵碼定位的方式尋找Windows 10
系統下面的PspCidTable
核心控制程式碼表地址。
首先引入一段基礎概念;
windows
下所有的資源都是用物件的方式進行管理的(檔案、程序、裝置等都是物件)
,當要存取一個物件時,如開啟一個檔案,系統就會建立一個物件控制程式碼,通過這個控制程式碼可以對這個檔案進行各種操作。PspCidTable 就是這樣的一種表(核心控制程式碼表)
,表的內部存放的是程序EPROCESS
和執行緒ETHREAD
的核心物件,並通過程序PID
和執行緒TID
進行索引,ID號以4遞增,核心控制程式碼表不屬於任何程序,也不連線在系統的控制程式碼表上,通過它可以返回系統的任何物件。
核心控制程式碼表與普通控制程式碼表完全一樣,但它與每個程序私有的控制程式碼表有以下不同;
PID
和TID
。EPROCESS和ETHREAD
,而每個程序私有的控制程式碼表則存放的是物件頭OBJECT_HEADER
。那麼在Windows10
系統中該如何列舉控制程式碼表;
PsLookupProcessByProcessId
函數地址,該函數是被匯出的可以動態拿到。PsLookupProcessByProcessId
地址中搜尋PspReferenceCidTableEntry
函數。PspReferenceCidTableEntry
地址中找到PspCidTable
函數。首先第一步先要得到PspCidTable
函數記憶體地址,輸入dp PspCidTable
即可得到,如果在程式中則是呼叫MmGetSystemRoutineAddress
取到。
PspCidTable是一個HANDLE_TALBE
結構,當新建一個程序時,對應的會在PspCidTable
存在一個該程序和執行緒對應的HANDLE_TABLE_ENTRY
項。在windows10
中依然採用動態擴充套件
的方法,當控制程式碼數少的時候就採用下層表,多的時候才啟用中層表或上層表。
接著我們解析ffffdc88-79605dc0
這個記憶體地址,執行dt _HANDLE_TABLE 0xffffdc8879605dc0
得到規範化結構體。
核心控制程式碼表分為三層如下;
HANDLE_TABLE_ENTRY
項的索引,整個表共有256
個元素,每個元素是一個8個位元組
長的HANDLE_TABLE_ENTRY
項及索引,HANDLE_TABLE_ENTRY
項中儲存著指向物件的指標,下層表可以看成是程序和執行緒的稠密索引。256
個元素,每個元素是4個位元組
長的指向下層表的入口指標及索引,中層表可以看成是程序和執行緒的稀疏索引。256
個元素,每個元素是4個位元組
長的指向中層表的入口指標及索引,上層表可以看成是中層表的稀疏索引。總結起來一個控制程式碼表有一個上層表,一個上層表最多可以有256
箇中層表的入口指標,每個中層表最多可以有256
個下層表的入口指標,每個下層表最多可以有256
個程序和執行緒物件的指標。PspCidTable
表可以看成是HANDLE_TBALE_ENTRY
項的多級索引。
如上圖所示TableCode
是指向控制程式碼表的指標,低二位(二進位制)記錄控制程式碼表的等級:0(00)表示一級表,1(01)表示二級表,2(10)表示三級表。這裡的 0xffffdc88-7d09b001
就說名它是一個二級表。
一級表裡存放的就是程序和執行緒物件(加密過的,需要一些計算來解密),二級表裡存放的是指向某個一級表的指標,同理三級表存放的是指向二級表的指標。
x64 系統中,每張表的大小是 0x1000(4096)
,一級表中存放的是 _handle_table_entry
結構(大小 = 16)
,二級表和三級表存放的是指標(大小 = 8)
。
我們對 0xffffdc88-7d09b001
抹去低二位,輸入dp 0xffffdc887d09b000
輸出的結果就是一張二級表,裡面儲存的就是一級表指標。
繼續檢視第一張一級表,輸入dp 0xffffdc887962a000
命令,我們知道一級控制程式碼表是根據程序或執行緒ID來索引的,且以4累加,所以第一行對應id = 0
,第二行對應id = 4
。根據嘗試,PID = 4
的程序是System
。
所以此處的第二行0xb281de28-3300ffa7
就是加密後的System
程序的EPROCESS
結構,對於Win10系統來說解密演演算法(value >> 0x10) & 0xfffffffffffffff0
是這樣的,我們通過程式碼計算出來。
#include <Windows.h>
#include <iostream>
int _tmain(int argc, _TCHAR* argv[])
{
std::cout << "hello lyshark.com" << std::endl;
ULONG64 ul_recode = 0xb281de283300ffa7;
ULONG64 ul_decode = (LONG64)ul_recode >> 0x10;
ul_decode &= 0xfffffffffffffff0;
std::cout << "解密後地址: " << std::hex << ul_decode << std::endl;
getchar();
return 0;
}
執行程式得到如下輸出,即可知道System
系統程序解密後的EPROCESS
結構地址是0xffffb281de283300
回到WinDBG偵錯程式,輸入命令dt _EPROCESS 0xffffb281de283300
解析以下這個結構,輸出結果是System程序。
理論知識總結已經結束了,接下來就是如何實現列舉程序執行緒了,列舉流程如下:
PspCidTable
的地址。HANDLE_TBALE
的地址。TableCode
來判斷層次結構。這裡先來實現獲取PspCidTable
函數的動態地址,程式碼如下。
#include <ntifs.h>
#include <windef.h>
// 獲取 PspCidTable
// By: LyShark.com
BOOLEAN get_PspCidTable(ULONG64* tableAddr)
{
// 獲取 PsLookupProcessByProcessId 地址
UNICODE_STRING uc_funcName;
RtlInitUnicodeString(&uc_funcName, L"PsLookupProcessByProcessId");
ULONG64 ul_funcAddr = MmGetSystemRoutineAddress(&uc_funcName);
if (ul_funcAddr == NULL)
{
return FALSE;
}
DbgPrint("PsLookupProcessByProcessId addr = %p \n", ul_funcAddr);
// 前 40 位元組有 call(PspReferenceCidTableEntry)
/*
0: kd> uf PsLookupProcessByProcessId
nt!PsLookupProcessByProcessId:
fffff802`0841cfe0 48895c2418 mov qword ptr [rsp+18h],rbx
fffff802`0841cfe5 56 push rsi
fffff802`0841cfe6 4883ec20 sub rsp,20h
fffff802`0841cfea 48897c2438 mov qword ptr [rsp+38h],rdi
fffff802`0841cfef 488bf2 mov rsi,rdx
fffff802`0841cff2 65488b3c2588010000 mov rdi,qword ptr gs:[188h]
fffff802`0841cffb 66ff8fe6010000 dec word ptr [rdi+1E6h]
fffff802`0841d002 b203 mov dl,3
fffff802`0841d004 e887000000 call nt!PspReferenceCidTableEntry (fffff802`0841d090)
fffff802`0841d009 488bd8 mov rbx,rax
fffff802`0841d00c 4885c0 test rax,rax
fffff802`0841d00f 7435 je nt!PsLookupProcessByProcessId+0x66 (fffff802`0841d046) Branch
*/
ULONG64 ul_entry = 0;
for (INT i = 0; i < 100; i++)
{
// fffff802`0841d004 e8 87 00 00 00 call nt!PspReferenceCidTableEntry (fffff802`0841d090)
if (*(PUCHAR)(ul_funcAddr + i) == 0xe8)
{
ul_entry = ul_funcAddr + i;
break;
}
}
if (ul_entry != 0)
{
// 解析 call 地址
INT i_callCode = *(INT*)(ul_entry + 1);
DbgPrint("i_callCode = %p \n", i_callCode);
ULONG64 ul_callJmp = ul_entry + i_callCode + 5;
DbgPrint("ul_callJmp = %p \n", ul_callJmp);
// 來到 call(PspReferenceCidTableEntry) 內找 PspCidTable
/*
0: kd> uf PspReferenceCidTableEntry
nt!PspReferenceCidTableEntry+0x115:
fffff802`0841d1a5 488b0d8473f5ff mov rcx,qword ptr [nt!PspCidTable (fffff802`08374530)]
fffff802`0841d1ac b801000000 mov eax,1
fffff802`0841d1b1 f0480fc107 lock xadd qword ptr [rdi],rax
fffff802`0841d1b6 4883c130 add rcx,30h
fffff802`0841d1ba f0830c2400 lock or dword ptr [rsp],0
fffff802`0841d1bf 48833900 cmp qword ptr [rcx],0
fffff802`0841d1c3 0f843fffffff je nt!PspReferenceCidTableEntry+0x78 (fffff802`0841d108) Branch
*/
for (INT i = 0; i < 0x120; i++)
{
// fffff802`0841d1a5 48 8b 0d 84 73 f5 ff mov rcx,qword ptr [nt!PspCidTable (fffff802`08374530)]
if (*(PUCHAR)(ul_callJmp + i) == 0x48 && *(PUCHAR)(ul_callJmp + i + 1) == 0x8b && *(PUCHAR)(ul_callJmp + i + 2) == 0x0d)
{
// 解析 mov 地址
INT i_movCode = *(INT*)(ul_callJmp + i + 3);
DbgPrint("i_movCode = %p \n", i_movCode);
ULONG64 ul_movJmp = ul_callJmp + i + i_movCode + 7;
DbgPrint("ul_movJmp = %p \n", ul_movJmp);
// 得到 PspCidTable
*tableAddr = ul_movJmp;
return TRUE;
}
}
}
return FALSE;
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint(("Uninstall Driver Is OK \n"));
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint(("hello lyshark \n"));
ULONG64 tableAddr = 0;
get_PspCidTable(&tableAddr);
DbgPrint("PspCidTable Address = %p \n", tableAddr);
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
執行後即可得到動態地址,我們可以驗證一下是否一致:
繼續增加對與三級表的動態解析程式碼,最終程式碼如下所示:
#include <ntifs.h>
#include <windef.h>
// 獲取 PspCidTable
// By: LyShark.com
BOOLEAN get_PspCidTable(ULONG64* tableAddr)
{
// 獲取 PsLookupProcessByProcessId 地址
UNICODE_STRING uc_funcName;
RtlInitUnicodeString(&uc_funcName, L"PsLookupProcessByProcessId");
ULONG64 ul_funcAddr = MmGetSystemRoutineAddress(&uc_funcName);
if (ul_funcAddr == NULL)
{
return FALSE;
}
DbgPrint("PsLookupProcessByProcessId addr = %p \n", ul_funcAddr);
// 前 40 位元組有 call(PspReferenceCidTableEntry)
/*
0: kd> uf PsLookupProcessByProcessId
nt!PsLookupProcessByProcessId:
fffff802`0841cfe0 48895c2418 mov qword ptr [rsp+18h],rbx
fffff802`0841cfe5 56 push rsi
fffff802`0841cfe6 4883ec20 sub rsp,20h
fffff802`0841cfea 48897c2438 mov qword ptr [rsp+38h],rdi
fffff802`0841cfef 488bf2 mov rsi,rdx
fffff802`0841cff2 65488b3c2588010000 mov rdi,qword ptr gs:[188h]
fffff802`0841cffb 66ff8fe6010000 dec word ptr [rdi+1E6h]
fffff802`0841d002 b203 mov dl,3
fffff802`0841d004 e887000000 call nt!PspReferenceCidTableEntry (fffff802`0841d090)
fffff802`0841d009 488bd8 mov rbx,rax
fffff802`0841d00c 4885c0 test rax,rax
fffff802`0841d00f 7435 je nt!PsLookupProcessByProcessId+0x66 (fffff802`0841d046) Branch
*/
ULONG64 ul_entry = 0;
for (INT i = 0; i < 100; i++)
{
// fffff802`0841d004 e8 87 00 00 00 call nt!PspReferenceCidTableEntry (fffff802`0841d090)
if (*(PUCHAR)(ul_funcAddr + i) == 0xe8)
{
ul_entry = ul_funcAddr + i;
break;
}
}
if (ul_entry != 0)
{
// 解析 call 地址
INT i_callCode = *(INT*)(ul_entry + 1);
DbgPrint("i_callCode = %p \n", i_callCode);
ULONG64 ul_callJmp = ul_entry + i_callCode + 5;
DbgPrint("ul_callJmp = %p \n", ul_callJmp);
// 來到 call(PspReferenceCidTableEntry) 內找 PspCidTable
/*
0: kd> uf PspReferenceCidTableEntry
nt!PspReferenceCidTableEntry+0x115:
fffff802`0841d1a5 488b0d8473f5ff mov rcx,qword ptr [nt!PspCidTable (fffff802`08374530)]
fffff802`0841d1ac b801000000 mov eax,1
fffff802`0841d1b1 f0480fc107 lock xadd qword ptr [rdi],rax
fffff802`0841d1b6 4883c130 add rcx,30h
fffff802`0841d1ba f0830c2400 lock or dword ptr [rsp],0
fffff802`0841d1bf 48833900 cmp qword ptr [rcx],0
fffff802`0841d1c3 0f843fffffff je nt!PspReferenceCidTableEntry+0x78 (fffff802`0841d108) Branch
*/
for (INT i = 0; i < 0x120; i++)
{
// fffff802`0841d1a5 48 8b 0d 84 73 f5 ff mov rcx,qword ptr [nt!PspCidTable (fffff802`08374530)]
if (*(PUCHAR)(ul_callJmp + i) == 0x48 && *(PUCHAR)(ul_callJmp + i + 1) == 0x8b && *(PUCHAR)(ul_callJmp + i + 2) == 0x0d)
{
// 解析 mov 地址
INT i_movCode = *(INT*)(ul_callJmp + i + 3);
DbgPrint("i_movCode = %p \n", i_movCode);
ULONG64 ul_movJmp = ul_callJmp + i + i_movCode + 7;
DbgPrint("ul_movJmp = %p \n", ul_movJmp);
// 得到 PspCidTable
*tableAddr = ul_movJmp;
return TRUE;
}
}
}
return FALSE;
}
/* 解析一級表
// By: LyShark.com
BaseAddr:一級表的基地址
index1:第幾個一級表
index2:第幾個二級表
*/
VOID parse_table_1(ULONG64 BaseAddr, INT index1, INT index2)
{
// 遍歷一級表(每個表項大小 16 ),表大小 4k,所以遍歷 4096/16 = 526 次
PEPROCESS p_eprocess = NULL;
PETHREAD p_ethread = NULL;
INT i_id = 0;
for (INT i = 0; i < 256; i++)
{
if (!MmIsAddressValid((PVOID64)(BaseAddr + i * 16)))
{
DbgPrint("非法地址= %p \n", BaseAddr + i * 16);
continue;
}
ULONG64 ul_recode = *(PULONG64)(BaseAddr + i * 16);
// 解密
ULONG64 ul_decode = (LONG64)ul_recode >> 0x10;
ul_decode &= 0xfffffffffffffff0;
// 判斷是程序還是執行緒
i_id = i * 4 + 1024 * index1 + 512 * index2 * 1024;
if (PsLookupProcessByProcessId(i_id, &p_eprocess) == STATUS_SUCCESS)
{
DbgPrint("程序PID: %d | ID: %d | 記憶體地址: %p | 物件: %p \n", i_id, i, BaseAddr + i * 0x10, ul_decode);
}
else if (PsLookupThreadByThreadId(i_id, &p_ethread) == STATUS_SUCCESS)
{
DbgPrint("執行緒TID: %d | ID: %d | 記憶體地址: %p | 物件: %p \n", i_id, i, BaseAddr + i * 0x10, ul_decode);
}
}
}
/* 解析二級表
// By: LyShark.com
BaseAddr:二級表基地址
index2:第幾個二級表
*/
VOID parse_table_2(ULONG64 BaseAddr, INT index2)
{
// 遍歷二級表(每個表項大小 8),表大小 4k,所以遍歷 4096/8 = 512 次
ULONG64 ul_baseAddr_1 = 0;
for (INT i = 0; i < 512; i++)
{
if (!MmIsAddressValid((PVOID64)(BaseAddr + i * 8)))
{
DbgPrint("非法二級表指標(1):%p \n", BaseAddr + i * 8);
continue;
}
if (!MmIsAddressValid((PVOID64)*(PULONG64)(BaseAddr + i * 8)))
{
DbgPrint("非法二級表指標(2):%p \n", BaseAddr + i * 8);
continue;
}
ul_baseAddr_1 = *(PULONG64)(BaseAddr + i * 8);
parse_table_1(ul_baseAddr_1, i, index2);
}
}
/* 解析三級表
// By: LyShark.com
BaseAddr:三級表基地址
*/
VOID parse_table_3(ULONG64 BaseAddr)
{
// 遍歷三級表(每個表項大小 8),表大小 4k,所以遍歷 4096/8 = 512 次
ULONG64 ul_baseAddr_2 = 0;
for (INT i = 0; i < 512; i++)
{
if (!MmIsAddressValid((PVOID64)(BaseAddr + i * 8)))
{
continue;
}
if (!MmIsAddressValid((PVOID64)* (PULONG64)(BaseAddr + i * 8)))
{
continue;
}
ul_baseAddr_2 = *(PULONG64)(BaseAddr + i * 8);
parse_table_2(ul_baseAddr_2, i);
}
}
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"));
ULONG64 tableAddr = 0;
get_PspCidTable(&tableAddr);
DbgPrint("PspCidTable Address = %p \n", tableAddr);
// 獲取 _HANDLE_TABLE 的 TableCode
ULONG64 ul_tableCode = *(PULONG64)(((ULONG64)*(PULONG64)tableAddr) + 8);
DbgPrint("ul_tableCode = %p \n", ul_tableCode);
// 取低 2位(二級制11 = 3)
INT i_low2 = ul_tableCode & 3;
DbgPrint("i_low2 = %X \n", i_low2);
// 一級表
if (i_low2 == 0)
{
// TableCode 低 2位抹零(二級制11 = 3)
parse_table_1(ul_tableCode & (~3), 0, 0);
}
// 二級表
else if (i_low2 == 1)
{
// TableCode 低 2位抹零(二級制11 = 3)
parse_table_2(ul_tableCode & (~3), 0);
}
// 三級表
else if (i_low2 == 2)
{
// TableCode 低 2位抹零(二級制11 = 3)
parse_table_3(ul_tableCode & (~3));
}
else
{
DbgPrint("LyShark提示: 錯誤,非法! ");
return FALSE;
}
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
執行如上完整程式碼,我們可以在WinDBG中捕捉到列舉到的程序資訊:
執行緒資訊在程序資訊的下面,列舉效果如下:
至此文章就結束了,這裡多說一句,實際上ZwQuerySystemInformation
列舉系統控制程式碼時就是走的這條雙鏈,列舉系統程序如果使用的是這個API函數,那麼不出意外它也是在這些核心表中做的解析。
http://www.blogfshare.com/details-in-pspcidtbale.html
https://blog.csdn.net/whatday/article/details/17189093
https://www.cnblogs.com/kuangke/p/5761615.html