Loading... # UofTCTF 2025复现 ## Highly Optimized 简单VM,实现了一个大数取模得到flag `uoftctf{vmr00m_vmr00m}`,too ez ~~~c __int64 __fastcall main(int a1, char **a2, char **a3) { int v3; // ebx int v4; // edx int v5; // r13d __int64 v6; // rdx __int64 v7; // rax __int64 v8; // rax _QWORD v10[137]; // [rsp+0h] [rbp-448h] BYREF v3 = 0; v10[131] = __readfsqword(0x28u); puts("I will tell you the flag, if you don't mind waiting a few moments..."); memset(v10, 0, 0x410uLL); v4 = 0; while ( 1 ) { v5 = v4 + 1; switch ( qword_555555558020[v4] ) { case 0LL: v8 = v3; v4 += 2; ++v3; v10[v8 + 1] = qword_555555558020[v5]; break; case 1LL: v6 = v10[v3]; v7 = v3++; v10[v7 + 1] = v6; v4 = v5; break; case 2LL: v10[v3 - 1] -= v10[v3]; --v3; ++v4; break; case 3LL: v10[v3 - 1] = v10[v3] < v10[v3 - 1]; --v3; ++v4; break; case 4LL: --v3; v4 += 2; if ( v10[v3 + 1] ) v4 -= LODWORD(qword_555555558020[v5]); break; case 5LL: putchar(SLOBYTE(v10[v3--])); v4 = v5; break; case 6LL: return 0LL; default: ++v4; break; } } } ~~~ ## Py_FlagChecker ~~~python def main(): import marshal, inspect c=b'\x87\xfcA@\xc7\xc4\xea\xf5\xa6\x87\x84\x02\xd6\x9e\x85\x93\xdeM\xa9\xe7\'\xf1\xaf\xe7\xc5\xde:\xff\xff\x95\x9a\xce\x05v\xd21\xce~\xa5\xb6\x19KI\xafd\xf2\xb5D\x1d\xa9:<7\x97<\xc8\xfd\x02\x9fK\x9a\x14\xb3\xc8\xb82\x90\x1a7\x140j\xffw\xe6\xb1s\xcd|s\xfd\x99\xf4b\xe1Z\x9e\x86\xf1%q\x05\xf0\xfb\xd2\xf0G\xd5x\xc0A\xac\xc4\xe5\xca~}2\x8e.\x9d6\xbe\xe2\x9a\xa0u=\xe0\x98\xaa\xc1xl\x1aI~T\xec\xfe\x95F\xbbS1)\x7f\x01\xdam\xb7>\x91\xf0\xb9-"1\xa1V\xf4\x18{3\'\x8b\x16\xeb\x92%\r<\xbe\xe3\xda\xcc\xc1\x03\xae\xf7h\xe6\xd7\xa8h\xe4\x1dM\xf53.-o\xd7\xb4\x1b\xdd\x1f\x7fG\xeb[o\xbab\x1fZF\xa1\xbd\x81\xe5\xb3)\xdf\'\x01\xdb\xe0j\xd6\xf6\x8b+\x1b\xee\x97\xcb\x9c\xf7\x03\x00p\xdb\xe89}\xc8\xd6\xd2Tu\x85y\x08\xfa\xe2\x98\xd2\xfa\n\x00v\xd4\x0cr\x02ew\x99\xe6\xe2\x1b\x8b\xbd]\x08\xb2\x07\xdfg\xc2\xf3\xd32\xe65\xf1\xa4\x02:\xbe\xf6\xa4\xea\xe1\xc9 \xa4E\x9c\xe3\xf95\x81\xbdd\xb3&\xf7%\x8a\x05\x18\x1a\xdc\x00\x08\xd2\x95\xd8\x06\xc7\xe4\xfaI\xe5\x80K\x99\xfa\xd9\x8a\xc5d\x18\x03\xc6\xd4\x13m4\xf1ts\x866\xe0\xae8$\xb0z\x85\x0bU\xfdC\xe6\xc2\xef:Ra\xd2\x07h\xdf\x9b\xaa\xd8\xa3\xc1\xee}\x0b\xd0\x97\xb7\x11U\x98\x89E\x88\xfd\xa8\x85\x84Ez@\x92U\x8a6\xcb\x18\xb7\xe0$\xfe\xa6\x0f]\xbd\x05p\xc4\xa8"\xc0w\xd7I\xfd\x84\xe8\xb5\xa7\xc9A\x01\tV\xbb\xaa@HTiK\xab)W\xb3\xdf\x0e\xb0\xd8\x93Oh`\xf7[b\xbaB\x8f\xe2\x9d\tpXA\x11\x04\xa2N\x9e;\x07J\x9c@"\x90^9\xdc\x10\x87`9\xbe\xfc|\x04\xb6"\x95\xd7l\xf4\x07;\xfb\x8a\xf3\xc4\xd6\xa2T\xcc\x18v~y8RE\xca,Q\xf5\xb5\xed\xd6,6\xf1i%\x92\xc3\x83\xda\xdc-\x0fWe&/I\x04\x1e\xf4Y\x14]\xb3e\x97\x84\xe1\x922\xd0\x96e\xc2\x161\xebtr\xe8\xa3\xea(\x18!\x83\xf9\x0b+\xc1\x01\x1f\xcec\x1f\x91\xf2\xd8f\xbav\xf9\t\xd7\xab\xd4\x84\x10L\x95\xe7\xf5\xcf\x15\xdf\x9d\xad\xfa\xac\x9d\xcbJ\x86\x14^P\n\xbc\xbd\x1f\xbb\xaen\xe4\xd0q\xc0\xd8\xb3\x97\xdc\x92P\xaa\xe4j\x813~\xd0_q\x88y\xff[\x00\xaa@\x90\x87\x905\xb0\xc3\r\x91\t\x9f>\xdd\x17\x19\xe1.\x8eR\x06/\x99\x1b\xff\x8a\x95qY\xa3h\x10\xcau\x0c\x0b\xb8\xb5\x13\xf6\xde\x06\x9c\xb6\xffS\x819\x8e8\'\x1e\x0fker\xbcD\xc99\x9d\xda\x8b\xbd\x0e\x9cbF?\xe8,\x17w\x8d\xafk\xee\'\x7f\x9b\x07\x91{I\xd0\xbbi$\xf8\xad\xed\xcf\x83\xb8\xee\xbe\x8aM\xa3Ea\xe5\xe3s\xaf\xe5\xcc:<!\xee\x03\x96[\xf6\xde\xe0\xfa\xb0}\xfa\xdb\x95\x9eio\xba\xc8Oo\xc4^_\x12\x88V\xe5\x0c\x01\x07\x87{\xfc\x93,h\x94\xc1\x80\x96\xac6\xdf\x98+M\xd6\xd0\xcf\x15\xd6>H\x11)\x140\xf7\x12t[\x1a\xa7\xcc\x80v\x18<\xb7\xafOa;o\xf4\x86K\xcf\xe6\x87\xaa\xaaLfk\xc7w \x7fNk\xc0\x9f\xc8b\x1aFo\x80\x07 YRa\x96\xa6\xbcH\x84)SK\xda\xaeX\xbd\x82\x8d6\x11U6d\x91\xb101I\x17\xe0\xe7\xf0\xcc\xd7\x1a@t\xe4\\\x06]\x19\x975\xa2(\xc3\x13\xe3^my\xbe<\xe0\x05\xb8Cue\x9c\x18o3\xe9\xd4-\x10\x14\x9ea\xad\xcb\xd2\xee\xaa\xa0>re\x8a\xb9\x1d\xba\xd2\xb81[\xe7\xd19\xf5\x12\xfe~\xf4\xc5)\x15\xe0o\x01%?#\x94\xde\xa9\xc3g(WDN9\xd8\xeb\xb4\xef8?\xf6\x18\xbdu\x16\x8c"\xbe?c\xb7P\xd1V\x8d\x14\x94\xb8\xaa\xac,=\x81g\xd7G\xac\xf6\x84\xe8\x90\x7f\xce\x1crz@\x9e\xaf\xed\xa1\xb2@\x91\x8c\x89\xe6\xa8\xa5\x9c|\x1cq\xb0|1\x06\xe6\x87\xfcn3\x80\xc2\xb0\x8c\x0cI"\x1c48yC\xe2.\x7f\x7f"k\xbb\xae\xf5a/\xfa\x9f\n\x01\x00\xc7\xb9\xb0C\xff\xb6Y)\xf5a)5`\xf9iqx\xe03Z\xf0\x0bW\x0e\xa7\t\xb4\x98J\xf6\xe2\xa4\xaa\x1eG\xeeD\xe1\xadD\xbe\xfb\r/\x97v\x86j~\xbe\x9d\xf4F\xc0:$\x8a\x06n\x12`sE\xd4\xdc\xe5i\x1a$^\xd4\x06r\x87-bj9Ik[\ru\x8f\xb3q\xb9Y\xc1\xa6\xe8\xb3\xd8)' def e(m, k): r = [] r += [m[0]^(sum(k)&0o377)] for _, i in enumerate(m[1:]): k = e(k, [m[_]]) r += [i^(sum(k)&0o377)] return bytes(r) print("This is a simple python flag checking service.") flag = input("Please give the flag to check: ") k=[(a*b)&0xff for a,b in zip(map(sum,inspect.getsource(main).encode().splitlines()),map(len,inspect.getsource(main).encode().splitlines()))] try: exec(marshal.loads(e(c, k))) except: print("That is not the flag") if __name__ == "__main__": main() ~~~ 一眼看到有个splitlines,很明显不能随便动代码,直接动调拿到k值为`[117, 167, 0, 48, 34, 58, 159, 220, 195, 70, 136, 149, 48, 125, 238]` 接着不要exec执行,改用dis.dis看汇编 ~~~ dis.dis(marshal.loads(e(c, k))) 1 0 LOAD_NAME 0 (locals) 2 CALL_FUNCTION 0 4 STORE_NAME 1 (I) 6 LOAD_NAME 2 (dict) 8 LOAD_NAME 1 (I) 10 CALL_FUNCTION 1 12 STORE_NAME 3 (l) 2 14 LOAD_NAME 4 (marshal) 16 LOAD_ATTR 5 (dumps) 18 STORE_NAME 6 (i) 3 20 LOAD_NAME 7 (str) 22 LOAD_CONST 0 (<code object <listcomp> at 0x00000121393FED40, file "Nice Progress! I wonder if there is a better way to do this", line 4>) 24 LOAD_CONST 1 ('<listcomp>') 26 MAKE_FUNCTION 0 28 LOAD_NAME 3 (l) 30 LOAD_METHOD 8 (values) 32 CALL_METHOD 0 34 GET_ITER 36 CALL_FUNCTION 1 38 CALL_FUNCTION 1 40 LOAD_METHOD 9 (encode) 42 CALL_METHOD 0 44 STORE_NAME 10 (l2) 4 46 LOAD_CONST 2 (<code object e at 0x00000121393FEEA0, file "Nice Progress! I wonder if there is a better way to do this", line 6>) 48 LOAD_CONST 3 ('e') 50 MAKE_FUNCTION 0 52 STORE_NAME 11 (e) 6 54 LOAD_CONST 4 (0) 56 LOAD_CONST 5 (None) 58 IMPORT_NAME 12 (traceback) 60 STORE_NAME 12 (traceback) 8 62 LOAD_NAME 11 (e) 64 LOAD_NAME 11 (e) 66 LOAD_NAME 10 (l2) 68 CALL_FUNCTION 1 70 CALL_FUNCTION 1 72 STORE_NAME 10 (l2) 9 74 LOAD_NAME 10 (l2) 76 LOAD_NAME 7 (str) 78 LOAD_NAME 3 (l) 80 LOAD_CONST 6 ('marshal') 82 BINARY_SUBSCR 84 CALL_FUNCTION 1 86 LOAD_METHOD 9 (encode) 88 CALL_METHOD 0 90 LOAD_NAME 3 (l) 92 LOAD_CONST 7 ('c') 94 BINARY_SUBSCR 96 BINARY_ADD 98 INPLACE_ADD 100 STORE_NAME 10 (l2) 10 102 LOAD_NAME 11 (e) 104 LOAD_NAME 10 (l2) 106 CALL_FUNCTION 1 108 LOAD_NAME 6 (i) 110 LOAD_NAME 11 (e) 112 LOAD_ATTR 13 (__code__) 114 LOAD_ATTR 14 (co_code) 116 CALL_FUNCTION 1 118 BINARY_ADD >> 120 LOAD_NAME 12 (traceback) 122 LOAD_METHOD 15 (format_stack) 124 CALL_METHOD 0 126 LOAD_CONST 8 (-1) 128 BINARY_SUBSCR 130 LOAD_METHOD 9 (encode) 132 CALL_METHOD 0 134 DUP_TOP >> 136 STORE_NAME 16 (tb) 138 BINARY_ADD 140 STORE_NAME 10 (l2) 11 142 LOAD_NAME 11 (e) 144 LOAD_NAME 10 (l2) 146 CALL_FUNCTION 1 148 LOAD_NAME 6 (i) 150 LOAD_NAME 17 (inspect) 152 LOAD_ATTR 18 (getsource) 154 LOAD_ATTR 13 (__code__) 156 LOAD_ATTR 14 (co_code) 158 CALL_FUNCTION 1 160 BINARY_ADD 162 LOAD_NAME 6 (i) 164 LOAD_NAME 3 (l) 166 LOAD_CONST 3 ('e') 168 BINARY_SUBSCR 170 LOAD_ATTR 13 (__code__) 172 LOAD_ATTR 14 (co_code) 174 CALL_FUNCTION 1 176 BINARY_ADD 178 STORE_NAME 10 (l2) 12 180 LOAD_NAME 11 (e) 182 LOAD_NAME 11 (e) 184 LOAD_NAME 11 (e) 186 LOAD_NAME 11 (e) 188 LOAD_NAME 11 (e) 190 LOAD_NAME 10 (l2) 192 CALL_FUNCTION 1 194 LOAD_NAME 11 (e) 196 LOAD_NAME 6 (i) 198 LOAD_NAME 19 (open) 200 LOAD_NAME 20 (__file__) 202 CALL_FUNCTION 1 204 LOAD_METHOD 21 (read) 206 CALL_METHOD 0 208 CALL_FUNCTION 1 210 CALL_FUNCTION 1 212 BINARY_ADD 214 CALL_FUNCTION 1 216 CALL_FUNCTION 1 218 CALL_FUNCTION 1 220 CALL_FUNCTION 1 222 STORE_NAME 10 (l2) 13 224 LOAD_NAME 19 (open) 226 LOAD_CONST 9 ('chall.flag') 228 LOAD_CONST 10 ('rb') 230 CALL_FUNCTION 2 232 STORE_NAME 11 (e) 15 234 LOAD_CONST 11 (b'') 236 STORE_NAME 1 (I) 16 238 NOP 17 240 LOAD_NAME 11 (e) 242 LOAD_METHOD 21 (read) 244 LOAD_CONST 13 (3) 246 CALL_METHOD 1 248 STORE_NAME 6 (i) 18 250 LOAD_NAME 22 (int) 252 LOAD_METHOD 23 (from_bytes) 254 LOAD_NAME 6 (i) 256 LOAD_CONST 14 ('big') 258 CALL_METHOD 2 260 DUP_TOP 262 STORE_NAME 6 (i) 264 LOAD_CONST 4 (0) 266 COMPARE_OP 2 (==) 268 POP_JUMP_IF_FALSE 136 19 270 JUMP_FORWARD 20 (to 292) 20 272 LOAD_NAME 1 (I) 274 LOAD_NAME 3 (l) 276 LOAD_CONST 3 ('e') 278 BINARY_SUBSCR 280 LOAD_NAME 11 (e) 282 LOAD_METHOD 21 (read) 284 LOAD_NAME 6 (i) 286 CALL_METHOD 1 288 LOAD_NAME 10 (l2) 290 CALL_FUNCTION 2 >> 292 INPLACE_ADD 294 STORE_NAME 1 (I) 21 296 LOAD_NAME 3 (l) 298 LOAD_CONST 3 ('e') 300 BINARY_SUBSCR 302 LOAD_NAME 10 (l2) 304 LOAD_NAME 10 (l2) 306 CALL_FUNCTION 2 308 STORE_NAME 10 (l2) 22 310 JUMP_ABSOLUTE 120 17 312 LOAD_NAME 24 (exec) 314 LOAD_NAME 4 (marshal) 316 LOAD_METHOD 25 (loads) 318 LOAD_NAME 1 (I) 320 CALL_METHOD 1 322 CALL_FUNCTION 1 324 POP_TOP 326 LOAD_CONST 5 (None) 328 RETURN_VALUE Disassembly of <code object <listcomp> at 0x00000121393FED40, file "Nice Progress! I wonder if there is a better way to do this", line 4>: 4 0 BUILD_LIST 0 >> 2 LOAD_FAST 0 (.0) 4 FOR_ITER 6 (to 12) 6 STORE_FAST 1 (_) 8 LOAD_GLOBAL 0 (dir) 10 LOAD_FAST 1 (_) >> 12 CALL_FUNCTION 1 14 LIST_APPEND 2 16 JUMP_ABSOLUTE 2 18 RETURN_VALUE Disassembly of <code object e at 0x00000121393FEEA0, file "Nice Progress! I wonder if there is a better way to do this", line 6>: 6 0 LOAD_GLOBAL 0 (bytes) 2 LOAD_CONST 1 (<code object <listcomp> at 0x00000121393FEDF0, file "Nice Progress! I wonder if there is a better way to do this", line 7>) 4 LOAD_CONST 2 ('e.<locals>.<listcomp>') 6 MAKE_FUNCTION 0 8 LOAD_GLOBAL 1 (zip) 10 LOAD_FAST 0 (m) 12 LOAD_FAST 0 (m) 14 LOAD_GLOBAL 2 (len) 16 LOAD_FAST 0 (m) 18 CALL_FUNCTION 1 20 LOAD_CONST 3 (2) 22 BINARY_FLOOR_DIVIDE 24 LOAD_CONST 0 (None) 26 BUILD_SLICE 2 28 BINARY_SUBSCR 30 CALL_FUNCTION 2 32 GET_ITER 34 CALL_FUNCTION 1 36 CALL_FUNCTION 1 38 RETURN_VALUE Disassembly of <code object <listcomp> at 0x00000121393FEDF0, file "Nice Progress! I wonder if there is a better way to do this", line 7>: 7 0 BUILD_LIST 0 >> 2 LOAD_FAST 0 (.0) 4 FOR_ITER 8 (to 14) 6 UNPACK_SEQUENCE 2 8 STORE_FAST 1 (a) 10 STORE_FAST 2 (b) 12 LOAD_FAST 1 (a) >> 14 LOAD_FAST 2 (b) 16 BINARY_XOR 18 LIST_APPEND 2 20 JUMP_ABSOLUTE 2 22 RETURN_VALUE ~~~ 借助api实现了个写pyc的 ~~~python import time, marshal, importlib._bootstrap_external as _bx def write_pyc_timestamp(code_obj, pyc_path, mtime, source_size=0): data = _bx._code_to_timestamp_pyc(code_obj, mtime, source_size) with open(pyc_path, "wb") as f: f.write(data) code_obj = marshal.loads(e(c, k)) write_pyc_timestamp(code_obj, "payload.pyc", mtime=int(time.time())) ~~~ 直接反编译 ~~~python I = locals() l = dict(I) i = marshal.dumps l2 = str((lambda .0: [ dir(_) for _ in .0 ])(l.values())).encode() def e(m): return bytes((lambda .0: [ a ^ b for a, b in .0 ])(zip(m, m[len(m) // 2:]))) import traceback l2 = e(e(l2)) l2 += str(l['marshal']).encode() + l['c'] l2 = traceback.format_stack()[-1].encode() + tb = traceback.format_stack()[-1].encode() l2 = e(l2) + i(inspect.getsource.__code__.co_code) + i(l['e'].__code__.co_code) l2 = e(e(e(e(e(l2) + e(i(open(__file__).read())))))) e = open('chall.flag', 'rb') I = b'' i = e.read(3) int.from_bytes(i, 'big') += I if i = int.from_bytes(i, 'big') == 0 else l['e'](e.read(i), l2) l2 = l['e'](l2, l2) continue exec(marshal.loads(I)) ~~~ 需要注意两种e函数,一个是单参一个是双参,后者很明显是第一层里的e函数 发现对抗手段特别狠,基本不能动代码,pdb调试手段也不行,看了官方wp一个思路是直接改源码编译一份,我不打算这么搞,我直接ida调试python38.dll,然后再marshal.loads下断点拿到了传入的参数,如下,461h表示长度,后面都是字节码  直接dump一份 这个题真够恶心的,没想到会获取环境内容,所以不管是IDA附加调试还是pdb调试都是不行的,他们的环境都会往里面加东西!md! 所以说到底,还是得玩魔改python,找python源码exec或者marshal.loads ~~~c static PyObject * marshal_loads_impl(PyObject *module, Py_buffer *bytes) /*[clinic end generated code: output=9fc65985c93d1bb1 input=6f426518459c8495]*/ { RFILE rf; char *s = bytes->buf; Py_ssize_t n = bytes->len; PyObject* result; PySys_WriteStdout("[marshal.loads] len=%zd\n", n); for (Py_ssize_t i = 0; i < n; i++) { PySys_WriteStdout("%02x%s", (unsigned char)s[i], (i+1)%16 ? " " : "\n"); } PySys_WriteStdout("\n"); rf.fp = NULL; rf.readable = NULL; rf.ptr = s; rf.end = s + n; rf.depth = 0; if ((rf.refs = PyList_New(0)) == NULL) return NULL; result = read_object(&rf); Py_DECREF(rf.refs); return result; } ~~~ 此时再用这个新编译的python跑一边就可以看到dump出来的marshal.loads传参值,结果发现太多了不知道哪个是第二次marshal.loads传入的,绷不住 一个比较好的方法是利用前pdb调试看传入marshal.loads变量长度  ok找到正确长度,提取出来写入pyc再反编译得到代码,里面有个比较可以直接拿到flag ~~~python b'(I117\nI111\nI102\nI116\nI99\nI116\nI102\nI123\nI109\nI48\nI100\nI49\nI102\nI121\nI49\nI110\nI54\nI95\nI55\nI104\nI51\nI95\nI53\nI48\nI117\nI114\nI99\nI51\nI95\nI48\nI102\nI95\nI112\nI121\nI55\nI104\nI48\nI110\nI95\nI49\nI53\nI95\nI102\nI117\nI110\nI125\nt.'.replace(b"I", b",").replace(b"\n", b"") b'(,117,111,102,116,99,116,102,123,109,48,100,49,102,121,49,110,54,95,55,104,51,95,53,48,117,114,99,51,95,48,102,95,112,121,55,104,48,110,95,49,53,95,102,117,110,125t.' bytes([117,111,102,116,99,116,102,123,109,48,100,49,102,121,49,110,54,95,55,104,51,95,53,48,117,114,99,51,95,48,102,95,112,121,55,104,48,110,95,49,53,95,102,117,110,125]) b'uoftctf{m0d1fy1n6_7h3_50urc3_0f_py7h0n_15_fun}' ~~~ ## Bloatware 老外怎么都喜欢玩js混淆 首先需要还原里面字符串的调用,调试可以发现出现`_0x3269ad['push'](_0x3269ad['shift']`的函数修改了_0x17b618字符串顺序,调试得到最终字符串,然后去找这些字符串调用的位置,使用正则去除部分混淆 ~~~python import re with open("chal.js") as f: code = f.read() def repl(m): idx = int(m.group(1), 16) # 取第 1 个捕获组,也就是括号里的数字 return f"'{strings[idx-0x10b]}'" strings = [ "map", "282sYzjLE", "reduce", "188pgfxnw", "2868768dRvTVT", "1096yWQTch", "createInte", "124052kiOzDS", "15FeTOZN", "question", "charCodeAt", "readline", "3340895tLmyzT", "correct!", "every", "flag: ", "7177959hwqIPd", "4632190uCcGWB", "log", "toString", "rface", "stdin", "13629ChQgcp", "rrect!", "Flag is in", "split", "from", "stdout", "Flag is co", "length", "Enter the ", "close" ] for var in ["_0x4dbadc", "_0x42ff8b", "_0x49fa57", "_0x12d5eb"]: code = re.sub(var+r"\((.*?)\)", repl, code) with open("deobf_chal.js", "w") as f: f.write(code) ~~~ 部分函数如下 ~~~js function _0x4d5c() { const _0x17b618 = ['Flag\x20is\x20in', 'split', 'from', 'stdout', 'Flag\x20is\x20co', 'length', 'Enter\x20the\x20', 'close', 'map', '282sYzjLE', 'reduce', '188pgfxnw', '2868768dRvTVT', '1096yWQTch', 'createInte', '124052kiOzDS', '15FeTOZN', 'question', 'charCodeAt', 'readline', '3340895tLmyzT', 'correct!', 'every', 'flag:\x20', '7177959hwqIPd', '4632190uCcGWB', 'log', 'toString', 'rface', 'stdin', '13629ChQgcp', 'rrect!']; _0x4d5c = function() { return _0x17b618; } ; return _0x4d5c(); } const _0x42ff8b = _0x1461; (function(_0xbc9557, _0x3de0b9) { const _0x4dbadc = _0x1461; const _0x3269ad = _0xbc9557(); while (!![]) { try { const _0x4adba0 = parseInt('282sYzjLE') / 0x1 * (parseInt('188pgfxnw') / 0x2) + -parseInt('15FeTOZN') / 0x3 * (-parseInt('124052kiOzDS') / 0x4) + parseInt('3340895tLmyzT') / 0x5 + -parseInt('2868768dRvTVT') / 0x6 + -parseInt('13629ChQgcp') / 0x7 * (parseInt('1096yWQTch') / 0x8) + parseInt('7177959hwqIPd') / 0x9 + -parseInt('4632190uCcGWB') / 0xa; if (_0x4adba0 === _0x3de0b9) { break; } else { _0x3269ad['push'](_0x3269ad['shift']()); } } catch (_0x5166af) { _0x3269ad['push'](_0x3269ad['shift']()); } } }(_0x4d5c, 0x6b3b1)); const readline = require('readline'); const rl = readline['createInte' + 'rface']({ 'input': process['stdin'], 'output': process['stdout'] }); let check_flag = function check_flag(_0x5bb3d9) { return check_flag = _0x2a129d => { const _0x49fa57 = _0x1461; _0x2a129d[_0x2a129d['length']] = check_flag['toString']()['split']('')['map'](_0x58b61e => _0x58b61e['charCodeAt'](0x0))['reduce']( (_0x6b6747, _0x55931a) => _0x6b6747 + _0x55931a, 0x0) ^ _0x5bb3d9; if (_0x2a129d['length'] !== 0x79e) return ![]; conds = [...]; return conds['every'](_0x491a3b => _0x491a3b === !![]); }; }(0x27847c9d); function _0x1461(_0x3db3b0, _0x837359) { const _0x4d5cfc = _0x4d5c(); _0x1461 = function(_0x1461c0, _0x14c2a8) { _0x1461c0 = _0x1461c0 - 0x10b; let _0x59a0a5 = _0x4d5cfc[_0x1461c0]; return _0x59a0a5; }; return _0x1461(_0x3db3b0, _0x837359); } rl['question']('Enter the ' + 'flag: ', _0x2a0015 => { const _0x12d5eb = _0x42ff8b; const _0xac8521 = Array['from'](_0x2a0015)['map'](_0x4eb9cb => _0x4eb9cb['charCodeAt'](0x0)); const _0x3a461a = check_flag(_0xac8521); if (_0x3a461a) { console['log']('Flag is co' + 'rrect!'); } else { console['log']('Flag is in' + 'correct!'); } rl['close'](); }); ~~~ 可以看出核心check在conds里,要求里面每个等式都成立 每个等式都非常大,但通过gpt的分析还是可以看到很多奇怪的表达式,最终化简实际为0。例如`1 * (flag[388] & ~flag[961]) + 1 * flag[961] + -1 * (flag[388] | flag[961]) + 0 * ~(flag[388] ^ flag[961])` ~~~js 1 * (flag[388] & ~flag[961]) + 1 * flag[961] + -1 * (flag[388] | flag[961]) + 0 * ~(flag[388] ^ flag[961]) + (1 * (~flag[1298] & flag[1570]) + 1 * (flag[1298] & flag[1570]) + 0 * ~(flag[1298] | flag[1570]) + -1 * flag[1570]) + (-1 * (flag[85] & ~flag[1067]) + 1 * (~flag[85] & flag[1067]) + -1 * flag[1067] + 1 * flag[85]) + (-1 * (flag[204] & flag[342]) + -1 * (flag[204] & ~flag[342]) + 0 * (flag[204] | ~flag[342]) + 1 * flag[204]) + (-~((-(flag[1206] + flag[1572]) + ~-(flag[1206] + flag[1572]) ^ ~(-(flag[1206] + flag[1572]) + -~(~flag[1949] + (flag[1949] & ~flag[1949])))) + flag[1949] ^ ~flag[1949] ^ flag[1949] | ~-((-(flag[1206] + flag[1572]) + ~-(flag[1206] + flag[1572]) ^ ~(-(flag[1206] + flag[1572]) + -~(~flag[1949] + (flag[1949] & ~flag[1949])))) + flag[1949] ^ ~flag[1949] ^ flag[1949]) & ((-(flag[1206] + flag[1572]) + ~-(flag[1206] + flag[1572]) ^ ~(-(flag[1206] + flag[1572]) + -~(~flag[1949] + (flag[1949] & ~flag[1949])))) + flag[1949] ^ ~flag[1949] ^ flag[1949]) + ((~(-(flag[1206] + flag[1572]) + -~~(~(flag[1206] + flag[1572] & ~(-~(flag[1206] + flag[1572]) + ~(flag[1206] + flag[1572] + -flag[1949]) | flag[1949])) & ~(~flag[1949] + (flag[1949] & ~flag[1949])))) ^ -(flag[1206] + flag[1572]) + ~-~~(flag[1206] + flag[1572] & (-(flag[1206] + flag[1572]) + (flag[1206] + flag[1572] + (flag[1206] + flag[1572])) & flag[1206] + flag[1572] + ~(flag[1206] + flag[1572])))) + flag[1949] ^ ~flag[1949] ^ flag[1949])) + flag[1949]) + (0 * (~flag[1476] & flag[33]) + 1 * -1 + -1 * ~(flag[1476] ^ flag[33]) + -1 * (flag[1476] ^ flag[33])) + (-1 * ~(flag[909] | flag[1552]) + 1 * (~flag[909] | flag[1552]) + 0 * flag[909] + -1 * flag[1552]) + (1 * ~(flag[1003] ^ flag[1734]) + -1 * (~flag[1003] | flag[1734]) + 1 * (~flag[1003] & flag[1734]) + 0 * (flag[1003] & flag[1734])) == 0 * ~(flag[1288] | flag[1269]) + -1 * (~flag[1288] | flag[1269]) + 1 * ~flag[1288] + 1 * (flag[1288] & flag[1269]) + (739 * (7 * (flag[1106] | ~flag[1523]) + -4 * flag[1523] + -6 * -1 + -7 * ~(flag[1106] & flag[1523]) + 2 * ~flag[1106] + -4 * (flag[1106] & ~flag[1523]) + 11 * (flag[1106] ^ flag[1523]) + -1 * flag[1106] + 4 * (~flag[1106] | flag[1523]) + 469738) + 274) % 2 ** 11 + (0 * ~flag[417] + -1 * ~flag[69] + 1 * ~(flag[417] | flag[69]) + 1 * (flag[417] & ~flag[69])) + (0 * ~(flag[1187] | flag[1412]) + -1 * -1 + 1 * (~flag[1187] & flag[1412]) + 1 * (flag[1187] | ~flag[1412])) + (-1 * (flag[709] | flag[1453]) + 0 * (~flag[709] | flag[1453]) + 1 * (flag[709] & flag[1453]) + 1 * (flag[709] ^ flag[1453])) + (1 * -1 + -1 * (flag[1675] ^ flag[1244]) + -1 * ~(flag[1675] ^ flag[1244]) + 0 * (~flag[1675] | flag[1244])) + (2 * (flag[651] & flag[1128]) + -1 * (flag[651] | ~flag[1128]) + -1 * flag[1128] + 1 * ~(flag[651] & flag[1128])) + (1 * ~(flag[105] ^ flag[443]) + -1 * flag[105] + 1 * (flag[105] | flag[443]) + -1 * (~flag[105] | flag[443])) ~~~ 经过化简实际只是简单的等式 ~~~js flag[52] + flag[1581] + flag[1949] == 663 ~~~ 到这里基本知道题目考啥了,不在费劲实现代码了,直接找wp借板子,略 这里考察的原来是一种研究领域呢,MBA,看到不少论文和项目在研究MBA以及反MBA 在读博客过程中灵光一闪哈哈,很受启发,有了一个新的解题思路,还是比较妙的 ## Encrypted Flag gemini牛逼些,直接给了爆破脚本,但是发现往前面爆破了好几年都没找到正确seed值 ~~~c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <time.h> // 定义爆破的时间范围 // 这里的范围示例:2024-01-01 到 2025-01-01 // 你可以根据 flag.enc 文件的修改时间(stat命令查看)来缩小范围,那样会瞬间解出 #define START_TIME 1451577600 #define END_TIME 1577808000 int main() { FILE *fp; unsigned char file_sbox[256]; unsigned char generated_sbox[256]; unsigned long seed; int v4, v6, i; long v7; // random() returns long char v10; // 1. 读取 flag.enc 的前 256 字节 (即 S-Box) fp = fopen("flag.enc", "rb"); if (!fp) { perror("Error opening flag.enc"); return 1; } if (fread(file_sbox, 1, 256, fp) != 256) { fprintf(stderr, "Error reading flag.enc (file too small)\n"); fclose(fp); return 1; } fclose(fp); printf("[*] S-Box loaded. Starting brute-force from %u to %u...\n", START_TIME, END_TIME); // 2. 遍历时间种子 for (seed = START_TIME; seed < END_TIME; seed++) { // 每检查 1000万个种子打印一次进度 if (seed % 10000000 == 0) { printf("[*] Checking around timestamp: %lu\n", seed); } // 初始化随机数生成器 srandom((unsigned int)seed); // 初始化 S-Box 为 0xFF (模拟 memset(buf, 255, sizeof(buf))) // 为了速度,我们可以尝试不每次都完全 memset,但为了逻辑正确性还是加上 // 在 C 里 memset 很快 memset(generated_sbox, 0xFF, 256); // 3. 模拟 S-Box 生成算法 // 原代码逻辑: // v4 = 64; do { ... } while(v4) v4 = 64; do { v6 = 0; v7 = random(); // 这里的 random() 调用次数非常关键 do { // 寻找空位 // i = (unsigned __int8)v7 i = (unsigned char)v7; // while(buf[i] != 0xFF) i++ while (generated_sbox[i] != 0xFF) { i = (unsigned char)(i + 1); } // v10 = -4 * v4 + v6 // 注意:这里利用 char 的溢出特性 v10 = (char)(-4 * v4 + v6); generated_sbox[i] = v10; // v7 >>= 8 v7 >>= 8; v6++; } while (v6 != 4); v4--; } while (v4); // 4. 对比生成的 S-Box 和文件中的 S-Box // 实际上只要对比前 16 个字节一致,基本就是正确的种子 if (memcmp(generated_sbox, file_sbox, 256) == 0) { printf("\n[+] Found Seed: %lu\n", seed); // 种子正确后,继续调用两次 random() 获取 Key long key1 = random(); long key2 = random(); printf("[+] Key Part 1: %ld (0x%lx)\n", key1, key1); printf("[+] Key Part 2: %ld (0x%lx)\n", key2, key2); // 输出 Python 脚本需要的 Hex Key 格式 // Key 是两个 QWORD (64位),但 random() 在 Linux 上通常返回 31位正整数 // 我们按照 Little Endian 打印出来方便填入 // 内存布局: [Key1 (8 bytes)] [Key2 (8 bytes)] unsigned long long k1 = (unsigned long long)key1; unsigned long long k2 = (unsigned long long)key2; printf("[+] Full Key (Hex for Python): "); // 按照 Little Endian 字节序输出 unsigned char *p = (unsigned char*)&k1; for(int k=0; k<8; k++) printf("%02x", p[k]); p = (unsigned char*)&k2; for(int k=0; k<8; k++) printf("%02x", p[k]); printf("\n"); return 0; } } printf("[-] Seed not found in the given range.\n"); return 0; } ~~~ 此时需要换思路了,由main可知我们实际只需确定第一个随机数即可,不需要完整生成整个sbox ~~~c __int64 __fastcall main(int a1, char **a2, char **a3) { // [COLLAPSED LOCAL DECLARATIONS. PRESS NUMPAD "+" TO EXPAND] v63 = __readfsqword(0x28u); memset(buf, 255, sizeof(buf)); if ( a1 == 1 ) exit(0); if ( a1 == 2 ) { v4 = 64; v5 = time(0LL); srandom(v5); do { v6 = 0; v7 = random(); do { for ( i = (unsigned __int8)v7; ; i = (unsigned __int8)(i + 1) ) { v9 = i; if ( buf[i] == 0xFF ) break; } v10 = -4 * v4 + v6++; v7 >>= 8; buf[v9] = v10; } while ( v6 != 4 ); --v4; } while ( v4 ); v57 = random(); v58 = random(); fd = open("flag.enc", 65, 420LL); v55 = open(a2[1], 0); write(fd, buf, 0x100uLL); v59 = 0LL; do { v56 = read(v55, &v59, 0x10uLL); if ( !v56 ) break; v11 = 0LL; v61[0] = v57; v61[1] = v58; v12 = v61; do { v13 = v12; ++v11; v12 += 2; v14 = (unsigned __int8)v13[13]; v15 = buf[(unsigned __int8)v13[15]]; v16 = (unsigned __int8)v13[12]; v13[17] = v13[1] ^ buf[(unsigned __int8)v13[14]]; v17 = v13[3] ^ buf[v16]; v13[18] = v13[2] ^ v15; v13[19] = v17; v13[16] = buf[v14] ^ byte_555555556020[v11] ^ *v13; for ( j = 0LL; j != 12; *((_BYTE *)v12 + j + 3) = v13[j + 3] ^ *((_BYTE *)v12 + j - 1) ) { v19 = *((_BYTE *)v12 + j); j += 4LL; *((_BYTE *)v12 + j) = v13[j] ^ v19; *((_BYTE *)v12 + j + 1) = v13[j + 1] ^ *((_BYTE *)v12 + j - 3); *((_BYTE *)v12 + j + 2) = v13[j + 2] ^ *((_BYTE *)v12 + j - 2); } } while ( v11 != 10 ); for ( k = 0LL; k != 16; ++k ) *((_BYTE *)&v59 + k) ^= *((_BYTE *)v61 + k); do { v21 = &v59; v22 = &v59; do { v23 = *(unsigned __int8 *)v22; v22 = (__int128 *)((char *)v22 + 1); *((_BYTE *)v22 - 1) = buf[v23]; } while ( &v60 != v22 ); LOBYTE(v3) = BYTE8(v59); LOBYTE(v24) = v59; HIBYTE(v24) = BYTE5(v59); v25 = v24; v26 = v3; BYTE1(v26) = BYTE13(v59); v27 = ((unsigned __int64)BYTE1(v59) << 40) | ((unsigned __int64)BYTE12(v59) << 32) | (BYTE7(v59) << 24) | ((unsigned __int64)BYTE2(v59) << 16) | v26 & 0xFFFF00000000FFFFLL; v28 = BYTE6(v59); *(_QWORD *)&v59 = v25 + ((unsigned __int64)BYTE3(v59) << 56) + ((unsigned __int64)BYTE4(v59) << 32) + ((unsigned __int64)BYTE9(v59) << 40) + ((unsigned __int64)BYTE10(v59) << 16) + ((unsigned __int64)BYTE14(v59) << 48) + ((unsigned __int64)HIBYTE(v59) << 24); v29 = (unsigned __int8 *)&v59; v30 = ((unsigned __int64)BYTE11(v59) << 56) | (v28 << 48) & 0xFFFFFFFFFFFFFFLL | v27 & 0xFFFFFFFFFFFFLL; v31 = &v60; *((_QWORD *)&v59 + 1) = v30; do { v32 = *v29; v33 = v29[2]; v31 = (__int128 *)((char *)v31 + 4); v29 += 4; v34 = *(v29 - 3); v35 = *(v29 - 1); v36 = *(v29 - 3); *((_BYTE *)v31 - 4) = byte_555555556440[v34] ^ byte_555555556540[v32] ^ v35 ^ v33; v37 = byte_555555556540[v34] ^ v35 ^ v32; v38 = *(v29 - 2); v39 = byte_555555556440[v38] ^ v37; v40 = byte_555555556540[v38] ^ v36 ^ v32; *((_BYTE *)v31 - 1) = byte_555555556540[v35] ^ byte_555555556440[v32] ^ *(v29 - 2) ^ v36; v41 = byte_555555556440[v35] ^ v40; *((_BYTE *)v31 - 3) = v39; *((_BYTE *)v31 - 2) = v41; } while ( &v60 != (__int128 *)v29 ); v42 = 0LL; v59 = v60; do { *((_BYTE *)&v59 + v42) ^= *((_BYTE *)v61 + k + v42); ++v42; } while ( v42 != 16 ); k += 16LL; } while ( k != 160 ); do { v43 = *(unsigned __int8 *)v21; v21 = (__int128 *)((char *)v21 + 1); *((_BYTE *)v21 - 1) = buf[v43]; } while ( &v60 != v21 ); v44 = v52; LOBYTE(v44) = v59; v45 = v44; BYTE1(v45) = BYTE5(v59); v52 = v45; v46 = v53; LOBYTE(v46) = BYTE8(v59); BYTE1(v46) = BYTE13(v59); v53 = v46; v47 = (unsigned __int64)BYTE1(v59) << 40; v48 = ((unsigned __int64)BYTE12(v59) << 32) | (BYTE7(v59) << 24) | ((unsigned __int64)BYTE2(v59) << 16) | v46 & 0xFFFF00000000FFFFLL; v49 = BYTE6(v59); *(_QWORD *)&v59 = (unsigned __int16)v45 + ((unsigned __int64)BYTE3(v59) << 56) + ((unsigned __int64)BYTE4(v59) << 32) + ((unsigned __int64)BYTE9(v59) << 40) + ((unsigned __int64)BYTE10(v59) << 16) + ((unsigned __int64)BYTE14(v59) << 48) + ((unsigned __int64)HIBYTE(v59) << 24); *((_QWORD *)&v59 + 1) = ((unsigned __int64)BYTE11(v59) << 56) | (v49 << 48) & 0xFFFFFFFFFFFFFFLL | (v47 | v48) & 0xFFFFFFFFFFFFLL; for ( m = 0LL; m != 16; ++m ) *((_BYTE *)&v59 + m) ^= *((_BYTE *)&v61[20] + m); write(fd, &v59, 0x10uLL); } while ( v56 == 16 ); close(fd); close(v55); } return 0LL; } ~~~ gemini给出提取第一个随机数脚本得到`1943927208` ~~~python def get_target_random(): try: with open("flag.enc", "rb") as f: sbox = f.read(256) except FileNotFoundError: print("[-] 找不到 flag.enc") return None # S-Box 生成逻辑回顾: # v4=64 (外层) # v6=0: 填入 -4*64 + 0 = -256 = 0 (mod 256) # v6=1: 填入 -4*64 + 1 = -255 = 1 (mod 256) # v6=2: 填入 -4*64 + 2 = -254 = 2 (mod 256) # v6=3: 填入 -4*64 + 3 = -253 = 3 (mod 256) # 因为这是最开始填入的4个数,S-Box全是空的,没有冲突。 # 所以: # random() 的 byte0 = 值 0 在 S-Box 中的索引 # random() 的 byte1 = 值 1 在 S-Box 中的索引 # random() 的 byte2 = 值 2 在 S-Box 中的索引 # random() 的 byte3 = 值 3 在 S-Box 中的索引 try: idx_0 = sbox.index(0) idx_1 = sbox.index(1) idx_2 = sbox.index(2) idx_3 = sbox.index(3) except ValueError: print("[-] S-Box 解析错误,未找到 0-3 的值") return None # 重组 random() 的返回值 (Little Endian 逻辑) # v7 >>= 8 在循环末尾,所以最低位对应 idx_0 target_random = idx_0 | (idx_1 << 8) | (idx_2 << 16) | (idx_3 << 24) # 注意:Linux glibc 的 random() 返回的是 long int (31位正数) # 如果计算出的值最高位是1,可能说明 random() 实现有差异或者理解有误, # 但通常 flag 题目生成的 random 值都在 0 - 0x7FFFFFFF 之间。 # 我们可以忽略符号位问题,直接按无符号处理。 print(f"[*] 值 0 的位置: {idx_0}") print(f"[*] 值 1 的位置: {idx_1}") print(f"[*] 值 2 的位置: {idx_2}") print(f"[*] 值 3 的位置: {idx_3}") print(f"[*] 逆推得到的第一个 random() 返回值应该是: {target_random} (0x{target_random:X})") return target_random if __name__ == "__main__": get_target_random() ~~~ 重新爆破 ~~~c #include <stdio.h> #include <stdlib.h> #include <time.h> int main(int argc, char *argv[]) { if (argc != 2) { printf("Usage: %s <target_random_value_from_python>\n", argv[0]); return 1; } long target = atol(argv[1]); printf("[*] Searching for seed that produces random() = %ld ...\n", target); unsigned int seed; // 遍历所有可能的 32 位种子 // 使用 long long 防止死循环 for (long long i = 0; i <= 4294967295; i++) { seed = (unsigned int)i; // 进度条 if (seed % 100000000 == 0) { printf("[*] Checked up to %u...\n", seed); } srandom(seed); if (random() == target) { printf("\n[!] FOUND SEED: %u\n", seed); srandom(seed); // 消耗掉生成 S-Box 的 64 次调用 for(int k=0; k<64; k++) random(); long k1 = random(); long k2 = random(); printf("[+] Key Part 1: %ld\n", k1); printf("[+] Key Part 2: %ld\n", k2); unsigned long long uk1 = (unsigned long long)k1; unsigned long long uk2 = (unsigned long long)k2; printf("[+] Full Hex Key: "); unsigned char *p = (unsigned char*)&uk1; for(int k=0; k<8; k++) printf("%02x", p[k]); p = (unsigned char*)&uk2; for(int k=0; k<8; k++) printf("%02x", p[k]); printf("\n"); } } printf("[-] Failed to find seed in full 32-bit space.\n"); printf("[-] If you are running this on Windows, it will FAIL. Please use Linux.\n"); return 0; } ~~~ 可以拿到多个seed值和key值,但实际上这样的爆破还是非常慢放弃 ~~~ [*] Searching for seed that produces random() = 1943927208 ... [*] Checked up to 0... [!] FOUND SEED: 19175311 [+] Key Part 1: 538342352 [+] Key Part 2: 853606110 [+] Full Hex Key: d073162000000000defee03200000000 ~~~ 看了wp果然利用了random()的密码学特性,本质上是一个非线性加法反馈生成器,如果知道数列前31个随机数的值就可以通过加法推断出之后无限随机数,不需要知道seed值,copy一份[官方脚本](https://github.com/UofTCTF/uoftctf-2025-chals-public/blob/master/encrypted-flag/solve/solve.py) ~~~python import itertools import tqdm import aes def divide_chunks(l, n): for i in range(0, len(l), n): yield l[i:i + n] f = open("flag.enc", "rb") sbox = list(f.read(256)) aes.s_box = sbox aes.inv_s_box = [0]*256 for i in range(256): aes.inv_s_box[i] = aes.s_box.index(i) curr_sbox = [-1]*256 randStates = [] def checkPossible(n, rand): idx = sbox.index(n) while rand != idx: if sbox[rand] > n: return False rand = (rand + 1)%256 return True def checkFull(n, rand): return (n>>24) < 128 and checkPossible(n*4, rand&0xff) and checkPossible(n*4 + 1, (rand>>8)&0xff) and checkPossible(n*4 + 2, (rand>>16)&0xff) and checkPossible(n*4 + 3, (rand>>24)&0xff) for i in range(31 * 4): pos = [] idx = sbox.index(i) curr_sbox[idx] = i while curr_sbox[idx] != -1: pos.append(idx) idx = (idx - 1) % 256 randStates.append(pos) randStates = [list(map(lambda x: int.from_bytes(bytes(x), 'little'), itertools.product(*i))) for i in divide_chunks(randStates, 4)] print([len(i) for i in randStates]) print([[hex(j) for j in i] for i in randStates]) for i in range(31, 64): newState = set() for a, b in itertools.product(randStates[-31], randStates[-3]): newState.add((a + b)%(2**31)) newState.add((a + b + 1)%(2**31)) newState = list(filter(lambda x: checkFull(i, x), newState)) print(i, len(newState)) randStates.append(newState) for i in range(64, 66): newState = set() for a, b in itertools.product(randStates[-31], randStates[-3]): newState.add((a + b)%(2**31)) newState.add((a + b + 1)%(2**31)) newState = list(filter(lambda x: (x>>24) < 128, newState)) randStates.append(list(newState)) print(list(map(len, randStates))) possible_keys = randStates[-2:] enc = list(divide_chunks(f.read(), 16)) print(list(map(len, possible_keys))) print(len(possible_keys[0])*len(possible_keys[1])) for a, b in tqdm.tqdm(itertools.product(*possible_keys), total=len(possible_keys[0])*len(possible_keys[1])): key = a.to_bytes(8, 'little') + b.to_bytes(8, 'little') ciph = aes.AES(key) dec = ciph.decrypt_block(enc[0]) if dec.startswith(b'uoftctf'): print(b''.join([ciph.decrypt_block(i) for i in enc])) exit() ~~~ 其实就是aes插了点随机数相关的密码,不去看了 ## 总结 * 学会了pdb调试,这东西太好用了 * 不得不说python给源码的在ai面前无所遁形了,各种反修改代码手段ai都是能分析出来的 * js混淆又了解了些,比较贴近实战 * MBA接触了不少,见识了一些工具 最后修改:2025 年 11 月 20 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 如果觉得我的文章对你有用,请随意赞赏