23/7/20
Amateurs CTF 2023 逆向题解共10题
也不算难,但是真的很抽象……
ELF64
Inspired by recent "traumatic" events.
nc amt.rs 31010
在IDA中分析,要求输入3个数 b, v, p 满足
满足条件后再进行如下检测,其中 N = 4919,函数已重命名
1
2
3
4
|
v4 = cnt_digits(v);
if
( v4 == cnt_digits(b)
&& (v5 = sum_digits(v), v5 == sum_digits(b))
&& (v6 = fast_pow(N, v, p), v6 == fast_pow(N, b, p)) )
|
取 p = 4919可满足最后一个条件,按 k = 1~100查询 b 的各位和与位数,构造得
b = 1254536 = 54 * 22890 + 18476,
v = 1048562 = 1111_1111_1111_1111_0010b
就可以过关。
amateursCTF{yep_th0se_l00k_th3_s4me_to_m3!_:clueless:}
ELF64
Ugh... my head hurts...
Flag is amateursCTF{[a-zA-Z0-9_]+}
在IDA中分析,发现这个函数,感觉是用异或做的壳。
1
2
3
4
5
6
7
8
9
10
11
12
|
__int64
__fastcall sub_401290(_BYTE *s)
{
_DWORD *v2;
// rsi
if
( (*s ^ s[25]) != 86 )
return
0LL;
v2 = &loc_4012A4;
do
*v2++ ^= 0xEA228DE6;
while
( *(v2 - 1) != 0xEA228DE6 );
return
((
__int64
(*)(
void
))loc_4012A4)();
}
|
使用脚本还原后发现还有一层异或套壳,再解密发现还有一层,感到不太对劲。
观察了一下发现套壳的代码全部位于一段地址区间内,头部执行完一些逻辑之后跳转到尾部,
尾部对头尾区间内的数据异或解密,再跳转到新解密的代码的头部,如此往复,计算了一下大概有百来层。
头部长这样(rdi是函数输入的第一个参数即我们的输入)
mov r15b, [rdi+19h] xor r15b, [rdi] cmp r15b, 56h jz loc_404374 xor eax, eax retn loc_4012A4: ; 后面属于下一个区间
尾部长这样
loc_404374: mov eax, 0EA228DE6h lea rsi, loc_4012A4 loc_404381: xor [rsi], eax add rsi, 4 cmp [rsi-4], eax jnz short loc_404381 call loc_4012A4 retn
首先写IDA脚本还原这段地址区间的代码。
尾部的开头总是4个0x00标记上个区间的结尾,然后跟一个 mov eax, __MAGIC 。
用脚本提取之后需要nop掉加密代码并把call换成jmp,便于我们F5。
这里有一点很坑,在begin和end小于128时,字节码由call变成了call near,短了4个字节。
patch.py
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
|
from
ida_bytes
import
get_bytes, patch_bytes
def
xor_patch(BEGIN, END, MAGIC):
buf
=
get_bytes(BEGIN, END
-
BEGIN)
proc
=
bytes(buf[i] ^ MAGIC[i
%
len
(MAGIC)]
for
i
in
range
(
len
(buf)))
patch_bytes(BEGIN, proc)
print
(f
"0x{BEGIN:x}: patched {END - BEGIN} bytes"
)
begin
=
0x4012a4
end
=
0x404394
while
True
:
patch_bytes(end
-
8
, b
'\xe9'
)
# call to jmp
patch_bytes(end
-
3
, b
'\x90'
)
# ret to nop
end
-
=
36
if
end <
=
begin:
break
magic
=
get_bytes(end
+
5
,
4
)
xor_patch(begin, end, magic)
patch_bytes(end, b
'\x90'
*
28
)
# nop pad
begin
+
=
24
if
end
-
begin <
128
:
begin
-
=
4
# 破坏了原函数的最后一个ret,要改回来才行啊@_@
|
使用F5观察函数,大概有400多行,且所有的要求全为三元组 s[x] ^ s[y] = z。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
int
__fastcall check(_BYTE *s)
{
if
( (*s ^ s[25]) != 86 )
return
0;
if
( (s[14] ^ s[45]) != 29 )
return
0;
if
( (s[33] ^ s[34]) != 5 )
return
0;
if
( (s[40] ^ s[52]) != 5 )
return
0;
if
( (s[12] ^ s[56]) != 5 )
return
0;
// 后面省略
}
|
建立图 G
其中 vertex[0] = 'a' 已知,只需遍历图就可以得出整个字串。
当然首先要用正则提取一下 x , y , z 。
solve.py
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
|
import
re
pat1
=
re.
compile
(r
'\(\(s\[(\d+)\] \^ s\[(\d+)\]\) \!\= (\d+)\)'
)
pat2
=
re.
compile
(r
'\(s\[(\d+)\] \!\= s\[(\d+)\]\)'
)
flag
=
bytearray(b
'amateurs'
+
b
'\x00'
*
53
)
graph
=
[{}
for
i
in
range
(
61
)]
with
open
(
"src.c"
,
"r"
) as fp:
src
=
fp.read()
a
=
pat1.findall(src)
for
ai
in
a:
x, y, w
=
int
(ai[
0
]),
int
(ai[
1
]),
int
(ai[
2
])
if
y
in
graph[x]:
if
graph[x][y] !
=
w:
print
(f
"{x} {y} what??"
)
exit()
else
:
continue
graph[x][y]
=
graph[y][x]
=
w
print
(f
"{x} <-{w}-> {y}"
)
b
=
pat2.findall(src)
for
bi
in
b:
x, y
=
int
(bi[
0
]),
int
(bi[
1
])
if
y
in
graph[x]:
if
graph[x][y] !
=
w:
print
(f
"{x} {y} what??"
)
exit()
else
:
continue
graph[x][y]
=
graph[y][x]
=
0
print
(f
"{x} <-{0}-> {y}"
)
changed
=
True
while
changed:
changed
=
False
for
i
in
range
(
61
):
if
flag[i] !
=
0
:
for
j
in
graph[i].keys():
if
flag[j] !
=
0
:
continue
else
:
flag[j]
=
flag[i] ^ graph[i][j]
changed
=
True
print
(flag)
|
amateursCTF{i_h4v3_a_spli77ing_headache_1_r3qu1re_m04r_sl33p}
ELF64
Get rid of all your Rust rust with this brand new Rust-eze™ de-ruster.
Flag is amateursCTF{[a-zA-Z0-9_]+}
Rust编写的程序,先将输入和既定数组异或,再循环左移2位,最后和另一个既定数组比较。
1
2
3
4
5
6
7
|
for
( i = 0LL; i < 38; ++i )
{
v15 = e1[i] ^ input_str[i];
v23 = __ROL1__(v15, 2);
v15 = v23;
s[i] = v23;
}
|
solve.py
1
2
3
4
5
6
7
8
9
10
11
12
13
|
e1
=
[
39
,
151
,
87
,
225
,
169
,
117
,
102
,
62
,
27
,
99
,
-
29
,
-
96
,
5
,
115
,
89
,
-
5
,
10
,
67
,
-
113
,
-
32
,
-
70
,
-
64
,
84
,
-
103
,
6
,
-
65
,
-
97
,
47
,
-
60
,
-
86
,
-
90
,
116
,
30
,
-
35
,
-
105
,
34
,
-
19
,
-
59
]
e2
=
[
25
,
-
21
,
-
40
,
86
,
51
,
0
,
80
,
53
,
97
,
-
36
,
-
106
,
111
,
-
75
,
13
,
-
92
,
122
,
85
,
-
24
,
-
2
,
86
,
-
105
,
-
34
,
-
99
,
-
81
,
-
44
,
71
,
-
81
,
-
63
,
-
62
,
106
,
90
,
-
84
,
-
79
,
-
94
,
-
118
,
89
,
82
,
-
30
]
def
ror2(x):
x
=
x &
255
y
=
x &
3
return
((y <<
6
)
+
(x >>
2
)) &
255
flag
=
"".join(
chr
(ror2(y) ^ (x &
255
))
for
x, y
in
zip
(e1, e2))
print
(flag)
|
amateursCTF{h0pe_y0u_w3r3nt_t00_ru5ty}
PE64
My boss said Linux binaries wouldn't reach enough customers so I was forced to make a Windows version.
Flag is amateursCTF{[a-zA-Z0-9_]+}
Rust写的程序,但链接在Windows平台,可读性非常差。然而其逻辑基本与上题相同,按上题相同的解法可得字串'sorry_this_isnt_the_flag_this_time.'。
输入该字串,程序输出'Correct!',但并没有flag。
观察到加密函数中有语句(byte_7FF6BCD60000为一串乱码)
1
2
3
|
v14 = e1[idx] ^ s[idx]
if
( idx < 30 )
byte_7FF6BCD60000[idx] ^= v14;
|
假定输入的是flag的开头'amateursCTF',将byte_7FF6BCD60000, e1, s异或之后可得'sorry_this'。因此猜测如果输入上面得到的字串,该全局变量处就是flag。
debug得
amateursCTF{d0n3_4nd_deRust3d}
pyc
Which one do you hate more: decompiling pycs or reading Python bytecode disassembly? Just kidding that's a trick question.
Run with Python version 3.10.
Flag is amateursCTF{[a-zA-Z0-9_]+}
先用pycdc嗦一下,发现是个套娃:
把一串b64加密的python代码提取出来再exec。
1
2
3
|
b64decode
=
lambda
x:
id
.__self__.__dict__[
'exec'
](
id
.__self__.__dict__[
'__import__'
](
'base64'
).b64decode(x))
# 此处省略
b64decode(
'A'
.join(x[::
-
1
]))
|
这段代码解密如下,发现还是个套娃……
1
2
3
4
5
6
7
8
|
check
=
lambda
:
None
code
=
type
(check.__code__)(
1
,
0
,
0
,
6
,
5
,
67
, b
'|\x00d\x00d\x01\x85\x02\x19\x00d\x02k\x03r\x0et\x00j\x01j\x02d\x03\x19\x00S\x00|\x00d\x04\x19\x00d\x05k\x03r\x1at\x00j\x01j\x02d\x03\x19\x00S\x00|\x00d\x01d\x04\x85\x02\x19\x00}\x00t\x00j\x01j\x02d\x06\x19\x00|\x00\x83\x01d\x07k\x03r0t\x00j\x01j\x02d\x03\x19\x00S\x00g\x00}\x01t\x00j\x01j\x02d\x08\x19\x00|\x00\x83\x01D\x00]\r\\\x02}\x02}\x03|\x03d\tk\x02rG|\x01\xa0\x03|\x02\xa1\x01\x01\x00q:|\x01g\x00d\n\xa2\x01k\x03rTt\x00j\x01j\x02d\x03\x19\x00S\x00|\x00\xa0\x04\xa1\x00\xa0\x05d\x0b\xa1\x01}\x00|\x00d\x0c\x19\x00d\x00d\x00d\x04\x85\x03\x19\x00d\rk\x03rlt\x00j\x01j\x02d\x03\x19\x00S\x00|\x00d\x0e\x19\x00d\x0c\x19\x00|\x00d\x0e\x19\x00d\x0e\x19\x00\x17\x00|\x00d\x0e\x19\x00d\x0f\x19\x00\x18\x00|\x00d\x0e\x19\x00d\x0e\x19\x00|\x00d\x0e\x19\x00d\x0f\x19\x00\x17\x00|\x00d\x0e\x19\x00d\x0c\x19\x00\x18\x00|\x00d\x0e\x19\x00d\x0f\x19\x00|\x00d\x0e\x19\x00d\x0c\x19\x00\x17\x00|\x00d\x0e\x19\x00d\x0e\x19\x00\x18\x00f\x03d\x10k\x03r\xa9t\x00j\x01j\x02d\x03\x19\x00S\x00t\x00j\x01j\x02d\x11\x19\x00d\x12\x83\x01\xa0\x06|\x00d\x0f\x19\x00\xa1\x01\xa0\x07\xa1\x00d\x13k\x03r\xc0t\x00j\x01j\x02d\x03\x19\x00S\x00t\x00j\x01j\x02d\x11\x19\x00d\x14\x83\x01}\x04|\x04\xa0\x08|\x00d\x0f\x19\x00\xa1\x01\x01\x00t\x00j\x01j\x02d\x15\x19\x00|\x00d\x16\x19\x00\x83\x01|\x00d\x16<\x00|\x04\xa0\t|\x00d\x16\x19\x00\xa1\x01\x01\x00|\x00d\x16\x19\x00g\x00d\x17\xa2\x01k\x03r\xf0t\x00j\x01j\x02d\x03\x19\x00S\x00|\x00d\x18\x19\x00d\x19\x17\x00d\x1ak\x03r\xfet\x00j\x01j\x02d\x03\x19\x00S\x00t\x00j\x01j\x02d\x1b\x19\x00\xa0\n|\x00d\x1c\x19\x00d\x0cd\x18\x85\x02\x19\x00d\x1d\xa1\x02|\x04\xa0\x0bd\x0cd\x1e\xa1\x02A\x00d\x1fk\x03\x90\x01r\x1dt\x00j\x01j\x02d\x03\x19\x00S\x00t\x00j\x01j\x02d\x1b\x19\x00\xa0\n|\x00d\x1c\x19\x00d\x18d \x85\x02\x19\x00d\x1d\xa1\x02|\x04\xa0\x0bd\x0cd\x1e\xa1\x02A\x00d!k\x03\x90\x01r<t\x00j\x01j\x02d\x03\x19\x00S\x00t\x00j\x01j\x02d\x1b\x19\x00\xa0\n|\x00d\x1c\x19\x00d d\x01\x85\x02\x19\x00d"\x17\x00d\x1d\xa1\x02|\x04\xa0\x0bd\x0cd\x1e\xa1\x02A\x00d#k\x03\x90\x01r]t\x00j\x01j\x02d\x03\x19\x00S\x00d\x0c}\x05|\x00d$\x19\x00D\x00]\x0b}\x02|\x05d%9\x00}\x05|\x05|\x027\x00}\x05\x90\x01qct\x00j\x01j\x02d&\x19\x00|\x05\x83\x01d\'k\x03\x90\x01r\x80t\x00j\x01j\x02d\x03\x19\x00S\x00t\x00j\x01j\x02d(\x19\x00S\x00'
, (
None
,
12
,
'amateursCTF{'
,
'False'
,
-
1
,
'}'
,
'len'
,
42
,
'enumerate'
,
'_'
, (
7
,
11
,
13
,
20
,
23
,
35
), b
'_'
,
0
, b
'sn0h7YP'
,
1
,
2
, (
160
,
68
,
34
),
'__import__'
,
'hashlib'
,
'4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a'
,
'random'
,
'list'
,
3
, (
49
,
89
,
102
,
109
,
108
,
52
),
4
, b
'freebie'
, b
'0ffreebie'
,
'int'
,
5
,
'little'
,
4294967295
,
4227810561
,
8
,
825199122
, b
'\x00'
,
4277086886
,
6
,
128
,
'hex'
,
'0x29ee69af2f3'
,
'True'
,
'Did you know? pycdc can decompile marshaled code objects. Just make sure you mention the right version!'
), (
'id'
,
'__self__'
,
'__dict__'
,
'append'
,
'encode'
,
'split'
,
'sha256'
,
'hexdigest'
,
'seed'
,
'shuffle'
,
'from_bytes'
,
'randint'
), (
'input'
,
'underscores'
,
'i'
,
'x'
,
'r'
,
'c'
), '
', '
check
', 3, b'
\x10\x01\x0c\x01\x0c\x01\x0c\x01\x0c\x01\x14\x02\x0c\x01\x04\x02\x18\x01\x08\x01\n\x01\x02\x80\x0c\x01\x0c\x01\x0e\x02\x16\x01\x0c\x01n\x03\x0c\x01"\x02\x0c\x01\x10\x02\x0e\x01\x18\x01\x0e\x01\x10\x02\x0c\x01\x10\x02\x0c\x012\x02\x0c\x012\x02\x0c\x016\x02\x0c\x01\x04\x02\x0c\x01\x08\x01\x0c\x01\x16\x02\x0c\x01\x0c\x02', (), ())
check
=
type
(check)(code, {
'id'
:
id
})
if
check(
input
(
"Enter the flag: "
)):
print
(
"Correct!"
)
else
:
print
(
"Incorrect."
)
|
用dis去读这个代码对象的python字节码,解密要求如下:
输入为'amateurs{FLAG}',其中FLAG长度为42,由7段组成:0000000_111_2_333333_44_55555555555_666666
amateursCTF{PY7h0ns_ar3_4_f4m1lY_0f_N0Nv3nom0us_Sn4kes}
蟒蛇是一种无毒蛇。
ELF64(Coredump)
I was doing some homework for my Data Structures and Algorithms class, but my program unexpectedly crashed when I entered in my flag. Could you help me get it back?
Here's the coredump and the binary, I'll even toss in the header file. Can't give out the source code though, how do I know you won't cheat off me?
使用IDA分析,建立结构体如下:(data为1字节数据,7字节padding)
1
2
3
4
5
6
7
8
9
10
|
00
listnode_t struc
00
data db
8
dup(?)
08
ptr dq ? ; offset
10
listnode_t ends
00
list_t struc
00
len
dd ?
04
pad dd ?
08
head dq ? ; offset
10
list_t ends
|
阅读源程序可知,作者将输入字串提取成链表,但混淆数据时竟将指针异或导致段错误。
将coredump使用gdb调试,发现core格式错误无法调试。
考虑到其一定包含内存的镜像,直接用rehex打开寻找堆的位置。
具体方法是搜索后跟7个0x00字节的'a'字节,即b'\x61\x00\x00\x00\x00\x00\x00\x00'。
amateursCTF{l1nk3d_bY_4n_xor}
java
I heard my professor talking about some "Java Virtual Machine" and its weird gimmicks, so I took it upon myself to complete one. It wasn't even that hard? I don't know why he was complaining about it so much.
Compiled with openjdk 11.0.19.
Run with java JVM code.jvm.
字节码 | 解释 |
---|---|
0,1,2,3 | swap(reg[i0],reg[i1]) |
8 | reg[i1]+=i2 |
9 | reg[i1]+=reg[i2] |
12 | reg[i1]-=i2 |
13 | reg[i1]-=reg[i2] |
16 | reg[i1]*=i2 |
17 | reg[i1]*=reg[i2] |
20 | r[i1]/=i2 |
21 | r[i1]/=r[i2] |
24 | r[i1]%=i2 |
25 | r[i1]%=r[i2] |
28 | r[i1]<<=i2 |
29 | r[i1]<<=r[i2] |
31 | r[i1]=read() |
32 | push(read()) |
33 | write(r[i1]) |
34 | write(pop()) |
41 | jmp i2 if !r[i1] |
42 | jmp i2 if r[i1] |
43 | jmp i1 |
52 | push(r[i1]) |
53 | r[i1]=pop() |
54 | push(i1) |
127 | quit() |
disas.py
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
|
with
open
(
"code.jvm"
,
"rb"
) as fp:
with
open
(
"asm.txt"
,
"w"
) as fq:
program
=
bytearray(fp.read())
idx
=
0
while
idx <
len
(program):
b0
=
program[idx]
b1
=
program[idx
+
1
]
if
idx
+
1
<
len
(program)
else
0xff
b2
=
program[idx
+
2
]
if
idx
+
2
<
len
(program)
else
0xff
if
b0
=
=
0
or
b0
=
=
1
or
b0
=
=
2
or
b0
=
=
3
:
fq.write(f
"{idx} swap r{b0} r{b1}\n"
)
idx
+
=
2
elif
b0
=
=
8
:
fq.write(f
"{idx} add r{b1}, {b2}\n"
)
idx
+
=
3
elif
b0
=
=
9
:
fq.write(f
"{idx} add r{b1}, r{b2}\n"
)
idx
+
=
3
elif
b0
=
=
12
:
fq.write(f
"{idx} sub r{b1}, {b2}\n"
)
idx
+
=
3
elif
b0
=
=
13
:
fq.write(f
"{idx} sub r{b1}, r{b2}\n"
)
idx
+
=
3
elif
b0
=
=
16
:
fq.write(f
"{idx} mul r{b1}, {b2}\n"
)
idx
+
=
3
elif
b0
=
=
17
:
fq.write(f
"{idx} mul r{b1}, r{b2}\n"
)
idx
+
=
3
elif
b0
=
=
20
:
fq.write(f
"{idx} div r{b1}, {b2}\n"
)
idx
+
=
3
elif
b0
=
=
21
:
fq.write(f
"{idx} div r{b1}, r{b2}\n"
)
idx
+
=
3
elif
b0
=
=
24
:
fq.write(f
"{idx} mod r{b1}, {b2}\n"
)
idx
+
=
3
elif
b0
=
=
25
:
fq.write(f
"{idx} mod r{b1}, r{b2}\n"
)
idx
+
=
3
elif
b0
=
=
28
:
fq.write(f
"{idx} shl r{b1}, {b2}\n"
)
idx
+
=
3
elif
b0
=
=
29
:
fq.write(f
"{idx} shl r{b1}, r{b2}\n"
)
idx
+
=
3
elif
b0
=
=
31
:
fq.write(f
"{idx} read r{b1}\n"
)
idx
+
=
2
elif
b0
=
=
32
:
fq.write(f
"{idx} rdpush\n"
)
idx
+
=
1
elif
b0
=
=
33
:
fq.write(f
"{idx} write r{b1}\n"
)
idx
+
=
2
elif
b0
=
=
34
:
fq.write(f
"{idx} wrpop\n"
)
idx
+
=
1
elif
b0
=
=
41
:
fq.write(f
"{idx} jmp {b2} if !r{b1}\n"
)
idx
+
=
3
elif
b0
=
=
42
:
fq.write(f
"{idx} jmp {b2} if r{b1}\n"
)
idx
+
=
3
elif
b0
=
=
43
:
fq.write(f
"{idx} jmp {b1}\n"
)
idx
+
=
2
elif
b0
=
=
52
:
fq.write(f
"{idx} push r{b1}\n"
)
idx
+
=
2
elif
b0
=
=
53
:
fq.write(f
"{idx} pop r{b1}\n"
)
idx
+
=
2
elif
b0
=
=
54
:
fq.write(f
"{idx} push {b1}\n"
)
idx
+
=
2
elif
b0
=
=
127
:
fq.write(f
"{idx} quit\n"
)
idx
+
=
1
else
:
if
idx
+
1
<
len
(program)
and
idx
+
2
<
len
(program):
program[idx]
=
b0 ^ b1 ^ b2
program[idx
+
1
]
=
b1
program[idx
+
2
]
=
b0
else
:
print
(
"what??"
)
exit()
|
代码逻辑很简单,得
amateursCTF{wh4t_d0_yoU_m34n_j4v4_isnt_A_vm?}
JVM,指用Java做的VM。
scratch3.0
I was making this simple flag checker in Scratch, but my friend logged into my account and messed up all my variable names :(. Can you help me recover my flag please?
You should run on Turbowarp for better performance.
Turbowarp打开发现积木团在一起,变量名也混淆过。
Scratch的项目sb3其实是个zip,由资源文件和积木json文件压缩得到。
首先需要把变量名全部改好看点。
recover.py
1
2
3
4
5
6
7
8
9
10
|
import
re
pat1
=
re.
compile
(r
"(DAT_694206942069420(0*))"
)
def
rename(s):
return
'Name'
+
str
(
len
(s.groups()[
1
]))
with
open
(
"./project.json"
,
"r"
) as fp:
buf
=
re.sub(pat1, rename, fp.read())
with
open
(
"./recovered/project.json"
,
"w"
) as fq:
fq.write(buf)
|
再上Turbowarp发现还是不太好读,但注意到一个积木中的常数0x9e3779b9,应该是TEA加密。
只要把某块积木拆开就可以下断点,debug了一下发现确实如此。
solve.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
dump
=
[
239
,
202
,
230
,
114
,
17
,
147
,
199
,
39
,
182
,
230
,
119
,
248
,
78
,
246
,
224
,
46
,
99
,
164
,
112
,
134
,
30
,
216
,
53
,
194
,
60
,
75
,
223
,
122
,
67
,
202
,
207
,
56
,
16
,
128
,
216
,
142
,
248
,
16
,
27
,
202
,
119
,
105
,
158
,
232
,
251
,
201
,
158
,
69
,
242
,
193
,
90
,
191
,
63
,
96
,
38
,
164
]
key
=
[
69420
,
1412141
,
1936419188
,
1953260915
]
# print(len(dump))
# 56
def
decrypt(v, k):
total, delta
=
0xc6ef3720
,
0x9e3779b9
v0, v1
=
int
(v[
0
].
hex
(),
16
),
int
(v[
1
].
hex
(),
16
)
k0, k1, k2, k3
=
k[
0
], k[
1
], k[
2
], k[
3
]
for
i
in
range
(
32
):
v1
=
(v1
-
(((v0 <<
4
)
+
k2) ^ (v0
+
total)
^ ((v0 >>
5
)
+
k3))) &
0xffffffff
v0
=
(v0
-
(((v1 <<
4
)
+
k0) ^ (v1
+
total)
^ ((v1 >>
5
)
+
k1))) &
0xffffffff
total
=
(total
-
delta) &
0xffffffff
return
bytes.fromhex(
hex
(v0)[
2
:])
+
bytes.fromhex(
hex
(v1)[
2
:])
flag
=
b''
for
idx
in
range
(
0
,
len
(dump),
8
):
flag
+
=
decrypt([bytes(dump[idx:idx
+
4
]), bytes(dump[idx
+
4
:idx
+
8
])], key)
print
(flag)
|
amateursCTF{screw_scratch_llvm_we_code_by_hand_1a89c87b}
你说的对,但是Scratch是麻省理工学院的“终身幼儿园团队”在2007年发布的一种图形化编程工具。
emojicode
I apologize in advance.
Flag is amateursCTF{[a-z0-9_]+}
Compiled using the latest version of emojicode
Note that there may be multiple inputs that cause the program to print ✅. The correct input (and the flag) has sha256 hash 53cf379fa8fd802fd2f99b2aa395fe8b19b066ab5e2ff49e44633ce046c346c4.
emojicode,源码就特别抽象,需要对着官网的文档搜索查询。
过关要求:
输入的每个字符按二进制顺序排成新的字符串,
如 abcd->61626364->1100001_1100010_1100011_1100100 ,
然后将其顺序填入16*16的棋盘中,不够用0补齐。
在每一行和每一列中,统计所有连续的'1'的块的长度记为列表,
如 1101101110 中有3个连续块,长度为 [2, 2, 3] 。
输入flag,使得每一行每一列的连续块长度列表恰为给定值。
爆破感觉很麻烦,因为开头是'amateursCTF'已经给定了,所以打算像扫雷一样玩一下。
最后长这样:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
1100001110110111
0000111101001100
1011110101111001
0111001110000111
0101001000110111
1011110111110100
0110100111100111
0111111100010110
0101110110011011
1111011101100111
1110011101111111
0100111011101110
0111100011100100
1100101101111111
0110111010011101
0111000111111101
|
光是得到比特串还不够,因为flag可能是数字也可能是字母、符号(长度可能是6也可能是7),
所以还是需要爆破(enc为得到的比特串)。
solve.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
# 此处省略
keys
=
'0123456789_abcdefghijklmnopqrstuvwxyz'
def
crack(s, i):
if
i
=
=
len
(enc):
s
=
'amateursCTF{'
+
s
+
'}'
if
sha256(bytes(s,
'utf-8'
)).hexdigest()
=
=
sha256hex:
print
(s)
if
i
+
6
<
=
len
(enc):
x6
=
chr
(
int
(enc[i : i
+
6
],
2
))
if
x6
in
keys:
crack(s
+
x6, i
+
6
)
if
i
+
7
<
=
len
(enc):
x7
=
chr
(
int
(enc[i : i
+
7
],
2
))
if
x7
in
keys:
crack(s
+
x7, i
+
7
)
crack('',
0
)
|
amateursCTF{7his_belongs_ins1de_mi5c}
misc都没这个抽象……
javascript/html
Someone wanted this, so I delivered. Have fun!
jsrev.amt.rs
用three.js写的玩球的游戏,阅读源码发现这样一段
1
2
3
4
5
6
7
8
9
|
let positions = [];
await fetch(
'./coords.json'
)
.then((response) => response.json())
.then((data) => { positions = data; });
for
(let i = 0; i < positions.length; i++) {
const [x, y, z] = positions[i];
spheres[i].collider.set(
new
THREE.Vector3(x, y, z), SPHERE_RADIUS);
}
|
爬取coords.json发现是三元组组成的列表,每个三元组代表一个初始球的坐标。
又观察到coords.json按y轴大小拍好了序,y轴相同的点排在了一块。
于是怀疑这些点排成了flag,并且从xOz看,y轴的一片就代表了一个字符。
用pyplot绘制散点图,注意xOz要给一点偏移,不然所有的点从上往下看全部团一块了看不清。
solve.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
import
matplotlib.pyplot as pyplot
import
numpy
coords
=
[]
with
open
(
'coords.json'
,
'r'
) as fp:
coords
=
eval
(fp.read())
x
=
numpy.array([(c[
2
]
-
(c[
1
]
-
34
)
*
10
)
for
c
in
coords])
y
=
numpy.array([
-
c[
0
]
for
c
in
coords])
z
=
numpy.array([
0
for
c
in
coords])
ax
=
pyplot.subplot(projection
=
'3d'
)
ax.set_title(
'jsrev'
)
ax.scatter(x, y, z, c
=
'b'
)
ax.set_xlabel(
'x'
)
ax.set_ylabel(
'y'
)
ax.set_zlabel(
'z'
)
pyplot.axis(
'scaled'
)
# 很重要,比例保持原始,否则pyplot会主动变比例
pyplot.show()
|
amateursCTF{asK_4nD_thr33_5h4ll_r3c31v3}
完结撒花
更多【 Amateurs CTF 2023 逆向WP】相关视频教程:www.yxfzedu.com