ISCTF2025 赛后WP
只写自己做出来的,没做出来的以后再学学吧。
Re 逆向
ezzz_math
加密函数在 sub_401000() 里,点进去后看到是个解方程。
点击查看代码
BOOL __cdecl sub_401000(char *Str)
{
return 94 * Str[22]
+ 74 * Str[21]
+ 70 * Str[19]
+ 12 * Str[18]
+ 20 * Str[16]
+ 62 * Str[12]
+ 82 * Str[10]
+ 7 * Str[7]
+ 63 * Str[6]
+ 18 * Str[5]
+ 58 * Str[4]
+ 94 * Str[2]
+ 77 * *Str
- 43 * Str[1]
- 37 * Str[3]
- 97 * Str[8]
- 23 * Str[9]
- 86 * Str[11]
- 6 * Str[13]
- 5 * Str[14]
- 79 * Str[15]
- 63 * Str[17]
- 93 * Str[20] == 20156
&& 87 * Str[22]
+ 75 * Str[21]
+ 73 * Str[15]
+ 67 * Str[14]
+ 30 * Str[13]
+ (Str[11] << 6)
+ 35 * Str[9]
+ 91 * Str[7]
+ 91 * Str[5]
+ 34 * Str[3]
+ 74 * *Str
- 89 * Str[1]
- 72 * Str[2]
- 76 * Str[4]
- 32 * Str[6]
- 97 * Str[8]
- 39 * Str[10]
- 23 * Str[12]
+ 8 * Str[16]
- 98 * Str[17]
- 4 * Str[18]
- 80 * Str[19]
- 83 * Str[20] == 7183
&& 51 * Str[21]
+ 22 * Str[20]
+ 15 * Str[19]
+ 51 * Str[17]
+ 96 * Str[12]
+ 34 * Str[7]
+ 77 * Str[5]
+ 59 * Str[2]
+ 89 * Str[1]
+ 92 * *Str
- 85 * Str[3]
- 50 * Str[4]
- 51 * Str[6]
- 75 * Str[8]
- 40 * Str[10]
- 4 * Str[11]
- 74 * Str[13]
- 98 * Str[14]
- 23 * Str[15]
- 14 * Str[16]
- 92 * Str[18]
- 7 * Str[22] == -7388
&& 61 * Str[22]
+ 72 * Str[21]
+ 28 * Str[20]
+ 55 * Str[18]
+ 20 * Str[17]
+ 13 * Str[14]
+ 51 * Str[13]
+ 69 * Str[12]
+ 10 * Str[11]
+ 95 * Str[10]
+ 43 * Str[9]
+ 53 * Str[8]
+ 76 * Str[7]
+ 25 * Str[6]
+ 9 * Str[5]
+ 10 * Str[4]
+ 98 * Str[1]
+ 70 * *Str
- 22 * Str[2]
+ 2 * Str[3]
- 49 * Str[15]
+ 4 * Str[16]
- 77 * Str[19] == 69057
&& 7 * Str[22]
+ 21 * Str[16]
+ 22 * Str[13]
+ 55 * Str[9]
+ 66 * Str[8]
+ 78 * Str[5]
+ 10 * Str[3]
+ 80 * Str[1]
+ 65 * *Str
- 20 * Str[2]
- 53 * Str[4]
- 98 * Str[6]
+ 8 * Str[7]
- 78 * Str[10]
- 94 * Str[11]
- 93 * Str[12]
- 18 * Str[14]
- 48 * Str[15]
- 9 * Str[17]
- 73 * Str[18]
- 59 * Str[19]
- 68 * Str[20]
- 74 * Str[21] == -31438
&& 33 * Str[19]
+ 78 * Str[15]
+ 66 * Str[10]
+ 3 * Str[9]
+ 43 * Str[4]
+ 24 * Str[3]
+ 3 * Str[2]
+ 27 * *Str
- 18 * Str[1]
- 46 * Str[5]
- 18 * Str[6]
- Str[7]
- 33 * Str[8]
- 50 * Str[11]
- 23 * Str[12]
- 37 * Str[13]
- 45 * Str[14]
+ 2 * Str[16]
- Str[17]
- 60 * Str[18]
- 87 * Str[20]
- 72 * Str[21]
- 6 * Str[22] == -26121
&& 31 * Str[20]
+ 80 * Str[18]
+ 34 * Str[17]
+ 34 * Str[15]
+ 38 * Str[14]
+ 53 * Str[13]
+ 35 * Str[12]
+ 82 * Str[9]
+ 27 * Str[8]
+ 80 * Str[7]
+ 46 * Str[6]
+ 18 * Str[4]
+ 5 * Str[1]
+ 98 * *Str
- 12 * Str[2]
- 9 * Str[3]
- 57 * Str[5]
- 46 * Str[10]
- 31 * Str[11]
- 68 * Str[16]
- 94 * Str[19]
- 93 * Str[21]
- 15 * Str[22] == 26005
&& 81 * Str[21]
+ 40 * Str[20]
+ 34 * Str[19]
+ 94 * Str[18]
+ 98 * Str[17]
+ 11 * Str[14]
+ 63 * Str[13]
+ 95 * Str[12]
+ 43 * Str[11]
+ 99 * Str[10]
+ 29 * Str[9]
+ 81 * Str[6]
+ 72 * Str[5]
+ 54 * Str[3]
+ 21 * *Str
- 26 * Str[1]
- 90 * Str[2]
- 15 * Str[4]
- 54 * Str[7]
- 12 * Str[8]
- 38 * Str[15]
- 15 * Str[16]
- 56 * Str[22] == 57169
&& 71 * Str[18]
+ 39 * Str[17]
+ 73 * Str[15]
+ 14 * Str[14]
+ 56 * Str[12]
+ 56 * Str[10]
+ 27 * Str[9]
+ 68 * Str[7]
+ 39 * Str[6]
+ 26 * Str[5]
+ 40 * Str[4]
+ 24 * Str[3]
+ 11 * Str[2]
+ 14 * Str[1]
+ 94 * *Str
- 10 * Str[8]
- 11 * Str[11]
- 63 * Str[13]
- 39 * Str[16]
- 14 * Str[19]
- 17 * Str[20]
- 23 * Str[21]
- 7 * Str[22] == 40024
&& (Str[22] << 6)
+ 80 * Str[21]
+ 89 * Str[20]
+ 70 * Str[19]
+ 66 * Str[18]
+ 55 * Str[17]
+ 16 * Str[16]
+ 84 * Str[13]
+ 48 * Str[12]
+ 11 * Str[7]
+ 32 * Str[5]
+ 99 * *Str
- 26 * Str[1]
- 91 * Str[2]
- 96 * Str[3]
- 63 * Str[4]
- 67 * Str[6]
- 72 * Str[8]
+ 4 * Str[9]
- 84 * Str[10]
- 81 * Str[11]
- 80 * Str[14]
- 98 * Str[15] == 432
&& Str[21]
+ 41 * Str[17]
+ 46 * Str[12]
+ 44 * Str[9]
+ 63 * *Str
- 73 * Str[1]
- 43 * Str[2]
+ 4 * Str[3]
- 37 * Str[4]
- 54 * Str[5]
- 58 * Str[6]
- 95 * Str[7]
- 2 * Str[8]
- 37 * Str[10]
- 5 * Str[11]
+ 2 * Str[13]
- 46 * Str[14]
- 27 * Str[15]
- 19 * Str[16]
- 78 * Str[18]
- 51 * Str[19]
- 82 * Str[20]
- 59 * Str[22] == -57338
&& 10 * Str[22]
+ 58 * Str[18]
+ 16 * Str[17]
+ 69 * Str[16]
+ 6 * Str[15]
+ 5 * Str[12]
+ 87 * Str[7]
+ 47 * Str[5]
+ 91 * Str[4]
+ 54 * Str[2]
+ 21 * Str[1]
+ 52 * *Str
- 76 * Str[3]
- 96 * Str[6]
- 27 * Str[8]
- 43 * Str[9]
- 15 * Str[10]
- 35 * Str[11]
- 53 * Str[13]
+ 4 * Str[14]
- 83 * Str[19]
- 68 * Str[20]
- 18 * Str[21] == 1777
&& 66 * Str[22]
+ 92 * Str[21]
+ 29 * Str[20]
+ 42 * Str[19]
+ 55 * Str[14]
+ 72 * Str[13]
+ 40 * Str[12]
+ 31 * Str[10]
+ 88 * Str[9]
+ 61 * Str[8]
+ 59 * Str[7]
+ 35 * Str[6]
+ 16 * Str[3]
+ 24 * Str[1]
+ 60 * *Str
- 55 * Str[2]
- 8 * Str[4]
- 7 * Str[5]
- 17 * Str[11]
- 25 * Str[15]
- 22 * Str[16]
- 10 * Str[17]
- 59 * Str[18] == 47727
&& 3 * Str[21]
+ 54 * Str[18]
+ 6 * Str[15]
+ 93 * Str[14]
+ 74 * Str[10]
+ 6 * Str[7]
+ 98 * Str[4]
+ 65 * Str[3]
+ 84 * Str[2]
+ 18 * Str[1]
+ 35 * *Str
- 29 * Str[5]
- 40 * Str[6]
- 35 * Str[8]
+ 8 * Str[9]
- 15 * Str[11]
- 4 * Str[12]
- 83 * Str[16]
- 74 * Str[17]
- 72 * Str[19]
- 53 * Str[20]
- 31 * Str[22] == 6695
&& 45 * Str[20]
+ 14 * Str[19]
+ 76 * Str[18]
+ 17 * Str[16]
+ 86 * Str[14]
+ 28 * Str[11]
+ 19 * Str[5]
+ 46 * Str[1]
+ 75 * *Str
- 12 * Str[2]
- 27 * Str[3]
- 66 * Str[4]
- 27 * Str[6]
- 32 * Str[7]
- 69 * Str[8]
- 31 * Str[9]
- 65 * Str[10]
- 54 * Str[12]
- 6 * Str[13]
+ 2 * Str[15]
- 10 * Str[17]
- 89 * Str[21]
- 16 * Str[22] == -3780
&& 62 * Str[21]
+ 74 * Str[20]
+ 28 * Str[18]
+ 7 * Str[17]
+ 74 * Str[16]
+ 45 * Str[15]
+ 57 * Str[14]
+ 34 * Str[11]
+ 85 * Str[10]
+ 98 * Str[6]
+ 29 * Str[4]
+ 94 * Str[3]
+ 51 * Str[2]
+ 85 * Str[1]
- 36 * Str[5]
- Str[7]
- 3 * Str[8]
- 74 * Str[9]
- 70 * Str[12]
- 68 * Str[13]
- 3 * Str[19]
+ 8 * Str[22] == 47300
&& 22 * Str[22]
+ 45 * Str[21]
+ 14 * Str[19]
+ 32 * Str[18]
+ 77 * Str[17]
+ 70 * Str[12]
+ 7 * Str[10]
+ 99 * Str[4]
+ 82 * *Str
- 48 * Str[1]
- 40 * Str[2]
- 81 * Str[3]
- 27 * Str[5]
- 75 * Str[6]
- 79 * Str[7]
- 26 * Str[8]
- 68 * Str[9]
- 57 * Str[11]
- 77 * Str[13]
- 32 * Str[14]
- Str[15]
- 91 * Str[16]
- 14 * Str[20] == -34153
&& 65 * Str[21]
+ 13 * Str[20]
+ 61 * Str[17]
+ 97 * Str[13]
+ 24 * Str[10]
+ 40 * Str[5]
+ 20 * *Str
- 81 * Str[1]
- 17 * Str[2]
- 77 * Str[3]
- 79 * Str[4]
- 45 * Str[6]
- 61 * Str[7]
- 48 * Str[8]
- 97 * Str[9]
- 49 * Str[11]
- 14 * Str[12]
- 81 * Str[14]
- 20 * Str[15]
- 27 * Str[16]
- 89 * Str[18]
- 93 * Str[19]
- 46 * Str[22] == -55479
&& 60 * Str[21]
+ 70 * Str[20]
+ 13 * Str[15]
+ 87 * Str[13]
+ 76 * Str[11]
+ 88 * Str[9]
+ 87 * Str[3]
+ 87 * *Str
- 97 * Str[1]
- 40 * Str[2]
- 49 * Str[4]
- 23 * Str[5]
- 30 * Str[6]
- 50 * Str[7]
- 98 * Str[8]
- 21 * Str[10]
- 54 * Str[12]
- 65 * Str[14]
- 80 * Str[17]
- 28 * Str[18]
- 57 * Str[19]
- 70 * Str[22] == -20651
&& 54 * Str[20]
+ 86 * Str[17]
+ 92 * Str[16]
+ 41 * Str[15]
+ 70 * Str[10]
+ 9 * Str[9]
+ Str[8]
+ 96 * Str[7]
+ 45 * Str[6]
+ 78 * Str[5]
+ 3 * Str[4]
+ 90 * Str[3]
+ 71 * Str[2]
+ 96 * *Str
- 8 * Str[1]
+ 4 * Str[11]
- 55 * Str[12]
- 73 * Str[13]
- 54 * Str[14]
- 89 * Str[18]
- (Str[19] << 6)
- 67 * Str[21]
+ 4 * Str[22] == 35926
&& 5 * Str[22]
+ 88 * Str[20]
+ 52 * Str[19]
+ 21 * Str[17]
+ 25 * Str[16]
+ 3 * Str[13]
+ 88 * Str[10]
+ 39 * Str[8]
+ 48 * Str[7]
+ 74 * Str[6]
+ 86 * Str[4]
+ 46 * Str[2]
+ 17 * *Str
- 98 * Str[1]
- 50 * Str[3]
- 28 * Str[5]
- 73 * Str[9]
- 33 * Str[11]
- 75 * Str[12]
- 14 * Str[14]
- 31 * Str[15]
- 26 * Str[18]
- 52 * Str[21] == 8283
&& 96 * Str[22]
+ 85 * Str[20]
+ 55 * Str[19]
+ 99 * Str[13]
+ 19 * Str[11]
+ 77 * Str[10]
+ 52 * Str[9]
+ 66 * Str[8]
+ 96 * Str[6]
+ 72 * Str[4]
+ 90 * Str[3]
+ 60 * Str[1]
+ 94 * *Str
- 99 * Str[2]
- 26 * Str[5]
- 94 * Str[7]
- 49 * Str[12]
- 32 * Str[14]
- 54 * Str[15]
- 92 * Str[16]
- 71 * Str[17]
- 63 * Str[18]
- 23 * Str[21] == 33789
&& 15 * Str[22]
+ Str[19]
+ 26 * Str[17]
+ 65 * Str[16]
+ 80 * Str[11]
+ 92 * Str[8]
+ 28 * Str[5]
+ 79 * Str[4]
+ 73 * *Str
- 98 * Str[1]
- 2 * Str[2]
- 70 * Str[3]
- 10 * Str[6]
- 30 * Str[7]
- 51 * Str[9]
- 77 * Str[10]
- 32 * Str[12]
- 32 * Str[13]
+ 8 * Str[14]
+ 4 * Str[15]
- 11 * Str[18]
- 83 * Str[20]
- 85 * Str[21] == -10455;
}
python Z3 神力
点击查看代码
from z3 import *
# 创建求解器
solver = Solver()
# 定义 23 个整型变量 s[0] 到 s[22]
s = [Int(f's[{i}]') for i in range(23)]
# 添加变量范围约束(字符也是整数,且为ASCII码)
for i in range(23):
solver.add(s[i] >= 32, s[i] <= 126)
# 添加从 sub_401000 提取的方程约束
# 注意:C代码中的 *Str 替换为 s[0],Str[i] 替换为 s[i],<< 6 替换为 * 64
solver.add(94 * s[22] + 74 * s[21] + 70 * s[19] + 12 * s[18] + 20 * s[16] + 62 * s[12] + 82 * s[10] + 7 * s[7] + 63 * s[6] + 18 * s[5] + 58 * s[4] + 94 * s[2] + 77 * s[0] - 43 * s[1] - 37 * s[3] - 97 * s[8] - 23 * s[9] - 86 * s[11] - 6 * s[13] - 5 * s[14] - 79 * s[15] - 63 * s[17] - 93 * s[20] == 20156)
solver.add(87 * s[22] + 75 * s[21] + 73 * s[15] + 67 * s[14] + 30 * s[13] + (s[11] * 64) + 35 * s[9] + 91 * s[7] + 91 * s[5] + 34 * s[3] + 74 * s[0] - 89 * s[1] - 72 * s[2] - 76 * s[4] - 32 * s[6] - 97 * s[8] - 39 * s[10] - 23 * s[12] + 8 * s[16] - 98 * s[17] - 4 * s[18] - 80 * s[19] - 83 * s[20] == 7183)
solver.add(51 * s[21] + 22 * s[20] + 15 * s[19] + 51 * s[17] + 96 * s[12] + 34 * s[7] + 77 * s[5] + 59 * s[2] + 89 * s[1] + 92 * s[0] - 85 * s[3] - 50 * s[4] - 51 * s[6] - 75 * s[8] - 40 * s[10] - 4 * s[11] - 74 * s[13] - 98 * s[14] - 23 * s[15] - 14 * s[16] - 92 * s[18] - 7 * s[22] == -7388)
solver.add(61 * s[22] + 72 * s[21] + 28 * s[20] + 55 * s[18] + 20 * s[17] + 13 * s[14] + 51 * s[13] + 69 * s[12] + 10 * s[11] + 95 * s[10] + 43 * s[9] + 53 * s[8] + 76 * s[7] + 25 * s[6] + 9 * s[5] + 10 * s[4] + 98 * s[1] + 70 * s[0] - 22 * s[2] + 2 * s[3] - 49 * s[15] + 4 * s[16] - 77 * s[19] == 69057)
solver.add(7 * s[22] + 21 * s[16] + 22 * s[13] + 55 * s[9] + 66 * s[8] + 78 * s[5] + 10 * s[3] + 80 * s[1] + 65 * s[0] - 20 * s[2] - 53 * s[4] - 98 * s[6] + 8 * s[7] - 78 * s[10] - 94 * s[11] - 93 * s[12] - 18 * s[14] - 48 * s[15] - 9 * s[17] - 73 * s[18] - 59 * s[19] - 68 * s[20] - 74 * s[21] == -31438)
solver.add(33 * s[19] + 78 * s[15] + 66 * s[10] + 3 * s[9] + 43 * s[4] + 24 * s[3] + 3 * s[2] + 27 * s[0] - 18 * s[1] - 46 * s[5] - 18 * s[6] - s[7] - 33 * s[8] - 50 * s[11] - 23 * s[12] - 37 * s[13] - 45 * s[14] + 2 * s[16] - s[17] - 60 * s[18] - 87 * s[20] - 72 * s[21] - 6 * s[22] == -26121)
solver.add(31 * s[20] + 80 * s[18] + 34 * s[17] + 34 * s[15] + 38 * s[14] + 53 * s[13] + 35 * s[12] + 82 * s[9] + 27 * s[8] + 80 * s[7] + 46 * s[6] + 18 * s[4] + 5 * s[1] + 98 * s[0] - 12 * s[2] - 9 * s[3] - 57 * s[5] - 46 * s[10] - 31 * s[11] - 68 * s[16] - 94 * s[19] - 93 * s[21] - 15 * s[22] == 26005)
solver.add(81 * s[21] + 40 * s[20] + 34 * s[19] + 94 * s[18] + 98 * s[17] + 11 * s[14] + 63 * s[13] + 95 * s[12] + 43 * s[11] + 99 * s[10] + 29 * s[9] + 81 * s[6] + 72 * s[5] + 54 * s[3] + 21 * s[0] - 26 * s[1] - 90 * s[2] - 15 * s[4] - 54 * s[7] - 12 * s[8] - 38 * s[15] - 15 * s[16] - 56 * s[22] == 57169)
solver.add(71 * s[18] + 39 * s[17] + 73 * s[15] + 14 * s[14] + 56 * s[12] + 56 * s[10] + 27 * s[9] + 68 * s[7] + 39 * s[6] + 26 * s[5] + 40 * s[4] + 24 * s[3] + 11 * s[2] + 14 * s[1] + 94 * s[0] - 10 * s[8] - 11 * s[11] - 63 * s[13] - 39 * s[16] - 14 * s[19] - 17 * s[20] - 23 * s[21] - 7 * s[22] == 40024)
solver.add((s[22] * 64) + 80 * s[21] + 89 * s[20] + 70 * s[19] + 66 * s[18] + 55 * s[17] + 16 * s[16] + 84 * s[13] + 48 * s[12] + 11 * s[7] + 32 * s[5] + 99 * s[0] - 26 * s[1] - 91 * s[2] - 96 * s[3] - 63 * s[4] - 67 * s[6] - 72 * s[8] + 4 * s[9] - 84 * s[10] - 81 * s[11] - 80 * s[14] - 98 * s[15] == 432)
solver.add(s[21] + 41 * s[17] + 46 * s[12] + 44 * s[9] + 63 * s[0] - 73 * s[1] - 43 * s[2] + 4 * s[3] - 37 * s[4] - 54 * s[5] - 58 * s[6] - 95 * s[7] - 2 * s[8] - 37 * s[10] - 5 * s[11] + 2 * s[13] - 46 * s[14] - 27 * s[15] - 19 * s[16] - 78 * s[18] - 51 * s[19] - 82 * s[20] - 59 * s[22] == -57338)
solver.add(10 * s[22] + 58 * s[18] + 16 * s[17] + 69 * s[16] + 6 * s[15] + 5 * s[12] + 87 * s[7] + 47 * s[5] + 91 * s[4] + 54 * s[2] + 21 * s[1] + 52 * s[0] - 76 * s[3] - 96 * s[6] - 27 * s[8] - 43 * s[9] - 15 * s[10] - 35 * s[11] - 53 * s[13] + 4 * s[14] - 83 * s[19] - 68 * s[20] - 18 * s[21] == 1777)
solver.add(66 * s[22] + 92 * s[21] + 29 * s[20] + 42 * s[19] + 55 * s[14] + 72 * s[13] + 40 * s[12] + 31 * s[10] + 88 * s[9] + 61 * s[8] + 59 * s[7] + 35 * s[6] + 16 * s[3] + 24 * s[1] + 60 * s[0] - 55 * s[2] - 8 * s[4] - 7 * s[5] - 17 * s[11] - 25 * s[15] - 22 * s[16] - 10 * s[17] - 59 * s[18] == 47727)
solver.add(3 * s[21] + 54 * s[18] + 6 * s[15] + 93 * s[14] + 74 * s[10] + 6 * s[7] + 98 * s[4] + 65 * s[3] + 84 * s[2] + 18 * s[1] + 35 * s[0] - 29 * s[5] - 40 * s[6] - 35 * s[8] + 8 * s[9] - 15 * s[11] - 4 * s[12] - 83 * s[16] - 74 * s[17] - 72 * s[19] - 53 * s[20] - 31 * s[22] == 6695)
solver.add(45 * s[20] + 14 * s[19] + 76 * s[18] + 17 * s[16] + 86 * s[14] + 28 * s[11] + 19 * s[5] + 46 * s[1] + 75 * s[0] - 12 * s[2] - 27 * s[3] - 66 * s[4] - 27 * s[6] - 32 * s[7] - 69 * s[8] - 31 * s[9] - 65 * s[10] - 54 * s[12] - 6 * s[13] + 2 * s[15] - 10 * s[17] - 89 * s[21] - 16 * s[22] == -3780)
solver.add(62 * s[21] + 74 * s[20] + 28 * s[18] + 7 * s[17] + 74 * s[16] + 45 * s[15] + 57 * s[14] + 34 * s[11] + 85 * s[10] + 98 * s[6] + 29 * s[4] + 94 * s[3] + 51 * s[2] + 85 * s[1] - 36 * s[5] - s[7] - 3 * s[8] - 74 * s[9] - 70 * s[12] - 68 * s[13] - 3 * s[19] + 8 * s[22] == 47300)
solver.add(22 * s[22] + 45 * s[21] + 14 * s[19] + 32 * s[18] + 77 * s[17] + 70 * s[12] + 7 * s[10] + 99 * s[4] + 82 * s[0] - 48 * s[1] - 40 * s[2] - 81 * s[3] - 27 * s[5] - 75 * s[6] - 79 * s[7] - 26 * s[8] - 68 * s[9] - 57 * s[11] - 77 * s[13] - 32 * s[14] - s[15] - 91 * s[16] - 14 * s[20] == -34153)
solver.add(65 * s[21] + 13 * s[20] + 61 * s[17] + 97 * s[13] + 24 * s[10] + 40 * s[5] + 20 * s[0] - 81 * s[1] - 17 * s[2] - 77 * s[3] - 79 * s[4] - 45 * s[6] - 61 * s[7] - 48 * s[8] - 97 * s[9] - 49 * s[11] - 14 * s[12] - 81 * s[14] - 20 * s[15] - 27 * s[16] - 89 * s[18] - 93 * s[19] - 46 * s[22] == -55479)
solver.add(60 * s[21] + 70 * s[20] + 13 * s[15] + 87 * s[13] + 76 * s[11] + 88 * s[9] + 87 * s[3] + 87 * s[0] - 97 * s[1] - 40 * s[2] - 49 * s[4] - 23 * s[5] - 30 * s[6] - 50 * s[7] - 98 * s[8] - 21 * s[10] - 54 * s[12] - 65 * s[14] - 80 * s[17] - 28 * s[18] - 57 * s[19] - 70 * s[22] == -20651)
solver.add(54 * s[20] + 86 * s[17] + 92 * s[16] + 41 * s[15] + 70 * s[10] + 9 * s[9] + s[8] + 96 * s[7] + 45 * s[6] + 78 * s[5] + 3 * s[4] + 90 * s[3] + 71 * s[2] + 96 * s[0] - 8 * s[1] + 4 * s[11] - 55 * s[12] - 73 * s[13] - 54 * s[14] - 89 * s[18] - (s[19] * 64) - 67 * s[21] + 4 * s[22] == 35926)
solver.add(5 * s[22] + 88 * s[20] + 52 * s[19] + 21 * s[17] + 25 * s[16] + 3 * s[13] + 88 * s[10] + 39 * s[8] + 48 * s[7] + 74 * s[6] + 86 * s[4] + 46 * s[2] + 17 * s[0] - 98 * s[1] - 50 * s[3] - 28 * s[5] - 73 * s[9] - 33 * s[11] - 75 * s[12] - 14 * s[14] - 31 * s[15] - 26 * s[18] - 52 * s[21] == 8283)
solver.add(96 * s[22] + 85 * s[20] + 55 * s[19] + 99 * s[13] + 19 * s[11] + 77 * s[10] + 52 * s[9] + 66 * s[8] + 96 * s[6] + 72 * s[4] + 90 * s[3] + 60 * s[1] + 94 * s[0] - 99 * s[2] - 26 * s[5] - 94 * s[7] - 49 * s[12] - 32 * s[14] - 54 * s[15] - 92 * s[16] - 71 * s[17] - 63 * s[18] - 23 * s[21] == 33789)
solver.add(15 * s[22] + s[19] + 26 * s[17] + 65 * s[16] + 80 * s[11] + 92 * s[8] + 28 * s[5] + 79 * s[4] + 73 * s[0] - 98 * s[1] - 2 * s[2] - 70 * s[3] - 10 * s[6] - 30 * s[7] - 51 * s[9] - 77 * s[10] - 32 * s[12] - 32 * s[13] + 8 * s[14] + 4 * s[15] - 11 * s[18] - 83 * s[20] - 85 * s[21] == -10455)
# 求解
if solver.check() == sat:
model = solver.model()
# 获取解出的值,并进行异或还原
# 程序逻辑:输入 -> 异或0xC -> 校验
# 我们的求解结果是校验时的状态,所以 flag = 结果 ^ 0xC
flag = ""
for i in range(23):
val = model[s[i]].as_long()
flag += chr(val ^ 0xC)
print("Flag:", flag)
else:
print("No solution found")
ezpy
ida 打开看到一堆 python 有关的东西。

