[GHCTF 2025]FishingKit题解
fightcheng bait数据
可以使用z3求解器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| from z3 import * s = Solver() a0,a1,a2,a3,a4,a5,a6,a7,a8,a9 = BitVecs("a0 a1 a2 a3 a4 a5 a6 a7 a8 a9",12) 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}')]}") print(chr(t),end='')
|
求出bait(v4):DeluxeBait。
分析sub_7FF69CE42460函数发现,这是一个变种的RC4加密算法,解开后得到NSSCTF{Fake!Fake!Fake!},此时我才发现这是一个假flag,弄的我百思不得其解。 实在没办法,一个一个翻
发现sub_7FF651DB1CE0函数
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; }
|
该函数使用类似XTEA的加密算法,对三个64位数据块进行加密,每块24轮,使用一个20字节的密钥数组v32。加密后的结果与预设的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
| void *__fastcall sub_7FF651DB1060(void *a1) { int i; // [rsp+0h] [rbp-28h] char v3[32]; // [rsp+8h] [rbp-20h] BYREF
memset(a1, 0, 0x18ui64); qmemcpy(v3, "!V", 2); v3[2] = -105; v3[3] = -90; v3[4] = 26; v3[5] = -43; v3[6] = -60; v3[7] = -34; v3[8] = -92; v3[9] = -100; v3[10] = -126; v3[11] = 77; v3[12] = -47; v3[13] = 69; v3[14] = -56; v3[15] = 86; v3[16] = -89; v3[17] = -76; v3[18] = -106; v3[19] = 92; v3[20] = 77; v3[21] = 73; v3[22] = -121; v3[23] = 32; for ( i = 0; i < 24; ++i ) *((_BYTE *)a1 + i) = v3[i]; return a1; }
|
给出解密
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
| #include <iostream>
void decipher(uint32_t v[2], const uint32_t key[4]) { unsigned int i; uint32_t v0 = v[0], v1 = v[1], delta = 0x66778899, sum = delta * 24; for (i = 0; i < 24; i++) { v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3]); sum -= delta; v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]); } v[0] = v0; v[1] = v1; }
int main() { uint8_t EncFlag[24]{}; memcpy(EncFlag, "!V", 2); EncFlag[2] = -105; EncFlag[3] = -90; EncFlag[4] = 26; EncFlag[5] = -43; EncFlag[6] = -60; EncFlag[7] = -34; EncFlag[8] = -92; EncFlag[9] = -100; EncFlag[10] = -126; EncFlag[11] = 77; EncFlag[12] = -47; EncFlag[13] = 69; EncFlag[14] = -56; EncFlag[15] = 86; EncFlag[16] = -89; EncFlag[17] = -76; EncFlag[18] = -106; EncFlag[19] = 92; EncFlag[20] = 77; EncFlag[21] = 73; EncFlag[22] = -121; EncFlag[23] = 32;
uint8_t Key[] = "DeluxeBait\0\0\0\0\0\0";
decipher((uint32_t*)(EncFlag), (uint32_t*)Key); decipher((uint32_t*)(EncFlag + 8), (uint32_t*)Key); decipher((uint32_t*)(EncFlag + 16), (uint32_t*)Key);
printf("%.23s\n", EncFlag); return 0; }
|
得到正确答案NSSCTF{Wh@t_@_b1g_F1sh}
附:
什么是hook?
hook后调用原函数时,直接跳到指定的函数。
inlinehook是hook的一种方式。通过修改原函数开头的汇编指令,直接跳转到指定函数。
VirtualProtect 是 Windows 操作系统中的一个 API 函数,它允许应用程序改变一个内存页的保护属性。这个函数的原型如下:
1 2 3 4 5 6
| BOOL VirtualProtect( LPCVOID lpAddress, // 要改变保护属性的内存页的起始地址 SIZE_T dwSize, // 内存页的大小 DWORD flNewProtect, // 新的保护属性 PDWORD lpflOldProtect // 存储旧的保护属性 );
|
逆向分析中,VirtualProtect 函数通常用于代码自加密的场景。代码自加密是一种保护代码不被轻易逆向分析的技术,通过在程序运行时动态地修改代码的内存保护属性,使得代码在执行时可以被修改和执行,但在不执行时则不能被读取或修改。
这道题可以搜索字符串VirtualProtect,发行端倪
1 2 3 4
| .idata:00007FF651DB4008 extrn VirtualProtect:qword .idata:00007FF651DB4008 ; CODE XREF: sub_7FF651DB1C00+8B↑p .idata:00007FF651DB4008 ; sub_7FF651DB1C00+BA↑p .idata:00007FF651DB4008 ; DATA XREF: ...
|
进入sub_7FF651DB1C00
1 2 3 4 5 6 7 8 9 10 11 12 13
| char __fastcall sub_7FF651DB1C00(void *a1, __int64 a2) { DWORD flOldProtect; // [rsp+20h] [rbp-28h] BYREF __int64 Src[2]; // [rsp+28h] [rbp-20h] BYREF
LODWORD(Src[0]) = 9727; WORD2(Src[0]) = 0; *(__int64 *)((char *)Src + 6) = a2; VirtualProtect(a1, 0xEui64, 0x40u, &flOldProtect); memcpy(a1, Src, 0xEui64); VirtualProtect(a1, 0xEui64, flOldProtect, &flOldProtect); return 1; }
|
查看调用上层
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| char sub_7FF651DB1AD0() { int i; // [rsp+20h] [rbp-48h] int j; // [rsp+24h] [rbp-44h] HMODULE hModule; // [rsp+28h] [rbp-40h] FARPROC ProcAddress; // [rsp+30h] [rbp-38h] CHAR ProcName[8]; // [rsp+38h] [rbp-30h] BYREF CHAR ModuleName[16]; // [rsp+40h] [rbp-28h] BYREF
qmemcpy(ModuleName, "fpagqr`v=w", 10); ModuleName[10] = 127; ModuleName[11] = 127; ModuleName[12] = 0; for ( i = 0; i < 12; ++i ) ModuleName[i] ^= 0x13u; hModule = GetModuleHandleA(ModuleName); strcpy(ProcName, "`gap~c"); for ( j = 0; j < 6; ++j ) ProcName[j] ^= 0x13u; ProcAddress = GetProcAddress(hModule, ProcName); sub_7FF651DB1C00(ProcAddress, (__int64)sub_7FF651DB1CE0); return 1; }
|
“`gap~c”解密出来是strcmp,也就是对strcmp做了Hook,如此才解我心中疑惑。