在筆者上一篇文章`《驅動開發:內核取應用層模塊基地址》`中簡單為大家介紹瞭如何通過遍歷`PLIST_ENTRY32`鏈表的方式獲取到`32位`應用程式中特定模塊的基地址,由於是入門系列所以並沒有封裝實現太過於通用的獲取函數,本章將繼續延申這個話題,並依次實現通用版`GetUserModuleBase... ...
在筆者上一篇文章《驅動開發:內核取應用層模塊基地址》
中簡單為大家介紹瞭如何通過遍歷PLIST_ENTRY32
鏈表的方式獲取到32位
應用程式中特定模塊的基地址,由於是入門系列所以並沒有封裝實現太過於通用的獲取函數,本章將繼續延申這個話題,並依次實現通用版GetUserModuleBaseAddress()
取遠程進程中指定模塊的基址和GetModuleExportAddress()
取遠程進程中特定模塊中的函數地址,此類功能也是各類安全工具中常用的代碼片段。
首先封裝一個lyshark.h
頭文件,此類頭文件中的定義都是微軟官方定義好的規範,如果您想獲取該結構的詳細說明文檔請參閱微軟官方,此處不做過多的介紹。
// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]
#include <ntifs.h>
#include <ntimage.h>
#include <ntstrsafe.h>
// 導出未導出函數
NTKERNELAPI PPEB NTAPI PsGetProcessPeb(IN PEPROCESS Process);
NTKERNELAPI PVOID NTAPI PsGetProcessWow64Process(IN PEPROCESS Process);
typedef struct _PEB32
{
UCHAR InheritedAddressSpace;
UCHAR ReadImageFileExecOptions;
UCHAR BeingDebugged;
UCHAR BitField;
ULONG Mutant;
ULONG ImageBaseAddress;
ULONG Ldr;
ULONG ProcessParameters;
ULONG SubSystemData;
ULONG ProcessHeap;
ULONG FastPebLock;
ULONG AtlThunkSListPtr;
ULONG IFEOKey;
ULONG CrossProcessFlags;
ULONG UserSharedInfoPtr;
ULONG SystemReserved;
ULONG AtlThunkSListPtr32;
ULONG ApiSetMap;
} PEB32, *PPEB32;
typedef struct _PEB_LDR_DATA
{
ULONG Length;
UCHAR Initialized;
PVOID SsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA;
typedef struct _PEB
{
UCHAR InheritedAddressSpace;
UCHAR ReadImageFileExecOptions;
UCHAR BeingDebugged;
UCHAR BitField;
PVOID Mutant;
PVOID ImageBaseAddress;
PPEB_LDR_DATA Ldr;
PVOID ProcessParameters;
PVOID SubSystemData;
PVOID ProcessHeap;
PVOID FastPebLock;
PVOID AtlThunkSListPtr;
PVOID IFEOKey;
PVOID CrossProcessFlags;
PVOID KernelCallbackTable;
ULONG SystemReserved;
ULONG AtlThunkSListPtr32;
PVOID ApiSetMap;
} PEB, *PPEB;
typedef struct _PEB_LDR_DATA32
{
ULONG Length;
UCHAR Initialized;
ULONG SsHandle;
LIST_ENTRY32 InLoadOrderModuleList;
LIST_ENTRY32 InMemoryOrderModuleList;
LIST_ENTRY32 InInitializationOrderModuleList;
} PEB_LDR_DATA32, *PPEB_LDR_DATA32;
typedef struct _LDR_DATA_TABLE_ENTRY32
{
LIST_ENTRY32 InLoadOrderLinks;
LIST_ENTRY32 InMemoryOrderLinks;
LIST_ENTRY32 InInitializationOrderLinks;
ULONG DllBase;
ULONG EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING32 FullDllName;
UNICODE_STRING32 BaseDllName;
ULONG Flags;
USHORT LoadCount;
USHORT TlsIndex;
LIST_ENTRY32 HashLinks;
ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY32, *PLDR_DATA_TABLE_ENTRY32;
typedef struct _LDR_DATA_TABLE_ENTRY
{
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
USHORT LoadCount;
USHORT TlsIndex;
LIST_ENTRY HashLinks;
ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
GetUserModuleBaseAddress(): 實現取進程中模塊基址,該功能在《驅動開發:內核取應用層模塊基地址》
中詳細介紹過原理,這段代碼核心原理如下所示,此處最需要註意的是如果是32位進程
則我們需要得到PPEB32 Peb32
結構體,該結構體通常可以直接使用PsGetProcessWow64Process()
這個內核函數獲取到,而如果是64位進程
則需要將尋找PEB的函數替換為PsGetProcessPeb()
,其他的枚舉細節與上一篇文章中的方法一致。
// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]
#include <ntifs.h>
#include <windef.h>
#include "lyshark.h"
// 獲取特定進程內特定模塊的基址
PVOID GetUserModuleBaseAddress(IN PEPROCESS EProcess, IN PUNICODE_STRING ModuleName, IN BOOLEAN IsWow64)
{
if (EProcess == NULL)
return NULL;
__try
{
// 設置延遲時間為250毫秒
LARGE_INTEGER Time = { 0 };
Time.QuadPart = -250ll * 10 * 1000;
// 如果是32位則執行如下代碼
if (IsWow64)
{
// 得到PEB進程信息
PPEB32 Peb32 = (PPEB32)PsGetProcessWow64Process(EProcess);
if (Peb32 == NULL)
{
return NULL;
}
// 延遲載入等待時間
for (INT i = 0; !Peb32->Ldr && i < 10; i++)
{
KeDelayExecutionThread(KernelMode, TRUE, &Time);
}
// 沒有PEB載入超時
if (!Peb32->Ldr)
{
return NULL;
}
// 搜索模塊 InLoadOrderModuleList
for (PLIST_ENTRY32 ListEntry = (PLIST_ENTRY32)((PPEB_LDR_DATA32)Peb32->Ldr)->InLoadOrderModuleList.Flink; ListEntry != &((PPEB_LDR_DATA32)Peb32->Ldr)->InLoadOrderModuleList; ListEntry = (PLIST_ENTRY32)ListEntry->Flink)
{
UNICODE_STRING UnicodeString;
PLDR_DATA_TABLE_ENTRY32 LdrDataTableEntry32 = CONTAINING_RECORD(ListEntry, LDR_DATA_TABLE_ENTRY32, InLoadOrderLinks);
RtlUnicodeStringInit(&UnicodeString, (PWCH)LdrDataTableEntry32->BaseDllName.Buffer);
// 找到了返回模塊基址
if (RtlCompareUnicodeString(&UnicodeString, ModuleName, TRUE) == 0)
{
return (PVOID)LdrDataTableEntry32->DllBase;
}
}
}
// 如果是64位則執行如下代碼
else
{
// 同理,先找64位PEB
PPEB Peb = PsGetProcessPeb(EProcess);
if (!Peb)
{
return NULL;
}
// 延遲載入
for (INT i = 0; !Peb->Ldr && i < 10; i++)
{
KeDelayExecutionThread(KernelMode, TRUE, &Time);
}
// 找不到PEB直接返回
if (!Peb->Ldr)
{
return NULL;
}
// 遍歷鏈表
for (PLIST_ENTRY ListEntry = Peb->Ldr->InLoadOrderModuleList.Flink; ListEntry != &Peb->Ldr->InLoadOrderModuleList; ListEntry = ListEntry->Flink)
{
// 將特定鏈表轉換為PLDR_DATA_TABLE_ENTRY格式
PLDR_DATA_TABLE_ENTRY LdrDataTableEntry = CONTAINING_RECORD(ListEntry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
// 找到了則返回地址
if (RtlCompareUnicodeString(&LdrDataTableEntry->BaseDllName, ModuleName, TRUE) == 0)
{
return LdrDataTableEntry->DllBase;
}
}
}
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
return NULL;
}
return NULL;
}
那麼該函數該如何調用傳遞參數呢,如下代碼是DriverEntry
入口處的調用方法,首先要想得到特定進程的特定模塊地址則第一步就是需要PsLookupProcessByProcessId
找到模塊的EProcess
結構,接著通過PsGetProcessWow64Process
得到當前被操作進程是32位還是64位,通過調用KeStackAttachProcess
附加到進程記憶體中,然後調用GetUserModuleBaseAddress
並傳入需要獲取模塊的名字得到數據後返回給NtdllAddress
變數,最後調用KeUnstackDetachProcess
取消附加即可。
// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
HANDLE ProcessID = (HANDLE)7924;
PEPROCESS EProcess = NULL;
NTSTATUS Status = STATUS_SUCCESS;
KAPC_STATE ApcState;
DbgPrint("Hello LyShark.com \n");
// 根據PID得到進程EProcess結構
Status = PsLookupProcessByProcessId(ProcessID, &EProcess);
if (Status != STATUS_SUCCESS)
{
DbgPrint("獲取EProcessID失敗 \n");
return Status;
}
// 判斷目標進程是32位還是64位
BOOLEAN IsWow64 = (PsGetProcessWow64Process(EProcess) != NULL) ? TRUE : FALSE;
// 驗證地址是否可讀
if (!MmIsAddressValid(EProcess))
{
DbgPrint("地址不可讀 \n");
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
// 將當前線程連接到目標進程的地址空間(附加進程)
KeStackAttachProcess((PRKPROCESS)EProcess, &ApcState);
__try
{
UNICODE_STRING NtdllUnicodeString = { 0 };
PVOID NtdllAddress = NULL;
// 得到進程內ntdll.dll模塊基地址
RtlInitUnicodeString(&NtdllUnicodeString, L"Ntdll.dll");
NtdllAddress = GetUserModuleBaseAddress(EProcess, &NtdllUnicodeString, IsWow64);
if (!NtdllAddress)
{
DbgPrint("沒有找到基址 \n");
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
DbgPrint("[*] 模塊ntdll.dll基址: %p \n", NtdllAddress);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
}
// 取消附加
KeUnstackDetachProcess(&ApcState);
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
替換DriverEntry
入口函數處的ProcessID
並替換為當前需要獲取的應用層進程PID,運行驅動程式即可得到該進程內Ntdll.dll
的模塊基址,輸出效果如下;
GetModuleExportAddress(): 實現獲取特定模塊中特定函數的基地址,通常我們通過GetUserModuleBaseAddress()
可得到進程內特定模塊的基址,然後則可繼續通過GetModuleExportAddress()
獲取到該模塊內特定導出函數的記憶體地址,至於獲取導出表中特定函數的地址則可通過如下方式迴圈遍歷導出表函數獲取。
// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]
// 獲取特定模塊下的導出函數地址
PVOID GetModuleExportAddress(IN PVOID ModuleBase, IN PCCHAR FunctionName, IN PEPROCESS EProcess)
{
PIMAGE_DOS_HEADER ImageDosHeader = (PIMAGE_DOS_HEADER)ModuleBase;
PIMAGE_NT_HEADERS32 ImageNtHeaders32 = NULL;
PIMAGE_NT_HEADERS64 ImageNtHeaders64 = NULL;
PIMAGE_EXPORT_DIRECTORY ImageExportDirectory = NULL;
ULONG ExportDirectorySize = 0;
ULONG_PTR FunctionAddress = 0;
// 為空則返回
if (ModuleBase == NULL)
{
return NULL;
}
// 是不是PE文件
if (ImageDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
{
return NULL;
}
// 獲取NT頭
ImageNtHeaders32 = (PIMAGE_NT_HEADERS32)((PUCHAR)ModuleBase + ImageDosHeader->e_lfanew);
ImageNtHeaders64 = (PIMAGE_NT_HEADERS64)((PUCHAR)ModuleBase + ImageDosHeader->e_lfanew);
// 是64位則執行
if (ImageNtHeaders64->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC)
{
ImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(ImageNtHeaders64->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress + (ULONG_PTR)ModuleBase);
ExportDirectorySize = ImageNtHeaders64->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;
}
// 是32位則執行
else
{
ImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(ImageNtHeaders32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress + (ULONG_PTR)ModuleBase);
ExportDirectorySize = ImageNtHeaders32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;
}
// 得到導出表地址偏移和名字
PUSHORT pAddressOfOrds = (PUSHORT)(ImageExportDirectory->AddressOfNameOrdinals + (ULONG_PTR)ModuleBase);
PULONG pAddressOfNames = (PULONG)(ImageExportDirectory->AddressOfNames + (ULONG_PTR)ModuleBase);
PULONG pAddressOfFuncs = (PULONG)(ImageExportDirectory->AddressOfFunctions + (ULONG_PTR)ModuleBase);
// 迴圈搜索導出表
for (ULONG i = 0; i < ImageExportDirectory->NumberOfFunctions; ++i)
{
USHORT OrdIndex = 0xFFFF;
PCHAR pName = NULL;
// 搜索導出表下標索引
if ((ULONG_PTR)FunctionName <= 0xFFFF)
{
OrdIndex = (USHORT)i;
}
// 搜索導出表名字
else if ((ULONG_PTR)FunctionName > 0xFFFF && i < ImageExportDirectory->NumberOfNames)
{
pName = (PCHAR)(pAddressOfNames[i] + (ULONG_PTR)ModuleBase);
OrdIndex = pAddressOfOrds[i];
}
else
{
return NULL;
}
// 找到設置返回值並跳出
if (((ULONG_PTR)FunctionName <= 0xFFFF && (USHORT)((ULONG_PTR)FunctionName) == OrdIndex + ImageExportDirectory->Base) || ((ULONG_PTR)FunctionName > 0xFFFF && strcmp(pName, FunctionName) == 0))
{
FunctionAddress = pAddressOfFuncs[OrdIndex] + (ULONG_PTR)ModuleBase;
break;
}
}
return (PVOID)FunctionAddress;
}
如何調用此方法,首先將ProcessID
設置為需要讀取的進程PID,然後將上圖中所輸出的0x00007FF9553C0000
賦值給BaseAddress
接著調用GetModuleExportAddress()
並傳入BaseAddress
模塊基址,需要讀取的LdrLoadDll
函數名,以及當前進程的EProcess
結構。
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
HANDLE ProcessID = (HANDLE)4144;
PEPROCESS EProcess = NULL;
NTSTATUS Status = STATUS_SUCCESS;
// 根據PID得到進程EProcess結構
Status = PsLookupProcessByProcessId(ProcessID, &EProcess);
if (Status != STATUS_SUCCESS)
{
DbgPrint("獲取EProcessID失敗 \n");
return Status;
}
PVOID BaseAddress = (PVOID)0x00007FF9553C0000;
PVOID RefAddress = 0;
// 傳入Ntdll.dll基址 + 函數名 得到該函數地址
RefAddress = GetModuleExportAddress(BaseAddress, "LdrLoadDll", EProcess);
DbgPrint("[*] 函數地址: %p \n", RefAddress);
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
運行這段程式,即可輸出如下信息,此時也就得到了x64.exe
進程內ntdll.dll
模塊裡面的LdrLoadDll
函數的記憶體地址,如下所示;
文章出處:https://www.cnblogs.com/LyShark/p/17152894.html
本博客所有文章除特別聲明外,均採用 BY-NC-SA 許可協議。轉載請註明出處!