您当前的位置: 首页 >  windows

合天网安实验室

暂无认证

  • 0浏览

    0关注

    748博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

windows 堆分析

合天网安实验室 发布时间:2021-09-02 17:30:00 ,浏览量:0

windows和linux堆管理机制虽然呈现给用户的效果是一样的,大体思路也是差不太多,但是底层实现逻辑大相径庭,很多地方和glibc的ptmalloc差别很大。网上资料零零散散,而且都是通过逆向手段分析,所以每个版本资料还多少有些差异,在这里对windows堆管理机制做个归纳,学习一下。

接口

在glibc中,通常我们调用的分配函数就是malloc、calloc、realloc,但是这三个函数本质都差不多,本体还是malloc函数的逻辑。

在windows中,堆的分配函数就比较多了这里我们逐一介绍一下。

函数原型参数说明HeapAlloc(HANDLE hHeap, DWORD dwFlags, size_t dwSize)hHeap为进程堆开始位置,flag就是标志,size就是大小。内存是指定位置开始分配,且分配的内存不可移动。对应的释放函数是HeapFreeGlobalAlloc(UINT uFlags, size_t dwBytes)uflag标志信息:GMEM_FIXED分配固定内存,返回一个指针;GMEM_MOVABLE分配活动内存,返回内存对象句柄,这个句柄可以利用GlobalLock转化为指针。从全局堆中分配内存,相应的释放函数是GlobalFreeLocalAlloc(UINT uFlags,size_t dwBytes)参数同GlobalAlloc对应的释放函数为LocalFreeVirtualAlloc(LPVOID lpAddress, size_t dwSize,DWORD flAllocationType,DWORD flProtect)意义和参数名一样对应的释放函数为VirtualFreemalloc和linux一样freeHeapCreate(DWORD flOptions , DWORD dwInitialSize , DWORD dwMaxmumSize);flOptions:堆的可选属性。这些标记影响以后对这个堆的函数操作,函数有:HeapAlloc , HeapFree , HeapReAlloc , HeapSize .wInitialSize:堆的初始大小,单位为Bytes。这个值决定了分配给堆的初始物理空间大小。这个值将向上舍入知道下个page boundary(页界)。若需得到主机的页大小,使用GetSystemInfo 函数。dwMaxmumSize:如果该参数是一个非零的值,它指定了这个堆的最大大小,单位为Bytes。用来创造一块堆区域

从接口信息可以看出来,windows和linux堆的一个很大的不同点就是windows的堆有很多,linux的话都在一个区域里。

另外,globalalloc和localalloc在现代的win32以后的版本中没有区别,这两个函数刚开始是在16位windows中使用有区别的。在win32中每个程序都有一个自己的缺省堆,所以全局堆和局部堆在win32中都指向这个缺省堆,这俩没区别,甚至释放函数都可以混着用。等效于heapAlloc(GetProcessHeap(),flag,size)

malloc函数虽然不像其他的函数那样指明了堆区,但是实际上windows中malloc函数在初始化的时候自己HeapCreate了一段堆内存区域供他使用。每个模块的malloc都有自己的堆区域,所以不能一个dllfree掉另一个dll的堆指针。

概览

windows堆管理机制较之于linux比较复杂,管理机制也分好几套。

UWP即Windows通用应用平台,Windows 10中的Universal Windows Platform简称。UWP不同于传统pc上的exe应用,可以在所有Windows10设备上运行。UWP应用程序进程至少包括三个堆:(1) 默认堆(2) 用于向进程的会话Csrss.exe实例传递大参数的共享堆。这是由CsrClientConnectToServer函数创建的,该函数在Ntdll.dll完成的进程初始化早期执行。(3) 由Microsoft C运行库创建的堆。该堆是由C/C++内存分配函数(如Maloc、Free、等)内部使用的堆。

