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 有关的东西。

屏幕截图 2025-12-08 112808

拿出 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 的文件比较可疑。

拿打开看看。

屏幕截图 2025-12-08 120435

可以看到是 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 函数中。

  1. 密钥准备
    在 main 函数中:
qmemcpy(P4ssXORSecr3tK3y_, "P4ssXORSecr3tK3y!", sizeof(P4ssXORSecr3tK3y_)); 这里初始化了一个长度为 17 的字符串:"P4ssXORSecr3tK3y!"。
  1. 读取文件
    程序读取名为 payload.dat 的文件内容到内存中。这个文件包含了加密后的数据(Ciphertext)。

  2. 第一层处理:魔改 RC4 (Variant RC4)
    程序调用了 rc4_variant:

rc4_variant(ptr_1, i_1, &P4ssXORSecr3tK3y_[7], 10); 数据:文件内容。 密钥:&P4ssXORSecr3tK3y_[7],即从第7个字符开始,长度为10。 密钥字符串为:"Secr3tK3y!"。 RC4 变种分析: 标准的 RC4 KSA(密钥调度算法)逻辑是 j = (j + S[i] + key[i % keylen]) % 256。 但在此函数的 KSA 循环中: v8 = (unsigned __int8)((n256 & 0xAA) + v8 + v9 + *(_BYTE *)(a3 + n256 % n10)); 由于 n256 就是循环变量 i,逻辑变成了: j = ( (i & 0xAA) + j + S[i] + key[i % len] ) % 256

这增加了一个 (i & 0xAA) 的项,这是该算法与标准 RC4 唯一的区别。PRGA(伪随机生成算法)部分与标准 RC4 一致。

  1. 第二层处理:XOR
    RC4 处理完后,程序执行了一个简单的异或循环:
for ( i = 0; i != i_1; ++i ) ptr_1[i] ^= P4ssXORSecr3tK3y_[i % 7]; 密钥:P4ssXORSecr3tK3y_ 的前7个字符。 密钥字符串为:"P4ssXOR"。 5. 输出 最后程序打印结果:Result: %s。这意味着这段代码本身就是一个解密器。如果题目提供了 payload.dat 文件,这段代码的作用就是将其解密回明文。

解决方案
由于代码是对称加密(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 打开

屏幕截图 2025-12-08 232730

再结合题目里的 snow 可以猜测是 snow 隐写。

隐写的 key 就是解压的 key

拿随波逐流snow雪花隐写提取即可。

屏幕截图 2025-12-08 233857

阿利维亚的传说

阴间题。

解压出来得到一个 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)

F732CD48D6E1B9CE200CAD4923EDDF8C

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

31DEDE92432A445E38503CAFC734B8EF

提取出预言 2

预言1 在文档里。

V=Dortt
A=otuTa
N=NTsin

行了,预言是都找齐了,flag呢???

所有预言都是等式的形式,猜测可能是代换?

死活想不出来了。

直到我注意到预言3 等式左边从上往下正好是个TRUE

恍然大悟了。(貌似出题人还藏了点小彩蛋?)

ZIR82BC`}%~)SUM8FAZ8B)4

posted @ 2025-12-08 10:25  int_Hello_world  阅读(103)  评论(0)    收藏  举报