[GHCTF 2025]FishingKit

题目描述:一个钓鱼佬坐在河边准备钓鱼,从工具箱中掏出了钓鱼竿、钓鱼线,但是钓了半天一条鱼都没有。诶?似乎少了什么… …

拿到文件先查壳,发现这个无壳,64位。将它拖到IDA中分析。

bait是鱼饵的意思,一个勤恳的钓鱼佬怎么能没有鱼饵呢,所以我们要知道bait是什么。

如下所示,v4就是bait,这有个if语句,应该是给v4赋值的,然后就得到第二个东西(ps:我盲猜是个鱼钩)是v5,sub_7FF69CE42460函数就是v4作为密钥,将v5进行加密,得到Str1。strcmp函数将Str1与Str2进行比对,如果相同的话,就成功了,逆过来就可以得到flag。按照常规操作来讲的话,很对。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from z3 import *

s = Solver() # 创建Z3求解器

a0,a1,a2,a3,a4,a5,a6,a7,a8,a9 = BitVecs("a0 a1 a2 a3 a4 a5 a6 a7 a8 a9",12) #定义变量

#在Z3中,BitVec用于表示固定大小的位向量,这对于建模具有有限位宽(如8位、16位、32位等)的整数非常有用,
# 特别是在位操作至关重要的情况下,如位移、按位与、按位或等操作

s.add(202 * a8 + 216 * a5 - 4 * a4 - 330 * a9 - 13 * a4 - 268 * a6 == -14982)
s.add(325 * a8 + 195 * a0 + 229 * a1 - 121 * a6 - 409 * a6 - (a1 << 7) == 22606)
s.add(489 * a1 + 480 * a6 + 105 * a2 + 367 * a3 - 135 * a4 - 482 * a9 == 63236)
s.add(493 * a1 - 80 * a4 - 253 * a8 - 121 * a2 - 177 * a0 - 243 * a9 == -39664)
s.add(275 * a4 + 271 * a6 + 473 * a7 - 72 * a5 - 260 * a4 - 367 * a4 == 14255)
s.add(286 * a0 + 196 * a7 + 483 * a2 + 442 * a1 - 495 * a8 - 351 * a4 == 41171)
s.add(212 * a2 + 283 * a7 - 329 * a8 - 429 * a9 - 362 * a2 - 261 * a6 == -90284)
s.add(456 * a5 + 244 * a7 + 92 * a4 + 348 * a7 - 225 * a1 - 31 * a2 == 88447)
s.add(238 * a9 + 278 * a7 + 216 * a6 + 237 * a0 + 8 * a2 - 17 * a9 == 83838)
s.add(323 * a9 + 121 * a1 + 370 * a7 - (a4 << 6) - 196 * a9 - 422 * a0 == 26467)
s.add(166 * a9 + 90 * a1 + 499 * a2 + 301 * a8 - 31 * a2 - 206 * a2 == 88247)
s.add(355 * a0 + 282 * a4 + 44 * a9 + 359 * a8 - 167 * a5 - 62 * a3 == 76658)
s.add(488 * a6 + 379 * a9 + 318 * a2 - 85 * a1 - 357 * a2 - 277 * a5 == 35398)
s.add(40 * a0 + 281 * a4 + 217 * a5 - 241 * a1 - 407 * a7 - 309 * a7 == -35436)
s.add(429 * a3 + 441 * a3 + 115 * a1 + 96 * a8 + 464 * a1 - 133 * a7 == 157448)

if s.check() == sat:
ans = s.model()
for i in range(10):
t = int(f"{ans[eval(f'a{i}')]}") #将a[i]转换为整数
print(chr(t),end='')

使用z3求解器可以来求出bait(v4):DeluxeBait

接下来分析sub_7FF69CE42460函数里面进行的操作,可以发现,这是一个变种的RC4加密算法

byte_1400060C0 是RC4的S盒(256字节数组),sub_1400028B0 负责用密钥a3打乱S盒。

将密钥 a1 按字节循环填充到 v7 数组中,使其长度为256字节。

索引计算:v5 = (key[k] + S[k] + v5) % 256

交换操作:交换 S[k]S[v5]

思路:

  1. 还原S盒状态
    • 若密钥 a3(即v13)已知,可重新运行此函数生成相同的S盒。
    • 动态提取 byte_1400060C0 初始化后的值(调试器查看内存)。
  2. 加密流程适配
    • 后续的 sub_140002460 函数(PRGA)使用此S盒生成密钥流。
    • 加密时额外异或 0x14,解密需先异或 0x14 再按相同S盒解密。

3.目标明文:[0xE9, 0x37, 0xF8, 0xE2, 0x0C, 0x0F, 0x3D, 0xB9, 0x5C, 0xA3, 0xDE, 0x2D, 0x55, 0x96, 0xDF, 0xA2, 0x35, 0xFE, 0xB3, 0xDD, 0x7F, 0x91, 0x3C]