在Windows10和服务器2016之前,只有一种堆类型,我们称之为NT堆。Windows 10引入了一种称为段堆(segment heap)的新堆类型。这两种堆类型包括公共元素,但结构和实现方式不同。默认情况下,所有UWP应用程序和某些系统进程都使用段堆,而所有其他进程都使用NT堆。这可以在注册表中更改。

大部分场合默认使用的堆都是NT heapsegment heap通常会在winapp或者某些特殊的进程(核心进程)中会使用到。

而在NT heap中又分为前端管理和后端管理两套不同的堆分配管理策略。

而windows程序的堆又分为两种:

第一种叫做processheap,它包括两个部分,一个是default heap,其地址信息回存放于_PEB中,在调用malloc等函数的时候会用到。第二个是crtheap,但是其本质一样是default,封装了一些别的信息,存放于crt_heap中。

第二种叫做private heap,也就是我们通过HeapCreate创建的堆。

NT堆

大体流程

大体流程就是windows app调用msvcrt140.dll函数中的形如malloc、free等函数后,会调用kernel32.dll中的堆管理api,接着调用ntdll中的管理机制。

这里的管理机制中,LFH就是前端管理的核心,那么整个流程具体来说就是如下的逻辑:

(1) 小于或等于16368字节,使用LFH分配器。这与NT堆的逻辑类似。如果LFH还没有启动,那么将使用可变大小(VS)分配器。(2) 对于小于或等于128 KB的大小(不由LFH提供服务),使用VS分配器。VS和LFH分配器都使用后端根据需要创建所需的堆子段。(3) 大于128 KB且小于或等于508 KB的分配由堆后端直接提供服务。(4) 大于508kb的分配直接调用内存管理器(VirtualAlloc),因为这些分配非常大,因此使用默认的64kb分配粒度(并舍入到最接近的页面大小)就足够了。

如果LFH没有启用,那么就直接调用后端堆管理机制。

启用LFH后,第一次申请或者LFH内部空间不够时会从后端堆中申请一段大空间来使用。

如果LFH搞定了申请,那么直接由LFH返回,不调用后端。

可以看出前端分配器就有点类似于linux中的fastbin。

这里要说明一下,在之前的windows版本中,前端分配器并不是LFH,而是look aside表,也就是0day一书中提到的快表,但是windows10中已经不适用lookaside了。

数据结构

由前面的内容可以看出来windows有很多的堆,从linux的管理机制中,我们知道每个堆都由一个重要的数据结构malloc_state来管理,这些个mallocstate就称之为arena,主线程叫main_arena,别的叫thread_arena,这些个数据结构由指针链接形成链表。

那么在windows的堆管理机制中,同样也需要类似于arena这样的结构体。但是不同于linux,每个这样的堆管理结构体是存放于每个堆段的头部,并不是在某些dll的数据段中。