拿出 pyinstxtractor 得到 ezpy.pyc 文件。
再用 pycdc 去获取 python 伪代码。
结果弹出两行
Unsupported opcode: PUSH_EXC_INFO (105)
Unsupported opcode: TO_BOOL (123)
只能用 pycdas 了。
得到如下:
点击查看代码
ezpy.pyc (Python 3.13)
[Code]
File Name: ezpy.py
Object Name: <module>
Qualified Name: <module>
Arg Count: 0
Pos Only Arg Count: 0
KW Only Arg Count: 0
Stack Size: 4
Flags: 0x00000000
[Names]
'mypy'
'check'
'ImportError'
'print'
'exit'
'main'
'__name__'
[Locals+Names]
[Constants]
0
(
'check'
)
'Error: Cannot import mypy module'
1
[Code]
File Name: ezpy.py
Object Name: main
Qualified Name: main
Arg Count: 0
Pos Only Arg Count: 0
KW Only Arg Count: 0
Stack Size: 3
Flags: 0x00000003 (CO_OPTIMIZED | CO_NEWLOCALS)
[Names]
'input'
'strip'
'check'
'print'
[Locals+Names]
'user_input'
[Constants]
None
'Please input your flag: '
'Correct!'
'Wrong!'
[Disassembly]
0 RESUME 0
2 LOAD_GLOBAL 1: NULL + input
12 LOAD_CONST 1: 'Please input your flag: '
14 CALL 1
22 LOAD_ATTR 3: strip
42 CALL 0
50 STORE_FAST 0: user_input
52 LOAD_GLOBAL 5: NULL + check
62 LOAD_FAST 0: user_input
64 CALL 1
72 TO_BOOL
80 POP_JUMP_IF_FALSE 12 (to 106)
84 LOAD_GLOBAL 7: NULL + print
94 LOAD_CONST 2: 'Correct!'
96 CALL 1
104 POP_TOP
106 RETURN_CONST 0: None
108 LOAD_GLOBAL 7: NULL + print
118 LOAD_CONST 3: 'Wrong!'
120 CALL 1
128 POP_TOP
130 RETURN_CONST 0: None
[Exception Table]
'__main__'
None
[Disassembly]
0 RESUME 0
2 NOP
4 LOAD_CONST 0: 0
6 LOAD_CONST 1: ('check',)
8 IMPORT_NAME 0: mypy
10 IMPORT_FROM 1: check
12 STORE_NAME 1: check
14 POP_TOP
16 LOAD_CONST 4: <CODE> main
18 MAKE_FUNCTION
20 STORE_NAME 5: main
22 LOAD_NAME 6: __name__
24 LOAD_CONST 5: '__main__'
26 COMPARE_OP 88 (==)
30 POP_JUMP_IF_FALSE 8 (to 48)
34 LOAD_NAME 5: main
36 PUSH_NULL
38 CALL 0
46 POP_TOP
48 RETURN_CONST 6: None
50 RETURN_CONST 6: None
52 PUSH_EXC_INFO
54 LOAD_NAME 2: ImportError
56 CHECK_EXC_MATCH
58 POP_JUMP_IF_FALSE 19 (to 98)
62 POP_TOP
64 LOAD_NAME 3: print
66 PUSH_NULL
68 LOAD_CONST 2: 'Error: Cannot import mypy module'
70 CALL 1
78 POP_TOP
80 LOAD_NAME 4: exit
82 PUSH_NULL
84 LOAD_CONST 3: 1
86 CALL 1
94 POP_TOP
96 POP_EXCEPT
98 JUMP_BACKWARD_NO_INTERRUPT 42 (to 16)
100 RERAISE 0
102 COPY 3
104 POP_EXCEPT
106 RERAISE 1
[Exception Table]
4 to 16 -> 52 [0]
52 to 96 -> 102 [1] lasti
100 to 102 -> 102 [1] lasti
‘发现不包含跟 flag 有关的东西,找来找去只有一个 mypy.cp313-win_amd64.pyd 的文件比较可疑。
拿打开看看。

