在筆者上一篇文章《核心RIP劫持實現DLL注入》
介紹了通過劫持RIP指標控制程式執行流實現插入DLL的目的,本章將繼續探索全新的注入方式,通過NtCreateThreadEx
這個核心函數實現注入DLL的目的,需要注意的是該函數在微軟系統中未被匯出使用時需要首先得到該函數的入口地址,NtCreateThreadEx
函數最終會呼叫ZwCreateThread
,本章在尋找函數的方式上有所不同,前一章通過記憶體定位的方法得到所需地址,本章則是通過解析匯出表實現。
核心匯出表遠端執行緒是一種實現DLL注入的常見技術之一。通過使用該技術,注入程式碼可以利用目標程序的匯出表中已有的函數來載入DLL,並在遠端執行緒中執行DLL程式碼,從而實現DLL注入。
具體而言,核心匯出表遠端執行緒實現DLL注入的過程包括以下步驟:
需要注意的是,核心匯出表遠端執行緒作為一種核心級別的注入技術,可能會被安全軟體或作業系統檢測到,並對其進行防禦。因此,在使用這種技術進行DLL注入時,需要謹慎使用,並採取必要的安全措施,以防止被檢測和防禦。
在核心模式中實現這一過程的具體方法可分為如下步驟;
GetKeServiceDescriptorTable64
獲取到SSDT表基址KeStackAttachProcess
附加到遠端程序內GetUserModuleAddress
獲取到Ntdll.dll
模組記憶體基址GetModuleExportAddress
獲取到LdrLoadDll
函數的記憶體地址GetNative32Code
生成拉起特定DLL的ShellCode
片段NtCreateThreadEx
將ShellCode
執行起來,並自動載入DLLKeUnstackDetachProcess
取消附加遠端程序,並做好最後的清理工作首先需要定義一個標準標頭檔案,並將其命名為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>
#define THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER 0x00000004
// -----------------------------------------------------------------------------------
// 宣告未匯出函數
// -----------------------------------------------------------------------------------
NTKERNELAPI PPEB NTAPI PsGetProcessPeb(IN PEPROCESS Process);
NTKERNELAPI UCHAR* PsGetProcessImageFileName(IN PEPROCESS Process);
NTKERNELAPI PVOID NTAPI PsGetProcessWow64Process(IN PEPROCESS Process);
NTKERNELAPI HANDLE PsGetProcessInheritedFromUniqueProcessId(IN PEPROCESS Process);
NTSYSAPI NTSTATUS NTAPI ZwQueryInformationThread(
IN HANDLE ThreadHandle,
IN THREADINFOCLASS ThreadInformationClass,
OUT PVOID ThreadInformation,
IN ULONG ThreadInformationLength,
OUT PULONG ReturnLength OPTIONAL
);
typedef NTSTATUS(NTAPI* LPFN_NTCREATETHREADEX)(
OUT PHANDLE ThreadHandle,
IN ACCESS_MASK DesiredAccess,
IN PVOID ObjectAttributes,
IN HANDLE ProcessHandle,
IN PVOID StartAddress,
IN PVOID Parameter,
IN ULONG Flags,
IN SIZE_T StackZeroBits,
IN SIZE_T SizeOfStackCommit,
IN SIZE_T SizeOfStackReserve,
OUT PVOID ByteBuffer
);
// -----------------------------------------------------------------------------------
// 結構體宣告
// -----------------------------------------------------------------------------------
// SSDT表結構
typedef struct _SYSTEM_SERVICE_TABLE
{
PVOID ServiceTableBase;
PVOID ServiceCounterTableBase;
ULONGLONG NumberOfServices;
PVOID ParamTableBase;
} SYSTEM_SERVICE_TABLE, *PSYSTEM_SERVICE_TABLE;
PSYSTEM_SERVICE_TABLE KeServiceDescriptorTable;
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 _PEB_LDR_DATA
{
ULONG Length;
UCHAR Initialized;
PVOID SsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA;
// PEB32/PEB64
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
{
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 _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;
typedef struct _THREAD_BASIC_INFORMATION
{
NTSTATUS ExitStatus;
PVOID TebBaseAddress;
CLIENT_ID ClientId;
ULONG_PTR AffinityMask;
LONG Priority;
LONG BasePriority;
} THREAD_BASIC_INFORMATION, *PTHREAD_BASIC_INFORMATION;
typedef struct _NT_PROC_THREAD_ATTRIBUTE_ENTRY
{
ULONG Attribute; // PROC_THREAD_ATTRIBUTE_XXX
SIZE_T Size;
ULONG_PTR Value;
ULONG Unknown;
} NT_PROC_THREAD_ATTRIBUTE_ENTRY, *NT_PPROC_THREAD_ATTRIBUTE_ENTRY;
typedef struct _NT_PROC_THREAD_ATTRIBUTE_LIST
{
ULONG Length;
NT_PROC_THREAD_ATTRIBUTE_ENTRY Entry[1];
} NT_PROC_THREAD_ATTRIBUTE_LIST, *PNT_PROC_THREAD_ATTRIBUTE_LIST;
// 注入ShellCode結構
typedef struct _INJECT_BUFFER
{
UCHAR Code[0x200];
union
{
UNICODE_STRING Path64;
UNICODE_STRING32 Path32;
};
wchar_t Buffer[488];
PVOID ModuleHandle;
ULONG Complete;
NTSTATUS Status;
} INJECT_BUFFER, *PINJECT_BUFFER;
// -----------------------------------------------------------------------------------
// 一些開發中的通用函數封裝,可任意拷貝使用
// -----------------------------------------------------------------------------------
// 傳入函數名獲取SSDT匯出表RVA
// 引數1:傳入函數名稱
ULONG GetSSDTRVA(UCHAR *function_name)
{
NTSTATUS Status;
HANDLE FileHandle;
IO_STATUS_BLOCK ioStatus;
FILE_STANDARD_INFORMATION FileInformation;
// 設定NTDLL路徑
UNICODE_STRING uniFileName;
RtlInitUnicodeString(&uniFileName, L"\\SystemRoot\\system32\\ntoskrnl.exe");
// 初始化開啟檔案的屬性
OBJECT_ATTRIBUTES objectAttributes;
InitializeObjectAttributes(&objectAttributes, &uniFileName, OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, NULL, NULL);
// 開啟檔案
Status = IoCreateFile(&FileHandle, FILE_READ_ATTRIBUTES | SYNCHRONIZE, &objectAttributes, &ioStatus, 0, FILE_READ_ATTRIBUTES, FILE_SHARE_READ, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0, CreateFileTypeNone, NULL, IO_NO_PARAMETER_CHECKING);
if (!NT_SUCCESS(Status))
{
return 0;
}
// 獲取檔案資訊
Status = ZwQueryInformationFile(FileHandle, &ioStatus, &FileInformation, sizeof(FILE_STANDARD_INFORMATION), FileStandardInformation);
if (!NT_SUCCESS(Status))
{
ZwClose(FileHandle);
return 0;
}
// 判斷檔案大小是否過大
if (FileInformation.EndOfFile.HighPart != 0)
{
ZwClose(FileHandle);
return 0;
}
// 取檔案大小
ULONG uFileSize = FileInformation.EndOfFile.LowPart;
// 分配記憶體
PVOID pBuffer = ExAllocatePoolWithTag(PagedPool, uFileSize + 0x100, (ULONG)"PGu");
if (pBuffer == NULL)
{
ZwClose(FileHandle);
return 0;
}
// 從頭開始讀取檔案
LARGE_INTEGER byteOffset;
byteOffset.LowPart = 0;
byteOffset.HighPart = 0;
Status = ZwReadFile(FileHandle, NULL, NULL, NULL, &ioStatus, pBuffer, uFileSize, &byteOffset, NULL);
if (!NT_SUCCESS(Status))
{
ZwClose(FileHandle);
return 0;
}
// 取出匯出表
PIMAGE_DOS_HEADER pDosHeader;
PIMAGE_NT_HEADERS pNtHeaders;
PIMAGE_SECTION_HEADER pSectionHeader;
ULONGLONG FileOffset;
PIMAGE_EXPORT_DIRECTORY pExportDirectory;
// DLL記憶體資料轉成DOS頭結構
pDosHeader = (PIMAGE_DOS_HEADER)pBuffer;
// 取出PE頭結構
pNtHeaders = (PIMAGE_NT_HEADERS)((ULONGLONG)pBuffer + pDosHeader->e_lfanew);
// 判斷PE頭匯出表表是否為空
if (pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress == 0)
{
return 0;
}
// 取出匯出表偏移
FileOffset = pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
// 取出節頭結構
pSectionHeader = (PIMAGE_SECTION_HEADER)((ULONGLONG)pNtHeaders + sizeof(IMAGE_NT_HEADERS));
PIMAGE_SECTION_HEADER pOldSectionHeader = pSectionHeader;
// 遍歷節結構進行地址運算
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
{
if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
{
FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
}
// 匯出表地址
pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((ULONGLONG)pBuffer + FileOffset);
// 取出匯出表函數地址
PULONG AddressOfFunctions;
FileOffset = pExportDirectory->AddressOfFunctions;
// 遍歷節結構進行地址運算
pSectionHeader = pOldSectionHeader;
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
{
if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
{
FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
}
AddressOfFunctions = (PULONG)((ULONGLONG)pBuffer + FileOffset);
// 取出匯出表函數名字
PUSHORT AddressOfNameOrdinals;
FileOffset = pExportDirectory->AddressOfNameOrdinals;
// 遍歷節結構進行地址運算
pSectionHeader = pOldSectionHeader;
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
{
if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
{
FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
}
AddressOfNameOrdinals = (PUSHORT)((ULONGLONG)pBuffer + FileOffset);
//取出匯出表函數序號
PULONG AddressOfNames;
FileOffset = pExportDirectory->AddressOfNames;
//遍歷節結構進行地址運算
pSectionHeader = pOldSectionHeader;
// 迴圈所有節
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
{
// 尋找符合條件的節
if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
{
// 得到檔案偏移
FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
}
AddressOfNames = (PULONG)((ULONGLONG)pBuffer + FileOffset);
//DbgPrint("\n AddressOfFunctions %llX AddressOfNameOrdinals %llX AddressOfNames %llX \n", (ULONGLONG)AddressOfFunctions- (ULONGLONG)pBuffer, (ULONGLONG)AddressOfNameOrdinals- (ULONGLONG)pBuffer, (ULONGLONG)AddressOfNames- (ULONGLONG)pBuffer);
//DbgPrint("\n AddressOfFunctions %llX AddressOfNameOrdinals %llX AddressOfNames %llX \n", pExportDirectory->AddressOfFunctions, pExportDirectory->AddressOfNameOrdinals, pExportDirectory->AddressOfNames);
// 開始分析匯出表
ULONG uOffset;
LPSTR FunName;
ULONG uAddressOfNames;
ULONG TargetOff = 0;
// 迴圈匯出表
for (ULONG uIndex = 0; uIndex < pExportDirectory->NumberOfNames; uIndex++, AddressOfNames++, AddressOfNameOrdinals++)
{
uAddressOfNames = *AddressOfNames;
pSectionHeader = pOldSectionHeader;
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
{
// 函數地址在某個範圍內
if (pSectionHeader->VirtualAddress <= uAddressOfNames && uAddressOfNames <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
{
uOffset = uAddressOfNames - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
}
// 得到函數名
FunName = (LPSTR)((ULONGLONG)pBuffer + uOffset);
// 判斷是否符合要求
if (!_stricmp((const char *)function_name, FunName))
{
// 返回函數地址
TargetOff = (ULONG)AddressOfFunctions[*AddressOfNameOrdinals];
DbgPrint("索引 [ %p ] 函數名 [ %s ] 相對RVA [ %p ] \n", *AddressOfNameOrdinals, FunName, TargetOff);
}
}
ExFreePoolWithTag(pBuffer, (ULONG)"PGu");
ZwClose(FileHandle);
return TargetOff;
}
// 傳入函數名 獲取該函數所在模組下標
ULONG GetIndexByName(UCHAR *function_name)
{
NTSTATUS Status;
HANDLE FileHandle;
IO_STATUS_BLOCK ioStatus;
FILE_STANDARD_INFORMATION FileInformation;
// 設定NTDLL路徑
UNICODE_STRING uniFileName;
RtlInitUnicodeString(&uniFileName, L"\\SystemRoot\\system32\\ntdll.dll");
// 初始化開啟檔案的屬性
OBJECT_ATTRIBUTES objectAttributes;
InitializeObjectAttributes(&objectAttributes, &uniFileName, OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, NULL, NULL);
// 開啟檔案
Status = IoCreateFile(&FileHandle, FILE_READ_ATTRIBUTES | SYNCHRONIZE, &objectAttributes, &ioStatus, 0, FILE_READ_ATTRIBUTES, FILE_SHARE_READ, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0, CreateFileTypeNone, NULL, IO_NO_PARAMETER_CHECKING);
if (!NT_SUCCESS(Status))
{
return 0;
}
// 獲取檔案資訊
Status = ZwQueryInformationFile(FileHandle, &ioStatus, &FileInformation, sizeof(FILE_STANDARD_INFORMATION), FileStandardInformation);
if (!NT_SUCCESS(Status))
{
ZwClose(FileHandle);
return 0;
}
// 判斷檔案大小是否過大
if (FileInformation.EndOfFile.HighPart != 0)
{
ZwClose(FileHandle);
return 0;
}
// 取檔案大小
ULONG uFileSize = FileInformation.EndOfFile.LowPart;
// 分配記憶體
PVOID pBuffer = ExAllocatePoolWithTag(PagedPool, uFileSize + 0x100, (ULONG)"Ntdl");
if (pBuffer == NULL)
{
ZwClose(FileHandle);
return 0;
}
// 從頭開始讀取檔案
LARGE_INTEGER byteOffset;
byteOffset.LowPart = 0;
byteOffset.HighPart = 0;
Status = ZwReadFile(FileHandle, NULL, NULL, NULL, &ioStatus, pBuffer, uFileSize, &byteOffset, NULL);
if (!NT_SUCCESS(Status))
{
ZwClose(FileHandle);
return 0;
}
// 取出匯出表
PIMAGE_DOS_HEADER pDosHeader;
PIMAGE_NT_HEADERS pNtHeaders;
PIMAGE_SECTION_HEADER pSectionHeader;
ULONGLONG FileOffset;
PIMAGE_EXPORT_DIRECTORY pExportDirectory;
// DLL記憶體資料轉成DOS頭結構
pDosHeader = (PIMAGE_DOS_HEADER)pBuffer;
// 取出PE頭結構
pNtHeaders = (PIMAGE_NT_HEADERS)((ULONGLONG)pBuffer + pDosHeader->e_lfanew);
// 判斷PE頭匯出表表是否為空
if (pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress == 0)
{
return 0;
}
// 取出匯出表偏移
FileOffset = pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
// 取出節頭結構
pSectionHeader = (PIMAGE_SECTION_HEADER)((ULONGLONG)pNtHeaders + sizeof(IMAGE_NT_HEADERS));
PIMAGE_SECTION_HEADER pOldSectionHeader = pSectionHeader;
// 遍歷節結構進行地址運算
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
{
if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
{
FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
}
// 匯出表地址
pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((ULONGLONG)pBuffer + FileOffset);
// 取出匯出表函數地址
PULONG AddressOfFunctions;
FileOffset = pExportDirectory->AddressOfFunctions;
// 遍歷節結構進行地址運算
pSectionHeader = pOldSectionHeader;
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
{
if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
{
FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
}
// 此處需要注意foa和rva轉換過程
AddressOfFunctions = (PULONG)((ULONGLONG)pBuffer + FileOffset);
// 取出匯出表函數名字
PUSHORT AddressOfNameOrdinals;
FileOffset = pExportDirectory->AddressOfNameOrdinals;
// 遍歷節結構進行地址運算
pSectionHeader = pOldSectionHeader;
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
{
if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
{
FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
}
// 此處需要注意foa和rva轉換過程
AddressOfNameOrdinals = (PUSHORT)((ULONGLONG)pBuffer + FileOffset);
// 取出匯出表函數序號
PULONG AddressOfNames;
FileOffset = pExportDirectory->AddressOfNames;
// 遍歷節結構進行地址運算
pSectionHeader = pOldSectionHeader;
for (UINT16 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
{
if (pSectionHeader->VirtualAddress <= FileOffset && FileOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
{
FileOffset = FileOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
}
// 此處需要注意foa和rva轉換過程
AddressOfNames = (PULONG)((ULONGLONG)pBuffer + FileOffset);
// 分析匯出表
ULONG uNameOffset;
ULONG uOffset;
LPSTR FunName;
PVOID pFuncAddr;
ULONG uServerIndex;
ULONG uAddressOfNames;
for (ULONG uIndex = 0; uIndex < pExportDirectory->NumberOfNames; uIndex++, AddressOfNames++, AddressOfNameOrdinals++)
{
uAddressOfNames = *AddressOfNames;
pSectionHeader = pOldSectionHeader;
for (UINT32 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
{
if (pSectionHeader->VirtualAddress <= uAddressOfNames && uAddressOfNames <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
{
uOffset = uAddressOfNames - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
}
FunName = (LPSTR)((ULONGLONG)pBuffer + uOffset);
// 判斷開頭是否是Zw
if (FunName[0] == 'Z' && FunName[1] == 'w')
{
pSectionHeader = pOldSectionHeader;
// 如果是則根據AddressOfNameOrdinals得到檔案偏移
uOffset = (ULONG)AddressOfFunctions[*AddressOfNameOrdinals];
for (UINT32 Index = 0; Index < pNtHeaders->FileHeader.NumberOfSections; Index++, pSectionHeader++)
{
if (pSectionHeader->VirtualAddress <= uOffset && uOffset <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData)
{
uNameOffset = uOffset - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
}
}
pFuncAddr = (PVOID)((ULONGLONG)pBuffer + uNameOffset);
uServerIndex = *(PULONG)((ULONGLONG)pFuncAddr + 4);
FunName[0] = 'N';
FunName[1] = 't';
// 獲得指定的編號
if (!_stricmp(FunName, (const char *)function_name))
{
ExFreePoolWithTag(pBuffer, (ULONG)"Ntdl");
ZwClose(FileHandle);
return uServerIndex;
}
}
}
ExFreePoolWithTag(pBuffer, (ULONG)"Ntdl");
ZwClose(FileHandle);
return 0;
}
// 獲取模組匯出函數
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;
}
if (ImageDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
{
return NULL;
}
ImageNtHeaders32 = (PIMAGE_NT_HEADERS32)((PUCHAR)ModuleBase + ImageDosHeader->e_lfanew);
ImageNtHeaders64 = (PIMAGE_NT_HEADERS64)((PUCHAR)ModuleBase + ImageDosHeader->e_lfanew);
// 判斷PE結構位數
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;
}
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;
// 如果函數名小於等於0xFFFF 則說明是序號匯出
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;
}
// 獲取指定使用者模組基址
PVOID GetUserModuleAddress(IN PEPROCESS EProcess, IN PUNICODE_STRING ModuleName, IN BOOLEAN IsWow64)
{
if (EProcess == NULL)
{
return NULL;
}
__try
{
// 定時250ms毫秒
LARGE_INTEGER Time = { 0 };
Time.QuadPart = -250ll * 10 * 1000;
// 32位元執行
if (IsWow64)
{
// 得到程序PEB程序環境塊
PPEB32 Peb32 = (PPEB32)PsGetProcessWow64Process(EProcess);
if (Peb32 == NULL)
{
return NULL;
}
// 等待 250ms * 10
for (INT i = 0; !Peb32->Ldr && i < 10; i++)
{
// 等待一會在執行
KeDelayExecutionThread(KernelMode, TRUE, &Time);
}
// 沒有找到返回空
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
{
// 得到程序PEB程序環境塊
PPEB Peb = PsGetProcessPeb(EProcess);
if (!Peb)
{
return NULL;
}
// 等待
for (INT i = 0; !Peb->Ldr && i < 10; i++)
{
// 將當前執行緒置於指定間隔的可警報或不可操作的等待狀態
KeDelayExecutionThread(KernelMode, TRUE, &Time);
}
if (!Peb->Ldr)
{
return NULL;
}
// 遍歷連結串列
for (PLIST_ENTRY ListEntry = Peb->Ldr->InLoadOrderModuleList.Flink; ListEntry != &Peb->Ldr->InLoadOrderModuleList; ListEntry = ListEntry->Flink)
{
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;
}
//得到ntos的基址
ULONGLONG GetOsBaseAddress(PDRIVER_OBJECT pDriverObject)
{
UNICODE_STRING osName = { 0 };
WCHAR wzData[0x100] = L"ntoskrnl.exe";
RtlInitUnicodeString(&osName, wzData);
LDR_DATA_TABLE_ENTRY *pDataTableEntry, *pTempDataTableEntry;
// 雙迴圈連結串列定義
PLIST_ENTRY pList;
// 指向驅動物件的DriverSection
pDataTableEntry = (LDR_DATA_TABLE_ENTRY*)pDriverObject->DriverSection;
// 判斷是否為空
if (!pDataTableEntry)
{
return 0;
}
// 得到連結串列地址
pList = pDataTableEntry->InLoadOrderLinks.Flink;
// 判斷是否等於頭部
while (pList != &pDataTableEntry->InLoadOrderLinks)
{
pTempDataTableEntry = (LDR_DATA_TABLE_ENTRY *)pList;
// 如果是ntoskrnl.exe則返回該模組基址
if (RtlEqualUnicodeString(&pTempDataTableEntry->BaseDllName, &osName, TRUE))
{
return (ULONGLONG)pTempDataTableEntry->DllBase;
}
pList = pList->Flink;
}
return 0;
}
// 得到SSDT表的基地址
ULONGLONG GetKeServiceDescriptorTable64(PDRIVER_OBJECT DriverObject)
{
/*
nt!KiSystemServiceUser+0xdc:
fffff806`42c79987 8bf8 mov edi,eax
fffff806`42c79989 c1ef07 shr edi,7
fffff806`42c7998c 83e720 and edi,20h
fffff806`42c7998f 25ff0f0000 and eax,0FFFh
nt!KiSystemServiceRepeat:
fffff806`42c79994 4c8d15e59e3b00 lea r10,[nt!KeServiceDescriptorTable (fffff806`43033880)]
fffff806`42c7999b 4c8d1dde203a00 lea r11,[nt!KeServiceDescriptorTableShadow (fffff806`4301ba80)]
fffff806`42c799a2 f7437880000000 test dword ptr [rbx+78h],80h
fffff806`42c799a9 7413 je nt!KiSystemServiceRepeat+0x2a (fffff806`42c799be)
*/
char KiSystemServiceStart_pattern[14] = "\x8B\xF8\xC1\xEF\x07\x83\xE7\x20\x25\xFF\x0F\x00\x00";
/*
ULONG rva = GetRvaFromModule(L"\\SystemRoot\\system32\\ntoskrnl.exe", "_stricmp");
DbgPrint("NtReadFile VA = %p \n", rva);
ULONG _stricmp_offset = 0x19d710;
*/
ULONGLONG CodeScanStart = GetSSDTRVA((UCHAR *)"_stricmp") + GetOsBaseAddress(DriverObject);
ULONGLONG i, tbl_address, b;
for (i = 0; i < 0x50000; i++)
{
// 比較特徵
if (!memcmp((char*)(ULONGLONG)CodeScanStart + i, (char*)KiSystemServiceStart_pattern, 13))
{
for (b = 0; b < 50; b++)
{
tbl_address = ((ULONGLONG)CodeScanStart + i + b);
// 4c 8d 15 e5 9e 3b 00 lea r10,[nt!KeServiceDescriptorTable (fffff802`64da4880)]
// if (*(USHORT*)((ULONGLONG)tbl_address) == (USHORT)0x158d4c)
if (*(USHORT*)((ULONGLONG)tbl_address) == (USHORT)0x8d4c)
{
return ((LONGLONG)tbl_address + 7) + *(LONG*)(tbl_address + 3);
}
}
}
}
return 0;
}
// 根據SSDT序號得到函數基址
ULONGLONG GetSSDTFuncCurAddr(ULONG index)
{
/*
mov rax, rcx ; rcx=Native API 的 index
lea r10,[rdx] ; rdx=ssdt 基址
mov edi,eax ; index
shr edi,7
and edi,20h
mov r10, qword ptr [r10+rdi] ; ServiceTableBase
movsxd r11,dword ptr [r10+rax] ; 沒有右移的假ssdt的地址
mov rax,r11
sar r11,4
add r10,r11
mov rax,r10
ret
*/
LONG dwtmp = 0;
PULONG ServiceTableBase = NULL;
ServiceTableBase = (PULONG)KeServiceDescriptorTable->ServiceTableBase;
dwtmp = ServiceTableBase[index];
// 先右移4位元之後加上基地址 就可以得到ssdt的地址
dwtmp = dwtmp >> 4;
return (LONGLONG)dwtmp + (ULONGLONG)ServiceTableBase;
}
// 根據程序ID返回程序EPROCESS
PEPROCESS LookupProcess(HANDLE Pid)
{
PEPROCESS eprocess = NULL;
if (NT_SUCCESS(PsLookupProcessByProcessId(Pid, &eprocess)))
{
return eprocess;
}
else
{
return NULL;
}
}
// 根據使用者傳入程序名得到該程序PID
HANDLE GetProcessID(PCHAR ProcessName)
{
ULONG i = 0;
PEPROCESS eproc = NULL;
for (i = 4; i<100000000; i = i + 4)
{
eproc = LookupProcess((HANDLE)i);
if (eproc != NULL)
{
ObDereferenceObject(eproc);
// 根據程序名得到程序EPEPROCESS
if (strstr(PsGetProcessImageFileName(eproc), ProcessName) != NULL)
{
return PsGetProcessId(eproc);
}
}
}
return NULL;
}
為了能更好的完成驅動注入實現原理的講解,也可以讓使用者理解如上方所封裝的API函數的使用流程,接下來將依次講解上方這些通用API函數的作用以及使用方法,其目的是讓使用者可以更好的學會功能運用,以此在後期專案開發中可以更好的使用這些功能。
GetOsBaseAddress: 該函數可實現輸出特定核心模組的基地址,本例中寫死在了變數wzData
中,如果需要改進只需要替換引數傳遞即可實現自定義取值,呼叫該函數你只需要傳入PDRIVER_OBJECT
自身驅動物件即可,程式碼如下所示;
// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]
#include "lyshark.h"
VOID Unload(PDRIVER_OBJECT pDriverObj)
{
DbgPrint("[-] 驅動解除安裝 \n");
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegPath)
{
DbgPrint("Hello LyShark.com \n");
ULONGLONG kernel_base = GetOsBaseAddress(DriverObject);
DbgPrint("ntoskrnl.exe 模組基址: %p \n", kernel_base);
DriverObject->DriverUnload = Unload;
return STATUS_SUCCESS;
}
編譯並執行如上程式碼片段,即可輸出ntoskrnl.exe
核心模組的基址,效果圖如下所示;
GetSSDTFuncCurAddr: 該函數可實現根據使用者傳入的SSDT表下標,獲取到該函數的基址,程式碼如下所示;
// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]
#include "lyshark.h"
VOID Unload(PDRIVER_OBJECT pDriverObj)
{
DbgPrint("[-] 驅動解除安裝 \n");
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegPath)
{
DbgPrint("Hello LyShark.com \n");
// 得到SSDT基地址
KeServiceDescriptorTable = (PSYSTEM_SERVICE_TABLE)GetKeServiceDescriptorTable64(DriverObject);
DbgPrint("SSDT基地址: %p \n", KeServiceDescriptorTable->ServiceTableBase);
// 根據序號得到指定函數地址
ULONGLONG address = NULL;
address = GetSSDTFuncCurAddr(10);
DbgPrint("得到函數地址: %p \n", address);
address = GetSSDTFuncCurAddr(11);
DbgPrint("得到函數地址: %p \n", address);
address = GetSSDTFuncCurAddr(12);
DbgPrint("得到函數地址: %p \n", address);
DriverObject->DriverUnload = Unload;
return STATUS_SUCCESS;
}
編譯並執行如上程式碼片段,即可輸出下標為10,11,12
的SSDT函數基址,效果圖如下所示;
GetSSDTRVA: 根據傳入的函數名獲取該函數的RVA地址,使用者傳入一個特定模組下匯出函數的函數名,動態得到該函數的相對偏移地址。
// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]
#include "lyshark.h"
VOID Unload(PDRIVER_OBJECT pDriverObj)
{
DbgPrint("[-] 驅動解除安裝 \n");
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegPath)
{
DbgPrint("Hello LyShark.com \n");
ULONG64 ReadFile_RVA = GetSSDTRVA("NtReadFile");
DbgPrint("NtReadFile = %p \n", ReadFile_RVA);
ULONG64 NtCreateEnlistment_RVA = GetSSDTRVA("NtCreateEnlistment");
DbgPrint("NtCreateEnlistment = %p \n", NtCreateEnlistment_RVA);
DriverObject->DriverUnload = Unload;
return STATUS_SUCCESS;
}
編譯並執行如上程式碼片段,即可輸出NtReadFile,NtCreateEnlistment
兩個核心函數的RVA地址,效果圖如下所示;
GetIndexByName: 該函數接收使用者傳入的一個SSDT
函數名,並返回該函數所對應的下標,呼叫程式碼如下;
// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]
#include "lyshark.h"
VOID Unload(PDRIVER_OBJECT pDriverObj)
{
DbgPrint("[-] 驅動解除安裝 \n");
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegPath)
{
DbgPrint("Hello LyShark.com \n");
ULONG index1 = GetIndexByName((UCHAR *)"NtCreateThreadEx");
DbgPrint("函數NtCreateThreadEx下標: %d \n", index1);
ULONG index2 = GetIndexByName((UCHAR *)"NtReadFile");
DbgPrint("函數NtReadFile下標: %d \n", index2);
DriverObject->DriverUnload = Unload;
return STATUS_SUCCESS;
}
編譯並執行如上程式碼片段,即可輸出NtCreateThreadEx,NtReadFile
兩個核心函數的下標,效果圖如下所示;
GetUserModuleAddress: 該函數用於獲取程序模組基址,在核心模式下附加到應用層指定程序上,並動態獲取到該程序所載入的指定模組的基址,呼叫程式碼如下;
// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]
#include "lyshark.h"
VOID Unload(PDRIVER_OBJECT pDriverObj)
{
DbgPrint("[-] 驅動解除安裝 \n");
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegPath)
{
DbgPrint("Hello LyShark.com \n");
HANDLE ProcessID = (HANDLE)6932;
PEPROCESS EProcess = NULL;
NTSTATUS Status = STATUS_SUCCESS;
KAPC_STATE ApcState;
// 根據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");
DriverObject->DriverUnload = Unload;
return STATUS_SUCCESS;
}
// 將當前執行緒連線到目標程序的地址空間(附加程序)
KeStackAttachProcess((PRKPROCESS)EProcess, &ApcState);
__try
{
UNICODE_STRING NtdllUnicodeString = { 0 };
PVOID NtdllAddress = NULL;
// 得到程序內ntdll.dll模組基地址
RtlInitUnicodeString(&NtdllUnicodeString, L"Ntdll.dll");
NtdllAddress = GetUserModuleAddress(EProcess, &NtdllUnicodeString, IsWow64);
if (!NtdllAddress)
{
DbgPrint("[-] 沒有找到基址 \n");
DriverObject->DriverUnload = Unload;
return STATUS_SUCCESS;
}
DbgPrint("[*] 模組ntdll.dll基址: %p \n", NtdllAddress);
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
}
// 取消附加
KeUnstackDetachProcess(&ApcState);
DriverObject->DriverUnload = Unload;
return STATUS_SUCCESS;
}
編譯並執行如上程式碼片段,則獲取程序PID=6932
裡面的ntdll.dll
模組的基址,輸出效果圖如下所示;
GetModuleExportAddress: 該函數可用於獲取特定模組中特定函數的基址,此功能需要配合獲取模組基址一起使用,呼叫程式碼如下;
// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]
#include "lyshark.h"
VOID Unload(PDRIVER_OBJECT pDriverObj)
{
DbgPrint("[-] 驅動解除安裝 \n");
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegPath)
{
DbgPrint("Hello LyShark.com \n");
HANDLE ProcessID = (HANDLE)6932;
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)0x77540000;
PVOID RefAddress = 0;
// 傳入Ntdll.dll基址 + 函數名 得到該函數地址
RefAddress = GetModuleExportAddress(BaseAddress, "LdrLoadDll", EProcess);
DbgPrint("[*] 函數地址: %p \n", RefAddress);
DriverObject->DriverUnload = Unload;
return STATUS_SUCCESS;
}
編譯並執行如上程式碼片段,則獲取程序PID=6932
裡面的ntdll.dll
模組裡的LdrLoadDll
函數基址,輸出效果圖如下所示;
SeCreateThreadEx: 該函數則是實際執行注入的函數,此段程式碼中需要注意的是pPrevMode
中的偏移值,每個系統中都不相同,使用者需要自行在WinDBG
中輸入!_KTHREAD
得到執行緒資訊,並找到PreviousMode
欄位,該欄位中的偏移值需要(PUCHAR)PsGetCurrentThread() + 0x232
才可得到正確位置。
// 署名權
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]
#include "lyshark.h"
// -----------------------------------------------------------------------------------
// 注入程式碼生成函數
// -----------------------------------------------------------------------------------
// 建立64位元注入程式碼
PINJECT_BUFFER GetNative64Code(IN PVOID LdrLoadDll, IN PUNICODE_STRING DllFullPath)
{
NTSTATUS Status = STATUS_SUCCESS;
PINJECT_BUFFER InjectBuffer = NULL;
SIZE_T Size = PAGE_SIZE;
UCHAR Code[] = {
0x48, 0x83, 0xEC, 0x28, // sub rsp, 0x28
0x48, 0x31, 0xC9, // xor rcx, rcx
0x48, 0x31, 0xD2, // xor rdx, rdx
0x49, 0xB8, 0, 0, 0, 0, 0, 0, 0, 0, // mov r8, ModuleFileName offset +12
0x49, 0xB9, 0, 0, 0, 0, 0, 0, 0, 0, // mov r9, ModuleHandle offset +28
0x48, 0xB8, 0, 0, 0, 0, 0, 0, 0, 0, // mov rax, LdrLoadDll offset +32
0xFF, 0xD0, // call rax
0x48, 0xBA, 0, 0, 0, 0, 0, 0, 0, 0, // mov rdx, COMPLETE_OFFSET offset +44
0xC7, 0x02, 0x7E, 0x1E, 0x37, 0xC0, // mov [rdx], CALL_COMPLETE
0x48, 0xBA, 0, 0, 0, 0, 0, 0, 0, 0, // mov rdx, STATUS_OFFSET offset +60
0x89, 0x02, // mov [rdx], eax
0x48, 0x83, 0xC4, 0x28, // add rsp, 0x28
0xC3 // ret
};
Status = ZwAllocateVirtualMemory(ZwCurrentProcess(), &InjectBuffer, 0, &Size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (NT_SUCCESS(Status))
{
PUNICODE_STRING UserPath = &InjectBuffer->Path64;
UserPath->Length = 0;
UserPath->MaximumLength = sizeof(InjectBuffer->Buffer);
UserPath->Buffer = InjectBuffer->Buffer;
RtlUnicodeStringCopy(UserPath, DllFullPath);
// Copy code
memcpy(InjectBuffer, Code, sizeof(Code));
// Fill stubs
*(ULONGLONG*)((PUCHAR)InjectBuffer + 12) = (ULONGLONG)UserPath;
*(ULONGLONG*)((PUCHAR)InjectBuffer + 22) = (ULONGLONG)&InjectBuffer->ModuleHandle;
*(ULONGLONG*)((PUCHAR)InjectBuffer + 32) = (ULONGLONG)LdrLoadDll;
*(ULONGLONG*)((PUCHAR)InjectBuffer + 44) = (ULONGLONG)&InjectBuffer->Complete;
*(ULONGLONG*)((PUCHAR)InjectBuffer + 60) = (ULONGLONG)&InjectBuffer->Status;
return InjectBuffer;
}
UNREFERENCED_PARAMETER(DllFullPath);
return NULL;
}
// 建立32位元注入程式碼
PINJECT_BUFFER GetNative32Code(IN PVOID LdrLoadDll, IN PUNICODE_STRING DllFullPath)
{
NTSTATUS Status = STATUS_SUCCESS;
PINJECT_BUFFER InjectBuffer = NULL;
SIZE_T Size = PAGE_SIZE;
// Code
UCHAR Code[] = {
0x68, 0, 0, 0, 0, // push ModuleHandle offset +1
0x68, 0, 0, 0, 0, // push ModuleFileName offset +6
0x6A, 0, // push Flags
0x6A, 0, // push PathToFile
0xE8, 0, 0, 0, 0, // call LdrLoadDll offset +15
0xBA, 0, 0, 0, 0, // mov edx, COMPLETE_OFFSET offset +20
0xC7, 0x02, 0x7E, 0x1E, 0x37, 0xC0, // mov [edx], CALL_COMPLETE
0xBA, 0, 0, 0, 0, // mov edx, STATUS_OFFSET offset +31
0x89, 0x02, // mov [edx], eax
0xC2, 0x04, 0x00 // ret 4
};
Status = ZwAllocateVirtualMemory(ZwCurrentProcess(), &InjectBuffer, 0, &Size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (NT_SUCCESS(Status))
{
// Copy path
PUNICODE_STRING32 pUserPath = &InjectBuffer->Path32;
pUserPath->Length = DllFullPath->Length;
pUserPath->MaximumLength = DllFullPath->MaximumLength;
pUserPath->Buffer = (ULONG)(ULONG_PTR)InjectBuffer->Buffer;
// Copy path
memcpy((PVOID)pUserPath->Buffer, DllFullPath->Buffer, DllFullPath->Length);
// Copy code
memcpy(InjectBuffer, Code, sizeof(Code));
// Fill stubs
*(ULONG*)((PUCHAR)InjectBuffer + 1) = (ULONG)(ULONG_PTR)&InjectBuffer->ModuleHandle;
*(ULONG*)((PUCHAR)InjectBuffer + 6) = (ULONG)(ULONG_PTR)pUserPath;
*(ULONG*)((PUCHAR)InjectBuffer + 15) = (ULONG)((ULONG_PTR)LdrLoadDll - ((ULONG_PTR)InjectBuffer + 15) - 5 + 1);
*(ULONG*)((PUCHAR)InjectBuffer + 20) = (ULONG)(ULONG_PTR)&InjectBuffer->Complete;
*(ULONG*)((PUCHAR)InjectBuffer + 31) = (ULONG)(ULONG_PTR)&InjectBuffer->Status;
return InjectBuffer;
}
UNREFERENCED_PARAMETER(DllFullPath);
return NULL;
}
// -----------------------------------------------------------------------------------
// 啟動子執行緒函數(注入函數)
// -----------------------------------------------------------------------------------
// 啟動執行緒
NTSTATUS NTAPI SeCreateThreadEx(OUT PHANDLE ThreadHandle, IN ACCESS_MASK DesiredAccess, IN PVOID ObjectAttributes, IN HANDLE ProcessHandle, IN PVOID StartAddress, IN PVOID Parameter, IN ULONG Flags, IN SIZE_T StackZeroBits, IN SIZE_T SizeOfStackCommit, IN SIZE_T SizeOfStackReserve, IN PNT_PROC_THREAD_ATTRIBUTE_LIST AttributeList)
{
NTSTATUS Status = STATUS_SUCCESS;
// 根據字串NtCreateThreadEx得到下標,並通過下標查詢SSDT函數地址
LPFN_NTCREATETHREADEX NtCreateThreadEx = (LPFN_NTCREATETHREADEX)(GetSSDTFuncCurAddr(GetIndexByName((UCHAR *)"NtCreateThreadEx")));
DbgPrint("執行緒函數地址: %p --> 開始執行地址: %p \n", NtCreateThreadEx, StartAddress);
if (NtCreateThreadEx)
{
// 如果之前的模式是使用者模式,地址傳遞到ZwCreateThreadEx必須在使用者模式空間
// 切換到核心模式允許使用核心模式地址
/*
dt !_KTHREAD
+0x1c8 Win32Thread : Ptr64 Void
+ 0x140 WaitBlockFill11 : [176] UChar
+ 0x1f0 Ucb : Ptr64 _UMS_CONTROL_BLOCK
+ 0x232 PreviousMode : Char
*/
// Windows10 PreviousMode = 0x232
PUCHAR pPrevMode = (PUCHAR)PsGetCurrentThread() + 0x232;
// 64位元 pPrevMode = 01
UCHAR prevMode = *pPrevMode;
// 核心模式
*pPrevMode = KernelMode;
// 建立執行緒
Status = NtCreateThreadEx(ThreadHandle, DesiredAccess, ObjectAttributes, ProcessHandle, StartAddress, Parameter, Flags, StackZeroBits, SizeOfStackCommit, SizeOfStackReserve, AttributeList);
// 恢復之前的執行緒模式
*pPrevMode = prevMode;
}
else
{
Status = STATUS_NOT_FOUND;
}
return Status;
}
// 執行執行緒
NTSTATUS ExecuteInNewThread(IN PVOID BaseAddress, IN PVOID Parameter, IN ULONG Flags, IN BOOLEAN Wait, OUT PNTSTATUS ExitStatus)
{
HANDLE ThreadHandle = NULL;
OBJECT_ATTRIBUTES ObjectAttributes = { 0 };
// 初始化物件屬性
InitializeObjectAttributes(&ObjectAttributes, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);
// 建立執行緒
NTSTATUS Status = SeCreateThreadEx(&ThreadHandle, THREAD_QUERY_LIMITED_INFORMATION, &ObjectAttributes, ZwCurrentProcess(), BaseAddress, Parameter, Flags, 0, 0x1000, 0x100000, NULL);
// 等待執行緒完成
if (NT_SUCCESS(Status) && Wait != FALSE)
{
// 延遲 60s
LARGE_INTEGER Timeout = { 0 };
Timeout.QuadPart = -(60ll * 10 * 1000 * 1000);
Status = ZwWaitForSingleObject(ThreadHandle, TRUE, &Timeout);
if (NT_SUCCESS(Status))
{
// 查詢執行緒退出碼
THREAD_BASIC_INFORMATION ThreadBasicInfo = { 0 };
ULONG ReturnLength = 0;
Status = ZwQueryInformationThread(ThreadHandle, ThreadBasicInformation, &ThreadBasicInfo, sizeof(ThreadBasicInfo), &ReturnLength);
if (NT_SUCCESS(Status) && ExitStatus)
{
// 這裡是查詢當前的dll是否注入成功
*ExitStatus = ThreadBasicInfo.ExitStatus;
}
else if (!NT_SUCCESS(Status))
{
DbgPrint("%s: ZwQueryInformationThread failed with status 0x%X\n", __FUNCTION__, Status);
}
}
else
{
DbgPrint("%s: ZwWaitForSingleObject failed with status 0x%X\n", __FUNCTION__, Status);
}
}
else
{
DbgPrint("%s: ZwCreateThreadEx failed with status 0x%X\n", __FUNCTION__, Status);
}
if (ThreadHandle)
{
ZwClose(ThreadHandle);
}
return Status;
}
// 切換到目標程序建立核心執行緒進行注入 (cr3切換)
NTSTATUS AttachAndInjectProcess(IN HANDLE ProcessID, PWCHAR DllPath)
{
PEPROCESS EProcess = NULL;
KAPC_STATE ApcState;
NTSTATUS Status = STATUS_SUCCESS;
if (ProcessID == NULL)
{
Status = STATUS_UNSUCCESSFUL;
return Status;
}
// 獲取EProcess
Status = PsLookupProcessByProcessId(ProcessID, &EProcess);
if (Status != STATUS_SUCCESS)
{
return Status;
}
// 判斷目標程序x86 or x64
BOOLEAN IsWow64 = (PsGetProcessWow64Process(EProcess) != NULL) ? TRUE : FALSE;
// 將當前執行緒連線到目標程序的地址空間
KeStackAttachProcess((PRKPROCESS)EProcess, &ApcState);
__try
{
PVOID NtdllAddress = NULL;
PVOID LdrLoadDll = NULL;
UNICODE_STRING NtdllUnicodeString = { 0 };
UNICODE_STRING DllFullPath = { 0 };
// 獲取ntdll模組基地址
RtlInitUnicodeString(&NtdllUnicodeString, L"Ntdll.dll");
NtdllAddress = GetUserModuleAddress(EProcess, &NtdllUnicodeString, IsWow64);
if (!NtdllAddress)
{
Status = STATUS_NOT_FOUND;
}
// 獲取LdrLoadDll
if (NT_SUCCESS(Status))
{
LdrLoadDll = GetModuleExportAddress(NtdllAddress, "LdrLoadDll", EProcess);
if (!LdrLoadDll)
{
Status = STATUS_NOT_FOUND;
}
}
PINJECT_BUFFER InjectBuffer = NULL;
if (IsWow64)
{
// 注入32位元DLL
RtlInitUnicodeString(&DllFullPath, DllPath);
InjectBuffer = GetNative32Code(LdrLoadDll, &DllFullPath);
DbgPrint("[*] 注入32位元DLL \n");
}
else
{
// 注入64位元DLL
RtlInitUnicodeString(&DllFullPath, DllPath);
InjectBuffer = GetNative64Code(LdrLoadDll, &DllFullPath);
DbgPrint("[*] 注入64位元DLL \n");
}
//建立執行緒,執行構造的 shellcode
ExecuteInNewThread(InjectBuffer, NULL, THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER, TRUE, &Status);
if (!NT_SUCCESS(Status))
{
DbgPrint("ExecuteInNewThread Failed\n");
}
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
Status = STATUS_UNSUCCESSFUL;
}
// 釋放EProcess
KeUnstackDetachProcess(&ApcState);
ObDereferenceObject(EProcess);
return Status;
}
VOID Unload(PDRIVER_OBJECT pDriverObj)
{
DbgPrint("[-] 驅動解除安裝 \n");
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegPath)
{
DbgPrint("Hello LyShark \n");
// 獲取SSDT表基址
KeServiceDescriptorTable = (PSYSTEM_SERVICE_TABLE)GetKeServiceDescriptorTable64(DriverObject);
// 得到程序PID
HANDLE processid = GetProcessID("x32.exe");
DbgPrint("程序PID = %d \n", processid);
// 附加執行注入
AttachAndInjectProcess(processid, L"C:\\hook.dll");
DriverObject->DriverUnload = Unload;
return STATUS_SUCCESS;
}
執行如上這段程式碼片段,將編譯好的DLL檔案放入到C:\\hook.dll
目錄下,並執行x32.exe
程式,手動載入驅動即可注入成功,輸出效果圖如下所示;
回到應用層程序中,可看到我們的DLL已經被注入到目標程序內了,效果圖如下所示;
Blackbone OpenSource