原以为可以美美求解了,没想到flag给我虚晃一枪,哈哈哈哈这个是掩人耳目,是个错误的flag

这个时候,要进行动态调试,看看函数到底是如何调用的。在strcmp函数处下断点进行调试,单步步入F7,发现了它跳转到另一个函数里面去执行了。函数如下所示:进行分析发现是XTEA加密算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
__int64 __fastcall sub_7FF69CE41CE0(_BYTE *a1, unsigned __int8 *a2)
{
unsigned int v3; // [rsp+20h] [rbp-108h]
unsigned int v4; // [rsp+20h] [rbp-108h]
unsigned int v5; // [rsp+20h] [rbp-108h]
unsigned int v6; // [rsp+24h] [rbp-104h]
unsigned int v7; // [rsp+24h] [rbp-104h]
unsigned int v8; // [rsp+24h] [rbp-104h]
unsigned int v9; // [rsp+28h] [rbp-100h]
unsigned int v10; // [rsp+28h] [rbp-100h]
unsigned int v11; // [rsp+28h] [rbp-100h]
char v12; // [rsp+2Ch] [rbp-FCh]
int i; // [rsp+30h] [rbp-F8h]
int j; // [rsp+34h] [rbp-F4h]
int ii; // [rsp+38h] [rbp-F0h]
int jj; // [rsp+3Ch] [rbp-ECh]
int kk; // [rsp+40h] [rbp-E8h]
int mm; // [rsp+44h] [rbp-E4h]
int nn; // [rsp+48h] [rbp-E0h]
int v20; // [rsp+4Ch] [rbp-DCh]
int k; // [rsp+50h] [rbp-D8h]
int m; // [rsp+54h] [rbp-D4h]
int n; // [rsp+58h] [rbp-D0h]
HMODULE hModule; // [rsp+60h] [rbp-C8h]
FARPROC ProcAddress; // [rsp+68h] [rbp-C0h]
CHAR LibFileName[16]; // [rsp+70h] [rbp-B8h] BYREF
CHAR ProcName[16]; // [rsp+80h] [rbp-A8h] BYREF
char v28[3]; // [rsp+90h] [rbp-98h] BYREF
char v29[11]; // [rsp+93h] [rbp-95h] BYREF
char v30[8]; // [rsp+9Eh] [rbp-8Ah] BYREF
char v31[32]; // [rsp+A8h] [rbp-80h] BYREF
int v32[6]; // [rsp+C8h] [rbp-60h] BYREF
int v33[14]; // [rsp+E0h] [rbp-48h] BYREF
_BYTE *v34; // [rsp+130h] [rbp+8h]

v34 = a1;
memset(v32, 0, 0x14ui64); // 初始化v32,v33数组为0
memset(v33, 0, 0x32ui64);
for ( i = 0; i < 20; ++i )
*((_BYTE *)v32 + i) = a1[i - 80];
for ( j = 0; j < 50; ++j )
*((_BYTE *)v33 + j) = a1[j - 56];
v3 = 0; // 第一部分处理了v33[0],v33[1]
v6 = v33[0];
v9 = v33[1];
for ( k = 0; k < 24; ++k ) // XTEA
//
{
v6 += (v32[v3 & 3] + v3) ^ (v9 + ((v9 >> 5) ^ (16 * v9)));
v3 += 1719109785; // v3每次循环都增加一个常数
v9 += (v32[(v3 >> 11) & 3] + v3) ^ (v6 + ((v6 >> 5) ^ (16 * v6)));// v32数组中的元素作为密钥
}
v33[0] = v6;
v33[1] = v9;
v4 = 0;
v7 = v33[2]; // 第二部分处理了v33[2],v33[3]
v10 = v33[3];
for ( m = 0; m < 24; ++m )
{
v7 += (v32[v4 & 3] + v4) ^ (v10 + ((v10 >> 5) ^ (16 * v10)));// 在计算机中,左移运算(<<)本质上是将二进制数向左移动指定位数,低位补零。对于整数运算,左移 4 位等价于乘以 16
v4 += 1719109785;
v10 += (v32[(v4 >> 11) & 3] + v4) ^ (v7 + ((v7 >> 5) ^ (16 * v7)));
}
v33[2] = v7;
v33[3] = v10;
v5 = 0; // 第三部分处理了v33[4],v33[5]
v8 = v33[4];
v11 = v33[5];
for ( n = 0; n < 24; ++n )
{
v8 += (v32[v5 & 3] + v5) ^ (v11 + ((v11 >> 5) ^ (16 * v11)));
v5 += 1719109785;
v11 += (v32[(v5 >> 11) & 3] + v5) ^ (v8 + ((v8 >> 5) ^ (16 * v8)));
}
v33[4] = v8;
v33[5] = v11;
v12 = 1;
for ( ii = 0; ii < 24; ++ii ) // 处理后的v33数组的前24个字节byte_7FF6BF4463C8处的数据进行比较
{
if ( byte_7FF69CE463C8[ii] != *((unsigned __int8 *)v33 + ii) )// 要知道24节密文,逆推出v33加密前的明文
{
v12 = 0;
break;
}
}
if ( v12 ) // 如果匹配成功
{
strcpy(LibFileName, "dbtc\"#?u}}"); // 一堆异或
for ( jj = 0; jj < 10; ++jj )
LibFileName[jj] ^= 0x11u;
hModule = LoadLibraryA(LibFileName); // user32.dll
strcpy(ProcName, "\\tbbpvtS~iP");
for ( kk = 0; kk < 11; ++kk )
ProcName[kk] ^= 0x11u;
ProcAddress = GetProcAddress(hModule, ProcName);// MessageBoxA
strcpy(v31, "H~d6gt1rpdvye1p1sxv1wxby0");
for ( mm = 0; mm < 25; ++mm )
v31[mm] ^= 0x11u; // You've caught a big fish!
qmemcpy(v28, "R~", 2);
v28[2] = 127;
qmemcpy(v29, "vcped}pex~", 10);
v29[10] = 127;
strcpy(v30, "000");
for ( nn = 0; nn < 17; ++nn )
v28[nn] ^= 0x11u; // Congratulations
((void (__fastcall *)(_QWORD, char *, char *, _QWORD))ProcAddress)(0i64, v31, v28, 0i64);
}
while ( 1 )
{
v20 = (unsigned __int8)*v34 - *a2;
if ( v20 || !*v34 )
break;
++v34;
++a2;
}
if ( v20 > 0 )
return 1i64;
if ( v20 >= 0 )
return 0i64;
return 0xFFFFFFFFi64;
}

