在筆者之前的文章《驅動開發:核心特徵碼搜尋函數封裝》
中我們封裝實現了特徵碼定位功能,本章將繼續使用該功能,本次我們需要列舉核心LoadImage
映像回撥,在Win64環境下我們可以設定一個LoadImage
映像載入通告回撥,當有新驅動或者DLL被載入時,回撥函數就會被呼叫從而執行我們自己的回撥例程,映像回撥也儲存在陣列裡,列舉時從陣列中讀取值之後,需要進行位運算解密得到地址。
我們來看一款閉源ARK工具是如何實現的:
如上所述,如果我們需要拿到回撥陣列那麼首先要得到該陣列,陣列的符號名是PspLoadImageNotifyRoutine
我們可以在PsSetLoadImageNotifyRoutineEx
中找到。
第一步使用WinDBG輸入uf PsSetLoadImageNotifyRoutineEx
首先定位到,能夠找到PsSetLoadImageNotifyRoutineEx
這裡的兩個位置都可以被參照,當然了這個函數可以直接通過PsSetLoadImageNotifyRoutineEx
函數動態拿到此處不需要我們動態定位。
我們通過獲取到PsSetLoadImageNotifyRoutineEx
函數的記憶體首地址,然後向下匹配特徵碼搜尋找到488d0d88e8dbff
並取出PspLoadImageNotifyRoutine
記憶體地址,該記憶體地址就是LoadImage
映像模組的基址。
如果使用程式碼去定位這段空間,則你可以這樣寫,這樣即可得到具體特徵地址。
// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]
#include <ntddk.h>
#include <windef.h>
// 指定記憶體區域的特徵碼掃描
PVOID SearchMemory(PVOID pStartAddress, PVOID pEndAddress, PUCHAR pMemoryData, ULONG ulMemoryDataSize)
{
PVOID pAddress = NULL;
PUCHAR i = NULL;
ULONG m = 0;
// 掃描記憶體
for (i = (PUCHAR)pStartAddress; i < (PUCHAR)pEndAddress; i++)
{
// 判斷特徵碼
for (m = 0; m < ulMemoryDataSize; m++)
{
if (*(PUCHAR)(i + m) != pMemoryData[m])
{
break;
}
}
// 判斷是否找到符合特徵碼的地址
if (m >= ulMemoryDataSize)
{
// 找到特徵碼位置, 獲取緊接著特徵碼的下一地址
pAddress = (PVOID)(i + ulMemoryDataSize);
break;
}
}
return pAddress;
}
// 根據特徵碼獲取 PspLoadImageNotifyRoutine 陣列地址
PVOID SearchPspLoadImageNotifyRoutine(PUCHAR pSpecialData, ULONG ulSpecialDataSize)
{
UNICODE_STRING ustrFuncName;
PVOID pAddress = NULL;
LONG lOffset = 0;
PVOID pPsSetLoadImageNotifyRoutine = NULL;
PVOID pPspLoadImageNotifyRoutine = NULL;
// 先獲取 PsSetLoadImageNotifyRoutineEx 函數地址
RtlInitUnicodeString(&ustrFuncName, L"PsSetLoadImageNotifyRoutineEx");
pPsSetLoadImageNotifyRoutine = MmGetSystemRoutineAddress(&ustrFuncName);
if (NULL == pPsSetLoadImageNotifyRoutine)
{
return pPspLoadImageNotifyRoutine;
}
// 查詢 PspLoadImageNotifyRoutine 函數地址
pAddress = SearchMemory(pPsSetLoadImageNotifyRoutine, (PVOID)((PUCHAR)pPsSetLoadImageNotifyRoutine + 0xFF), pSpecialData, ulSpecialDataSize);
if (NULL == pAddress)
{
return pPspLoadImageNotifyRoutine;
}
// 先獲取偏移, 再計算地址
lOffset = *(PLONG)pAddress;
pPspLoadImageNotifyRoutine = (PVOID)((PUCHAR)pAddress + sizeof(LONG) + lOffset);
return pPspLoadImageNotifyRoutine;
}
VOID UnDriver(PDRIVER_OBJECT Driver)
{
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint("hello lyshark.com \n");
PVOID pPspLoadImageNotifyRoutineAddress = NULL;
RTL_OSVERSIONINFOW osInfo = { 0 };
UCHAR pSpecialData[50] = { 0 };
ULONG ulSpecialDataSize = 0;
// 獲取系統版本資訊, 判斷系統版本
RtlGetVersion(&osInfo);
if (10 == osInfo.dwMajorVersion)
{
// 48 8d 0d 88 e8 db ff
// 查詢指令 lea rcx,[nt!PspLoadImageNotifyRoutine (fffff804`44313ce0)]
/*
nt!PsSetLoadImageNotifyRoutineEx+0x41:
fffff801`80748a81 488d0dd8d3dbff lea rcx,[nt!PspLoadImageNotifyRoutine (fffff801`80505e60)]
fffff801`80748a88 4533c0 xor r8d,r8d
fffff801`80748a8b 488d0cd9 lea rcx,[rcx+rbx*8]
fffff801`80748a8f 488bd7 mov rdx,rdi
fffff801`80748a92 e80584a3ff call nt!ExCompareExchangeCallBack (fffff801`80180e9c)
fffff801`80748a97 84c0 test al,al
fffff801`80748a99 0f849f000000 je nt!PsSetLoadImageNotifyRoutineEx+0xfe (fffff801`80748b3e) Branch
*/
pSpecialData[0] = 0x48;
pSpecialData[1] = 0x8D;
pSpecialData[2] = 0x0D;
ulSpecialDataSize = 3;
}
// 根據特徵碼獲取地址 獲取 PspLoadImageNotifyRoutine 陣列地址
pPspLoadImageNotifyRoutineAddress = SearchPspLoadImageNotifyRoutine(pSpecialData, ulSpecialDataSize);
DbgPrint("[LyShark] PspLoadImageNotifyRoutine = 0x%p \n", pPspLoadImageNotifyRoutineAddress);
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
將這個驅動拖入到虛擬機器器中並執行,輸出結果如下:
有了陣列地址接下來就是要對陣列進行解密,如何解密?
pPspLoadImageNotifyRoutineAddress + sizeof(PVOID) * i
此處的i也就是下標。pNotifyRoutineAddress & 0xfffffffffffffff8
進行與運算。*(PVOID *)pNotifyRoutineAddress
取出裡面的引數。增加解密程式碼以後,這段程式的完整程式碼也就可以被寫出來了,如下所示。
// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]
#include <ntddk.h>
#include <windef.h>
// 指定記憶體區域的特徵碼掃描
PVOID SearchMemory(PVOID pStartAddress, PVOID pEndAddress, PUCHAR pMemoryData, ULONG ulMemoryDataSize)
{
PVOID pAddress = NULL;
PUCHAR i = NULL;
ULONG m = 0;
// 掃描記憶體
for (i = (PUCHAR)pStartAddress; i < (PUCHAR)pEndAddress; i++)
{
// 判斷特徵碼
for (m = 0; m < ulMemoryDataSize; m++)
{
if (*(PUCHAR)(i + m) != pMemoryData[m])
{
break;
}
}
// 判斷是否找到符合特徵碼的地址
if (m >= ulMemoryDataSize)
{
// 找到特徵碼位置, 獲取緊接著特徵碼的下一地址
pAddress = (PVOID)(i + ulMemoryDataSize);
break;
}
}
return pAddress;
}
// 根據特徵碼獲取 PspLoadImageNotifyRoutine 陣列地址
PVOID SearchPspLoadImageNotifyRoutine(PUCHAR pSpecialData, ULONG ulSpecialDataSize)
{
UNICODE_STRING ustrFuncName;
PVOID pAddress = NULL;
LONG lOffset = 0;
PVOID pPsSetLoadImageNotifyRoutine = NULL;
PVOID pPspLoadImageNotifyRoutine = NULL;
// 先獲取 PsSetLoadImageNotifyRoutineEx 函數地址
RtlInitUnicodeString(&ustrFuncName, L"PsSetLoadImageNotifyRoutineEx");
pPsSetLoadImageNotifyRoutine = MmGetSystemRoutineAddress(&ustrFuncName);
if (NULL == pPsSetLoadImageNotifyRoutine)
{
return pPspLoadImageNotifyRoutine;
}
// 查詢 PspLoadImageNotifyRoutine 函數地址
pAddress = SearchMemory(pPsSetLoadImageNotifyRoutine, (PVOID)((PUCHAR)pPsSetLoadImageNotifyRoutine + 0xFF), pSpecialData, ulSpecialDataSize);
if (NULL == pAddress)
{
return pPspLoadImageNotifyRoutine;
}
// 先獲取偏移, 再計算地址
lOffset = *(PLONG)pAddress;
pPspLoadImageNotifyRoutine = (PVOID)((PUCHAR)pAddress + sizeof(LONG) + lOffset);
return pPspLoadImageNotifyRoutine;
}
VOID UnDriver(PDRIVER_OBJECT Driver)
{
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint("hello lyshark.com \n");
PVOID pPspLoadImageNotifyRoutineAddress = NULL;
RTL_OSVERSIONINFOW osInfo = { 0 };
UCHAR pSpecialData[50] = { 0 };
ULONG ulSpecialDataSize = 0;
// 獲取系統版本資訊, 判斷系統版本
RtlGetVersion(&osInfo);
if (10 == osInfo.dwMajorVersion)
{
// 48 8d 0d 88 e8 db ff
// 查詢指令 lea rcx,[nt!PspLoadImageNotifyRoutine (fffff804`44313ce0)]
/*
nt!PsSetLoadImageNotifyRoutineEx+0x41:
fffff801`80748a81 488d0dd8d3dbff lea rcx,[nt!PspLoadImageNotifyRoutine (fffff801`80505e60)]
fffff801`80748a88 4533c0 xor r8d,r8d
fffff801`80748a8b 488d0cd9 lea rcx,[rcx+rbx*8]
fffff801`80748a8f 488bd7 mov rdx,rdi
fffff801`80748a92 e80584a3ff call nt!ExCompareExchangeCallBack (fffff801`80180e9c)
fffff801`80748a97 84c0 test al,al
fffff801`80748a99 0f849f000000 je nt!PsSetLoadImageNotifyRoutineEx+0xfe (fffff801`80748b3e) Branch
*/
pSpecialData[0] = 0x48;
pSpecialData[1] = 0x8D;
pSpecialData[2] = 0x0D;
ulSpecialDataSize = 3;
}
// 根據特徵碼獲取地址 獲取 PspLoadImageNotifyRoutine 陣列地址
pPspLoadImageNotifyRoutineAddress = SearchPspLoadImageNotifyRoutine(pSpecialData, ulSpecialDataSize);
DbgPrint("[LyShark] PspLoadImageNotifyRoutine = 0x%p \n", pPspLoadImageNotifyRoutineAddress);
// 遍歷回撥
ULONG i = 0;
PVOID pNotifyRoutineAddress = NULL;
// 獲取 PspLoadImageNotifyRoutine 陣列地址
if (NULL == pPspLoadImageNotifyRoutineAddress)
{
return FALSE;
}
// 獲取回撥地址並解密
for (i = 0; i < 64; i++)
{
pNotifyRoutineAddress = *(PVOID *)((PUCHAR)pPspLoadImageNotifyRoutineAddress + sizeof(PVOID) * i);
pNotifyRoutineAddress = (PVOID)((ULONG64)pNotifyRoutineAddress & 0xfffffffffffffff8);
if (MmIsAddressValid(pNotifyRoutineAddress))
{
pNotifyRoutineAddress = *(PVOID *)pNotifyRoutineAddress;
DbgPrint("[LyShark] 序號: %d | 回撥地址: 0x%p \n", i, pNotifyRoutineAddress);
}
}
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
執行這段完整的程式程式碼,輸出如下效果:
目前系統中只有兩個回撥,所以列舉出來的只有兩條,開啟ARK驗證一下會發現完全正確,忽略pyark
這是後期開啟的。