在筆者上一篇文章《內核RIP劫持實現DLL注入》
介紹了通過劫持RIP指針控制程序執行流實現插入DLL的目的,本章將繼續探索全新的注入方式,通過NtCreateThreadEx
這個內核函數實現注入DLL的目的,需要注意的是該函數在微軟系統中未被導出使用時需要首先得到該函數的入口地址,NtCreateThreadEx
函數最終會調用ZwCreateThread
,本章在尋找函數的方式上有所不同,前一章通過內存定位的方法得到所需地址,本章則是通過解析導出表實現。
內核導出表遠程線程是一種實現DLL注入的常見技術之一。通過使用該技術,注入代碼可以利用目標進程的導出表中已有的函數來加載DLL,并在遠程線程中執行DLL代碼,從而實現DLL注入。
具體而言,內核導出表遠程線程實現DLL注入的過程包括以下步驟:
- 打開目標進程,獲取其進程句柄。
- 在目標進程的內存空間中分配一段可執行代碼的內存空間,將注入代碼寫入其中。
- 通過GetProcAddress函數獲取目標進程中已有的一個導出函數的地址,如LoadLibraryA等函數。
- 在目標進程中創建一個遠程線程,將獲取到的導出函數地址作為線程的入口點,并將DLL路徑等參數傳遞給導出函數。
- 遠程線程在目標進程中運行,并調用導出函數。導出函數會將DLL加載到目標進程的內存中,并返回DLL的句柄。
- 遠程線程繼續執行注入代碼,利用DLL的句柄和GetProcAddress函數獲取目標函數的地址,從而實現DLL注入。
需要注意的是,內核導出表遠程線程作為一種內核級別的注入技術,可能會被安全軟件或操作系統檢測到,并對其進行防御。因此,在使用這種技術進行DLL注入時,需要謹慎使用,并采取必要的安全措施,以防止被檢測和防御。
在內核模式中實現這一過程的具體方法可分為如下步驟;
- 1.通過
GetKeServiceDescriptorTable64
獲取到SSDT表基址 - 2.通過
KeStackAttachProcess
附加到遠程進程內 - 3.通過
GetUserModuleAddress
獲取到Ntdll.dll
模塊內存基址 - 4.通過
GetModuleExportAddress
獲取到LdrLoadDll
函數的內存地址 - 5.調用
GetNative32Code
生成拉起特定DLL的ShellCode
片段 - 6.通過
NtCreateThreadEx
將ShellCode
執行起來,并自動加載DLL - 7.通過
KeUnstackDetachProcess
取消附加遠程進程,并做好最后的清理工作
首先需要定義一個標準頭文件,并將其命名為lyshark.h
其定義部分如下所示,此部分內容包含了微軟官方結構定義,以及一些通用函數的規整,已做較為詳細的分析和備注,由于前面課程中都有介紹,此處不再介紹具體原理,如果需要了解結構體內的含義,請去自行查閱微軟官方文檔。
#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_XXXSIZE_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);// 判斷開頭是否是Zwif (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 * 10for (INT i = 0; !Peb32->Ldr && i < 10; i++){// 等待一會在執行KeDelayExecutionThread(KernelMode, TRUE, &Time);}// 沒有找到返回空if (!Peb32->Ldr){return NULL;}// 搜索 InLoadOrderModuleListfor (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;// 指向驅動對象的DriverSectionpDataTableEntry = (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,eaxfffff806`42c79989 c1ef07 shr edi,7fffff806`42c7998c 83e720 and edi,20hfffff806`42c7998f 25ff0f0000 and eax,0FFFhnt!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],80hfffff806`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 的 indexlea r10,[rdx] ; rdx=ssdt 基址mov edi,eax ; indexshr edi,7and edi,20hmov r10, qword ptr [r10+rdi] ; ServiceTableBasemovsxd r11,dword ptr [r10+rax] ; 沒有右移的假ssdt的地址mov rax,r11sar r11,4add r10,r11mov rax,r10ret*/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);// 根據進程名得到進程EPEPROCESSif (strstr(PsGetProcessImageFileName(eproc), ProcessName) != NULL){return PsGetProcessId(eproc);}}}return NULL;
}
為了能更好的完成驅動注入實現原理的講解,也可以讓用戶理解如上方所封裝的API函數的使用流程,接下來將依次講解上方這些通用API函數的作用以及使用方法,其目的是讓用戶可以更好的學會功能運用,以此在后期項目開發中可以更好的使用這些功能。
GetOsBaseAddress
該函數可實現輸出特定內核模塊的基地址,本例中寫死在了變量wzData
中,如果需要改進只需要替換參數傳遞即可實現自定義取值,調用該函數你只需要傳入PDRIVER_OBJECT
自身驅動對象即可,代碼如下所示;
#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表下標,獲取到該函數的基址,代碼如下所示;
#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地址,用戶傳入一個特定模塊下導出函數的函數名,動態得到該函數的相對偏移地址。
#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
函數名,并返回該函數所對應的下標,調用代碼如下;
#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
該函數用于獲取進程模塊基址,在內核模式下附加到應用層指定進程上,并動態獲取到該進程所加載的指定模塊的基址,調用代碼如下;
#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
該函數可用于獲取特定模塊中特定函數的基址,此功能需要配合獲取模塊基址一起使用,調用代碼如下;
#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
才可得到正確位置。
#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, 0x280x48, 0x31, 0xC9, // xor rcx, rcx0x48, 0x31, 0xD2, // xor rdx, rdx0x49, 0xB8, 0, 0, 0, 0, 0, 0, 0, 0, // mov r8, ModuleFileName offset +120x49, 0xB9, 0, 0, 0, 0, 0, 0, 0, 0, // mov r9, ModuleHandle offset +280x48, 0xB8, 0, 0, 0, 0, 0, 0, 0, 0, // mov rax, LdrLoadDll offset +320xFF, 0xD0, // call rax0x48, 0xBA, 0, 0, 0, 0, 0, 0, 0, 0, // mov rdx, COMPLETE_OFFSET offset +440xC7, 0x02, 0x7E, 0x1E, 0x37, 0xC0, // mov [rdx], CALL_COMPLETE 0x48, 0xBA, 0, 0, 0, 0, 0, 0, 0, 0, // mov rdx, STATUS_OFFSET offset +600x89, 0x02, // mov [rdx], eax0x48, 0x83, 0xC4, 0x28, // add rsp, 0x280xC3 // 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 codememcpy(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;// CodeUCHAR Code[] = {0x68, 0, 0, 0, 0, // push ModuleHandle offset +1 0x68, 0, 0, 0, 0, // push ModuleFileName offset +60x6A, 0, // push Flags 0x6A, 0, // push PathToFile0xE8, 0, 0, 0, 0, // call LdrLoadDll offset +150xBA, 0, 0, 0, 0, // mov edx, COMPLETE_OFFSET offset +200xC7, 0x02, 0x7E, 0x1E, 0x37, 0xC0, // mov [edx], CALL_COMPLETE 0xBA, 0, 0, 0, 0, // mov edx, STATUS_OFFSET offset +310x89, 0x02, // mov [edx], eax0xC2, 0x04, 0x00 // ret 4};Status = ZwAllocateVirtualMemory(ZwCurrentProcess(), &InjectBuffer, 0, &Size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);if (NT_SUCCESS(Status)){// Copy pathPUNICODE_STRING32 pUserPath = &InjectBuffer->Path32;pUserPath->Length = DllFullPath->Length;pUserPath->MaximumLength = DllFullPath->MaximumLength;pUserPath->Buffer = (ULONG)(ULONG_PTR)InjectBuffer->Buffer;// Copy pathmemcpy((PVOID)pUserPath->Buffer, DllFullPath->Buffer, DllFullPath->Length);// Copy codememcpy(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 = 0x232PUCHAR pPrevMode = (PUCHAR)PsGetCurrentThread() + 0x232;// 64位 pPrevMode = 01UCHAR 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){// 延遲 60sLARGE_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;}// 獲取EProcessStatus = PsLookupProcessByProcessId(ProcessID, &EProcess);if (Status != STATUS_SUCCESS){return Status;}// 判斷目標進程x86 or x64BOOLEAN 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;}// 獲取LdrLoadDllif (NT_SUCCESS(Status)){LdrLoadDll = GetModuleExportAddress(NtdllAddress, "LdrLoadDll", EProcess);if (!LdrLoadDll){Status = STATUS_NOT_FOUND;}}PINJECT_BUFFER InjectBuffer = NULL;if (IsWow64){// 注入32位DLLRtlInitUnicodeString(&DllFullPath, DllPath);InjectBuffer = GetNative32Code(LdrLoadDll, &DllFullPath);DbgPrint("[*] 注入32位DLL \n");}else{// 注入64位DLLRtlInitUnicodeString(&DllFullPath, DllPath);InjectBuffer = GetNative64Code(LdrLoadDll, &DllFullPath);DbgPrint("[*] 注入64位DLL \n");}//創建線程,執行構造的 shellcodeExecuteInNewThread(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;}// 釋放EProcessKeUnstackDetachProcess(&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);// 得到進程PIDHANDLE 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已經被注入到目標進程內了,效果圖如下所示;