v32就是密钥,就是DeluxeBait。密文是byte_7FF69CE463C8,进行交叉引用查看

找到了密文,进行XTEA解密,这个加密轮数是24轮

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <stdio.h>
#include <stdint.h>
typedef uint32_t u32;
#define delta 0x66778899
#define rounds 24

void encrypt(uint32_t * v , uint32_t * k) {
u32 v0 = v[0], v1 = v[1];
u32 k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];
u32 sum = 0;

// xTEA

for (int i = 0; i < rounds; i ++) {
v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + k[sum & 3]);
sum += delta;
v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + k[(sum >> 11) & 3]);
}

v[0] = v0;
v[1] = v1;
}

void decrypt(uint32_t * v , uint32_t * k) {
u32 v0 = v[0], v1 = v[1];
u32 k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];
u32 sum = rounds * delta;


//xTEA
for (int i = 0; i < rounds; i ++) {
v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + k[(sum >> 11) & 3]);
sum -= delta;
v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + k[sum & 3]);
}

v[0] = v0;
v[1] = v1;
}

void encrypt_multiple_blocks(unsigned char * text , unsigned char * key) {
uint32_t *v = (uint32_t *)text;
uint32_t *k = (uint32_t*)key;
for (int i = 0; i < 6; i += 2) //处理三组
{
encrypt(v + i, k);
printf("加密后的数据:%u %u\n", v[i], v[i+1]);
}

for (int i = 0; i < 24; i++) {
printf("0x%x ,", text[i]);
}
}

void decrypt_multiple_blocks(unsigned char * text , unsigned char * key) {
uint32_t *v = (uint32_t *)text;
uint32_t *k = (uint32_t*)key;
for (int i = 0; i < 6; i += 2) //处理三组
{
decrypt(v + i, k); //将 v[i] 和 v[i + 1] 视为一个数据块(共 8 字节)传入解密函数。
printf("解密后的数据:%u %u\n", v[i], v[i+1]); //2个32位无符号整数
}

for (int i = 0; i < 24; i++) {
printf("%c", text[i]);
}
}


unsigned char keys[] = {0x44, 0x65, 0x6C, 0x75, 0x78, 0x65, 0x42, 0x61,0x69, 0x74, 0x00, 0x00,0x00, 0x00,0x00, 0x00};
unsigned char plain[] = {0x21, 0x56, 0x97, 0xA6, 0x1A, 0xD5, 0xC4, 0xDE, 0xA4, 0x9C, 0x82, 0x4D, 0xD1, 0x45, 0xC8, 0x56, 0xA7, 0xB4, 0x96, 0x5C, 0x4D, 0x49, 0x87, 0x20};
unsigned char cipher[] = {0x21, 0x56, 0x97, 0xA6, 0x1A, 0xD5, 0xC4, 0xDE, 0xA4, 0x9C, 0x82, 0x4D, 0xD1, 0x45, 0xC8, 0x56, 0xA7, 0xB4, 0x96, 0x5C, 0x4D, 0x49, 0x87, 0x20};
int main()
{
unsigned char a;
// encrypt_multiple_blocks(plain, keys);
decrypt_multiple_blocks(cipher , keys);
return 0;
}

解出来flag:NSSCTF{Wh@t_@_b1g_F1sh}