Skip to content

[Google CTF 2021] CPP WriteUp

Published: at 21:42

打完 CISCN 2021 无事,想起群里之前说到的 DRM,于是和队友一起尝试了一下(

ToC

预处理

由于各种 #xxx 完全没有缩进,因此使用 clang-format 对预处理器进行格式化处理:

Terminal window
clang-format --style='{IndentPPDirectives: AfterHash}' cpp.c

缩进后的代码虽然算不上好读,但至少能够分析了:

观察

#if __INCLUDE_LEVEL__ > 12 一行开始,接下来就是大段的 #if S == xx。在每个 #if 之后紧跟的又是 #unset S#defineS,对应的是 S++。每个 S 对应一条不同的指令。

简单观察一条指令,不难发现其每次都会对 0-7 进行 define。比如上面截图里就 defineR0-R7。因此不难想到一个 R 其实是由 8 位构成的,即一个字节。因此之后的整理也就基本基于这个思路了。

分析

分析后的指令对照如下所示:

#if __INCLUDE_LEVEL__ > 12
# if S == 0
# define S 24
# endif
# if S == 1
//# S++
//# R = !R
# endif
# if S == 2
//# S++
//# Z = 0b00000001
# endif
# if S == 3
//# S++
//# R = R + Z
# endif
# if S == 4
//# S++
//# R = R + Z
# endif
# if S == 5
//# S++
//# if R == 0 {
//# S = 38
//# }
# endif
# if S == 6
//# S++
//# R = R + Z
# endif
# if S == 7
//# S++
//# if R == 0 {
//# S = 59
//# }
# endif
# if S == 8
//# S++
//# R = R + Z
# endif
# if S == 9
//# S++
//# if R == 0 {
//# S = 59
//# }
# endif
# if S == 10
//# S++
# error "BUG"
# endif
# if S == 11
//# S = -1
# endif
# if S == 12
//# S++
//# X = 0b00000001
# endif
# if S == 13
//# S++
//# Y = 0
# endif
# if S == 14
//# S++
//# if X == 0 {
//# S = 22
//# }
# endif
# if S == 15
//# S++
//# Z = X
# endif
# if S == 16
//# S++
//# Z = Z & B
# endif
# if S == 17
//# S++
//# if Z == 0 {
//# S = 19
//# }
# endif
# if S == 18
//# S++
//# Y = Y + A
# endif
# if S == 19
//# S++
//# X = X + X
# endif
# if S == 20
//# S++
//# A = A + A
# endif
# if S == 21
//# S = 14
# endif
# if S == 22
//# S++
//# A = Y
# endif
# if S == 23
//# S = 1
# endif
# if S == 24
//# S++
//# I = 0
# endif
# if S == 25
//# S++
//# M = 0
# endif
# if S == 26
//# S++
//# N = 0b00000001
# endif
# if S == 27
//# S++
//# P = 0
# endif
# if S == 28
//# S++
//# Q = 0
# endif
# if S == 29
//# S++
//# B = 0b11100101
# endif
# if S == 30
//# S++
//# B = B + I
# endif
# if S == 31
//# S++
//# if B == 0 {
//# S = 56
//# }
# endif
# if S == 32
//# S++
//# B = 0b10000000
# endif
# if S == 33
//# S++
//# B = B + I
# endif
# if S == 34
//# S++
//# l = B
//# A = [l]
# endif
# if S == 35
//# S++
//# l = I
//# B = [l]
# endif
# if S == 36
//# S++
//# R = 0b00000001
# endif
# if S == 37
//# S = 12
# endif
# if S == 38
//# S++
//# O = M
# endif
# if S == 39
//# S++
//# O = O + N
# endif
# if S == 40
//# S++
//# M = N
# endif
# if S == 41
//# S++
//# N = O
# endif
# if S == 42
//# S++
//# A = A + M
# endif
# if S == 43
//# S++
//# B = 0b00100000
# endif
# if S == 44
//# S++
//# B = B + I
# endif
# if S == 45
//# S++
//# l = B
//# C = [l]
# endif
# if S == 46
//# S++
//# A = A ^ C
# endif
# if S == 47
//# S++
//# P = P + A
# endif
# if S == 48
//# S++
//# B = 0b01000000
# endif
# if S == 49
//# S++
//# B = B + I
# endif
# if S == 50
//# S++
//# l = B
//# A = [l]
# endif
# if S == 51
//# S++
//# A = P ^ A
# endif
# if S == 52
//# S++
//# Q = Q | A
# endif
# if S == 53
//# S++
//# A = 0b00000001
# endif
# if S == 54
//# S++
//# I = A + I
# endif
# if S == 55
//# S = 29
# endif
# if S == 56
//# S++
//# if Q == 0 {
//# S = 58
//# }
# endif
# if S == 57
//# S++
# error "INVALID_FLAG"
# endif
# if S == 58
//# S = -1
# endif
#else
# if S != -1
# include "cpp.c"
# endif
# if S != -1
# include "cpp.c"
# endif
#endif

整理后得到如下流程:

_1:
if (R == 1)
R = !R
RET
}
if (R == 2)
R = !R
JMP _59 (return)
}
if (R == 3)
R = !R
JMP _59 (return)
}
unreachable!()
_11:
return
############################################
// for (X = 1, Y = 0; X != 0; X *= 2, A *= 2)
_12:
X = 1
Y = 0
while(X != 0) {
Z = X & B
if (Z != 0) {
Y = Y + A
}
X *= 2 // 8 times
A *= 2
}
//for (X = 1; X <= 8; X++) {
// if (B & (1 << X)) {
// Y += A * (1 << X)
// }
//}
############################################
_22:
A = Y
JMP _1
_main:
I = 0
M = 0
N = 1
P = 0
Q = 0
_29:
B = 229 + I
if (B == 0) {
if (Q == 0) {
return
} else {
invalid flag
}
}
########################
A = FLAG[I]
B = [I]
R = 1
CALL _12
// A = A * [I]
########################
O = M + N
M = N
N = O
// M: 1 1 2 3 5 8 13 21 34 55 89 144 233 121 98 219 61 24 85 109 194 47 241 32 17 49 66
// A += fib(I)
A = A + M
B = I + 32
C = [B]
A = A ^ C
// P += (FLAG[I]*[I] + fib(I)) ^ [32+I]
P = P + A
B = I + 64
A = [B]
// P == [64+I]
A = A ^ P
Q = Q | A
I++
JMP _29

