在筆者上一篇文章《驅動開發:內核取應用層模塊基地址》中簡單為大家介紹了如何通過遍歷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 許可協議,轉載請注明出處!
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/556164.html
標籤:領域驅動設計
下一篇:返回列表