这个数据结构就称之为_HEAP,长这个样子:

 +0x000 Segment         : _HEAP_SEGMENT
  +0x000 Entry           : _HEAP_ENTRY
  +0x008 SegmentSignature : Uint4B //用来判断NT还是Segment
  +0x00c SegmentFlags     : Uint4B
  +0x010 SegmentListEntry : _LIST_ENTRY
  +0x018 Heap             : Ptr32 _HEAP
  +0x01c BaseAddress     : Ptr32 Void
  +0x020 NumberOfPages   : Uint4B
  +0x024 FirstEntry       : Ptr32 _HEAP_ENTRY
  +0x028 LastValidEntry   : Ptr32 _HEAP_ENTRY
  +0x02c NumberOfUnCommittedPages : Uint4B
  +0x030 NumberOfUnCommittedRanges : Uint4B
  +0x034 SegmentAllocatorBackTraceIndex : Uint2B
  +0x036 Reserved         : Uint2B
  +0x038 UCRSegmentList   : _LIST_ENTRY
  +0x040 Flags           : Uint4B
  +0x044 ForceFlags       : Uint4B
  +0x048 CompatibilityFlags : Uint4B
  +0x04c EncodeFlagMask   : Uint4B //用来表示是否encode header
  +0x050 Encoding         : _HEAP_ENTRY //用来encode的cookie
  +0x058 Interceptor     : Uint4B
  +0x05c VirtualMemoryThreshold : Uint4B
  +0x060 Signature       : Uint4B
  +0x064 SegmentReserve   : Uint4B
  +0x068 SegmentCommit   : Uint4B
  +0x06c DeCommitFreeBlockThreshold : Uint4B
  +0x070 DeCommitTotalFreeThreshold : Uint4B
  +0x074 TotalFreeSize   : Uint4B
  +0x078 MaximumAllocationSize : Uint4B
  +0x07c ProcessHeapsListIndex : Uint2B
  +0x07e HeaderValidateLength : Uint2B
  +0x080 HeaderValidateCopy : Ptr32 Void
  +0x084 NextAvailableTagIndex : Uint2B
  +0x086 MaximumTagIndex : Uint2B
  +0x088 TagEntries       : Ptr32 _HEAP_TAG_ENTRY
  +0x08c UCRList         : _LIST_ENTRY
  +0x094 AlignRound       : Uint4B
  +0x098 AlignMask       : Uint4B
  +0x09c VirtualAllocdBlocks : _LIST_ENTRY
  +0x0a4 SegmentList     : _LIST_ENTRY
  +0x0ac AllocatorBackTraceIndex : Uint2B
  +0x0b0 NonDedicatedListLength : Uint4B
  +0x0b4 BlocksIndex     : Ptr32 Void //用来管理后端的chunk列表
  +0x0b8 UCRIndex         : Ptr32 Void
  +0x0bc PseudoTagEntries : Ptr32 _HEAP_PSEUDO_TAG_ENTRY
  +0x0c0 FreeLists       : _LIST_ENTRY //用来管理后端所有的freechunk的链表
  +0x0c8 LockVariable     : Ptr32 _HEAP_LOCK
  +0x0cc CommitRoutine   : Ptr32     long
  +0x0d0 StackTraceInitVar : _RTL_RUN_ONCE
  +0x0d4 CommitLimitData : _RTL_HEAP_MEMORY_LIMIT_DATA
  +0x0e4 FrontEndHeap     : Ptr32 Void //指向前端堆的结构
  +0x0e8 FrontHeapLockCount : Uint2B
  +0x0ea FrontEndHeapType : UChar
  +0x0eb RequestedFrontEndHeapType : UChar
  +0x0ec FrontEndHeapUsageData : Ptr32 Wchar //指向前端管理chunk的列表
  +0x0f0 FrontEndHeapMaximumIndex : Uint2B
  +0x0f2 FrontEndHeapStatusBitmap : [257] UChar
  +0x1f4 Counters         : _HEAP_COUNTERS
  +0x250 TuningParameters : _HEAP_TUNING_PARAMETERS

其中比较重要的字段意义都写在了注释中。

在linux中,堆是由一个个chunk构成的,在windows中也一样,也是由一个个堆块构成。

这样一个堆块的结构,称之为_HEAP_ENTRY。这个结构比较奇怪,似乎有好几种实现方式?以为同样偏移有不同的意思。

ntdll!_HEAP_ENTRY
  +0x000 UnpackedEntry   : _HEAP_UNPACKED_ENTRY
  +0x000 PreviousBlockPrivateData : Ptr64 Void
  +0x008 Size             : Uint2B
  +0x00a Flags           : UChar
  +0x00b SmallTagIndex   : UChar
  +0x008 SubSegmentCode   : Uint4B
  +0x00c PreviousSize     : Uint2B
  +0x00e SegmentOffset   : UChar
  +0x00e LFHFlags         : UChar
  +0x00f UnusedBytes     : UChar
  +0x008 CompactHeader   : Uint8B
  +0x000 ExtendedEntry   : _HEAP_EXTENDED_ENTRY
  +0x000 Reserved         : Ptr64 Void
  +0x008 FunctionIndex   : Uint2B
  +0x00a ContextValue     : Uint2B
  +0x008 InterceptorValue : Uint4B
  +0x00c UnusedBytesLength : Uint2B
  +0x00e EntryOffset     : UChar
  +0x00f ExtendedBlockSignature : UChar
  +0x000 ReservedForAlignment : Ptr64 Void
  +0x008 Code1           : Uint4B
  +0x00c Code2           : Uint2B
  +0x00e Code3           : UChar
  +0x00f Code4           : UChar
  +0x00c Code234         : Uint4B
  +0x008 AgregateCode     : Uint8B