可以看到是 RC4加密
文存储在 unk_36F4D4050 提取出来是:
点击查看代码
cipher_bytes = [
0x1D, 0xD5, 0x38, 0x33, 0xAF, 0xB5, 0x51, 0xF3,
0x2C, 0x6B, 0x6E, 0xFE, 0x41, 0x24, 0x43, 0xD2,
0x71, 0xCF, 0xA4, 0x4C, 0xE3, 0x9A, 0x9A, 0xB5,
0x31
]
密钥在 sub_36F4D1519
点击查看代码
.text:000000036F4D1519 sub_36F4D1519 proc near ; DATA XREF: .data:000000036F4D30A8↓o
.text:000000036F4D1519 ; .pdata:000000036F4D5060↓o ...
.text:000000036F4D1519
.text:000000036F4D1519 var_132 = byte ptr -132h
.text:000000036F4D1519 var_12A = word ptr -12Ah
.text:000000036F4D1519 var_128 = byte ptr -128h
.text:000000036F4D1519 Str = qword ptr -20h
.text:000000036F4D1519
.text:000000036F4D1519 push rdi
.text:000000036F4D151A push rsi
.text:000000036F4D151B push rbx
.text:000000036F4D151C sub rsp, 140h
.text:000000036F4D1523 mov rcx, rdx
.text:000000036F4D1526 mov rax, 3230324654435349h
.text:000000036F4D1530 mov qword ptr [rsp+158h+var_132], rax
.text:000000036F4D1535 mov [rsp+158h+var_12A], 35h ; '5'
.text:000000036F4D153C lea r8, [rsp+158h+Str]
.text:000000036F4D1544 lea rdx, unk_36F4D4000
.text:000000036F4D154B call cs:__imp_PyArg_ParseTuple
.text:000000036F4D1551 test eax, eax
.text:000000036F4D1553 jz loc_36F4D1615
.text:000000036F4D1559 mov rsi, [rsp+158h+Str]
.text:000000036F4D1561 mov rcx, rsi ; Str
.text:000000036F4D1564 call strlen
.text:000000036F4D1569 mov rbx, cs:_Py_FalseStruct
.text:000000036F4D1570 cmp eax, 19h
.text:000000036F4D1573 jz short loc_36F4D1583
让 AI 帮忙从这一堆汇编里找到有用的地方。
text:000000036F4D1526 mov rax, 3230324654435349h
.text:000000036F4D1530 mov qword ptr [rsp+158h+var_132], rax
.text:000000036F4D1535 mov [rsp+158h+var_12A], 35h ; '5'
这是典型的 栈字符串初始化。
由于 x86/x64 架构是 小端序 (Little-Endian),我们需要把 3230324654435349h 这个十六进制数倒过来读(每两个字符一组):
原始十六进制 (rax): 32 30 32 46 54 43 53 49
内存顺序 (Little-Endian):
49 -> 'I'
53 -> 'S'
43 -> 'C'
54 -> 'T'
46 -> 'F'
32 -> '2'
30 -> '0'
32 -> '2'
拼接结果: ISCTF202
接下来的一个字节 (var_12A):
35h -> '5'
所以,RC4 的 Key 是:ISCTF2025
得到 key 。
密文和 key 都得到了,可以写脚本求 flag 了。
点击查看代码
def rc4_decrypt(key, data):
# Key Scheduling Algorithm (KSA)
S = list(range(256))
j = 0
key_bytes = [ord(c) for c in key]
for i in range(256):
j = (j + S[i] + key_bytes[i % len(key_bytes)]) % 256
S[i], S[j] = S[j], S[i]
# Pseudo-Random Generation Algorithm (PRGA)
out = []
i = j = 0
for byte in data:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
k = S[(S[i] + S[j]) % 256]
out.append(byte ^ k)
return bytes(out)
# 1. 密文 (从 .rdata unk_36F4D4050 提取)
cipher_data = bytes([
0x1D, 0xD5, 0x38, 0x33, 0xAF, 0xB5, 0x51, 0xF3,
0x2C, 0x6B, 0x6E, 0xFE, 0x41, 0x24, 0x43, 0xD2,
0x71, 0xCF, 0xA4, 0x4C, 0xE3, 0x9A, 0x9A, 0xB5,
0x31
])
# 2. 密钥 (从 .text 汇编分析得出)
key_str = "ISCTF2025"
# 3. 解密
try:
flag = rc4_decrypt(key_str, cipher_data)
print(f"Flag: {flag.decode()}")
except Exception as e:
print(f"Decryption error: {e}")
print(f"Raw bytes: {rc4_decrypt(key_str, cipher_data)}")
ELF
解压出一个叫 main 的神秘文件。
ida 打开后查看 string 也是看到了很多跟 python 有关的内容。
尝试复刻上一题的操作。
先用 pyinstxtractor 得到 main.pyc
再用 pycdc 得到 main.py
打开后得到伪代码
点击查看代码
# Source Generated with Decompyle++
# File: main.pyc (Python 3.10)
import base64
import hashlib
import random
flag = '8d13c398b72151b1dad78762553dbbd59dba9b0b2330b03b401ea4f2a6d4731d479220fe900b520f6b4753667fe1cdf9eff8d3b833a0013c4083fa1ad27d056486702bda245f3c1aa0fbf84b237d8f2dec9a80791fe66625adfe3669419a104cbb67293eaada20f79cebf69d84d326025dd35dec09a2c97ad838efa5beba9e72'
YourInput = input('Please input your flag:')
enc = ''
if len(YourInput) != 24:
print('Length Wrong!!!')
exit(0)
def Rep(hash_data):
random.seed(161)
result = list(hash_data)
for i in range(len(result) - 1, 0, -1):
swap_index = random.randint(0, i)
result[i] = result[swap_index]
result[swap_index] = result[i]
return ''.join(result)
for i in range(len(YourInput) // 3):
c2b = base64.b64encode(YourInput[i * 3:(i + 1) * 3].encode('utf-8'))
hash = hashlib.md5(c2b).hexdigest()
enc += Rep(hash)
if enc == flag:
print('Your are win!!!')
return None
None('Your are lose!!!')
代码逻辑分析
输入检查:Flag 的长度必须是 24 个字符。
分块处理:代码将输入的 Flag 分为 24 // 3 = 8 块,每块 3 个字符。
加密流程(针对每一块):
Base64 编码:将 3 个字符进行 Base64 编码。注意:3 个字节的 Base64 编码结果正好是 4 个字符。
MD5 哈希:对 Base64 后的字符串进行 MD5 哈希,得到 32 位的十六进制字符串。
置换 (Rep):使用固定的随机数种子 (161) 对 MD5 字符串的字符顺序进行打乱。
最终比对:将所有打乱后的哈希值拼接,与给定的 flag 字符串进行比对。
解题思路
我们要做的就是上述过程的逆过程:
还原置换:因为 random.seed(161) 是固定的,并且每次调用 Rep 函数时都会重置种子,所以每一块的打乱规则是完全相同的。我们可以模拟这个过程找出字符的原始位置。
还原 MD5:将给定的长字符串按 32 位一组切分,利用还原出的置换规则,恢复出 8 个原始的 MD5 哈希值。
爆破原文:我们需要找到 3 个字符,使得 MD5(Base64(这3个字符)) 等于还原出的 MD5 值。
由于每块只有 3 个字符,且字符集通常是可打印字符(ASCII 32-126),穷举范围极小(\(95^3 \approx 85\) 万次),计算瞬间即可完成。
爆破脚本:
点击查看代码
import base64
import hashlib
import random
import itertools
import string
# 题目给出的加密后的 Flag
target_enc = '8d13c398b72151b1dad78762553dbbd59dba9b0b2330b03b401ea4f2a6d4731d479220fe900b520f6b4753667fe1cdf9eff8d3b833a0013c4083fa1ad27d056486702bda245f3c1aa0fbf84b237d8f2dec9a80791fe66625adfe3669419a104cbb67293eaada20f79cebf69d84d326025dd35dec09a2c97ad838efa5beba9e72'
def get_perm_map():
"""
计算乱序重排的映射关系。
Rep 函数虽然看起来是赋值,但在 Python 的随机洗牌逻辑中,
它通常对应于 swap 操作。这里我们模拟打乱位置索引。
"""
random.seed(161)
# 初始索引 [0, 1, 2, ..., 31]
indices = list(range(32))
for i in range(len(indices) - 1, 0, -1):
swap_index = random.randint(0, i)
# 模拟交换
indices[i], indices[swap_index] = indices[swap_index], indices[i]
return indices
def solve():
# 1. 获取置换映射
# perm_indices[i] 表示:乱序后第 i 个位置的字符,原本是在 perm_indices[i] 这个位置
# 不对,模拟过程是:
# 数组 arr,经过 shuffle 变成 arr'
# arr'[i] 存储的是原数组中下标为 k 的元素
# 我们需要根据 arr'[i] = k 这个关系来还原
perm_indices = get_perm_map()
# 2. 将目标长字符串切分为 8 块 (每块 32 字符)
chunks = [target_enc[i:i+32] for i in range(0, len(target_enc), 32)]
recovered_md5s = []
for chunk in chunks:
original_list = [''] * 32
# 还原逻辑:
# perm_indices[i] 是位置 i 上的元素原本的值(也就是原本的索引)
# 所以:乱序字符串 chunk 的第 i 个字符,应该放回 original_list 的 perm_indices[i] 位置
for i, original_idx in enumerate(perm_indices):
original_list[original_idx] = chunk[i]
recovered_md5s.append("".join(original_list))
print(f"[*] Recovered MD5 hashes: {recovered_md5s}")
# 3. 爆破每块的 3 个字符
# 建立彩虹表或者直接爆破,这里选择直接爆破,因为空间很小
print("[*] Brute-forcing 3-char chunks...")
full_flag = ""
printable = string.printable # 或者使用 range(32, 127)
for target_md5 in recovered_md5s:
found = False
# 遍历所有可能的 3 字符组合
for p in itertools.product(printable, repeat=3):
candidate = "".join(p)
# 1. Base64 编码
b64 = base64.b64encode(candidate.encode('utf-8'))
# 2. MD5 哈希
h = hashlib.md5(b64).hexdigest()
if h == target_md5:
full_flag += candidate
found = True
break
if not found:
print(f"[-] Failed to crack hash: {target_md5}")
return
print(f"\n[+] SUCCESS! The Flag is: {full_flag}")
if __name__ == '__main__':
solve()
运行即得 flag
MysteriousStream
给了 challenge 和 payload.dat 两个文件。
先拿 ida 打开 challenge
得到这些伪代码:
点击查看代码
int __fastcall main(int argc, const char **argv, const char **envp)
{
FILE *stream; // rax
FILE *stream_1; // r12
signed __int64 size; // r14
char *ptr; // rax
char *ptr_1; // rbp
size_t i_1; // r13
__int64 i; // rcx
_BYTE P4ssXORSecr3tK3y_[17]; // [rsp+7h] [rbp-41h] BYREF
unsigned __int64 v12; // [rsp+18h] [rbp-30h]
v12 = __readfsqword(0x28u);
stream = fopen("payload.dat", "rb");
if ( stream )
{
stream_1 = stream;
fseek(stream, 0, 2);
size = ftell(stream_1);
if ( size < 0 )
{
puts("Get file size failed");
fclose(stream_1);
return 1;
}
else
{
fseek(stream_1, 0, 0);
ptr = (char *)malloc(size);
ptr_1 = ptr;
if ( ptr )
{
i_1 = fread(ptr, 1u, size, stream_1);
fclose(stream_1);
if ( size == i_1 )
{
qmemcpy(P4ssXORSecr3tK3y_, "P4ssXORSecr3tK3y!", sizeof(P4ssXORSecr3tK3y_));
rc4_variant(ptr_1, i_1, &P4ssXORSecr3tK3y_[7], 10);
if ( i_1 )
{
for ( i = 0; i != i_1; ++i )
ptr_1[i] ^= P4ssXORSecr3tK3y_[i % 7];
}
__printf_chk(1, "Result: %s\n", ptr_1);
free(ptr_1);
return 0;
}
else
{
__printf_chk(1, "Read failed! Expected %ld bytes, got %zu bytes\n", size, i_1);
free(ptr_1);
return 1;
}
}
else
{
puts("Malloc memory failed");
fclose(stream_1);
return 1;
}
}
}
else
{
puts("payload.dat not found");
return 1;
}
}
unsigned __int64 __fastcall rc4_variant(char *ptr, size_t i, __int64 a3, unsigned __int64 n10)
{
char *ptr_1; // r8
__int64 j; // rax
unsigned __int64 n256; // rcx
int v8; // ebx
char v9; // r11
char *ptr_2; // r9
char v11; // al
_BYTE v13[264]; // [rsp+0h] [rbp-118h]
unsigned __int64 v14; // [rsp+108h] [rbp-10h]
ptr_1 = ptr;
v14 = __readfsqword(0x28u);
for ( j = 0; j != 256; ++j )
v13[j] = j;
n256 = 0;
LOBYTE(v8) = 0;
do
{
v9 = v13[n256];
v8 = (unsigned __int8)((n256 & 0xAA) + v8 + v9 + *(_BYTE *)(a3 + n256 % n10));
v13[n256++] = v13[v8];
v13[v8] = v9;
}
while ( n256 != 256 );
if ( i )
{
ptr_2 = &ptr[i];
LOBYTE(ptr) = 0;
LOBYTE(i) = 0;
do
{
LODWORD(ptr) = (unsigned __int8)((_BYTE)ptr + 1);
v11 = v13[(unsigned int)ptr];
LODWORD(i) = (unsigned __int8)(v11 + i);
v13[(unsigned int)ptr] = v13[(unsigned int)i];
v13[(unsigned int)i] = v11;
*ptr_1++ ^= v13[(unsigned __int8)(v13[(unsigned int)ptr] + v11)];
}
while ( ptr_2 != ptr_1 );
}
return v14 - __readfsqword(0x28u);
}
LOAD:0000000000000000 dword_0 dd 464C457Fh ; DATA XREF: LOAD:00000000000003F0↓o
LOAD:0000000000000000 ; LOAD:0000000000000408↓o ...
LOAD:0000000000000000 ; File format: \x7FELF
LOAD:0000000000000004 db 2 ; File class: 64-bit
LOAD:0000000000000005 db 1 ; Data encoding: little-endian
LOAD:0000000000000006 db 1 ; File version
LOAD:0000000000000007 db 0 ; OS/ABI: UNIX System V ABI
LOAD:0000000000000008 db 0 ; ABI Version
LOAD:0000000000000009 db 7 dup(0) ; Padding
LOAD:0000000000000010 dw 3 ; File type: Shared object
LOAD:0000000000000012 dw 3Eh ; Machine: x86-64
LOAD:0000000000000014 dd 1 ; File version
LOAD:0000000000000018 dq offset _start ; Entry point
LOAD:0000000000000020 dq 40h ; PHT file offset
LOAD:0000000000000028 dq 3870h ; SHT file offset
LOAD:0000000000000030 dd 0 ; Processor-specific flags
LOAD:0000000000000034 dw 40h ; ELF header size
LOAD:0000000000000036 dw 38h ; PHT entry size
LOAD:0000000000000038 dw 0Dh ; Number of entries in PHT
LOAD:000000000000003A dw 40h ; SHT entry size
LOAD:000000000000003C dw 1Fh ; Number of entries in SHT
LOAD:000000000000003E dw 1Eh ; SHT entry index for string table
把这些扔给 AI 后,得到如下回答:
这是一个典型的逆向工程(Reverse Engineering)CTF 题目。你需要分析提供的 C 伪代码,理解其加密/解密逻辑,并编写脚本来还原原始数据(通常是 Flag)。
代码分析
程序的主要逻辑在 main 函数和 rc4_variant 函数中。
- 密钥准备
在 main 函数中:
-
读取文件
程序读取名为 payload.dat 的文件内容到内存中。这个文件包含了加密后的数据(Ciphertext)。 -
第一层处理:魔改 RC4 (Variant RC4)
程序调用了 rc4_variant:
这增加了一个 (i & 0xAA) 的项,这是该算法与标准 RC4 唯一的区别。PRGA(伪随机生成算法)部分与标准 RC4 一致。
- 第二层处理:XOR
RC4 处理完后,程序执行了一个简单的异或循环:
解决方案
由于代码是对称加密(RC4流密码 + XOR),解密过程就是按照相同的参数重新运行一遍算法。
你需要将题目提供的 payload.dat 文件放在脚本同目录下,然后运行以下 Python 脚本。
可以得到python脚本:
点击查看代码
import sys
def rc4_variant_process(data, key):
# key 转换为字节数组
key = [ord(k) for k in key]
key_len = len(key)
# 初始化 S-box
S = list(range(256))
# KSA (Key Scheduling Algorithm) - 魔改版
j = 0
for i in range(256):
# 原始代码: v8 = (n256 & 0xAA) + v8 + v9 + key_byte
# 对应: j = ((i & 0xAA) + j + S[i] + key[i % key_len]) % 256
term1 = i & 0xAA
term2 = j
term3 = S[i]
term4 = key[i % key_len]
j = (term1 + term2 + term3 + term4) % 256
# Swap
S[i], S[j] = S[j], S[i]
# PRGA (Pseudo-Random Generation Algorithm) - 标准版
# 生成密钥流并与数据异或
res = bytearray(data)
i = 0
j = 0
for k in range(len(res)):
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
K = S[(S[i] + S[j]) % 256]
res[k] ^= K
return res
def xor_process(data, key_str):
res = bytearray(data)
key = [ord(k) for k in key_str]
key_len = len(key)
for i in range(len(res)):
res[i] ^= key[i % key_len]
return res
def main():
try:
# 1. 读取 payload.dat
with open("payload.dat", "rb") as f:
ciphertext = f.read()
print(f"[+] Read {len(ciphertext)} bytes from payload.dat")
# 2. RC4 变种解密
# 密钥从偏移7开始,长度10 -> "Secr3tK3y!"
rc4_key = "Secr3tK3y!"
step1_data = rc4_variant_process(ciphertext, rc4_key)
# 3. XOR 解密
# 密钥为前7个字符 -> "P4ssXOR"
xor_key = "P4ssXOR"
final_data = xor_process(step1_data, xor_key)
# 4. 输出结果
print(f"[+] Decrypted Result: {final_data.decode('utf-8', errors='ignore')}")
print(f"[+] Hex: {final_data.hex()}")
except FileNotFoundError:
print("Error: 'payload.dat' not found. Please place the file in the same directory.")
except Exception as e:
print(f"An error occurred: {e}")
if __name__ == "__main__":
main()
运行后即可得到flag:
点击查看代码
[+] Read 40 bytes from payload.dat
[+] Decrypted Result: ISCTF{Y0u_a2e_2ea11y_a_1aby2inth_master}
[+] Hex: 49534354467b5930755f6132655f3265613131795f615f3161627932696e74685f6d61737465727d
vm_cool
真正开始上强度的题,真的好难
(写 WP 的时候发现复刻了半天,快破防了)
先行elf得到:
Unknown opcode: 0x10
Decrypted flag: kIB~o
再拿 ida 打开
点击查看代码
nt __fastcall main(int argc, const char **argv, const char **envp)
{
_QWORD src_[4]; // [rsp+0h] [rbp-220h] BYREF
__int64 v5; // [rsp+20h] [rbp-200h]
__int64 v6; // [rsp+28h] [rbp-1F8h]
__int64 encrypted_flag; // [rsp+30h] [rbp-1F0h]
_BYTE v8[15]; // [rsp+38h] [rbp-1E8h]
char v9; // [rsp+47h] [rbp-1D9h]
__int64 v10; // [rsp+48h] [rbp-1D8h]
__int64 v11; // [rsp+50h] [rbp-1D0h]
__int64 v12; // [rsp+58h] [rbp-1C8h]
__int64 v13; // [rsp+60h] [rbp-1C0h]
__int64 v14; // [rsp+68h] [rbp-1B8h]
__int64 v15; // [rsp+70h] [rbp-1B0h]
__int64 v16; // [rsp+78h] [rbp-1A8h]
__int64 v17; // [rsp+80h] [rbp-1A0h]
__int64 v18; // [rsp+88h] [rbp-198h]
__int64 v19; // [rsp+90h] [rbp-190h]
__int64 v20; // [rsp+98h] [rbp-188h]
__int64 v21; // [rsp+A0h] [rbp-180h]
__int64 v22; // [rsp+A8h] [rbp-178h]
__int64 v23; // [rsp+B0h] [rbp-170h]
__int64 v24; // [rsp+B8h] [rbp-168h]
__int64 v25; // [rsp+C0h] [rbp-160h]
__int64 v26; // [rsp+C8h] [rbp-158h]
__int64 v27; // [rsp+D0h] [rbp-150h]
__int64 v28; // [rsp+D8h] [rbp-148h]
__int64 v29; // [rsp+E0h] [rbp-140h]
__int64 v30; // [rsp+E8h] [rbp-138h]
__int64 v31; // [rsp+F0h] [rbp-130h]
__int64 v32; // [rsp+F8h] [rbp-128h]
unsigned __int8 s_[284]; // [rsp+100h] [rbp-120h] BYREF
unsigned int n0x16; // [rsp+21Ch] [rbp-4h]
v9 = 0;
v10 = 0;
v11 = 0;
v12 = 0;
v13 = 0;
v14 = 0;
v15 = 0;
v16 = 0;
v17 = 0;
v18 = 0;
v19 = 0;
v20 = 0;
v21 = 0;
v22 = 0;
v23 = 0;
v24 = 0;
v25 = 0;
v26 = 0;
v27 = 0;
v28 = 0;
v29 = 0;
v30 = 0;
v31 = 0;
v32 = 0;
src_[0] = vm_program;
src_[1] = qword_4068;
src_[2] = qword_4070;
src_[3] = qword_4078;
v5 = qword_4080;
v6 = qword_4088;
LOWORD(v5) = 0xAB17;
BYTE2(v5) = 55;
BYTE5(src_[0]) = 1;
encrypted_flag = ::encrypted_flag;
*(_QWORD *)v8 = qword_4048;
*(_QWORD *)&v8[7] = *(__int64 *)((char *)&qword_4048 + 7);
init_vm(s_, src_, 256);
run_vm((__int64)s_);
printf("Decrypted flag: ");
for ( n0x16 = 0; n0x16 <= 0x16; ++n0x16 )
putchar(s_[n0x16 + 64]);
putchar(10);
return 0;
}
设置vmcode 设置encrypt flag,然后运行run_vm进行加密
再往后看吧
点击查看代码
__int64 __fastcall run_vm(__int64 s)
{
__int64 n0xFF; // rax
while ( 1 )
{
n0xFF = *(unsigned int *)(s + 268);
if ( !(_DWORD)n0xFF )
break;
n0xFF = *(unsigned __int16 *)(s + 264);
if ( (unsigned __int16)n0xFF > 0xFFu )
break;
execute_instruction(s);
}
return n0xFF;
}
unsigned __int64 __fastcall execute_instruction(char *s)
{
v1 = *((_WORD *)s + 132);
*((_WORD *)s + 132) = v1 + 1;
n255 = s[v1];
if ( n255 > 0xAu )
{
if ( n255 == 255 )
{
s_1 = (unsigned __int64)s;
*((_DWORD *)s + 67) = 0;
return s_1;
}
goto LABEL_18;
}
if ( !n255 )
{
LABEL_18:
printf("Unknown opcode: 0x%02X\n", n255);
s_1 = (unsigned __int64)s;
*((_DWORD *)s + 67) = 0;
return s_1;
}
switch ( n255 )
{
case 1u:
v2 = *((_WORD *)s + 132);
*((_WORD *)s + 132) = v2 + 1;
s_2 = s[v2];
v3 = *((_WORD *)s + 132);
*((_WORD *)s + 132) = v3 + 1;
v4 = s[(unsigned __int8)s[v3]];
s_1 = s_2;
s[s_2 + 256] = v4;
break;
case 2u:
v6 = *((_WORD *)s + 132);
*((_WORD *)s + 132) = v6 + 1;
s_3 = s[v6];
v7 = *((_WORD *)s + 132);
*((_WORD *)s + 132) = v7 + 1;
v8 = s[(unsigned __int8)s[v7] + 256];
s_1 = s_3;
s[s_3] = v8;
break;
case 3u:
v9 = *((_WORD *)s + 132);
*((_WORD *)s + 132) = v9 + 1;
s_4 = s[v9];
v10 = *((_WORD *)s + 132);
*((_WORD *)s + 132) = v10 + 1;
v34 = s[v10];
v11 = *((_WORD *)s + 132);
*((_WORD *)s + 132) = v11 + 1;
v12 = s[(unsigned __int8)s[v11] + 256] + s[v34 + 256];
s_1 = s_4;
s[s_4 + 256] = v12;
break;
case 4u:
v13 = *((_WORD *)s + 132);
*((_WORD *)s + 132) = v13 + 1;
s_5 = s[v13];
v14 = *((_WORD *)s + 132);
*((_WORD *)s + 132) = v14 + 1;
v35 = s[v14];
v15 = *((_WORD *)s + 132);
*((_WORD *)s + 132) = v15 + 1;
v16 = s[v35 + 256] - s[(unsigned __int8)s[v15] + 256];
s_1 = s_5;
s[s_5 + 256] = v16;
break;
case 5u:
v17 = *((_WORD *)s + 132);
*((_WORD *)s + 132) = v17 + 1;
s_6 = s[v17];
v18 = *((_WORD *)s + 132);
*((_WORD *)s + 132) = v18 + 1;
v36 = s[v18];
v19 = *((_WORD *)s + 132);
*((_WORD *)s + 132) = v19 + 1;
v33 = s[v19];
s_1 = s_6;
s[s_6 + 256] = s[v33 + 256] ^ s[v36 + 256];
break;
case 6u:
v20 = *((_WORD *)s + 132);
*((_WORD *)s + 132) = v20 + 1;
s_7 = s[v20];
v21 = *((_WORD *)s + 132);
*((_WORD *)s + 132) = v21 + 1;
v22 = (unsigned __int8)s[s_7 + 256] << s[(unsigned __int8)s[v21] + 256];
s_1 = s_7;
s[s_7 + 256] = v22;
break;
case 7u:
v23 = *((_WORD *)s + 132);
*((_WORD *)s + 132) = v23 + 1;
s_8 = s[v23];
v24 = *((_WORD *)s + 132);
*((_WORD *)s + 132) = v24 + 1;
v25 = (int)(unsigned __int8)s[s_8 + 256] >> s[(unsigned __int8)s[v24] + 256];
s_1 = s_8;
s[s_8 + 256] = v25;
break;
case 8u:
v26 = *((_WORD *)s + 132);
*((_WORD *)s + 132) = v26 + 1;
v27 = (unsigned __int8)s[v26];
s_1 = (unsigned __int64)s;
*((_WORD *)s + 132) = v27;
break;
case 9u:
v28 = *((_WORD *)s + 132);
*((_WORD *)s + 132) = v28 + 1;
v44 = s[v28];
v29 = *((_WORD *)s + 132);
*((_WORD *)s + 132) = v29 + 1;
s_1 = (unsigned __int8)s[(unsigned __int8)s[v29] + 256];
if ( !(_BYTE)s_1 )
{
s_1 = (unsigned __int64)s;
*((_WORD *)s + 132) = v44;
}
break;
case 0xAu:
v30 = *((_WORD *)s + 132);
*((_WORD *)s + 132) = v30 + 1;
v45 = s[v30];
v31 = *((_WORD *)s + 132);
*((_WORD *)s + 132) = v31 + 1;
v32 = s[v45 + 256] == s[(unsigned __int8)s[v31] + 256];
s_1 = (unsigned __int64)s;
s[266] = v32;
break;
default:
goto LABEL_18;
}
return s_1;
}
解析一下所有的opcode:
| Opcode | 含义 | 参数数量(字节) |
|---|---|---|
| 1 | load | 2 |
| 2 | store | 2 |
| 3 | add | 3 |
| 4 | sub | 3 |
| 5 | xor | 3 |
| 6 | shl | 2 |
| 7 | shr | 2 |
| 8 | jmp | 1 |
| 9 | jz | 2 |
| 0x0A | cmp | 2 |
| 0xFF | end | 0 |
报错的0x10实际上是用来访问标志寄存器的,0x30是访问间接跳转地址
所以要把opcode里面的0x10和0x30去掉
0x01,0x00,0x20,
0x01,0x01,0x00,
0x01,0x02,0x21,
0x01,0x03,0x22,
0x01,0x04,0x01,
0x05,0x04,0x04,0x02,
0x03,0x04,0x04,0x03,
0x05,0x04,0x04,0x02,
0x04,0x04,0x04,0x03,
0x02,0x01,0x04,
0x03,0x01,0x01,0x05,
0x0A,0x01,0x00,
0x09,0x30,0x06,
0x08,0x10,
0xFF
整体的加密逻辑是:
R4 = R2 ^ R4
R4 = R[C[3]] + R4
R4 = R2 ^ R4
R4 = R4 - R[C[3]]
其实就是
enc[i]^=key1
enc[i]+=key2
enc[i]^=key1
enc[i]-=key2
那么由这个虚拟机的参数调用方式看,key1和key2都是存储在寄存器的单字节,那么从0-0xff爆破一下就好了,调试还怪麻烦的
exp如下:
enc = [0x78, 0x1E, 0x73, 0x71, 0x75, 0x68, 0x7F,
0x49, 0x43, 0x6D, 0x49, 0x84, 0x77, 0x53,
0x7E, 0x1E, 0x6B, 0x49, 0x1D, 0x42, 0x19,
0x7E, 0x6F]
def decrypt_byte(d, k1, k2):
c = (d + k2) & 0xff
b = c ^ k1
a = (b - k2) & 0xff
p = a ^ k1
return p
def decrypt(enc, k1, k2):
return bytes(decrypt_byte(d, k1, k2) for d in enc)
results = []
for k1 in range(256):
for k2 in range(256):
pt = decrypt(enc, k1, k2)
if pt.startswith(b"flag{"):
results.append((k1, k2, pt))
for k1, k2, pt in results:
print(f"k1 = {k1:02x}, k2 = {k2:02x}, plaintext = {pt.decode(errors='ignore')}")
发现其实是多种组合(评价为出糊了)
k1 = 2b, k2 = 37, plaintext = flag{VM_1s_reALly_c0oL}
k1 = 2b, k2 = 77, plaintext = flag{VM_1s_reALly_c0oL}
k1 = 2b, k2 = b7, plaintext = flag{VM_1s_reALly_c0oL}
k1 = 2b, k2 = f7, plaintext = flag{VM_1s_reALly_c0oL}
k1 = ab, k2 = 37, plaintext = flag{VM_1s_reALly_c0oL}
k1 = ab, k2 = 77, plaintext = flag{VM_1s_reALly_c0oL}
k1 = ab, k2 = b7, plaintext = flag{VM_1s_reALly_c0oL}
k1 = ab, k2 = f7, plaintext = flag{VM_1s_reALly_c0oL}
已经。。。燃尽了。。。
最后 flag 是 ISCTF{VM_1s_reALly_c0oL}
Recall
扔 ida
主函数
点击查看代码
int __cdecl main(int argc, const char **argv, const char **envp)
{
HANDLE hHandle; // [esp+0h] [ebp-74h]
int i; // [esp+4h] [ebp-70h]
char Str[4]; // [esp+Ch] [ebp-68h] BYREF
int v7; // [esp+10h] [ebp-64h] BYREF
int v8; // [esp+14h] [ebp-60h] BYREF
int v9; // [esp+18h] [ebp-5Ch] BYREF
int v10; // [esp+1Ch] [ebp-58h] BYREF
int v11; // [esp+20h] [ebp-54h] BYREF
puts("Please tell me what tea do you like?");
puts("input:");
sub_401640("%80s", Str);
if ( strlen(Str) == 24 )
{
memmove(&dword_41E9C8, Str, 4u);
memmove(&dword_41E9C8 + 1, &v7, 4u);
memmove(&dword_41E9C8 + 2, &v8, 4u);
memmove(&dword_41E9C8 + 3, &v9, 4u);
memmove(&dword_41E9C8 + 4, &v10, 4u);
memmove(&dword_41E9C8 + 5, &v11, 4u);
sub_4011C0(&dword_41E9C8, 2, &dword_41E004);
dword_41E014[0] = dword_41E9C8;
dword_41E014[1] = *(&dword_41E9C8 + 1);
hHandle = CreateThread(0, 0, StartAddress, 0, 0, 0);
WaitForSingleObject(hHandle, 0xEA60u);
CloseHandle(hHandle);
sub_4011C0(&dword_41E9C8 + 4, 2, &dword_41E004);
dword_41E014[4] = *(&dword_41E9C8 + 4);
dword_41E014[5] = *(&dword_41E9C8 + 5);
for ( i = 0; i < 6; ++i )
{
if ( dword_41E014[i] != dword_41E048[i] )
{
puts("That's not a good tea.");
sub_404B07("pause");
return 0;
}
}
puts("That's a best tea!!!");
sub_404B07("pause");
return 0;
}
else
{
puts("Invalid.");
return 0;
}
}
点击查看代码
DWORD __stdcall StartAddress(LPVOID lpThreadParameter)
{
sub_4011C0(&dword_41E9C8 + 2, 2, &n946775355);
dword_41E014[2] = *(&dword_41E9C8 + 2);
dword_41E014[3] = *(&dword_41E9C8 + 3);
return 0;
}
XXTEA加密主函数
点击查看代码
unsigned int __cdecl sub_4011C0(int *a1, int n2, int *a3)
{
unsigned int v3; // ecx
int v4; // eax
unsigned int v5; // edx
unsigned int result; // eax
int v7; // [esp+8h] [ebp-1Ch]
int v8; // [esp+10h] [ebp-14h]
unsigned int v9; // [esp+14h] [ebp-10h]
unsigned int v10; // [esp+1Ch] [ebp-8h]
unsigned int i; // [esp+20h] [ebp-4h]
v9 = 0;
v8 = 52 / n2 + 6;
v10 = a1[n2 - 1];
do
{
v9 += dword_41E000;
v7 = (v9 >> 2) & 3;
for ( i = 0; i < n2 - 1; ++i )
{
v3 = ((v10 ^ a3[v7 ^ i & 3]) + (a1[i + 1] ^ v9))
^ (((16 * v10) ^ ((unsigned int)a1[i + 1] >> 3)) + ((4 * a1[i + 1]) ^ (v10 >> 5)));
v4 = a1[i];
a1[i] = v3 + v4;
v10 = v3 + v4;
}
v5 = (((v10 ^ a3[v7 ^ i & 3]) + (*a1 ^ v9)) ^ (((16 * v10) ^ ((unsigned int)*a1 >> 3)) + ((4 * *a1) ^ (v10 >> 5))))
+ a1[n2 - 1];
a1[n2 - 1] = v5;
result = v5;
v10 = v5;
--v8;
}
while ( v8 );
return result;
}
对dword_41E000查找引用,定位到以下函数
点击查看代码
int __stdcall TlsCallback_0(int a1, int n4, int a3)
{
int n4_1; // eax
dword_41E000 = 0x88A3F735;
if ( IsDebuggerPresent() )
dword_41E000 = 0x3E81DF12;
n4_1 = n4;
switch ( n4 )
{
case 0:
*(&dword_41E004 + 1) = 0x9E31BDD8;
break;
case 1:
dword_41E004 = 0x386EA53B;
n4_1 = 4;
break;
case 2:
*(&dword_41E004 + 2) = 0x291E3726;
break;
case 3:
*(&dword_41E004 + 3) = 0x88A3F735;
n4_1 = 4;
break;
default:
return n4_1;
}
return n4_1;
}
dump出主函数中用于比较的加密flag部分
unsigned int dword_41E048[6] = {
0x2D66FD90, 0xF6FB537A, 0xE32FCE6D, 0x07248633, 0xDF96A0AD, 0x65E18188
};
解密脚本:
点击查看代码
import struct
dword_41E048 = [
0x2D66FD90, 0xF6FB537A,
0xE32FCE6D, 0x07248633,
0xDF96A0AD, 0x65E18188
]
DELTA = 0x88A3F735
def to_uint32(x):
return x & 0xFFFFFFFF
def mx(sum_val, y, z, p, e, k):
return to_uint32(((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4)) ^ ((sum_val ^ y) + (k[(p & 3) ^ e] ^ z)))
def xxtea_decrypt(v, k):
n = len(v)
if n == 0: return v
rounds = 6 + 52 // n
sum_val = to_uint32(rounds * DELTA)
y = v[0]
while sum_val != 0:
e = (sum_val >> 2) & 3
for p in range(n - 1, -1, -1):
z = v[p - 1]
y = v[p] = to_uint32(v[p] - mx(sum_val, y, z, p, e, k))
sum_val = to_uint32(sum_val - DELTA)
return v
k_original = [0x5319AC34, 0xD7E2667D, 0xC38166DB, 0x2913A100]
k_changed = [0x386EA53B, 0, 0x291E3726, 0x88A3F735]
# Process Attach
key1 = [k_changed[0], k_original[1], k_original[2], k_original[3]]
data1 = [dword_41E048[0], dword_41E048[1]]
dec1 = xxtea_decrypt(data1, key1)
# Thread Attach
key2 = [k_changed[0], k_original[1], k_changed[2], k_original[3]]
data2 = [dword_41E048[2], dword_41E048[3]]
dec2 = xxtea_decrypt(data2, key2)
# Thread Detach
key3 = [k_changed[0], k_original[1], k_changed[2], k_changed[3]]
data3 = [dword_41E048[4], dword_41E048[5]]
dec3 = xxtea_decrypt(data3, key3)
full_dec = dec1 + dec2 + dec3
flag = b""
for val in full_dec:
flag += struct.pack("<I", val)
print(flag.decode('utf-8', errors='ignore')) # ISCTF{Y9r_gO0D@_Tl5_T3A}
密码 Crypto
easy_RSA
简单密码题特有の标题即考点。
考察 RSA 算法。
RSA算法的基本流程 :
RSA算法的核心原理是利用两个大素数的乘积作为公钥的一部分,并通过求解大素数的因子来实现加密与解密。具体步骤如下:
选择两个不同的大素数p和q。
计算N = p * q,N用作模数。
计算欧拉函数φ(N) = (p - 1) * (q - 1)。
选择一个与φ(N)互质的整数e作为公钥指数,1 < e < φ(N)。
计算d,满足(e * d) mod φ(N) ≡ 1,d作为私钥指数。
公钥为(N, e),私钥为(N, d)。
加密与解密过程:
-
加密时,将明文m通过公式c = m^e mod N进行加密。
-
解密时,将密文c通过公式m = c^d mod N进行解密。
给出一个 easy_RSA.py 文件,idle 打开得到如下:
点击查看代码
from Crypto.Util.number import *
p = getPrime(1024)
q = getPrime(1024)
N = p*q
e = 65537
msg = bytes_to_long(b"ISCTF{dummy_flag}")
ct1 = pow(msg, e, N)
ct2 = pow(msg, p+q, N)
print(f"{N = }")
print(f"{ct1 = }")
print(f"{ct2 = }")
"""
N = 17630258257080557797062320474423515967705950026415012912087655679315479168903980901728425140787005046038000068414269936806478828260848859753400786557270120330760791255046985114127285672634413513991988895166115794242018674042563788348381567565190146278040811257757119090296478610798393944581870309373529884950663990485525646200034220648901490835962964029936321155200390798215987316069871958913773199197073860062515329879288106446016695204426001393566351524023857332978260894409698596465474214898402707157933326431896629025197964209580991821222557663589475589423032130993456522178540455360695933336455068507071827928617
ct1 = 5961639119243884817956362325106436035547108981120248145301572089585639543543496627985540773185452108709958107818159430835510386993354596106366458898765597405461225798615020342640056386757104855709899089816838805631480329264128349465229327090721088394549641366346516133008681155817222994359616737681983784274513555455340301061302815102944083173679173923728968671113926376296481298323500774419099682647601977970777260084799036306508597807029122276595080580483336115458713338522372181732208078117809553781889555191883178157241590455408910096212697893247529197116309329028589569527960811338838624831855672463438531266455
ct2 = 11792054298654397865983651507912282632831471680334312509918945120797862876661899077559686851237832931501121869814783150387308320349940383857026679141830402807715397332316601439614741315278033853646418275632174160816784618982743834204997402866931295619202826633629690164429512723957241072421663170829944076753483616865208617479794763412611604625495201470161813033934476868949612651276104339747165276204945125001274777134529491152840672010010940034503257315555511274325831684793040209224816879778725612468542758777428888563266233284958660088175139114166433501743740034567850893745466521144371670962121062992082312948789
"""
写出解密脚本:
点击查看代码
from Crypto.Util.number import long_to_bytes, inverse
# Values from the challenge
N = 17630258257080557797062320474423515967705950026415012912087655679315479168903980901728425140787005046038000068414269936806478828260848859753400786557270120330760791255046985114127285672634413513991988895166115794242018674042563788348381567565190146278040811257757119090296478610798393944581870309373529884950663990485525646200034220648901490835962964029936321155200390798215987316069871958913773199197073860062515329879288106446016695204426001393566351524023857332978260894409698596465474214898402707157933326431896629025197964209580991821222557663589475589423032130993456522178540455360695933336455068507071827928617
ct1 = 5961639119243884817956362325106436035547108981120248145301572089585639543543496627985540773185452108709958107818159430835510386993354596106366458898765597405461225798615020342640056386757104855709899089816838805631480329264128349465229327090721088394549641366346516133008681155817222994359616737681983784274513555455340301061302815102944083173679173923728968671113926376296481298323500774419099682647601977970777260084799036306508597807029122276595080580483336115458713338522372181732208078117809553781889555191883178157241590455408910096212697893247529197116309329028589569527960811338838624831855672463438531266455
ct2 = 11792054298654397865983651507912282632831471680334312509918945120797862876661899077559686851237832931501121869814783150387308320349940383857026679141830402807715397332316601439614741315278033853646418275632174160816784618982743834204997402866931295619202826633629690164429512723957241072421663170829944076753483616865208617479794763412611604625495201470161813033934476868949612651276104339747165276204945125001274777134529491152840672010010940034503257315555511274325831684793040209224816879778725612468542758777428888563266233284958660088175139114166433501743740034567850893745466521144371670962121062992082312948789
e1 = 65537
# Derived exponent
# ct2 = m^(p+q) = m^(N+1 - phi) = m^(N+1) * m^-phi = m^(N+1) mod N
e2 = N + 1
# Extended Euclidean Algorithm to find u, v such that u*e1 + v*e2 = 1
def egcd(a, b):
if a == 0:
return (b, 0, 1)
else:
g, y, x = egcd(b % a, a)
return (g, x - (b // a) * y, y)
g, u, v = egcd(e1, e2)
# Ensure gcd is 1
assert g == 1
# Calculate common modulus attack
# m = ct1^u * ct2^v mod N
# Python's pow(a, b, m) handles negative b if coprime, or we can invert manually
if u < 0:
ct1 = inverse(ct1, N)
u = -u
if v < 0:
ct2 = inverse(ct2, N)
v = -v
m = (pow(ct1, u, N) * pow(ct2, v, N)) % N
print(long_to_bytes(m).decode())
小蓝鲨的LFSR系统
解压得到一个 task.py 和一个 challenge_output.txt
思路:
这是一个典型的 LFSR(线性反馈移位寄存器)已知明文/状态攻击问题。
原理分析
加密逻辑:
LFSR 的反馈逻辑是:feedback = sum(state[i] & mask[i]) % 2。
这本质上是一个线性方程:\(S_{new} = (S_0 \cdot M_0) \oplus (S_1 \cdot M_1) \oplus \dots \oplus (S_{127} \cdot M_{127})\)。
加密使用的 key 直接由 mask 生成。
mask 是未知的,我们需要求出它。
已知条件:
我们拥有完整的 outputState。这包含了初始状态和随后生成的每一个比特。
outputState 的第 0 到 127 位是初始状态。
outputState 的第 128 位是基于第 0-127 位和 mask 计算出的第一个反馈位。
outputState 的第 129 位是基于第 1-128 位和 mask 计算出的第二个反馈位。
以此类推。
破解方法:
我们可以构建一个线性方程组。
未知数是 mask 的 128 个比特 (\(m_0, m_1, \dots, m_{127}\))。
每一个生成的反馈位都能提供一个方程。
由于我们有大量的输出状态(远多于 128 位),我们可以构建一个矩阵并使用 高斯消元法 在 GF(2)(二进制域)上求解 mask。
求出 mask 后,就可以生成 key 并解密。
解密脚本:
点击查看代码
import binascii
# 1. 题目提供的原始数据
initState = [0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0]
outputState = [0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1]
ciphertext_hex = '4b3be165a0a0edd67ca8f143884826725107fd42d6a6'
def solve():
# 2. 关键修正:拼接完整的状态历史
# full_state[0..127] 是初始状态
# full_state[128] 是由 0..127 算出的第一个反馈,以此类推
full_state = initState + outputState
N = 128
matrix = []
# 构建方程组:full_state[i:i+N] * mask = full_state[i+N]
# 我们有 len(outputState) 个方程
num_equations = len(outputState)
for i in range(num_equations):
# 取 128 位作为系数
row = full_state[i : i + N]
# 第 129 位作为结果
target = full_state[i + N]
# 构建增广矩阵行
matrix.append(row + [target])
# 3. 高斯消元 (GF(2))
pivot_row = 0
num_rows = len(matrix)
num_cols = N
for col in range(num_cols):
if pivot_row >= num_rows:
break
# 寻找主元
curr = pivot_row
while curr < num_rows and matrix[curr][col] == 0:
curr += 1
if curr == num_rows:
continue # 该列自由或无解,继续下一列
# 交换行
matrix[pivot_row], matrix[curr] = matrix[curr], matrix[pivot_row]
# 消元 (对所有其他行进行异或,包括上方和下方,即 Gauss-Jordan)
for i in range(num_rows):
if i != pivot_row and matrix[i][col] == 1:
for j in range(col, num_cols + 1):
matrix[i][j] ^= matrix[pivot_row][j]
pivot_row += 1
# 4. 提取 Mask
mask = [0] * N
for i in range(N):
# 此时矩阵应该是对角矩阵形式
if matrix[i][i] == 1:
mask[i] = matrix[i][N]
else:
print(f"Error: 无法求解第 {i} 位的 mask")
return
# 5. 恢复 Key 并解密
key_bytes = bytes(int(''.join(str(bit) for bit in mask[i*8:(i+1)*8]), 2) for i in range(16))
print(f"Recovered Key: {key_bytes.hex()}")
ciphertext = binascii.unhexlify(ciphertext_hex)
keystream = (key_bytes * (len(ciphertext)//16 + 1))[:len(ciphertext)]
plaintext = bytes(p ^ k for p, k in zip(ciphertext, keystream))
try:
print(f"Plaintext: {plaintext}")
print(f"Flag: {plaintext.decode('utf-8')}")
except:
print(f"Decoded bytes: {plaintext}")
if __name__ == '__main__':
solve()
Misc 杂项
Guess!
猜数游戏,二分猜数即可。
美丽的风景照
不喜欢这个题目,有种抛弃大脑强度更高的美。
题目给出了七张图片,第七张图里的二维码无效。
每张图片里都有一串密文。
看样子很像 base64
每张图片都是不同颜色,有一张还特意放了个青花瓷照片上去。
可以猜测出要按彩虹颜色顺序排序
拿AI写了个脚本,发现求不出结果。
怀疑是 base64 出了问题,因为图片很糊,怕难以辨认有可能使用了 base58
再拿 base58 试一遍,怎么还不行。。。
做破防了之后选择暴力枚举(真好用)
脚本如下:
点击查看代码
import base58
from itertools import permutations, product
import math
import sys
# The pieces of the flag provided
pieces = [
"jqW2",
"ZXw8T",
"7HLo8",
"6yRWh",
"Dg2C",
"98Mz",
"3CaEK"
]
def solve():
"""
Brute-forces all combinations and reversals of the pieces to find the flag.
"""
num_pieces = len(pieces)
# Calculate the total number of combinations to check
# Number of permutations: n!
# For each permutation, number of reversal combinations: 2^n
total_permutations = math.factorial(num_pieces)
total_reversal_combos = 2**num_pieces
total_attempts = total_permutations * total_reversal_combos
print(f"[*] Starting brute force...")
print(f"[*] Pieces: {num_pieces}")
print(f"[*] Permutations to check: {total_permutations}")
print(f"[*] Reversal combinations per permutation: {total_reversal_combos}")
print(f"[*] Total combinations to test: {total_attempts:,}")
print("-" * 30)
count = 0
found_flags = []
# 1. Iterate through every possible order of the pieces
for p in permutations(pieces):
# p is a tuple of the pieces in a specific order, e.g., ('jqW2', 'ZXw8T', ...)
# 2. For this specific order, iterate through every reversal combination.
# product([False, True], repeat=num_pieces) generates all 2^n tuples
# of booleans, e.g., (False, False, ...), (False, True, ...), etc.
for reversals in product([False, True], repeat=num_pieces):
# Build the candidate string from the current permutation and reversal combination
candidate_list = []
for i in range(num_pieces):
piece = p[i]
if reversals[i]:
# If the reversal flag is True, reverse the piece
candidate_list.append(piece[::-1])
else:
# Otherwise, use it as is
candidate_list.append(piece)
candidate_string = "".join(candidate_list)
# Update progress indicator
count += 1
if count % 20000 == 0:
progress = (count / total_attempts) * 100
print(f"\r[*] Progress: {progress:.2f}% ({count:,}/{total_attempts:,})", end="")
sys.stdout.flush()
# 3. Try to decode the concatenated string and check the format
try:
# Decode the base58 string into bytes
decoded_bytes = base58.b58decode(candidate_string)
# Decode the bytes into a UTF-8 string
decoded_string = decoded_bytes.decode('utf-8')
if decoded_string.startswith("ISCTF{") and decoded_string.endswith("}"):
print(f"\n\n[+] SUCCESS! Found a potential flag: {decoded_string}")
found_flags.append(decoded_string)
except Exception:
# Ignore errors from invalid base58 strings or non-utf8 decodes
continue
# Final summary
print(f"\r[*] Progress: 100.00% ({total_attempts:,}/{total_attempts:,})")
print("-" * 30)
if found_flags:
print(f"[*] Brute force complete. Found {len(found_flags)} possible flag(s):")
for flag in found_flags:
print(f" - {flag}")
else:
print("[!] Brute force complete. No flag found.")
if __name__ == "__main__":
solve()
湖心亭看雪
snow 加密入门
题目给了一张湖心亭.jpg
扔 010editor 里,结尾看到了熟悉的 50 4B 05 06
藏了一个 .ZIP 文件,但是 50 4B 03 04 丢了,补上!
(其实需要先删了FF D9 再补。。。调了好久)
得到了一个需要密码才能打开的 flag.txt
随波逐流检测到 AES 加密密码,直接爆破。
破解半个小时无果后决定试试其他思路。
直到我看到文件夹里的 test.py 。。。。。
是的,题目解压出来还带了个 test.py (但我没看到)
点击查看代码
a = b'*********' #这个东西你以后要用到
b = b'blueshark'
c = bytes([x ^ y for x, y in zip(a, b)])
print(c.hex())
#c = 53591611155a51405e
写个小脚本运行一下
点击查看代码
# 已知的 hex 字符串
hex_c = "53591611155a51405e"
# 将 hex 转换为 bytes 对象
c = bytes.fromhex(hex_c)
# 已知的 b
b = b'blueshark'
# 利用异或性质还原 a (a = c ^ b)
a = bytes([x ^ y for x, y in zip(c, b)])
print(a)
得到 key b'15ctf2025' (密码是15ctf2025)
得到 flag.txt ,直接打开发现有隐写,拿 010editor 打开

再结合题目里的 snow 可以猜测是 snow 隐写。
隐写的 key 就是解压的 key
拿随波逐流snow雪花隐写提取即可。

阿利维亚的传说
阴间题。
解压出来得到一个 TiTan.png 和 阿利维亚的传说.docx
TiTan.png 图片里有三把钥匙,分别是蓝红绿,可以联想到 RGB (思维有点跳脱了)
.docx 文档里告诉我们 flag 有三段。
先把图片扔进随波逐流里头,分析出如下内容:
点击查看内容
含{}符号: ....{J} | z".*{\]} | cEcq{"} | /.Oz${,z Q'3} | ~v{} | .gf~{{{} | \oA.{@%=l{nva6} | .{jQvi[Y} | .|.S{~;} | .m'{q} | \.{} | Z'9{ h?} | k~F.{6} | )y{u|3} | g%..{=} | q_{FtW} | /^{} | .v{y} | D.loj{z[%7} | r{c>} | .Vp.{V} | .U{hZ} | 1P{*} | v<a`{<<=-Wm} | ho_e{vH} | ]{V>?} | Jn{} | ..`{} | .Y({g} | /E-{} | ;.{} | &U.7{} | %t:x{} | hTDv{5Vb^O} | mkO6{I/} | z|yy9{^*%-b} | .{} | .+.v{Uw} | NN.T.{b} | ...{} | ..!.D{yys} | .w.9z{} | =..5A{M} | }.M.m{H} | .K.{~~nb} | {DdN} | .rY{G} | .-'{IfWMvHz|} | ....{} | ._.S0{)} | ru|};{yg} | ....{GX} | ]9K{>Q/^Ut} | .W{} | #L.^{Oj4} | v2{D#P|m} | .,{_#,3,} | +%..{} | dI{'Ao(J\} | RR:\{n\} | #..L.{3D"} | #l.,%{RDX} | .Rwu}}{{{} | ZG.{-l6} | \yc]{wo} | %{Rd} | d.q.{s} | W.}w}{{{} | .B.O{`} | |xsw{ywy} | f={M} | m.Gb{UNf<} | .;s{{} | :W.{s'V]spLy9?S} | W.{Lw~[} | :f.{wg} | .4p{>iwO+<{],?u} | ."VxO{Ux} | VU.[U{3_&} | J.]){f7]"2h} | ..8{} | EW&.{&} | w{} | u.{j} | ..{jNg(} | B..{h#} | '.{k} | D?{} | .M]'h{} | .nf{} | M[D{B%lYe} | H./~:{]} | RP_{_} | .#.LB{7} | 8wi{b} | |S;|{bD} | .{} | u..o.{&} | rHV{*} | Hd0{!} | 270;e{9} | .Y.\;{} | ~..{W<} | ;Oq}p{#qFx;U} | .n-.{-j} | zPJ.3{D)B} | M{UF} | ?{i} | J9g.{|];} | '].{(} | RZF[v{/q} | @~x{} | Hmn{LyEc,} | ks{4g4oF} | <$!U{.F^]S<Fm4} | !B.{h@d} | x.~{} | w.1i{[} | {} | O..|{y} | &[.{<p;} | .5~.!{} | 7.{:} | O.{} | ;\Q{} | ,h$^.R{?l?)} | ..{} | ..{lvb} | .@{} | m.6{]xRT} | W.{d-} | .p'{^#} | G+.{I{} | fsH_{M_=i*} | o]U{{C} | 8lG.{} | .]4.H4{I} | Slf>.-{ 58)} | .o.{Z5L{} | {"7} | 9UY}c{ST} | .423{} | Aon{} | Yx.{1F.} | [E{+} | EpRp{B-9} | Qz..{dP#Pwh@yrfK\^_.YR} | dOz{|,x} | kc.-{+} | h1;m..{wsl} | .j3I..{} | ..n{W} | XGOiz{=} | oQ0{*} | .{>} | va..w{} | E.0{,Z<} | ~.z[{Bh~t'hZ(ko:} | ~6{j} | .oo{} | .*{U} | ."{yn} | {ow} | '%->.{&s.} | 4.n{a} | ?|={@0<} | ..{xOVNLD,9OS&} | >{W} | [{} | Bz.{S?O8} | xf{R_s} | :.h{=} | QF{?+t} | .*}{} | ..j{0U} | LE.d{} | .{H} | Z."1p{xT} | Y4{7|76m3ve} | .;{7y} | K@.0{U[%} | .d..t{l2} | 6:`d{} | 8...{_} | 3".{s[-} | IzA{*EPNEO,} | E..M{cl(E_.} | 4S~{?yqU} | ..{K} | E.{|s} | {0a;RZ} | x:D.{wO1:Wk} | .U]=j`{!} | zpx^?{/>i} | 6&Vg@h{u!]} | 8.95-{B)} | .o.%v{} | SU9>{;} | .R^zu{sS0t|t|zj)E} | <.u{LEz{} | gYh5{>} | *x{wZ[} | :z{WDdVDx8)e7]^\2} | VnV.Z{L/^} | .N.u0.{} | Z.e{{U} | !.[{uA0f\CDDJ5{py} | ryysx{i} | sS9{v} | $u&-{} | .%.-\{O} | rd{b?} | x(B.|{O1} | .w/{o} | fVC{-L} | X.{?F5G} | .)tss{u} | {|q} | .i.{w:z} | Mf_{} | .j[r`j.{/jv8<k} | WRfo.{km.K} | O|{j{H} | z%Z.{ucE} | 3U`{6>O_?b} | Z.U#E{K-} | W.{cen} | SZ{i/-z B=IeKb)~o_} | .GR{w} | ..9{fss} | ...se{~e{} | U..'{WOw} | sSU+.{} | d>{} | .7{x+m9"} | N6{B`} | Av{o:Id<} | bxw{x} | ]-1~t{} | (&.{m} | .8z{?j:>s} | |7.z{u} | .h+a{} | E`V...{<:b} | ..u{} | ...{-GUnG} | -a@{|7} | 8R.{"f} | 5U{^} | .s?{} | !,*Ch{w} | T.J.{6qk/1\+>} | Jk7..{} | o..{y2&^} | >._..U{z|xx(ue} | 2t.{2qhCUdV-`} | ren{u} | NR{} | .{&~} | Jw30{`{} | .zFe{.} | ..6&{7nt} | N'K{!U$<eYV:y} | j...{u-o0(z} | 99.{I|Z?} | eS){} | .{?} | ?vm1N{[BLm gm} | BP.c.{b:~XOIg} | 0BSg{+} | ...<w{goFtE;-} | U,YY${3G} | u.1{} | \w.{76f]} | .03{@w} | zk.{PtW} | kl|{_} | ]my|{} | ).{} | t.{pDx6} | nO.7{} | U{]ZYZO@N} | L.({m} | .k..j{W"o9} | x3R;{Y`pg} | 3'{?} | .pJ0,`{nj&} | ac?..{sH?} | .{r#zD} | jt#'..{)9} | gb{} | ..{t]} | .._}{} | ng%{er} | .s]z.\{jK} | yXR{F} | l.h?{|} | ]`?{} | ..c{tDT} | R.$.G{} | EgD{B} | wl|{Ee/oF0es-4'|=XF:B5} | }L>o{g} | ^.{6SA3} | K.{~z} | uT.<{;L&Sq9#} | . {Q} | .$f{|} | gN{C} | 8.n{} | _~{/_} | (9./{} | .:.!{C(lF.K;{~g^/} | (J.{+"} | Do{g2eC*mbc[[} | oZ"7.{Gy{} | OI#={#UD} | #O/...{& i} | o".Jm}{:?J4I} | DG{nm} | $C.X{$g} | DQdD{} | v]o{} | .v{{} | }.a".{Lm} | J7.{F} | *.r{} | ).s~9{s;x} | zKd.{_X#f'^#;y} | $C.#{} | 3^.{Y).8} | 6f!.{4} | .W?o'{r9W} | {7} | /?}{} | o_|.{]} | t.O{q`4} | 8=I{G)0IC4jf} | ....{$+} | v{?zk} | %My{_} | hY{:} | 7h]{7{k} | .r..F{} | .W.m{~\_[\?} | .!wE{vr} | .1N{)UYi} | P4Qom{{m/u{} | Od{{{} | ..e{0D]} | c.{} | ZhRz{_~?} | _+Bs{} | .|..{~} | v3m{1} | ?`v3{}
◆包含'=='字符串(可能是base64编码): T== | NR== | H== | === | w== | 4== | y8== | === | === | a8== | === | B== | I== | === | M0== | === | === | 4=== | p8== | === | 3== | === | === | === | Q== | 4== | 6== | Ui== | a== | 4== | === | === | 8== | Ly== | 8== | 9== | S== | K== | === | s==
◆包含flag字符串: flag3.txt | flag3.txt
◆文件仅包含默认数据流,无额外NTFS数据流
◆Png文件结束标志[49 45 4E 44 AE 42 60 82]共1个,后面包含其他信息,可能存在多个文件叠加隐写或后接文本隐写。
可以看到藏了一个 flag3.txt ,随波逐流自动提出图片末尾的信息整理成了一个 Titan_end.txt
压缩包爆破先拿 flag3 (并非flag,实则预言3)

问问无敌的 AI ,知道有 StegSolve 这么个好东西,配合随波逐流

提取出预言 2
预言1 在文档里。
V=Dortt
A=otuTa
N=NTsin
行了,预言是都找齐了,flag呢???
所有预言都是等式的形式,猜测可能是代换?
死活想不出来了。
直到我注意到预言3 等式左边从上往下正好是个TRUE
恍然大悟了。(貌似出题人还藏了点小彩蛋?)


浙公网安备 33010602011771号