WARMUP VBS逆向,用VScode等文本编辑器打开.vbs文件,将开头处的Execute
修改为wscript.echo
再运行即可得到源码如下:
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 MsgBox "Dear CTFER. Have fun in XYCTF 2025!" flag = InputBox("Enter the FLAG:" , "XYCTF" ) wefbuwiue = "90df4407ee093d309098d85a42be57a2979f1e51463a31e8d15e2fac4e84ea0df622a55c4ddfb535ef3e51e8b2528b826d5347e165912e99118333151273cc3fa8b2b3b413cf2bdb1e8c9c52865efc095a8dd89b3b3cfbb200bbadbf4a6cd4" qwfe = "rc4key" Function RunRC(sMessage, strKey) Dim kLen, i, j, temp, pos, outHex Dim s(255 ), k(255 ) kLen = Len(strKey) For i = 0 To 255 s(i) = i k(i) = Asc(Mid (strKey, (i Mod kLen) + 1 , 1 )) Next j = 0 For i = 0 To 255 j = (j + s(i) + k(i)) Mod 256 temp = s(i) s(i) = s(j) s(j) = temp Next i = 0 : j = 0 : outHex = "" For pos = 1 To Len(sMessage) i = (i + 1 ) Mod 256 j = (j + s(i)) Mod 256 temp = s(i) s(i) = s(j) s(j) = temp Dim plainChar, cipherByte plainChar = Asc(Mid (sMessage, pos, 1 )) cipherByte = s((s(i) + s(j)) Mod 256 ) Xor plainChar outHex = outHex & Right("0" & Hex(cipherByte), 2 ) Next RunRC = outHex End Function If LCase(RunRC(flag, qwfe)) = LCase(wefbuwiue) Then MsgBox "Congratulations! Correct FLAG!" Else MsgBox "Wrong flag." End If
分析可知该程序进行的是RE4加密,用密码学工具箱跑一下即得flag:flag{We1c0me_t0_XYCTF_2025_reverse_ch@lleng3_by_th3_w@y_p3cd0wn‘s_chall_is_r3@lly_gr3@t_&_fuN!}
Dragon .bc
文件,先放入有clang编译器的环境,尝试编译
1 clang Dragon.bc -o Dragon
但是报错,尝试将其先编译为汇编代码
1 llc -o Dragon.s Dragon.bc
得到.s的汇编代码尝试编译仍然报错。喂给AI帮忙分析为C语言得到如下内容
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 #include <stdio.h> #include <string.h> #include <stdint.h> #define CRC64_POLY 0x42F0E1EBA9EA3693ULL uint64_t calculate_crc64_direct (const unsigned char *data, size_t length) { uint64_t crc = ~0ULL ; for (size_t i = 0 ; i < length; i++) { crc ^= (uint64_t )data[i] << 56 ; for (int j = 0 ; j < 8 ; j++) { if (crc & 0x8000000000000000U LL) { crc = (crc << 1 ) ^ CRC64_POLY; } else { crc <<= 1 ; } } } return crc ^ ~0ULL ; } int main () { const uint64_t enc[] = { 0xdc63e34e419f7b47 , 0x031ef8d4e7b2bfc6 , 0x12d62fbc625fd89e , 0x83e8b6e1cc5755e8 , 0xfc7bb1eb2ab665cc , 0x9382ca1b2a62d96b , 0xb1fff8a07673c387 , 0x0da81627388e05e1 , 0x9ef1e61ae8d0aab7 , 0x92783fd2e7f26145 , 0x63c97ca1f56fe60b , 0x9bd3a8b043b73aab }; char input[100 ] = {0 }; printf ("Input U flag:" ); scanf ("%s" , input); size_t enc_index = 0 ; int valid = 1 ; for (size_t i = 0 ; i < strlen (input); i += 2 ) { uint64_t crc = calculate_crc64_direct((unsigned char *)input + i, 2 ); if (crc != enc[enc_index++]) { printf ("Error!" ); valid = 0 ; break ; } } if (valid) { printf ("Success" ); } return 0 ; }
得知是CRC64,这是一种64位长度的循环冗余校验算法,用于传输数据时完整性检测,类似于哈希算法(但不是),使用爆破得出答案:
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 def calculate_crc64_direct (data ): crc = 0xFFFFFFFFFFFFFFFF for byte in data: crc ^= (byte << 56 ) crc &= 0xFFFFFFFFFFFFFFFF for _ in range (8 ): msb = crc & (1 << 63 ) crc = (crc << 1 ) & 0xFFFFFFFFFFFFFFFF if msb: crc ^= 0x42F0E1EBA9EA3693 return crc ^ 0xFFFFFFFFFFFFFFFF enc = [ 0xdc63e34e419f7b47 , 0x031ef8d4e7b2bfc6 , 0x12d62fbc625fd89e , 0x83e8b6e1cc5755e8 , 0xfc7bb1eb2ab665cc , 0x9382ca1b2a62d96b , 0xb1fff8a07673c387 , 0x0da81627388e05e1 , 0x9ef1e61ae8d0aab7 , 0x92783fd2e7f26145 , 0x63c97ca1f56fe60b , 0x9bd3a8b043b73aab ] flag = [] for target in enc: found = False for c1 in range (32 , 127 ): for c2 in range (32 , 127 ): if calculate_crc64_direct([c1, c2]) == target: flag.append(chr (c1) + chr (c2)) found = True break if found: break if not found: print ("Failed to find a valid pair" ) exit() print ("Flag:" , '' .join(flag))
补充:CRC64算法流程介绍(以”UC”为例):
初始化:
1 crc = 0xFFFFFFFFFFFFFFFF
处理第一个字节U(0x55):
此时最高8位被修改,例如:0x55… → 10101011…
处理8个bit(每个bit左移1次):
1 2 3 4 5 for _ in range (8 ): msb = crc & 0x8000000000000000 crc = (crc << 1 ) & 0xFFFFFFFFFFFFFFFF if msb: crc ^= 0x42F0E1EBA9EA3693
关键操作:如果移出的最高位是1,就异或这个魔法数
处理第二个字节C(0x43):
重复步骤2-3,继续处理8个bit
最终处理:
1 crc ^= 0xFFFFFFFFFFFFFFFF
Moon 打开main.py,发现核心加密函数moon.check_flag()
在导入的moon库中,而moon库以pyd文件
形式给出,使用python的help函数查看该模块的详细文档,得到以下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 PS D:\桌面\moon> python main.py Help on module moon: NAME moon FUNCTIONS check_flag(input_str) 返回验证结果的元组:(是否通过, 错误类型) xor_crypt(seed_value, data_bytes) DATA SEED = 1131796 TARGET_HEX = '426b87abd0ceaa3c58761bbb0172606dd8ab064491a2a76af9a93e1a...' __test__ = {} FILE d:\桌面\moon\moon.pyd
注:help函数的使用
由此得知该moon库中主要设置的两个函数为check_flag(input_str)
和xor_crypt(seed_value, data_bytes)
,后者接受两个参数,一个seed,一个data_bytes,而DATA部分正好包含SEED的值,以及一段类似密文的数据
用IDA分析moon.pyd,搜索字符串找到moon.xor_crypt,定位到对应函数sub_180001130,AI协助分析得知:该函数是一个Python C扩展模块的导出函数,用于实现名为xor_crypt的加密/解密功能。主要职责是解析Python调用参数,验证参数合法性,并调用底层加密逻辑,sub_180001370是实际执行异或加密/解密的函数 。
进一步跟进sub_180001370函数,其是Python C扩展模块中的异或加密/解密核心逻辑,负责将输入数据(字节流或列表)与密钥逐元素进行异或运算,并返回处理后的字节流。
因为check的逻辑不复杂,而且异或可逆,所以可以黑盒调用xor_crypt,传入密文得到明文flag
1 2 3 4 5 import moon seed_val = 0x114514 data = [0x42 ,0x6B ,0x87 ,0xAB ,0xD0 ,0xCE ,0xAA ,0x3C ,0x58 ,0x76 ,0x1B ,0xBB ,0x01 ,0x72 ,0x60 ,0x6D ,0xD8 ,0xAB ,0x06 ,0x44 ,0x91 ,0xA2 ,0xA7 ,0x6A ,0xF9 ,0xA9 ,0x3E ,0x1A ,0xE5 ,0x6F ,0xA8 ,0x42 ,0x06 ,0xA2 ,0xF7 ] print (moon.xor_crypt(seed_val,data))
补充:之前不会使用help指令,曾尝试动态调试,首先搜索字符串时顺便发现了python311的信息,配置好环境以及IDA后尝试调试,能成功运行,但是断点不能命中,故后来放弃
附IDA调试pyd文件配置方法:
Lake 找不到核心算法代码,先在start函数里下断点然后步进/步过跟踪,对比软件调试时显示对应文本的时间,结合静态分析排除部分非关键函数,最后找到主函数sub_100001B70。
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 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 int __fastcall __noreturn main (int argc, const char **argv, const char **envp) { __int64 v3; void *v4; __int64 v5; __int64 v6; void *v7; __int64 v8; __int64 v9; void *v10; __int64 v11; __int64 v12; void *v13; __int64 v14; __int64 v15; __int16 v16; __int64 v17; void *v18; __int64 v19; void *v20; __int64 v21; sub_100008570(); i = -1 ; do { aJF[(unsigned __int16)++i] ^= 0x33u ; v3 = sub_10000C190(); sub_10000C760(0 i64, v3, LOBYTE(aJF[(unsigned __int16)i])); sub_100008510(); sub_10000C2F0(v3); sub_100008510(); if ( qword_100021B50 ) v4 = (void *)qword_100021B50((unsigned int )dword_100020580); else v4 = &unk_100020588; output((__int64)v4); sub_100008510(); sub_1000130D0(0x64u ); } while ( i < 94 ); v5 = sub_10000C190(); sub_10000C310(v5); sub_100008510(); i = -1 ; do { *(_WORD *)&byte_100015210[2 * (unsigned __int16)++i] -= 37 ; v6 = sub_10000C190(); sub_10000C760(0 i64, v6, byte_100015210[2 * (unsigned __int16)i]); sub_100008510(); sub_10000C2F0(v6); sub_100008510(); if ( qword_100021B50 ) v7 = (void *)qword_100021B50((unsigned int )dword_100020580); else v7 = &unk_100020588; output((__int64)v7); sub_100008510(); sub_1000130D0(0x64u ); } while ( i < 73 ); v8 = sub_10000C190(); sub_10000C310(v8); sub_100008510(); i = -1 ; do { *(_WORD *)&byte_1000152B0[2 * (unsigned __int16)++i] ^= 0x77u ; v9 = sub_10000C190(); sub_10000C760(0 i64, v9, byte_1000152B0[2 * (unsigned __int16)i]); sub_100008510(); sub_10000C2F0(v9); sub_100008510(); if ( qword_100021B50 ) v10 = (void *)qword_100021B50((unsigned int )dword_100020580); else v10 = &unk_100020588; output((__int64)v10); sub_100008510(); sub_1000130D0(0x64u ); } while ( i < 55 ); v11 = sub_10000C190(); sub_10000C310(v11); sub_100008510(); sub_1000130D0(0x1F4u ); i = -1 ; do { aFgo[(unsigned __int16)++i] += 8 ; v12 = sub_10000C190(); sub_10000C760(0 i64, v12, LOBYTE(aFgo[(unsigned __int16)i])); sub_100008510(); sub_10000C2F0(v12); sub_100008510(); if ( qword_100021B50 ) v13 = (void *)qword_100021B50((unsigned int )dword_100020580); else v13 = &unk_100020588; output((__int64)v13); sub_100008510(); sub_1000130D0(0xC8u ); } while ( i < 49 ); v14 = sub_10000C190(); sub_10000C310(v14); sub_100008510(); v15 = sub_10000C160(); scanf (v15, input, 255 i64); sub_100008510(); sub_10000C940(v15); sub_100008510(); sub_1000026E0(byte_100020010, 40 i64, 0 i64); v16 = (unsigned __int8)input[0 ]; if ( input[0 ] ) { i = 0 ; do { if ( i++ <= 39 i64 ) byte_100020010[i - 1 ] = input[(unsigned __int8)i]; } while ( v16 > i ); } i = 0 ; while ( i < 123 i64 ) { word_100020060 = word_100015470[(unsigned __int16)i]; word_100020070 = word_100015470[i + 1 ]; word_100020080 = word_100015470[i + 2 ]; i += 3 ; if ( word_100020060 >= 1 ) { switch ( word_100020060 ) { case 1 : fuc_add(word_100020070, word_100020080); break ; case 2 : fuc_sub(word_100020070, word_100020080); break ; case 3 : fuc_mul(word_100020070, word_100020080); break ; case 4 : fuc_div(word_100020070, word_100020080); break ; case 5 : fuc_rem(word_100020070, word_100020080); break ; case 6 : fuc_and(word_100020070, word_100020080); break ; case 7 : fuc_or(word_100020070, word_100020080); break ; case 8 : fuc_xor(word_100020070, word_100020080); break ; } } } sub_1000019B0(byte_100020010, 39 i64); word_100015570 = 0 ; word_100020050 = -1 ; while ( 1 ) { ++word_100020050; if ( byte_100020010[(unsigned __int16)word_100020050] != byte_100015440[(unsigned __int16)word_100020050] ) word_100015570 = 1 ; if ( word_100020050 >= 39 ) { if ( word_100015570 ) { i = -1 ; do { aEiqjmQgGDlLgEg[(unsigned __int16)++i] ^= 0x28u ; v19 = sub_10000C190(); sub_10000C760(0 i64, v19, LOBYTE(aEiqjmQgGDlLgEg[(unsigned __int16)i])); sub_100008510(); sub_10000C2F0(v19); sub_100008510(); if ( qword_100021B50 ) v20 = (void *)qword_100021B50((unsigned int )dword_100020580); else v20 = &unk_100020588; output((__int64)v20); sub_100008510(); sub_1000130D0(0x64u ); } while ( i < 35 ); } else { i = -1 ; do { aKgfoziDiAgf[(unsigned __int16)++i] ^= 0x28u ; v17 = sub_10000C190(); sub_10000C760(0 i64, v17, LOBYTE(aKgfoziDiAgf[(unsigned __int16)i])); sub_100008510(); sub_10000C2F0(v17); sub_100008510(); if ( qword_100021B50 ) v18 = (void *)qword_100021B50((unsigned int )dword_100020580); else v18 = &unk_100020588; output((__int64)v18); sub_100008510(); sub_1000130D0(0x64u ); } while ( i < 43 ); } v21 = sub_10000C190(); sub_10000C310(v21); sub_100008510(); sub_100008980(); } } }
经分析可知前面几个循环是在实现逐字节输出的动画,对应的内容已用注释标记。
important1部分把输入字符串复制到另一个数组,然后进行第一次加密。第一次加密的流程是VM虚拟机,先是读取指令、操作对象和操作数,然后跳转对应指令进行计算(分别定义了加减乘除取余或异或等指令)
接着由sub_1000019B0执行第二次加密,流程如下:
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 char __fastcall sub_1000019B0 (__int64 a1, __int64 a2) { char result; __int16 v3; __int16 v4; char v5[40 ]; __int64 v6; __int64 v7; v7 = a1; v6 = a2; v3 = -1 ; do { if ( 4 i64 * ++v3 + 2 <= 39 ) v5[4 * v3] = (8 * *(_BYTE *)(v7 + 4 i64 * v3 + 1 )) | (*(_BYTE *)(v7 + 4 i64 * v3 + 2 ) >> 5 ); if ( 4 i64 * v3 + 3 <= 39 ) v5[4 * v3 + 1 ] = (8 * *(_BYTE *)(v7 + 4 i64 * v3 + 2 )) | (*(_BYTE *)(v7 + 4 i64 * v3 + 3 ) >> 5 ); if ( 4 i64 * v3 <= 39 ) v5[4 * v3 + 2 ] = (*(_BYTE *)(v7 + 4 i64 * v3) >> 5 ) | (8 * *(_BYTE *)(v7 + 4 i64 * v3 + 3 )); if ( 4 i64 * v3 + 1 <= 39 ) v5[4 * v3 + 3 ] = (8 * *(_BYTE *)(v7 + 4 i64 * v3)) | (*(_BYTE *)(v7 + 4 i64 * v3 + 1 ) >> 5 ); } while ( v3 < 9 ); v4 = -1 ; do { result = v5[(unsigned __int16)++v4]; *(_BYTE *)(v7 + v4) = result; } while ( v4 < 39 ); return result; }
最后进行了循环比较,可知密文储存在byte_100015440中
动调发现VM部分实际使用的指令只有ADD、SUB和XOR,所以在这三处打断点再次调试,得到每次的操作对象和操作数并记录
然后对sub_1000019B0函数进行逆向并整合即可写出exp
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 #include <iostream> #include <windows.h> void decrypt_func (unsigned char *data, int len) { unsigned char temp[40 ]; memcpy (temp, data, 40 ); for (int i = 0 ; i < 10 ; i++) { int base = 4 * i; unsigned char block[4 ]; if (base < len) { block[0 ] = ((temp[base + 2 ] & 0x1F ) << 5 ) | (temp[base + 3 ] >> 3 ); block[1 ] = (temp[base] >> 3 ) | ((temp[base + 3 ] & 0x07 ) << 5 ); block[2 ] = ((temp[base] & 0x07 ) << 5 ) | (temp[base + 1 ] >> 3 ); block[3 ] = ((temp[base + 1 ] & 0x07 ) << 5 ) | (temp[base + 2 ] >> 3 ); for (int j = 0 ; j < 4 && base + j < len; j++) { data[base + j] = block[j]; } } } } int main () { unsigned char Input[48 ] = { 0x4A , 0xAB , 0x9B , 0x1B , 0x61 , 0xB1 , 0xF3 , 0x32 , 0xD1 , 0x8B , 0x73 , 0xEB , 0xE9 , 0x73 , 0x6B , 0x22 , 0x81 , 0x83 , 0x23 , 0x31 , 0xCB , 0x1B , 0x22 , 0xFB , 0x25 , 0xC2 , 0x81 , 0x81 , 0x73 , 0x22 , 0xFA , 0x03 , 0x9C , 0x4B , 0x5B , 0x49 , 0x97 , 0x87 , 0xDB , 0x51 }; decrypt_func (Input, 40 ); Input[2 ] += 12 ; Input[26 ] -= 85 ; Input[35 ] -= 12 ; Input[14 ] += 9 ; Input[27 ] -= 6 ; Input[6 ] ^= 5 ; Input[1 ] ^= 5 ; Input[27 ] += 14 ; Input[25 ] += 3 ; Input[26 ] += 4 ; Input[4 ] ^= 8 ; Input[3 ] -= 12 ; Input[12 ] += 10 ; Input[37 ] -= 2 ; Input[32 ] -= 2 ; Input[9 ] -= 12 ; Input[26 ] ^= 5 ; Input[4 ] += 13 ; Input[8 ] ^= 15 ; Input[10 ] += 14 ; Input[16 ] -= 7 ; Input[12 ] -= 7 ; Input[34 ] ^= 8 ; Input[21 ] ^= 10 ; Input[39 ] -= 126 ; Input[7 ] += 2 ; Input[15 ] ^= 3 ; Input[10 ] ^= 10 ; Input[34 ] -= 11 ; Input[18 ] += 8 ; Input[25 ] += 9 ; Input[14 ] ^= 6 ; Input[0 ] ^= 5 ; Input[10 ] -= 8 ; Input[27 ] ^= 7 ; Input[13 ] ^= 6 ; Input[13 ] ^= 4 ; Input[23 ] ^= 12 ; Input[34 ] ^= 14 ; Input[18 ] += 52 ; Input[38 ] -= 119 ; printf ("%.40s\n" , Input); return 0 ; }
本文部分内容参考了这位师傅的WP:https://tkazer.github.io/2025/04/07/XYCTF2025/index.html#XYCTF2025-%E9%80%86%E5%90%91WP
后续的题目就非我能力范围所及了,想进一步学习的可移步至上文给出链接里Liv师傅的WP