在老外逆出来的c版本中是这样的:

//0x10 bytes (sizeof)
struct _HEAP_ENTRY
{
   union
  {
       struct _HEAP_UNPACKED_ENTRY UnpackedEntry;                          //0x0
       struct
      {
           VOID* PreviousBlockPrivateData;                                 //0x0
           union
          {
               struct
              {
                   USHORT Size;                                            //0x8
                   UCHAR Flags;                                            //0xa
                   UCHAR SmallTagIndex;                                    //0xb
              };
               struct
              {
                   ULONG SubSegmentCode;                                   //0x8
                   USHORT PreviousSize;                                    //0xc
                   union
                  {
                       UCHAR SegmentOffset;                                //0xe
                       UCHAR LFHFlags;                                     //0xe
                  };
                   UCHAR UnusedBytes;                                      //0xf
              };
               ULONGLONG CompactHeader;                                    //0x8
          };
      };
       struct _HEAP_EXTENDED_ENTRY ExtendedEntry;                          //0x0
       struct
      {
           VOID* Reserved;                                                 //0x0
           union
          {
               struct
              {
                   USHORT FunctionIndex;                                   //0x8
                   USHORT ContextValue;                                    //0xa
              };
               ULONG InterceptorValue;                                     //0x8
          };
           USHORT UnusedBytesLength;                                       //0xc
           UCHAR EntryOffset;                                              //0xe
           UCHAR ExtendedBlockSignature;                                   //0xf
      };
       struct
      {
           VOID* ReservedForAlignment;                                     //0x0
           union
          {
               struct
              {
                   ULONG Code1;                                            //0x8
                   union
                  {
                       struct
                      {
                           USHORT Code2;                                   //0xc
                           UCHAR Code3;                                    //0xe
                           UCHAR Code4;                                    //0xf
                      };
                       ULONG Code234;                                      //0xc
                  };
              };
               ULONGLONG AgregateCode;                                     //0x8
          };
      };
  };
};

这里的话主要是因为一个chunk(也就是_HEAP_ENTRY,这么叫方便些)有不同的状态,所以就union一下。

那么具体来说,一个chunk有三种状态:使用(allocated)、释放(free)、虚拟(virtual alloc)(mmap出来的chunk)。

使用状态(inuse):