最后得到脚本:

function to_byte(n) {
return n & 0xff;
}
const first = [
0b10111011, 0b01010101, 0b10101011, 0b11000101, 0b10111001, 0b10011101,
0b11001001, 0b01101001, 0b10111011, 0b00110111, 0b11011001, 0b11001101,
0b00100001, 0b10110011, 0b11001111, 0b11001111, 0b10011111, 0b00001001,
0b10110101, 0b00111101, 0b11101011, 0b01111111, 0b01010111, 0b10100001,
0b11101011, 0b10000111, 0b01100111,
];
const second = [
0b00001000, 0b01100100, 0b01100100, 0b00110101, 0b10010001, 0b01100100,
0b11100111, 0b10100000, 0b00000110, 0b10101010, 0b11011101, 0b01110101,
0b00010111, 0b10011101, 0b01101101, 0b01011100, 0b01011110, 0b00011001,
0b11111101, 0b11101001, 0b00001100, 0b11111001, 0b10110100, 0b10000011,
0b10000110, 0b00100010, 0b01000010,
];
const third = [
0b11111010, 0b01111011, 0b00011011, 0b10111010, 0b00011110, 0b10110100,
0b10110011, 0b01011000, 0b11000110, 0b11110011, 0b10001100, 0b10010000,
0b00111011, 0b10111010, 0b00011001, 0b01101110, 0b11001110, 0b11011111,
0b11110001, 0b00100101, 0b10001101, 0b01000000, 0b10000000, 0b01110000,
0b11100000, 0b01001101, 0b00011100,
];
const possible =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789{}_"
.split("")
.map(c => c.charCodeAt(0));
const fib = [
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584,
4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418,
].map(to_byte);
function guess(i) {
return possible
.filter(c => {
let P = i === 0 ? 0 : third[i - 1];
P += (to_byte(c * first[i]) + fib[i]) ^ second[i];
P = to_byte(P);
return P === third[i];
})
.map(c => String.fromCharCode(c));
}
[...Array(27).keys()]
.map(i => guess(i))
.map(a => a[0])
.join("");

flagCTF{pr3pr0cess0r_pr0fe5sor}


Previous Post
获取 アソビステージ 的实际播放链接
Next Post
Project Anni 之旅 01 - 从 clap-builder 到 derive