偏移&名称大小意义0x0: PreviousBlockPrivateData8bytes前一个chunk的数据,由于需要0x10对其所以算在头部0x8: Size2bytes本chunk的大小,这里的大小是 real_size >> 40xa: Flag1byte表示当前chunk是否inuse0xb: smallTagIndex1byte前三个byte(size和flag)做xor后的值,验证作用0xc: PreviousSIze2bytes表示前一个chunk的size,同样也是右移4位后的值0xe: SegmentOffset1byte某些情况下用来找segment0xf: UnusedBytes1byte记录malloc后所剩的chunk大小,可用来判断是前端或者后端0x10: userdata(Size > 40xa: Flag1byte表示当前chunk是否inuse0xb: smallTagIndex1byte前三个byte(size和flag)做xor后的值,验证作用0xc: PreviousSIze2bytes表示前一个chunk的size,同样也是右移4位后的值0xe: SegmentOffset1byte某些情况下用来找segment0xf: UnusedBytes1byte恒为00x10:flink8bytes指向list中后一个chunk0x18: blink8bytes指向list中前一个chunk

virtualAlloc状态:

偏移&名称大小意义0x0: flink8bytes双向链表指针0x8: blink8bytes双向链表指针0x10: size2bytes这里的size是unusedsize,且没有进行移位0x12: flag1byte0x13: smallTagIndex1byte0x14: PreviousSIze2bytes0x16: SegmentOffset1byte0x17:UnusedBytes1byte恒为4

这里的virtualalloc的chunk状态可能有些勘误,因为网上关于这里的资料比较少。

这里要说明一下,关于chunk头部的验证:

在之前的_HEAP结构体中有一个encoding字段,这个cookie就是为了加密头部来用的,具体来说就是xor一下,所以在对chunk进行操作的时候会验证其有效性。同时SmallTagIndex也会对flag和size做一个验证。

free_list

这个是在_HEAP中的一个指针,指向的是free的chunk的链表,双向有序链表。在一个chunk被释放后,会插入到这个list中(类似于unsortedbin)

BlocksIndex

这个指针的结构是_HEAP_LIST_LOOKUP。

这一结构体长这个样子:

//0x38 bytes (sizeof)
struct _HEAP_LIST_LOOKUP
{
   struct _HEAP_LIST_LOOKUP* ExtendedLookup;                               //0x0 指向下一个lookup,通常chunk会更大
   ULONG ArraySize;                                                        //0x8 管理的最大chunk大小(右移4位后)
   ULONG ExtraItem;                                                        //0xc
   ULONG ItemCount;                                                        //0x10 当前管理的chunk数
   ULONG OutOfRangeItems;                                                  //0x14 超出该结构体管理的chunk数量
   ULONG BaseIndex;                                                        //0x18 该结构管理的chunk的起始index(listhint中)
   struct _LIST_ENTRY* ListHead;                                           //0x20 指向freelist的head
   ULONG* ListsInUseUlong;                                                 //0x28 判断listhint中是否有合适大小的chunk, 是一个bitmap
   struct _LIST_ENTRY** ListHints;                                         //0x30 用来指向对应大小chunk的array,0x10递增
};

这两个链表之间的关系如下图(摘自angelboy的slide)

可以看到,所有的chunk都是存储在freelist中,而blockindex用来定位这些在freelist中的chunk的位置,快速找到合适大小的chunk。

NT后端管理机制

管理机制无非就是申请和释放的逻辑。

申请

申请时,分为三种情况:

1.Size4
  +0x002 SizeIndex       : UChar //使用大小>>4
  +0x003 UseAffinity     : Pos 0, 1 Bit
  +0x003 DebugFlags       : Pos 1, 2 Bits
  +0x003 Flags           : UChar

_HEAP_LOCAL_SEGMENT_INFO

ntdll!_HEAP_LOCAL_SEGMENT_INFO
  +0x000 LocalData       : Ptr64 _HEAP_LOCAL_DATA//对应 _LFH_HEAP->LocalData ,便于从 SegmentInfo 找回 _LFH_HEAP
  +0x008 ActiveSubsegment : Ptr64 _HEAP_SUBSEGMENT//对应已分配的Subsegment,用于管理userblock记录剩余多少chunk、最大分配书等等
  +0x010 CachedItems     : [16] Ptr64 _HEAP_SUBSEGMENT//_HEAP_SUBSEGMENT array
//存放对应此SegmentInfo且还有可以分配chunk给user的Subsegment
//当ActiveSubsegment⽤完时,将从这里填充,并置换掉ActiveSubsegment
  +0x090 SListHeader     : _SLIST_HEADER
  +0x0a0 Counters         : _HEAP_BUCKET_COUNTERS
  +0x0a8 LastOpSequence   : Uint4B
  +0x0ac BucketIndex     : Uint2B
  +0x0ae LastUsed         : Uint2B
  +0x0b0 NoThrashCount   : Uint2B

其中,cachedItems比较重要,其结构体为_HEAP_SUBSEGMENT:

ntdll!_HEAP_SUBSEGMENT
  +0x000 LocalInfo       : Ptr64 _HEAP_LOCAL_SEGMENT_INFO//指向对应的_HEAP_LOCAL_SEGMENT_INFO
  +0x008 UserBlocks       : Ptr64 _HEAP_USERDATA_HEADER //记录要分配出去的chunk所在位置,开头存储一些metadata来管理这些chunk
  +0x010 DelayFreeList   : _SLIST_HEADER
  +0x020 AggregateExchg   : _INTERLOCK_SEQ //用来管理对应的userblock中还有多少freedchunk,LFH以此来判断是否从此userblock中分配
  +0x024 BlockSize       : Uint2B //此userblock中每个chunk的大小
  +0x026 Flags           : Uint2B
  +0x028 BlockCount       : Uint2B //此userblock中chunk的总数
  +0x02a SizeIndex       : UChar //该userblock对应的sizeindex
  +0x02b AffinityIndex   : UChar
  +0x024 Alignment       : [2] Uint4B
  +0x02c Lock             : Uint4B
  +0x030 SFreeListEntry   : _SINGLE_LIST_ENTRY

_INTERLOCK_SEQ

ntdll!_INTERLOCK_SEQ
  +0x000 Depth           : Uint2B //该userblock剩余freechunk的数量
  +0x002 Hint             : Pos 0, 15 Bits
  +0x002 Lock             : Pos 15, 1 Bit
  +0x002 Hint16           : Uint2B
  +0x000 Exchg           : Int4B

_HEAP_USERDATA_HEADER

ntdll!_HEAP_USERDATA_HEADER
  +0x000 SFreeListEntry   : _SINGLE_LIST_ENTRY
  +0x000 SubSegment       : Ptr64 _HEAP_SUBSEGMENT //指回对应的_HEAP_SUBSEGMENT
  +0x008 Reserved         : Ptr64 Void
  +0x010 SizeIndexAndPadding : Uint4B
  +0x010 SizeIndex       : UChar
  +0x011 GuardPagePresent : UChar
  +0x012 PaddingBytes     : Uint2B
  +0x014 Signature       : Uint4B
  +0x018 EncodedOffsets   : _HEAP_USERDATA_OFFSETS //用于检查Userdata的头部字段
  +0x020 BusyBitmap       : _RTL_BITMAP_EX //bitmap,用来记录使用的chunk
  +0x030 BitmapData       : [1] Uint8B

其中的EncodingOffset字段就是个验证,在USERBLOCK初始化时会生成这个数值作为验证用,其数值具体来说是以下四个值的xor:

(sizeof(userblock header)) | (blockunit*0x10 > 4
((chunk address) - (UserBlock address))  0x10的时候,置位_HEAP->CompatibilityFlag |= 0x20000000,下一次Allocate就会对LFH进行初始化:

  • 首先会ExtendFrontENdUsageData,也就是将这个数值增大,然后增加更大的_HEAP->BlocksIndex,因为这里_HEAP->BlocksIndex可以理解为一个_HEAP_LIST_LOOKUP结构的单向链表(参考上面Back-End的解释),且默认初始情况下只存在一个管理比较小的(0x0 ~ 0x80)的chunk的_HEAP_LIST_LOOKUP,所以这里会扩展到(0x80 ~ 0x400),即在链表尾追加一个管理更大chunk的_HEAP_LIST_LOOKUP结构体结点。

在 FrontEndHeapUsageData 写上对应的index,此时 enable LFH 范围变为 (idx: 0-0x400)
FrontEndHeapUsageData中分为两部分:对应用于判断LFH是否需要初始化的map以及已经enable LFH的chunk size (例如enable malloc 0x50大小的chunk,则写入0x50>>4=5)
 原BlocksIndex进行扩展,即新建一个BlocksIndex,写入原BlocksIndex->ExtendedLookup,进行扩展
  • 建立并初始化_HEAP->FrontEndHeap(通过mmap),即初始化_LFH_HEAP的一些metadata。

  • 建立并初始化_LFH_HEAP->SegmentInfoArrays[x],在SegmentInfoArrays[BucketIndex]处填上对应的_HEAP_LOCAL_SEGMENT_INFO结构体指针。

在初始化后,从LFH分配内存的逻辑为:

1.先看ActiveSubSegment中是否有可以分配的chunk,这个是否有的判断标准就是ActiveSubSegment->AggregateExchg->depth

2.如果没有就从CachedItem中找,找到的话会把ActiveSubSegment换成CachedItem中的SubSegment

到了这一步时,LFH分配器就找到了UserBlock,UserBlock中有很多的chunk可以供用户使用,LFH选取chunk的标准如下:

1.首先从RtlpLowFragHeapRandomData中下标为x处取一个值,这个名字很长的数组是一个长度为256byte的元素大小范围为0-0x7f的随机数数组,每次取,x都会自增1,如果x超过了256,那么x = rand()%256.

2.最终获取的index为RtlLowFragHeapRandomData[x]*maxidx >> 7,检查bitmap是否为0,如果冲突了的话就往后找最近的

3.检查(unused byte & 0x3f)!=0(表示chunk是free的)

4.最后设置index(chunk头部中的previoussize)和unusedbyte返回给用户。

释放

1.将unused位改成0x80

2.根据头部中的字段找到userblock,然后找会Subsegment,根据index设置bitmap

3.更新ActiveSubSegment->AggregateExchg

4.如果释放的chunk不属于当前的ActiveSubSegment就看一下能不能放到cachedItems中,可以就放进去。

利用方式 地址问题

先不考虑如何利用的事,首先关注最基本的问题,要泄漏什么地址?地址在哪?

假设我们有了任意内存地址读写,那么我们就需要泄漏一些关键的函数地址,比如说system,以及攻击的目标点,比如栈地址。

不同于linux,windows有一堆dll函数库。

这里,根据angelboy的slide,需要泄漏的地址为kernelbase以及stackaddress,这两个地址在kernel32.dll。

那么如何泄漏ntdll呢?_HEAP_LOCK相关的信息会指向ntdll,具体来说,就是_HEAP->LockVariable.Lock以及CriticalSection->DebugInfo

在ntdll!PebLdr中,_PEB_LDR_DATA可以找到所有dll的位置。

同样可以从IAT表中找到kernel32,不过需要先泄漏binary的地址。

在KERNELBASE!BasepFilterInfo中,会有大概率包含stack的指针,这个主要是因为内存没有初始化。

如果这个上面没有想要的地址,可以从PEB向后算一个page,通常会是TEB上,这上面也会有stack的地址信息。

攻击的话,angelboy提出的方式就是泄漏地址,然后攻击栈写rop或者shellcode。

后端利用方式 unlink

和linux中的unlink很像(都是双向链表的节点移除),但是绕过条件和linux不同,因为头部的信息不同,需要对一些encode的字段构造一下。还有就是flink和blink指向的是userdata部分。

具体构造就是p -> fd = &p-8, p->bk = &p.

前端利用方式

angelboy同样是只是草草的介绍了下如果有了uaf的话,如何绕过随机在LFHuserblock中分配到指定chunk的方式,具体来说就是填满其他的,下一次肯定就会落到目标点。那么有了uaf之后呢,劫持哪些指针劫持到哪里并没有说明。所以这里的话还需要后续调试的时候整理。

具体怎么攻击才叫合理?哪些攻击面呢?

由于Angelboy给的利用方式太少,而且比较笼统局限,所以我又参考了别的资料,想找到一些类似于linux堆利用手法的攻击方式。

然而现实打了我一巴掌,根据冠城大佬的ppt,在windows中,想通过攻击堆的头部或者其他字段来进行getshell几乎不可能,因为windows堆的防御机制十分严格。堆中比较合理的攻击手法似乎就只有unlink或者其他形式的修改函数指针的方式。

戳“阅读原文”体验免费靶场!

关注
打赏
1665306545
查看更多评论
立即登录/注册

微信扫码登录

1.1102s