Loading... # QWB S9 wp ## PolyEncryption 非常可惜啊,比赛中做的时间最长的题目,但是中间有个地方代码分析错了,导致整个trace过程全部出问题 赛后重新做了下,还是可以出的 首先这个题非常恐怖的套了三层语言通讯代码,互相修补对方的代码,分别是c#、Java、python 题目给的dll实际上是saw.dll+一个打包的zip(里面有Java class和python代码) saw.dll可以使用ILSpy正确反编译,得到main函数如下 ~~~c# private static async Task Main(string[] args) { if (args.Length != 1) { Console.WriteLine("Usage: dotnet saw.dll [input]"); Console.WriteLine(" where input is 16 hexa bytes (eg abe74e3a9c375b3428bf31d1f8fa49c1)"); Environment.Exit(1); } man.BlockSize = 128; man.Mode = CipherMode.CBC; man.Padding = PaddingMode.PKCS7; k = SHA256.Create().ComputeHash(Encoding.ASCII.GetBytes(typeof(Saw).Assembly.FullName)); s[1023] = 3207972492L; // 8c s[1024] = 1190065579L; // ab s[1025] = 4165979424L; // 20 s[1026] = 2693353696L; // e0 s[1027] = 3628337899L; // eb s[1028] = 1707638109L; // 5d s[1029] = 1003779598L; // 0e s[1030] = 2653425729L; // 41 s[1031] = 795752593L; // 91 s[1032] = 2469382657L; // 01 long[] array = new long[16]; Array.Copy(s, 1017, array, 0, 16); iv = Array.ConvertAll(array, (long x) => (byte)x); TcpListener tcpListener = new TcpListener(IPAddress.Loopback, 0); tcpListener.Start(); Process p = Process.Start(new ProcessStartInfo("python3", "-m saw " + ((IPEndPoint)tcpListener.LocalEndpoint).Port) { Environment = { { "PYTHONPATH", Assembly.GetEntryAssembly().Location } } }); client = tcpListener.AcceptTcpClient(); await Hack(); await Track(); int rr = 0; while (true) { Console.Error.Write("."); await Shreck(); if (rr == 0) { s[32] = Convert.ToInt64(args[0].Substring(0, 8), 16); s[33] = Convert.ToInt64(args[0].Substring(8, 8), 16); s[34] = Convert.ToInt64(args[0].Substring(16, 8), 16); s[35] = Convert.ToInt64(args[0].Substring(24, 8), 16); } else if (rr % 2 == 1) { if (rr != 1) { await Stop(await Crack(new byte[32] { 34, 38, 13, 204, 106, 91, 87, 35, 115, 198, 124, 92, 83, 151, 127, 227, 60, 108, 237, 212, 71, 88, 37, 39, 222, 28, 237, 242, 207, 61, 89, 65 })); } else { await Stop(await Crack(new byte[32] { 54, 26, 153, 43, 207, 163, 66, 102, 1, 251, 172, 137, 133, 103, 252, 200, 133, 6, 227, 185, 100, 190, 49, 220, 132, 230, 14, 201, 184, 28, 84, 127 })); sp += 3; } long[] array2 = s; array2[2] = await Stop(await Crack(new byte[16] { 132, 17, 89, 19, 158, 87, 26, 91, 138, 184, 20, 203, 22, 142, 33, 67 })); array2 = s; array2[3] = await Stop(await Crack(new byte[16] { 98, 124, 165, 114, 4, 73, 122, 239, 35, 224, 182, 58, 230, 239, 169, 171 })); if (rr == 1) { s[4] = 1038097261L; } else { s[4] ^= s[3]; } } else { sp -= 27; await Stop(await Crack(new byte[32] { 181, 158, 168, 62, 5, 218, 108, 22, 225, 44, 69, 4, 169, 112, 225, 186, 212, 234, 186, 217, 245, 102, 219, 46, 107, 236, 2, 37, 80, 8, 162, 183 })); await Stop(await Crack(new byte[32] { 71, 166, 143, 191, 134, 170, 65, 147, 11, 69, 123, 125, 140, 156, 32, 225, 68, 85, 198, 59, 6, 58, 62, 75, 142, 62, 22, 169, 7, 21, 26, 177 })); for (int ii = 0; ii < 4; ii++) { await Stop(await Crack(new byte[32] { 88, 244, 228, 34, 209, 136, 53, 33, 174, 103, 199, 211, 90, 244, 91, 46, 98, 141, 50, 45, 70, 160, 34, 209, 131, 19, 95, 66, 187, 185, 87, 236 })); await Stop(await Crack(new byte[32] { 59, 134, 219, 11, 168, 34, 42, 6, 142, 30, 47, 12, 175, 97, 187, 166, 191, 165, 206, 91, 56, 92, 35, 91, 63, 232, 2, 235, 246, 41, 249, 60 })); await Stop(await Crack(new byte[32] { 30, 47, 78, 72, 232, 144, 186, 158, 145, 46, 78, 42, 66, 78, 48, 177, 201, 84, 194, 246, 32, 77, 153, 26, 204, 139, 24, 149, 70, 89, 23, 50 })); await Stop(await Crack(new byte[32] { 88, 244, 228, 34, 209, 136, 53, 33, 174, 103, 199, 211, 90, 244, 91, 46, 98, 141, 50, 45, 70, 160, 34, 209, 131, 19, 95, 66, 187, 185, 87, 236 })); await Stop(await Crack(new byte[32] { 59, 134, 219, 11, 168, 34, 42, 6, 142, 30, 47, 12, 175, 97, 187, 166, 191, 165, 206, 91, 56, 92, 35, 91, 63, 232, 2, 235, 246, 41, 249, 60 })); await Stop(await Crack(new byte[32] { 30, 47, 78, 72, 232, 144, 186, 158, 145, 46, 78, 42, 66, 78, 48, 177, 201, 84, 194, 246, 32, 77, 153, 26, 204, 139, 24, 149, 70, 89, 23, 50 })); await Stop(await Crack(new byte[32] { 88, 244, 228, 34, 209, 136, 53, 33, 174, 103, 199, 211, 90, 244, 91, 46, 98, 141, 50, 45, 70, 160, 34, 209, 131, 19, 95, 66, 187, 185, 87, 236 })); await Stop(await Crack(new byte[32] { 59, 134, 219, 11, 168, 34, 42, 6, 142, 30, 47, 12, 175, 97, 187, 166, 191, 165, 206, 91, 56, 92, 35, 91, 63, 232, 2, 235, 246, 41, 249, 60 })); await Stop(await Crack(new byte[32] { 30, 47, 78, 72, 232, 144, 186, 158, 145, 46, 78, 42, 66, 78, 48, 177, 201, 84, 194, 246, 32, 77, 153, 26, 204, 139, 24, 149, 70, 89, 23, 50 })); await Stop(await Crack(new byte[32] { 47, 110, 238, 143, 41, 255, 113, 160, 229, 92, 84, 132, 31, 128, 72, 253, 98, 154, 102, 114, 68, 165, 121, 194, 102, 210, 191, 221, 49, 209, 210, 215 })); await Stop(await Crack(new byte[32] { 59, 134, 219, 11, 168, 34, 42, 6, 142, 30, 47, 12, 175, 97, 187, 166, 68, 235, 237, 14, 9, 156, 105, 253, 49, 93, 204, 138, 86, 162, 159, 183 })); await Stop(await Crack(new byte[32] { 59, 134, 219, 11, 168, 34, 42, 6, 142, 30, 47, 12, 175, 97, 187, 166, 191, 165, 206, 91, 56, 92, 35, 91, 63, 232, 2, 235, 246, 41, 249, 60 })); await Stop(await Crack(new byte[32] { 30, 47, 78, 72, 232, 144, 186, 158, 145, 46, 78, 42, 66, 78, 48, 177, 201, 84, 194, 246, 32, 77, 153, 26, 204, 139, 24, 149, 70, 89, 23, 50 })); } await Stop(await Crack(new byte[32] { 164, 53, 148, 8, 250, 208, 160, 70, 126, 59, 160, 104, 222, 18, 194, 130, 172, 224, 232, 3, 9, 43, 214, 128, 13, 213, 251, 91, 231, 216, 221, 212 })); long[] array2 = s; long num = s[1]; array2[32] = num ^ await Stop(await Crack(new byte[16] { 132, 17, 89, 19, 158, 87, 26, 91, 138, 184, 20, 203, 22, 142, 33, 67 })); array2 = s; num = s[2]; array2[33] = num ^ await Stop(await Crack(new byte[16] { 98, 124, 165, 114, 4, 73, 122, 239, 35, 224, 182, 58, 230, 239, 169, 171 })); array2 = s; num = s[3]; array2[34] = num ^ await Stop(await Crack(new byte[16] { 41, 105, 145, 250, 243, 41, 66, 60, 57, 250, 209, 67, 192, 42, 111, 95 })); array2 = s; num = s[4]; array2[35] = num ^ await Stop(await Crack(new byte[16] { 46, 125, 65, 197, 84, 64, 181, 17, 52, 236, 114, 213, 42, 180, 217, 142 })); } if (rr == 22) { await blient.GetStream().WriteAsync(new byte[15] { 0, 0, 0, 15, 0, 0, 0, 1, 0, 1, 10, 0, 0, 0, 0 }, default(CancellationToken)); p.Kill(); _ = new byte[16]; for (int i = 0; i < 4; i++) { byte[] bytes = BitConverter.GetBytes((uint)s[32 + i]); Array.Reverse(bytes); Console.Write(BitConverter.ToString(bytes).Replace("-", "")); } Environment.Exit(0); } await Breck(); await client.GetStream().WriteAsync(BitConverter.GetBytes(0), default(CancellationToken)); while (await Hack()) { } bool flag = true; while (flag) { flag = await Horror(); } rr++; } } ~~~ 发现首先定义了一个AES,k是`typeof(Saw).Assembly.FullName`值的sha256,iv是数组的最后1字节,前面是0字节 跑起来python程序(运行了saw模块)并监听它 接着是Hack,分析可知和python程序做通讯,当接收数据长度不为0时,将得到数据转为C#汇编代码,提取里面的Curse.Do方法并传入s、sp,返回值更新sp,同时向python发送s[0]的值 ~~~c# private static async Task<bool> Hack() { NetworkStream ns = client.GetStream(); byte[] buffer = new byte[4]; if (await ns.ReadAsync(buffer, 0, buffer.Length) == 0) { return false; } int len = BitConverter.ToInt32(buffer, 0); if (len == 0) { if (blient != null) { return false; } if (await ns.ReadAsync(buffer, 0, buffer.Length) == 0) { return false; } a = BitConverter.ToInt32(buffer, 0); await Slash(); return false; } byte[] buf2 = new byte[len]; int num; for (int pos = 0; pos < len; pos += num) { num = await ns.ReadAsync(buf2, pos, buf2.Length - pos); if (num == 0) { return false; } } AssemblyLoadContext assemblyLoadContext = new AssemblyLoadContext("tmp", isCollectible: true); Assembly assembly = null; using (MemoryStream assembly2 = new MemoryStream(buf2)) { assembly = assemblyLoadContext.LoadFromStream(assembly2); } sp = (int)assembly.GetType("Curse").GetMethod("Do").Invoke(null, new object[2] { s, sp }); byte[] bytes = BitConverter.GetBytes(s[0]); await ns.WriteAsync(bytes, default(CancellationToken)); return true; } ~~~ 当长度是0时,调用了Slash ~~~c# private static async Task Slash() { blient = new TcpClient("127.0.0.1", a); NetworkStream ns = blient.GetStream(); await ns.WriteAsync(Encoding.ASCII.GetBytes("JDWP-Handshake"), default(CancellationToken)); byte[] array = new byte[14]; await ns.ReadAsync(array, 0, array.Length); BinaryReader sr = new BinaryReader2(ns); byte[] head = new byte[11]; await ns.WriteAsync(new byte[11] { 0, 0, 0, 11, 0, 0, 0, 1, 0, 1, 7 }, default(CancellationToken)); await ns.ReadAsync(head, 0, head.Length); for (int i = 0; i < 5; i++) { vals[i] = sr.ReadInt32(); } tit = new byte[vals[2]]; await ns.WriteAsync(new byte[11] { 0, 0, 0, 11, 0, 0, 0, 1, 0, 1, 3 }, default(CancellationToken)); sr.ReadBytes(11); uint num = sr.ReadUInt32(); for (int j = 0; j < num; j++) { sr.ReadByte(); byte[] array2 = sr.ReadBytes(vals[3]); int count = sr.ReadInt32(); switch (Encoding.ASCII.GetString(sr.ReadBytes(count))) { case "LSaw;": swcl = array2; break; case "LCurse;": ccl = array2; break; case "LHack;": hcl = array2; break; } sr.ReadInt32(); } byte[] obj = new byte[18] { 0, 0, 0, 0, 0, 0, 0, 1, 0, 15, 1, 40, 1, 0, 0, 0, 1, 4 }; obj[3] = (byte)(11 + vals[3] + 1 + 1 + 4 + 1); await ns.WriteAsync(obj, default(CancellationToken)); await ns.WriteAsync(ccl, default(CancellationToken)); sr.ReadBytes(15); byte[] obj2 = new byte[11] { 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 4 }; obj2[3] = (byte)(11 + vals[3]); await ns.WriteAsync(obj2, default(CancellationToken)); await ns.WriteAsync(swcl, default(CancellationToken)); sr.ReadBytes(11); num = sr.ReadUInt32(); for (int k = 0; k < num; k++) { byte[] array3 = sr.ReadBytes(vals[0]); int count2 = sr.ReadInt32(); if (Encoding.ASCII.GetString(sr.ReadBytes(count2)) == "calc") { swcm = array3; } count2 = sr.ReadInt32(); sr.ReadBytes(count2); sr.ReadInt32(); } byte[] obj3 = new byte[11] { 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 4 }; obj3[3] = (byte)(11 + vals[3]); await ns.WriteAsync(obj3, default(CancellationToken)); await ns.WriteAsync(ccl, default(CancellationToken)); sr.ReadBytes(11); num = sr.ReadUInt32(); for (int l = 0; l < num; l++) { byte[] array4 = sr.ReadBytes(vals[0]); int count3 = sr.ReadInt32(); if (Encoding.ASCII.GetString(sr.ReadBytes(count3)) == "hack") { ccm = array4; } count3 = sr.ReadInt32(); sr.ReadBytes(count3); sr.ReadInt32(); } byte[] obj4 = new byte[11] { 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 5 }; obj4[3] = (byte)(11 + vals[3]); await ns.WriteAsync(obj4, default(CancellationToken)); await ns.WriteAsync(hcl, default(CancellationToken)); sr.ReadBytes(11); SortedDictionary<string, byte[]> sortedDictionary = new SortedDictionary<string, byte[]>(); num = sr.ReadUInt32(); for (int m = 0; m < num; m++) { byte[] value = sr.ReadBytes(vals[1]); int count4 = sr.ReadInt32(); string @string = Encoding.ASCII.GetString(sr.ReadBytes(count4)); if (@string[0] != '<') { sortedDictionary.Add(@string, value); } count4 = sr.ReadInt32(); sr.ReadBytes(count4); sr.ReadInt32(); } hcm = new List<byte[]>(sortedDictionary.Values); } ~~~ 可以看到里面出现了LSaw、LCurse、LHack,正好是压缩包中的class类,通过ai以及网上资料可知,和Java程序做了JDWP通讯,可以获取和调用Java类中的方法,分析可知swcl对应Saw类,ccl对应Curse类,hcl对应Hack类,swcm对应Saw里的calc字段,ccm对应Saw里的hack,hcm对应Hack里的所有方法(一个字典) 接着回到主函数,Hack后是Track,可以发现和saw类做了通讯,猜测是修改saw.calc后面分析到Java再分析 ~~~c# private static async Task Track() { NetworkStream ns = blient.GetStream(); byte[] head = new byte[11]; byte[] obj = new byte[11] { 0, 0, 0, 0, 0, 0, 0, 1, 0, 3, 2 }; obj[3] = (byte)(11 + vals[3] + vals[0] + 4 + 4); await ns.WriteAsync(obj, default(CancellationToken)); await ns.WriteAsync(swcl, default(CancellationToken)); // saw await ns.WriteAsync(new byte[4] { 0, 0, 0, 1 }, default(CancellationToken)); await ns.WriteAsync(swcm, default(CancellationToken)); // saw.calc await ns.WriteAsync(new byte[4], default(CancellationToken)); await ns.ReadAsync(head, 0, head.Length); } ~~~ Track后初始化rr为0,进入到大循环,当rr为0时,读取了控制台参数args\[0]到s[32:36],正好是run.sh中传入的16字节 rr不为0时检查奇偶,出现很多Stop+Crack ~~~c# private static async Task<byte[]> Crack(byte[] msg) { NetworkStream ns = client.GetStream(); await ns.WriteAsync(BitConverter.GetBytes(msg.Length), default(CancellationToken)); await ns.WriteAsync(msg, default(CancellationToken)); byte[] b = new byte[4]; await ns.ReadAsync(b, 0, b.Length); int num = BitConverter.ToInt32(b, 0); byte[] m2 = new byte[num]; int num2; for (int pos = 0; pos < m2.Length; pos += num2) { num2 = await ns.ReadAsync(m2, pos, m2.Length - pos); if (num2 == 0) { return m2; } } return m2; } ~~~ Crack很明显,把msg传送给python,然后再读回来,我们去看看saw.py ~~~python from Crypto.Cipher import AES from Crypto.Util.Padding import unpad import sys import socket import struct import subprocess import base64 import hashlib from remote_pdb import RemotePdb def p32(i): return struct.pack("I", i) def p64(i): return struct.pack("Q", i) def u32(b): return struct.unpack("I", b)[0] def u64(b): return struct.unpack("Q", b)[0] def lol(b): hack.send(p32(len(curse))) hack.send(curse[:0x25C] + b.ljust(0xDF, b'\x00') + curse[0x33B:]) return u64(hack.recv(8)) hack = socket.socket(socket.AF_INET, socket.SOCK_STREAM) hack.connect(('127.0.0.1', int(sys.argv[1]))) curse = __loader__.get_data("Curse.dll") ch = None def child(p): global ch ch = subprocess.Popen(["java", "--add-exports", "java.base/jdk.internal.vm=ALL-UNNAMED", "-Xdebug", "-Xrunjdwp:server=y,transport=dt_socket,address=0,suspend=n", "-jar", __loader__.archive, str(p)], stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL) rdb = RemotePdb(host='127.0.0.1', port=0, port_callback=child) rdb.set_trace() hack.send(p32(0)) hack.send(p32(a)) s = ( 0x9b, 0xac, 0x16, 0x92, 0x5d, 0x9c, 0x1f, 0xed, 0xf8, 0x52, 0x18, 0xc4, 0xd9, 0x59, 0xa0, 0x82, 0x3c, 0x88, 0x69, 0x4a, 0x5a, 0xf6, 0x34, 0xc1, 0xba, 0x27, 0xec, 0x23, 0x10, 0x51, 0x1, 0xe5, 0x5e, 0xb1, 0x12, 0xca, 0xe2, 0x9f, 0x65, 0x22, 0x7b, 0x2f, 0x3b, 0xeb, 0x4c, 0xf3, 0xb6, 0xb8, 0x1d, 0x50, 0xc5, 0x8e, 0x36, 0xd4, 0xd1, 0x89, 0x48, 0xad, 0xfe, 0x6e, 0xc0, 0x37, 0xb2, 0xa4, 0x6f, 0x71, 0x98, 0xa6, 0x49, 0x3a, 0x33, 0xff, 0x31, 0xb0, 0x8f, 0x76, 0xe4, 0xc8, 0x47, 0xab, 0xfd, 0x13, 0xd7, 0xc6, 0xdd, 0x73, 0xb5, 0x90, 0x70, 0x6a, 0xb9, 0x60, 0x1b, 0xfa, 0x1c, 0x45, 0xd8, 0x6, 0x68, 0x99, 0xa2, 0x4f, 0x7, 0x54, 0x4d, 0x17, 0x2a, 0x39, 0xa8, 0xa1, 0x84, 0x83, 0x64, 0x9e, 0x80, 0x7f, 0x29, 0xda, 0x61, 0x58, 0x20, 0x9, 0xdb, 0x8, 0x0f, 0xaf, 0x4, 0xd3, 0xf7, 0x5c, 0xee, 0xc9, 0x0c, 0x9d, 0x5, 0x93, 0xf2, 0x57, 0x4b, 0xf1, 0xcf, 0x15, 0xbf, 0xe8, 0xce, 0xea, 0x0e, 0x67, 0x91, 0x38, 0x6d, 0x3, 0x24, 0x25, 0x32, 0x85, 0xf5, 0xa5, 0x95, 0x5b, 0xbe, 0xbc, 0xdf, 0x0b, 0xbd, 0x7e, 0x35, 0x30, 0xae, 0xde, 0xef, 0x87, 0x8c, 0xb3, 0x1e, 0x28, 0x78, 0x6c, 0x75, 0x0a, 0x8a, 0x0d, 0x66, 0x8d, 0xcd, 0x40, 0x3d, 0xfb, 0x4e, 0xe1, 0xf4, 0x53, 0x2c, 0x77, 0x43, 0x26, 0x74, 0x94, 0x9a, 0xb7, 0x11, 0xa3, 0xe7, 0xfc, 0xd5, 0x96, 0x7c, 0xe0, 0xe6, 0x8b, 0xcb, 0x1a, 0x55, 0x62, 0xdc, 0xaa, 0x2, 0x63, 0x86, 0x7d, 0x14, 0x3f, 0x97, 0xa7, 0x72, 0x2e, 0x19, 0x2b, 0x0, 0x6b, 0xe9, 0x5f, 0xc2, 0x21, 0x2d, 0xd0, 0xf0, 0xd6, 0x7a, 0x3e, 0x46, 0x56, 0xd2, 0xe3, 0xbb, 0xb4, 0x44, 0xf9, 0xc7, 0x79, 0x81, 0x41, 0x42, 0xcc, 0xa9, 0xc3 ) rr = 0 m = [0] * 128 def crack(a): r = base64.b64encode(a) rdb.set_trace() return base64.b64decode(r) while True: rlen = u32(hack.recv(4)) while rlen > 0: d = unpad(AES.new(hashlib.sha256(curse).digest(), AES.MODE_CBC, iv).decrypt(hack.recv(rlen)), 16) hack.send(p32(len(d))) hack.send(d) rlen = u32(hack.recv(4)) if rr % 2 == 0: if rr == 0: lol(crack(b'm\xaa\x1eP3\x02\x16q\x08\xfd\xb6:\x1e\xf9\x0e\xe9')) lol(crack(b'\x15\x15\xf3^\x17\xf5v\xd1g\x82*\t\xaex\xcd\xac\x86$\x06\x89X\xf1\x89\xbbI\x90\xa0\x06\x1e5\xd4^')) else: lol(crack(b'X~\x87\x1fsx\x07\x86\x8bn4\x85\xc8\xe3G\x00')) lol(crack(b'>-D\xd4o<\xa1\x90N\x9e\xdc\x8c\x14\x05~\x9a')) lol(crack(b'\xfb\xf1}W\x90@\xf4\xb8\x1a\x18\x99\x0eVg\xc7+\xe0\x11tV\xbf\xfc\x9b\xcb\x08V\\\xe083\n\x93')) lol(crack(b'X~\x87\x1fsx\x07\x86\x8bn4\x85\xc8\xe3G\x00')) lol(crack(b'\n\x8b\x01!\x9d\\+W\x9c|\xbb\xb1\x1d\xf4pt')) lol(crack(b'u\x13o\x16vbV\xef\x89=\x1e\xe9Xd\xb9\x99')) tmp = lol(crack(b'i\x13%\xbd\x03d\xbd\xd8\xe5\x8d\x8d\xd8\x84i\x88N')) tmp = struct.unpack("I", bytes(map(lambda x : s[x], struct.pack("I", tmp))))[0] lol(crack(b'qE\nl[\x1eqv\xc5\xbc\xa8wyhL\xae') + p64(tmp) + crack(b'%\xd7}\xa4\x132\x12\xb3\x1ab\xb75\xc4\x01C\x1a')) lol(crack(b')\xb7\x05D\x9b\xd7\x94W\xc6\xe5\xeeK(\xab\x83;\xc6_\xdf#\x81\xb4d\x1b\xdb\x81J+\x95\xf1)\xa7')) lol(crack(b'\xfczD\xa0\x0f\xd4\xbc\x8b\xe9\x009\xd2u\x10\xe4#\x1d\x9b\x07}\xe8\xd7\x9d\x1dh\xfdy-\x91\xe2\xd3\xdf')) m[0] = lol(crack(b'Ui1\x92\x8a"\xcb1\xfb>\xd4V@\xaa\xcd\x01')) m[1] = lol(crack(b'o\xf8\xc3\x91\x93\xce!H\x08\xb9\x1ee\x8b7\x03\x1e')) m[2] = lol(crack(b'\x80\xd7@\x94\xfc\xec\x8f$z\xe8\xd7\xa1@]\x8a\x9e')) else: lol(crack(b'\x15[\xae\xe9\xb7Q\xd2!D\x18#\xeb\xc4\xce\x0fk')) m[:4] = [struct.unpack("I", bytes(map(lambda x : s[x], struct.pack("I", lol(crack(b'i\x13%\xbd\x03d\xbd\xd8\xe5\x8d\x8d\xd8\x84i\x88N'))))))[0] for _ in range(4)][::-1] hack.send(p32(0)) r = 0 while True: rdb.set_trace() if r == 0: break exec(r) rr += 1 ~~~ 发现和c#做通讯的是hack,里面有个大循环里,循环接收数据做了AES解密,对应c#中的crack发送数据,密钥是Curse.dll sha256值,iv是Java中的`YdlvQDCjuS5T89m1`(Java里通过pdb做了iv设置),解密代码如下 ~~~python def dll_Crack(s1): enc1 = AES.new(bytes.fromhex("e33eb8a9ae522cacc94de3e5fcf7e1dd2e9d3a5230b42e7ceb81c97f1bb6ba47"), iv=b"YdlvQDCjuS5T89m1", mode=AES.MODE_CBC) return unpad(enc1.decrypt(s1), 16) ~~~ 继续分析Stop,传入的是之前AES解密后的数据,可以看到他又和blient也就是Java端进行了通讯,由hcm可知调用了Hack.class的方法 ~~~c# private static async Task<long> Stop(byte[] data) { NetworkStream ns = blient.GetStream(); byte[] bytes = BitConverter.GetBytes(11 + vals[3] + vals[2] + vals[1] + 4 + data.Length - 1); Array.Reverse(bytes); await ns.WriteAsync(bytes, default(CancellationToken)); await ns.WriteAsync(new byte[7] { 0, 0, 0, 1, 0, 3, 3 }, default(CancellationToken)); await ns.WriteAsync(hcl, default(CancellationToken)); await ns.WriteAsync(tit, default(CancellationToken)); await ns.WriteAsync(hcm[data[0]], default(CancellationToken)); await ns.WriteAsync(data, 1, data.Length - 1); await ns.WriteAsync(new byte[4] { 0, 0, 0, 1 }, default(CancellationToken)); byte[] b = new byte[4]; await ns.ReadAsync(b, 0, b.Length); Array.Reverse(b); int len = BitConverter.ToInt32(b, 0); byte[] array = new byte[8]; await ns.ReadAsync(array, 0, array.Length); byte[] res = new byte[8]; await ns.ReadAsync(res, 0, res.Length); byte[] array2 = new byte[len - 12 - 8]; await ns.ReadAsync(array2, default(CancellationToken)); Array.Reverse(res); return BitConverter.ToInt64(res); } ~~~ 查看Hack.class反编译代码 ~~~java class Hack { public static long[] m; static { Hack.m = new long[0x80]; } public static long a(long v, long v1) { return v + v1; } public static long b(long v, long v1) { return v - v1; } public static long c(long v, long v1) { Hack.m[((int)v)] = v1; return 0L; } public static long d(long v) { return Hack.m[((int)v)]; } public static long e(long v, long v1) { int v2 = (int)v; Hack.m[v2] ^= Hack.m[((int)v1)]; return 0L; } public static long f(long v, long v1) { long[] arr_v = Hack.m; int v2 = (int)arr_v[((int)v)]; arr_v[v2] <<= (int)v1; return 0L; } public static long g(long v, long v1) { int v2 = (int)v; Hack.m[v2] += v1; return 0L; } public static long h(long v, long v1) { long[] arr_v = Hack.m; int v2 = (int)arr_v[((int)v)]; arr_v[v2] ^= arr_v[((int)arr_v[((int)v1)])]; return 0L; } } ~~~ 我们分析下AES解密后的数据,第一个Crack里的数据AES解密后hex为`02000000024a00000000000000014a0000000049e99e07`,根据Java参数约定可知第一字节为方法ID号,然后是dword大小的值表示参数个数为2,然后2个数据,4a对应J,表示long类型数据,往后读取8字节,所以相当于调用了Hack的第3个函数(c),传入两个参数,值分别为1和0x49e99e07。因此我们完全可以直接把所有数据全部解密拿到正确操作,下面是比赛中我写好的Stop同构,其实完全可以回去把数据直接替换为对应操作的,毕竟数据固定的 ~~~python def stop(s1): global hack_m s1 = list(s1) op = s1[0] a = int.from_bytes(s1[6:14], 'big') if len(s1) > 14: b = int.from_bytes(s1[15:23], 'big') if op == 0: print(f"add {a}, {hex(b)}") return a + b elif op == 2: hack_m[a] = b print(f"mov m[{a}], {hex(b)}") return 0 elif op == 3: print(f"return m[{a}]({hex(hack_m[a])})") return hack_m[a] elif op == 4: print(f"xor m[{a}]({hex(hack_m[a])}), m[{b}]({hex(hack_m[b])})") hack_m[a] ^= hack_m[b] return 0 elif op == 5: print(f"shl m[{hack_m[a]}]({hex(hack_m[hack_m[a]])}), {hex(b)}") hack_m[hack_m[a]] <<= b hack_m[hack_m[a]] &= 0xffffffffffffffff return 0 elif op == 6: print(f"add m[{a}], {hex(b)}") hack_m[a] += b hack_m[a] &= 0xffffffffffffffff return 0 elif op == 7: print(f"xor m[{hack_m[a]}]({hex(hack_m[hack_m[a]])}), m[{hack_m[b]}]({hex(hack_m[hack_m[b]])})") hack_m[hack_m[a]] ^= hack_m[hack_m[b]] return 0 ~~~ 每轮循环最后又进入了Hack,此时数据很明显不是0,因此实际上调用了Curse.Do,回到saw.py分析python如何把C#字节码发送回来的 关注到rr开始里出现大量lol+crack,模式和C#的Stop+Crack非常相似,其中crack先base64加密,然后pdb设置断点,操作交给了Java端 ~~~python def crack(a): r = base64.b64encode(a) rdb.set_trace() return base64.b64decode(r) ~~~ 来看Java的Saw.class做了什么,首先建立通讯接口,然后println相当于输入,"global a; a=" + ((Properties)method0.invoke(null)).getProperty("sun.jdwp.listenerAddress").split(":")[1]也就是设置了a的值,后面接着设置了iv的值,然后continue表示继续执行 再次断点的时候(也就是crack里的断点),进入for里的while循环,首先输入`p r`,表示输出r值,然后检查r值是否为0,不为0进行base64解密,然后AES解密,密钥是class0.getCanonicalName().getBytes(),也就是`jdk.internal.vm.VMSupport`的sha256值,iv是method0.getName().substring(0, 16).getBytes()也就是`getAgentProperties`前16个字符`getAgentProperti`。所以这个循环做的正是处理python端crack里r数据 ~~~java public static void main(String[] arr_s) throws Exception { Curse.magic(); Hack.a(1L, 1L); Saw.sock = new Socket("127.0.0.1", Integer.parseInt(arr_s[0])); BufferedReader bufferedReader0 = new BufferedReader(new InputStreamReader(Saw.sock.getInputStream())); PrintWriter printWriter0 = new PrintWriter(Saw.sock.getOutputStream(), true); bufferedReader0.readLine(); Class class0 = Class.forName("jdk.internal.vm.VMSupport"); Method method0 = class0.getMethod("getAgentProperties"); printWriter0.println("global a; a=" + ((Properties)method0.invoke(null)).getProperty("sun.jdwp.listenerAddress").split(":")[1]); printWriter0.println("global iv; iv=b\'YdlvQDCjuS5T89m1\'"); printWriter0.println("continue"); SecretKeySpec secretKeySpec0 = new SecretKeySpec(MessageDigest.getInstance("SHA-256").digest(class0.getCanonicalName().getBytes()), "AES"); IvParameterSpec ivParameterSpec0 = new IvParameterSpec(method0.getName().substring(0, 16).getBytes()); while(Saw.calc != 0) { Thread.sleep(1L); } for(int v = 0; true; ++v) { Curse.magic(); while(true) { bufferedReader0.readLine(); printWriter0.println("p r"); String s = bufferedReader0.readLine(); if(s.equals("(Pdb) 0")) { break; } byte[] arr_b = Base64.getDecoder().decode(s.split("\'")[1]); Cipher cipher0 = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher0.init(2, secretKeySpec0, ivParameterSpec0); printWriter0.println("!r=\'" + Base64.getEncoder().encodeToString(cipher0.doFinal(arr_b)) + "\'"); printWriter0.println("continue"); } if(v % 2 == 0) { if(v == 0) { printWriter0.println(Saw.hack(new byte[]{28, (byte)0xC1, 46, 26, 25, 58, 0x1F, 97, 30, 59, 58, 108, 61, -84, (byte)0x90, -59, -34, 5, 109, -20, 51, 15, -39, -51, 4, 65, 37, 0x79, -65, -78, -75, (byte)0x8B})); printWriter0.println(Saw.hack(new byte[]{0, 1, 45, 0x74, 39, -22, 40, -110, (byte)0x86, -81, -69, -55, 87, 78, 2, -45, -6, -21, -5, 0x79, 91, (byte)0x8E, 105, -83, -4, -84, -101, 23, 34, 66, -15, 0x77, -54, -84, 105, 98, (byte)0x8D, (byte)0x82, -43, 0x2F, 46, -41, -107, 105, -33, 36, 0x5F, 66, 5, 74, 59, -73, (byte)0xC1, 86, -85, -103, 67, 77, 0x7B, 40, 99, 0x2F, 74, -16, 58, -23, 8, (byte)0x82, -40, -26, 24, 0x7D, -36, 60, -83, 75, 94, 0x7E, (byte)0xE0, 27})); printWriter0.println(Saw.hack(new byte[]{52, -41, -106, -110, 107, 0x7E, (byte)0x8B, -34, -86, 0x7D, 65, 97, 4, 110, -82, 10})); } else { printWriter0.println(Saw.hack(new byte[]{69, 0x20, -33, 106, -77, 65, 91, (byte)0xE1, 27, 0x60, 0x71, -105, -24, -99, -26, -52})); } printWriter0.println("continue"); bufferedReader0.readLine(); printWriter0.println(Saw.hack(new byte[]{71, 89, 71, -105, 85, -68, -66, 0x2F, -29, -99, -104, 0x7E, (byte)0x86, -2, (byte)0x8E, -9})); long[] arr_v = Hack.m; arr_v[0] = Long.parseLong(bufferedReader0.readLine().split(" ")[1]); printWriter0.println(Saw.hack(new byte[]{72, -107, 88, (byte)0xD1, 21, 87, -84, 86, 0x7D, 0x79, -24, 0x7C, -18, -27, -120, -46})); long[] arr_v1 = Hack.m; arr_v1[1] = Long.parseLong(bufferedReader0.readLine().split(" ")[1]); } else { printWriter0.println(Saw.hack(new byte[]{-74, -18, 24, 71, -73, 57, 23, 100, -35, 46, -21, -26, 0x72, -61, 0x7E, 97, -3, -11, (byte)0xC0, 40, -81, 33, 0x40, 0x71, 97, -5, (byte)0xD1, 1, 27, -33, -41, 15})); printWriter0.println("continue"); bufferedReader0.readLine(); for(int v1 = 0; v1 < 4; ++v1) { printWriter0.println(Saw.hack(new byte[]{-44, -3, 0x70, 89, -107, 0x3F, -108, 77, -108, 51, 25, (byte)0x8B, (byte)0x82, -29, -75, 105, 68, (byte)0x83, 62, 82, 13, 41, -25, 0x7C, -77, 103, -2, 86, -41, 62, 13, 84, -39, 105, -7, 103, 0x20, 7, 11, -51, 94, -105, -66, 77, 66, -23, -3, -120})); printWriter0.println("continue"); bufferedReader0.readLine(); printWriter0.println(Saw.hack(new byte[]{120, 30, -36, (byte)0x83, -53, -13, -54, -6, 0x1F, (byte)0x91, 3, -106, 6, (byte)0x91, 65, 80, 28, 0x7D, -81, -57, -107, 82, -106, -61, -26, (byte)0xD1, -36, 107, (byte)0xE0, -109, 49, 0x7E, -70, 41, 13, -7, 0x75, -110, (byte)0xB1, -12, 50, 10, 21, 92, 0x76, 76, -13, 39})); printWriter0.println("continue"); bufferedReader0.readLine(); printWriter0.println(Saw.hack(new byte[]{84, -15, 0x20, 24, 0x4F, 0x20, 19, 85, -24, -50, -43, 13, -54, (byte)0xC0, 0x30, (byte)0x81, -98, 44, (byte)0xC1, -49, (byte)0xE1, 99, -101, 51, -28, 30, -17, -101, -93, 78, 65, 35, 29, -107, 35, -6, (byte)0x82, -30, -66, 1, 81, 59, 102, -28, 52, -93, 42, 14, 15, -5, 46, 0x7F, -8, 18, 15, -110, -21, 21, (byte)0x89, -86, -65, -84, 0, 0x70, -90, 0x1F, -16, 40, 60, -6, 107, 66, (byte)0xA1, (byte)0x86, 46, -110, 120, (byte)0xC0, -57, 42, -73, -18, 0x72, 61, (byte)0xE1, 109, 120, -100, 57, 91, 0x72, -44, -43, -61, -71, 97, 60, -1, 24, 27, -109, -82, 42, -14, -22, 10, 0x2F, 67, 35, -81, -76, (byte)0x83, -74, -51, 0x2F, 15, 85, 30, (byte)0x83, 0x77, -9, -44, (byte)0xE1, 101, -99, 92, 56, 40, 54, 26, (byte)0xC0, -83, -19, 57, -20, 34, 57, -78, -73, -72, 67, 36, 84, 77, -53, (byte)0x87, 78, 13, -104, -87, 41, 0, -54, -100, 22, 36, -18, 61, -24, 11})); printWriter0.println("continue"); bufferedReader0.readLine(); } for(int v2 = 0; v2 < 4; ++v2) { printWriter0.println(Saw.hack(new byte[]{-109, -19, 100, 9, 20, 0x76, -78, 0x76, (byte)0x89, -39, (byte)0xD0, -37, 62, 33, 40, 0x4F})); long[] arr_v2 = Hack.m; arr_v2[v2 * 4] = Long.parseLong(bufferedReader0.readLine().split(" ")[1]); printWriter0.println(Saw.hack(new byte[]{84, -15, 0x20, 24, 0x4F, 0x20, 19, 85, -24, -50, -43, 13, -54, (byte)0xC0, 0x30, (byte)0x81, 4, -24, 29, 61, 53, 40, 52, 37, 0x30, 57, 100, 26, 9, -26, 11, -92})); printWriter0.println("continue"); bufferedReader0.readLine(); printWriter0.println(Saw.hack(new byte[]{22, 38, 5, 69, 0x40, 16, -17, 49, 93, -19, 72, 100, 50, -65, -110, 12})); long[] arr_v3 = Hack.m; arr_v3[v2 * 4 + 1] = Long.parseLong(bufferedReader0.readLine().split(" ")[1]); printWriter0.println(Saw.hack(new byte[]{84, -15, 0x20, 24, 0x4F, 0x20, 19, 85, -24, -50, -43, 13, -54, (byte)0xC0, 0x30, (byte)0x81, 4, -24, 29, 61, 53, 40, 52, 37, 0x30, 57, 100, 26, 9, -26, 11, -92})); printWriter0.println("continue"); bufferedReader0.readLine(); printWriter0.println(Saw.hack(new byte[]{-67, -13, 10, -91, 49, 52, -13, 69, (byte)0x90, (byte)0x89, 18, -25, 84, 38, 101, (byte)0x85})); long[] arr_v4 = Hack.m; arr_v4[v2 * 4 + 2] = Long.parseLong(bufferedReader0.readLine().split(" ")[1]); printWriter0.println(Saw.hack(new byte[]{84, -15, 0x20, 24, 0x4F, 0x20, 19, 85, -24, -50, -43, 13, -54, (byte)0xC0, 0x30, (byte)0x81, 4, -24, 29, 61, 53, 40, 52, 37, 0x30, 57, 100, 26, 9, -26, 11, -92})); printWriter0.println("continue"); bufferedReader0.readLine(); printWriter0.println(Saw.hack(new byte[]{61, -93, 69, -74, -70, 49, (byte)0xA1, 0x79, 0x40, -70, -45, (byte)0xD0, -44, (byte)0x89, 1, (byte)0xA1})); long[] arr_v5 = Hack.m; arr_v5[v2 * 4 + 3] = Long.parseLong(bufferedReader0.readLine().split(" ")[1]); printWriter0.println(Saw.hack(new byte[]{84, -15, 0x20, 24, 0x4F, 0x20, 19, 85, -24, -50, -43, 13, -54, (byte)0xC0, 0x30, (byte)0x81, 4, -24, 29, 61, 53, 40, 52, 37, 0x30, 57, 100, 26, 9, -26, 11, -92})); printWriter0.println("continue"); bufferedReader0.readLine(); printWriter0.println(Saw.hack(new byte[]{84, -15, 0x20, 24, 0x4F, 0x20, 19, 85, -24, -50, -43, 13, -54, (byte)0xC0, 0x30, (byte)0x81, 4, -24, 29, 61, 53, 40, 52, 37, 0x30, 57, 100, 26, 9, -26, 11, -92})); printWriter0.println("continue"); bufferedReader0.readLine(); } } printWriter0.println(Saw.hack(new byte[]{109, 82, -84, (byte)0xD0, 54, -41, -91, 0x2F, 0x20, 88, 51, -49, 89, -37, (byte)0x8F, -8})); printWriter0.println("continue"); Curse.hack = "!"; Curse.magic(); } } ~~~ 接着看lol函数,这个函数将Curse.dll数据和前面crack解密后的数据做了拼接发送给了C#端 ~~~python def lol(b): hack.send(p32(len(curse))) hack.send(curse[:0x25C] + b.ljust(0xDF, b'\x00') + curse[0x33B:]) return u64(hack.recv(8)) ~~~ 反编译Curse.dll可知里面的Do方法是空的,由此可知这个地方解密出来的正是C#字节码,填充Do方法,然后再C#的Hack中触发调用 因此需要把所有情况全部反编译出来,写了个脚本 ~~~python import struct from Crypto.Cipher import AES from Crypto.Util.Padding import unpad extracted_byte_arrays = [ # rr == 0 block b'm\xaa\x1eP3\x02\x16q\x08\xfd\xb6:\x1e\xf9\x0e\xe9', b'\x15\x15\xf3^\x17\xf5v\xd1g\x82*\t\xaex\xcd\xac\x86$\x06\x89X\xf1\x89\xbbI\x90\xa0\x06\x1e5\xd4^', # rr != 0 and rr % 2 == 0 block b'X~\x87\x1fsx\x07\x86\x8bn4\x85\xc8\xe3G\x00', b'>-D\xd4o<\xa1\x90N\x9e\xdc\x8c\x14\x05~\x9a', b'\xfb\xf1}W\x90@\xf4\xb8\x1a\x18\x99\x0eVg\xc7+\xe0\x11tV\xbf\xfc\x9b\xcb\x08V\\\xe083\n\x93', b'X~\x87\x1fsx\x07\x86\x8bn4\x85\xc8\xe3G\x00', # Duplicate call b'\n\x8b\x01!\x9d\\+W\x9c|\xbb\xb1\x1d\xf4pt', b'u\x13o\x16vbV\xef\x89=\x1e\xe9Xd\xb9\x99', b'i\x13%\xbd\x03d\xbd\xd8\xe5\x8d\x8d\xd8\x84i\x88N', # Inside tmp calculation # b'qE\nl[\x1eqv\xc5\xbc\xa8wyhL\xae'+struct.pack("Q", 0)+b'%\xd7}\xa4\x132\x12\xb3\x1ab\xb75\xc4\x01C\x1a', # Second part of combined lol call b')\xb7\x05D\x9b\xd7\x94W\xc6\xe5\xeeK(\xab\x83;\xc6_\xdf#\x81\xb4d\x1b\xdb\x81J+\x95\xf1)\xa7', b'\xfczD\xa0\x0f\xd4\xbc\x8b\xe9\x009\xd2u\x10\xe4#\x1d\x9b\x07}\xe8\xd7\x9d\x1dh\xfdy-\x91\xe2\xd3\xdf', # Assignments to m[0], m[1], m[2] b'Ui1\x92\x8a"\xcb1\xfb>\xd4V@\xaa\xcd\x01', b'o\xf8\xc3\x91\x93\xce!H\x08\xb9\x1ee\x8b7\x03\x1e', b'\x80\xd7@\x94\xfc\xec\x8f$z\xe8\xd7\xa1@]\x8a\x9e', # rr % 2 != 0 block b'\x15[\xae\xe9\xb7Q\xd2!D\x18#\xeb\xc4\xce\x0fk', b'i\x13%\xbd\x03d\xbd\xd8\xe5\x8d\x8d\xd8\x84i\x88N', # Inside list comprehension for m[:4] (appears 4 times logically) ] with open("Curse.dll", "rb") as f: curse = f.read() def crack(s): enc = AES.new(bytes.fromhex("26f196237743ea19fc45acc1f5964b03316ddb56ef6de22aefca12154291464a"), iv=b"getAgentProperti", mode=AES.MODE_CBC) return unpad(enc.decrypt(s), 16) def lol(b, i): global sp, s with open(f"new_dll/Curse{i}.dll", "wb") as f: f.write(curse[:0x25C] + b.ljust(0xDF, b'\x00') + curse[0x33B:]) # for i in range(len(extracted_byte_arrays)): # lol(crack(extracted_byte_arrays[i]), i) lol(crack(b'qE\nl[\x1eqv\xc5\xbc\xa8wyhL\xae')+struct.pack("Q", 0)+crack(b'%\xd7}\xa4\x132\x12\xb3\x1ab\xb75\xc4\x01C\x1a'), 16) ~~~ 得到后一个个反编译,发现Do都是实现了传入s、sp,返回更新后的sp值,因此Hack这里的逻辑可以直接替换为python的逻辑,我比赛中也实现出了lol如下 比赛过程中就是这里犯错了,问题出在这几行代码 ~~~python m[:4] = [struct.unpack("I", bytes(map(lambda x : s[x], struct.pack("I", lol(crack(b'i\x13%\xbd\x03d\xbd\xd8\xe5\x8d\x8d\xd8\x84i\x88N'))))))[0] for _ in range(4)][::-1] tmp = lol(crack(b'i\x13%\xbd\x03d\xbd\xd8\xe5\x8d\x8d\xd8\x84i\x88N')) tmp = struct.unpack("I", bytes(map(lambda x : s[x], struct.pack("I", tmp))))[0] ~~~ 首先是`m[:4]`实际上做了四次循环,每次调用case15应该是4次sp-1,我只写了一次,直接导致明文只读了一个dword值 接着tmp这里是做了一次sbox换表的,遗漏了 ~~~python def lol(b): global sp, s, tmp, hack_m, m if b == 0: s[sp] = 0x90a94de2 print(f"mov s[{sp}] 0x90a94de2") sp += 1 elif b == 1: s[256] = 1023 print(f"mov s[256], 1023") elif b == 2: s[sp] = s[4] print(f"mov s[{sp}], s[4]({hex(s[4])})") elif b == 3: s[sp] <<= 8 print(f"shl s[{sp}]({hex(s[sp])}), 8") elif b == 4: s[sp] &= 0xffffffff print(f"s[{sp}]&0xffffffff") sp += 1 elif b == 5: s[sp] = s[4] print(f"mov s[{sp}], s[4]({hex(s[4])})") elif b == 6: s[sp] >>= 24 print(f"shr s[{sp}]({hex(s[sp])}), 24") elif b == 7: s[sp - 1] = (s[sp] | s[sp - 1]) print(f"or s[{sp-1}], s[{sp}]({hex(s[sp])})") elif b == 8: s[0] = s[sp - 1] print(f"pop s[{sp-1}]({hex(s[0])})") sp -= 1 tmp = s[0] print(f"mov tmp, s[0]({hex(s[0])})") elif b == 9: s[1] = (s[1] ^ s[s[256]]) print(f"xor s[1], s[s[256]]({s[256]}, {hex(s[s[256]])})") elif b == 10: print(f"inc s[256]({hex(s[256])})") s[256] = s[256] + 1 elif b == 11: s[0] = s[1] m[0] = s[0] print(f"mov m[0], s[1]({hex(s[1])})") elif b == 12: s[0] = s[2] m[1] = s[0] print(f"mov m[1], s[2]({hex(s[2])})") elif b == 13: s[0] = s[3] m[2] = s[0] print(f"mov m[2], s[2]({hex(s[3])})") elif b == 14: sp += 31 print(f"sp += 31") elif b == 15: for i in range(4): s[0] = s[sp - 1] print(f"pop s[{sp - 1}]({hex(s[0])})") sp -= 1 m[3-i] = struct.unpack("I", bytes(map(lambda x: sbox[x], struct.pack("I", s[0]))))[0] print(f"sbox", hex(m[3-i])) elif b == 16: s[1] = (s[1] ^ struct.unpack("I", bytes(map(lambda x : sbox[x], struct.pack("I", tmp))))[0]) print(f"xor s[1], tmp({hex(tmp)})") ~~~ 接着分析C#,看到进入了while循环里面调用了Horror,里面调用了Saw.hack,并接收数据做base64解密,然后AES解密,密钥iv是main开始的时候初始化的,解密后的数据重新发送回Java端 ~~~c# private static async Task<bool> Horror() { NetworkStream ns = blient.GetStream(); byte[] b = new byte[4]; await ns.ReadAsync(b, 0, b.Length); Array.Reverse(b); int num = BitConverter.ToInt32(b, 0); byte[] array = new byte[num]; await ns.ReadAsync(array, 0, array.Length); byte[] obj = new byte[11] { 0, 0, 0, 0, 0, 0, 0, 1, 0, 2, 6 }; obj[3] = (byte)(11 + vals[3] + vals[0] + 4); await ns.WriteAsync(obj, default(CancellationToken)); await ns.WriteAsync(ccl, default(CancellationToken)); await ns.WriteAsync(new byte[4] { 0, 0, 0, 1 }, default(CancellationToken)); await ns.WriteAsync(ccm, default(CancellationToken)); // Saw.hack await ns.ReadAsync(b, 0, b.Length); Array.Reverse(b); num = BitConverter.ToInt32(b, 0) - 4; byte[] array2 = new byte[num - vals[1]]; await ns.ReadAsync(array2, 0, array2.Length); byte[] truth = new byte[vals[1]]; await ns.ReadAsync(truth, 0, truth.Length); byte[] obj2 = new byte[11] { 0, 0, 0, 0, 0, 0, 0, 1, 0, 10, 1 }; obj2[3] = (byte)(11 + vals[1]); await ns.WriteAsync(obj2, default(CancellationToken)); await ns.WriteAsync(truth, default(CancellationToken)); byte[] head = new byte[11]; await ns.ReadAsync(head, 0, head.Length); await ns.ReadAsync(b, 0, b.Length); Array.Reverse(b); num = BitConverter.ToInt32(b, 0); byte[] fame = new byte[num]; await ns.ReadAsync(fame, 0, fame.Length); if (Encoding.ASCII.GetString(fame) == "!") { await ns.WriteAsync(new byte[11] { 0, 0, 0, 11, 0, 0, 0, 1, 0, 1, 9 }, default(CancellationToken)); await ns.ReadAsync(head, 0, head.Length); return false; } byte[] buffer = Convert.FromBase64String(Encoding.ASCII.GetString(fame)); byte[] rep; using (ICryptoTransform transform = man.CreateDecryptor(k, iv)) { using MemoryStream stream = new MemoryStream(buffer); using CryptoStream cryptoStream = new CryptoStream(stream, transform, CryptoStreamMode.Read); using MemoryStream memoryStream = new MemoryStream(); cryptoStream.CopyTo(memoryStream); rep = memoryStream.ToArray(); } rep = Encoding.ASCII.GetBytes(Convert.ToBase64String(rep)); byte[] haha = BitConverter.GetBytes(rep.Length); byte[] bytes = BitConverter.GetBytes(rep.Length + 11 + 4); Array.Reverse(haha); Array.Reverse(bytes); await ns.WriteAsync(bytes, default(CancellationToken)); await ns.WriteAsync(new byte[7] { 0, 0, 0, 2, 0, 1, 11 }, default(CancellationToken)); await ns.WriteAsync(haha, default(CancellationToken)); await ns.WriteAsync(rep, default(CancellationToken)); await ns.ReadAsync(head, 0, head.Length); byte[] bid = new byte[vals[1]]; await ns.ReadAsync(bid, 0, bid.Length); byte[] obj3 = new byte[11] { 0, 0, 0, 0, 0, 0, 0, 1, 0, 3, 2 }; obj3[3] = (byte)(11 + vals[3] + vals[0] + 4 + vals[1]); await ns.WriteAsync(obj3, default(CancellationToken)); await ns.WriteAsync(ccl, default(CancellationToken)); await ns.WriteAsync(new byte[4] { 0, 0, 0, 1 }, default(CancellationToken)); await ns.WriteAsync(ccm, default(CancellationToken)); await ns.WriteAsync(bid, default(CancellationToken)); await ns.ReadAsync(head, 0, head.Length); await ns.WriteAsync(new byte[11] { 0, 0, 0, 11, 0, 0, 0, 1, 0, 1, 9 }, default(CancellationToken)); await ns.ReadAsync(head, 0, head.Length); return true; } ~~~ 我们回到Java端看哪里base64加密了,发现正是Saw.hack,传入了多组数据,我们尝试解密 ~~~python from Crypto.Cipher import AES from Crypto.Util.Padding import unpad import struct def dec(s): s = bytes([i&0xff for i in s]) enc = AES.new(bytes.fromhex("f8963561a24b24f219999d30d026f9421cfa89731cafe47e47560a3aaf557559"), iv=bytes.fromhex("0000000000008cab20e0eb5d0e419101"), mode=AES.MODE_CBC) return unpad(enc.decrypt(s), 16) i = 0 while True: # 省略了 Curse.magic() 和 AES 解密循环 if i % 2 == 0: if i == 0: print(dec([28, -63, 46, 26, 25, 58, 31, 97, 30, 59, 58, 108, 61, -84, -112, -59, -34, 5, 109, -20, 51, 15, -39, -51, 4, 65, 37, 121, -65, -78, -75, -117])) print(dec([0, 1, 45, 116, 39, -22, 40, -110, -122, -81, -69, -55, 87, 78, 2, -45, -6, -21, -5, 121, 91, -114, 105, -83, -4, -84, -101, 23, 34, 66, -15, 119, -54, -84, 105, 98, -115, -126, -43, 47, 46, -41, -107, 105, -33, 36, 95, 66, 5, 74, 59, -73, -63, 86, -85, -103, 67, 77, 123, 40, 99, 47, 74, -16, 58, -23, 8, -126, -40, -26, 24, 125, -36, 60, -83, 75, 94, 126, -32, 27])) print(dec([52, -41, -106, -110, 107, 126, -117, -34, -86, 125, 65, 97, 4, 110, -82, 10])) print("continue") # 省略 bufferedReader.readLine(); else: print(dec([69, 32, -33, 106, -77, 65, 91, -31, 27, 96, 113, -105, -24, -99, -26, -52])) print("continue") # 省略 bufferedReader.readLine(); print(dec([71, 89, 71, -105, 85, -68, -66, 47, -29, -99, -104, 126, -122, -2, -114, -9])) # 省略 Hack.m[0] = Long.parseLong(bufferedReader.readLine().split(" ")[1]); print(dec([72, -107, 88, -47, 21, 87, -84, 86, 125, 121, -24, 124, -18, -27, -120, -46])) # 省略 Hack.m[1] = Long.parseLong(bufferedReader.readLine().split(" ")[1]); else: print(dec([-74, -18, 24, 71, -73, 57, 23, 100, -35, 46, -21, -26, 114, -61, 126, 97, -3, -11, -64, 40, -81, 33, 64, 113, 97, -5, -47, 1, 27, -33, -41, 15])) print("continue") # 省略 bufferedReader.readLine(); # for i2 in range(4): # Java: for (int i2 = 0; i2 < 4; i2++) print(dec([-44, -3, 112, 89, -107, 63, -108, 77, -108, 51, 25, -117, -126, -29, -75, 105, 68, -125, 62, 82, 13, 41, -25, 124, -77, 103, -2, 86, -41, 62, 13, 84, -39, 105, -7, 103, 32, 7, 11, -51, 94, -105, -66, 77, 66, -23, -3, -120])) print("continue") # 省略 bufferedReader.readLine(); print(dec([120, 30, -36, -125, -53, -13, -54, -6, 31, -111, 3, -106, 6, -111, 65, 80, 28, 125, -81, -57, -107, 82, -106, -61, -26, -47, -36, 107, -32, -109, 49, 126, -70, 41, 13, -7, 117, -110, -79, -12, 50, 10, 21, 92, 118, 76, -13, 39])) print("continue") # 省略 bufferedReader.readLine(); print(dec([84, -15, 32, 24, 79, 32, 19, 85, -24, -50, -43, 13, -54, -64, 48, -127, -98, 44, -63, -49, -31, 99, -101, 51, -28, 30, -17, -101, -93, 78, 65, 35, 29, -107, 35, -6, -126, -30, -66, 1, 81, 59, 102, -28, 52, -93, 42, 14, 15, -5, 46, 127, -8, 18, 15, -110, -21, 21, -119, -86, -65, -84, 0, 112, -90, 31, -16, 40, 60, -6, 107, 66, -95, -122, 46, -110, 120, -64, -57, 42, -73, -18, 114, 61, -31, 109, 120, -100, 57, 91, 114, -44, -43, -61, -71, 97, 60, -1, 24, 27, -109, -82, 42, -14, -22, 10, 47, 67, 35, -81, -76, -125, -74, -51, 47, 15, 85, 30, -125, 119, -9, -44, -31, 101, -99, 92, 56, 40, 54, 26, -64, -83, -19, 57, -20, 34, 57, -78, -73, -72, 67, 36, 84, 77, -53, -121, 78, 13, -104, -87, 41, 0, -54, -100, 22, 36, -18, 61, -24, 11])) print("continue") # 省略 bufferedReader.readLine(); # for i3 in range(4): # Java: for (int i3 = 0; i3 < 4; i3++) print(dec([-109, -19, 100, 9, 20, 118, -78, 118, -119, -39, -48, -37, 62, 33, 40, 79])) # 省略 Hack.m[i3 * 4] = Long.parseLong(bufferedReader.readLine().split(" ")[1]); print(dec([84, -15, 32, 24, 79, 32, 19, 85, -24, -50, -43, 13, -54, -64, 48, -127, 4, -24, 29, 61, 53, 40, 52, 37, 48, 57, 100, 26, 9, -26, 11, -92])) print("continue") # 省略 bufferedReader.readLine(); print(dec([22, 38, 5, 69, 64, 16, -17, 49, 93, -19, 72, 100, 50, -65, -110, 12])) # 省略 Hack.m[(i3 * 4) + 1] = Long.parseLong(bufferedReader.readLine().split(" ")[1]); print(dec([84, -15, 32, 24, 79, 32, 19, 85, -24, -50, -43, 13, -54, -64, 48, -127, 4, -24, 29, 61, 53, 40, 52, 37, 48, 57, 100, 26, 9, -26, 11, -92])) print("continue") # 省略 bufferedReader.readLine(); print(dec([-67, -13, 10, -91, 49, 52, -13, 69, -112, -119, 18, -25, 84, 38, 101, -123])) # 省略 Hack.m[(i3 * 4) + 2] = Long.parseLong(bufferedReader.readLine().split(" ")[1]); print(dec([84, -15, 32, 24, 79, 32, 19, 85, -24, -50, -43, 13, -54, -64, 48, -127, 4, -24, 29, 61, 53, 40, 52, 37, 48, 57, 100, 26, 9, -26, 11, -92])) print("continue") # 省略 bufferedReader.readLine(); print(dec([61, -93, 69, -74, -70, 49, -95, 121, 64, -70, -45, -48, -44, -119, 1, -95])) # 省略 Hack.m[(i3 * 4) + 3] = Long.parseLong(bufferedReader.readLine().split(" ")[1]); print(dec([84, -15, 32, 24, 79, 32, 19, 85, -24, -50, -43, 13, -54, -64, 48, -127, 4, -24, 29, 61, 53, 40, 52, 37, 48, 57, 100, 26, 9, -26, 11, -92])) print("continue") # 省略 bufferedReader.readLine(); print(dec([84, -15, 32, 24, 79, 32, 19, 85, -24, -50, -43, 13, -54, -64, 48, -127, 4, -24, 29, 61, 53, 40, 52, 37, 48, 57, 100, 26, 9, -26, 11, -92])) print("continue") # 省略 bufferedReader.readLine(); print(dec([109, 82, -84, -48, 54, -41, -91, 47, 32, 88, 51, -49, 89, -37, -113, -8])) print("continue") # 省略了 Curse.hack = "!" 和 Curse.magic(); i += 1 if i == 3: break ~~~ 打印结果如下 ~~~ b"!r='m[1]=1166827919'" b'!xt = lambda a: (((a << 1) ^ 0x1B) & 0xFF) if (a & 0x80) else (a << 1)' b"!ii = '>I'" continue b'p m[1]' b'p m[2]' b'!r=0' continue b"!r='m[4]=list()'" continue b"!r='m[5]=list(struct.pack(ii,m[len(m[4])]))'" continue b"!r='m[6]=m[5][0]^m[5][1]^m[5][2]^m[5][3]'" continue b"!r='m[4].append([m[5][0]^m[6]^xt(m[5][0]^m[5][1]),m[5][1]^m[6]^xt(m[5][1]^m[5][2]),m[5][2]^m[6]^xt(m[5][2]^m[5][3]),m[5][3]^m[6]^xt(m[5][3]^m[5][0])])'" continue b'p m[4][0][0]' b"!r='m[4].append(m[4].pop(0))'" continue b'p m[4][0][1]' b"!r='m[4].append(m[4].pop(0))'" continue b'p m[4][0][2]' b"!r='m[4].append(m[4].pop(0))'" continue b'p m[4][0][3]' b"!r='m[4].append(m[4].pop(0))'" continue b"!r='m[4].append(m[4].pop(0))'" continue b'!r=0' continue b"!r='m[1]^=m[0]'" continue b'p m[1]' b'p m[2]' b'!r=0' continue ~~~ 可以看到又是pdb传输了不少数据回到python,回到saw.py我们在结尾发现通讯逻辑 ~~~python r = 0 while True: rdb.set_trace() if r == 0: break exec(r) rr += 1 ~~~ 初始r为0,然后下断点交给Java,然后Java执行了一个赋值r的操作,然后continue回到python执行exec,也就是执行了r里的内容,只有当最后r=0时退出循环 到这里我们已经把三种语言的逻辑全部分析完了,下面要做的是把Java和python端的逻辑回填到C#,毕竟C#才是主函数,但我们可以用python来实现整个逻辑 ~~~python import struct rr = 0 sp = 1 s = [0]*1034 s[1023] = 3207972492 s[1024] = 1190065579 s[1025] = 4165979424 s[1026] = 2693353696 s[1027] = 3628337899 s[1028] = 1707638109 s[1029] = 1003779598 s[1030] = 2653425729 s[1031] = 795752593 s[1032] = 2469382657 a = [] hack_m = [0]*128 tmp = 0 sbox = ( 0x9b, 0xac, 0x16, 0x92, 0x5d, 0x9c, 0x1f, 0xed, 0xf8, 0x52, 0x18, 0xc4, 0xd9, 0x59, 0xa0, 0x82, 0x3c, 0x88, 0x69, 0x4a, 0x5a, 0xf6, 0x34, 0xc1, 0xba, 0x27, 0xec, 0x23, 0x10, 0x51, 0x1, 0xe5, 0x5e, 0xb1, 0x12, 0xca, 0xe2, 0x9f, 0x65, 0x22, 0x7b, 0x2f, 0x3b, 0xeb, 0x4c, 0xf3, 0xb6, 0xb8, 0x1d, 0x50, 0xc5, 0x8e, 0x36, 0xd4, 0xd1, 0x89, 0x48, 0xad, 0xfe, 0x6e, 0xc0, 0x37, 0xb2, 0xa4, 0x6f, 0x71, 0x98, 0xa6, 0x49, 0x3a, 0x33, 0xff, 0x31, 0xb0, 0x8f, 0x76, 0xe4, 0xc8, 0x47, 0xab, 0xfd, 0x13, 0xd7, 0xc6, 0xdd, 0x73, 0xb5, 0x90, 0x70, 0x6a, 0xb9, 0x60, 0x1b, 0xfa, 0x1c, 0x45, 0xd8, 0x6, 0x68, 0x99, 0xa2, 0x4f, 0x7, 0x54, 0x4d, 0x17, 0x2a, 0x39, 0xa8, 0xa1, 0x84, 0x83, 0x64, 0x9e, 0x80, 0x7f, 0x29, 0xda, 0x61, 0x58, 0x20, 0x9, 0xdb, 0x8, 0x0f, 0xaf, 0x4, 0xd3, 0xf7, 0x5c, 0xee, 0xc9, 0x0c, 0x9d, 0x5, 0x93, 0xf2, 0x57, 0x4b, 0xf1, 0xcf, 0x15, 0xbf, 0xe8, 0xce, 0xea, 0x0e, 0x67, 0x91, 0x38, 0x6d, 0x3, 0x24, 0x25, 0x32, 0x85, 0xf5, 0xa5, 0x95, 0x5b, 0xbe, 0xbc, 0xdf, 0x0b, 0xbd, 0x7e, 0x35, 0x30, 0xae, 0xde, 0xef, 0x87, 0x8c, 0xb3, 0x1e, 0x28, 0x78, 0x6c, 0x75, 0x0a, 0x8a, 0x0d, 0x66, 0x8d, 0xcd, 0x40, 0x3d, 0xfb, 0x4e, 0xe1, 0xf4, 0x53, 0x2c, 0x77, 0x43, 0x26, 0x74, 0x94, 0x9a, 0xb7, 0x11, 0xa3, 0xe7, 0xfc, 0xd5, 0x96, 0x7c, 0xe0, 0xe6, 0x8b, 0xcb, 0x1a, 0x55, 0x62, 0xdc, 0xaa, 0x2, 0x63, 0x86, 0x7d, 0x14, 0x3f, 0x97, 0xa7, 0x72, 0x2e, 0x19, 0x2b, 0x0, 0x6b, 0xe9, 0x5f, 0xc2, 0x21, 0x2d, 0xd0, 0xf0, 0xd6, 0x7a, 0x3e, 0x46, 0x56, 0xd2, 0xe3, 0xbb, 0xb4, 0x44, 0xf9, 0xc7, 0x79, 0x81, 0x41, 0x42, 0xcc, 0xa9, 0xc3 ) m = [0] * 128 while True: # c# if rr == 0: s[32:36] = [0x61616161, 0x62626262, 0x63636363, 0x64646464] elif rr % 2 == 1: if rr != 1: hack_m[1] ^= hack_m[0] else: hack_m[1] = 1240047111 sp += 3 s[2] = hack_m[0] s[3] = hack_m[1] if rr == 1: s[4] = 1038097261 else: s[4] ^= s[3] else: sp -= 27 hack_m[17] = 1 hack_m[16] ^= hack_m[16] for iii in range(4): hack_m[hack_m[16]] <<= 8 hack_m[hack_m[16]] &= 0xffffffffffffffff hack_m[hack_m[16]] ^= hack_m[hack_m[17]] hack_m[17] += 1 hack_m[17] &= 0xffffffffffffffff hack_m[hack_m[16]] <<= 8 hack_m[hack_m[16]] &= 0xffffffffffffffff hack_m[hack_m[16]] ^= hack_m[hack_m[17]] hack_m[17] += 1 hack_m[17] &= 0xffffffffffffffff hack_m[hack_m[16]] <<= 8 hack_m[hack_m[16]] &= 0xffffffffffffffff hack_m[hack_m[16]] ^= hack_m[hack_m[17]] hack_m[17] += 1 hack_m[17] &= 0xffffffffffffffff hack_m[16] += 1 hack_m[16] &= 0xffffffffffffffff hack_m[hack_m[16]] ^= hack_m[hack_m[16]] hack_m[hack_m[16]] ^= hack_m[hack_m[17]] hack_m[17] += 1 hack_m[17] &= 0xffffffffffffffff s[32] = hack_m[0] ^ s[1] s[33] = hack_m[1] ^ s[2] s[34] = hack_m[2] ^ s[3] s[35] = hack_m[3] ^ s[4] if rr == 22: output = "" for i in range(4): packed_bytes = struct.pack("<I", s[32 + i] & 0xFFFFFFFF) output += packed_bytes[::-1].hex() print(output) exit(0) # python if rr % 2 == 0: if rr == 0: s[sp] = 0x90a94de2 sp += 1 s[256] = 1023 else: s[sp] = s[4] s[sp] <<= 8 s[sp] &= 0xffffffff sp += 1 s[sp] = s[4] s[sp] >>= 24 s[sp - 1] = (s[sp] | s[sp - 1]) s[0] = s[sp - 1] sp -= 1 tmp = s[0] tmp = struct.unpack("I", bytes(map(lambda x : sbox[x], struct.pack("I", tmp))))[0] s[1] = (s[1] ^ tmp) s[1] = (s[1] ^ s[s[256]]) s[256] = s[256] + 1 s[0] = s[1] m[0] = s[0] s[0] = s[2] m[1] = s[0] s[0] = s[3] m[2] = s[0] else: sp += 31 for i in range(4): s[0] = s[sp - 1] sp -= 1 m[3 - i] = struct.unpack("I", bytes(map(lambda x: sbox[x], struct.pack("I", s[0]))))[0] # java if rr % 2 == 0: if rr == 0: m[1] = 1166827919 xt = lambda a: (((a << 1) ^ 0x1B) & 0xFF) if (a & 0x80) else (a << 1) ii = '>I' else: m[1] ^= m[0] hack_m[0] = m[1] hack_m[1] = m[2] else: m[4] = list() for i2 in range(4): m[5] = list(struct.pack(ii, m[len(m[4])])) m[6] = m[5][0] ^ m[5][1] ^ m[5][2] ^ m[5][3] m[4].append([m[5][0] ^ m[6] ^ xt(m[5][0] ^ m[5][1]), m[5][1] ^ m[6] ^ xt(m[5][1] ^ m[5][2]), m[5][2] ^ m[6] ^ xt(m[5][2] ^ m[5][3]), m[5][3] ^ m[6] ^ xt(m[5][3] ^ m[5][0])]) for i3 in range(4): hack_m[i3*4] = m[4][0][0] m[4].append(m[4].pop(0)) hack_m[i3*4+1] = m[4][0][1] m[4].append(m[4].pop(0)) hack_m[i3*4+2] = m[4][0][2] m[4].append(m[4].pop(0)) hack_m[i3*4+3] = m[4][0][3] m[4].append(m[4].pop(0)) m[4].append(m[4].pop(0)) rr += 1 ~~~ 然后交给gemini逆向即可,可以发现首先模拟正向把roundkey跑出来,然后逆转求解即可 ~~~python import struct # --- $GF(2^8)$ 场运算 (AES 标准, 0x11B) --- # (这是 InvMixColumns 所必需的) def xt(a): """xtime: 乘以 0x02""" return (((a << 1) ^ 0x1B) & 0xFF) if (a & 0x80) else (a << 1) def mul(a, b): """$GF(2^8)$ 乘法""" p = 0 for _ in range(8): if b & 1: p ^= a a = xt(a) b >>= 1 return p # --- S-Box 和加密器定义 (来自您的代码) --- sbox = ( 0x9b, 0xac, 0x16, 0x92, 0x5d, 0x9c, 0x1f, 0xed, 0xf8, 0x52, 0x18, 0xc4, 0xd9, 0x59, 0xa0, 0x82, 0x3c, 0x88, 0x69, 0x4a, 0x5a, 0xf6, 0x34, 0xc1, 0xba, 0x27, 0xec, 0x23, 0x10, 0x51, 0x1, 0xe5, 0x5e, 0xb1, 0x12, 0xca, 0xe2, 0x9f, 0x65, 0x22, 0x7b, 0x2f, 0x3b, 0xeb, 0x4c, 0xf3, 0xb6, 0xb8, 0x1d, 0x50, 0xc5, 0x8e, 0x36, 0xd4, 0xd1, 0x89, 0x48, 0xad, 0xfe, 0x6e, 0xc0, 0x37, 0xb2, 0xa4, 0x6f, 0x71, 0x98, 0xa6, 0x49, 0x3a, 0x33, 0xff, 0x31, 0xb0, 0x8f, 0x76, 0xe4, 0xc8, 0x47, 0xab, 0xfd, 0x13, 0xd7, 0xc6, 0xdd, 0x73, 0xb5, 0x90, 0x70, 0x6a, 0xb9, 0x60, 0x1b, 0xfa, 0x1c, 0x45, 0xd8, 0x6, 0x68, 0x99, 0xa2, 0x4f, 0x7, 0x54, 0x4d, 0x17, 0x2a, 0x39, 0xa8, 0xa1, 0x84, 0x83, 0x64, 0x9e, 0x80, 0x7f, 0x29, 0xda, 0x61, 0x58, 0x20, 0x9, 0xdb, 0x8, 0x0f, 0xaf, 0x4, 0xd3, 0xf7, 0x5c, 0xee, 0xc9, 0x0c, 0x9d, 0x5, 0x93, 0xf2, 0x57, 0x4b, 0xf1, 0xcf, 0x15, 0xbf, 0xe8, 0xce, 0xea, 0x0e, 0x67, 0x91, 0x38, 0x6d, 0x3, 0x24, 0x25, 0x32, 0x85, 0xf5, 0xa5, 0x95, 0x5b, 0xbe, 0xbc, 0xdf, 0x0b, 0xbd, 0x7e, 0x35, 0x30, 0xae, 0xde, 0xef, 0x87, 0x8c, 0xb3, 0x1e, 0x28, 0x78, 0x6c, 0x75, 0x0a, 0x8a, 0x0d, 0x66, 0x8d, 0xcd, 0x40, 0x3d, 0xfb, 0x4e, 0xe1, 0xf4, 0x53, 0x2c, 0x77, 0x43, 0x26, 0x74, 0x94, 0x9a, 0xb7, 0x11, 0xa3, 0xe7, 0xfc, 0xd5, 0x96, 0x7c, 0xe0, 0xe6, 0x8b, 0xcb, 0x1a, 0x55, 0x62, 0xdc, 0xaa, 0x2, 0x63, 0x86, 0x7d, 0x14, 0x3f, 0x97, 0xa7, 0x72, 0x2e, 0x19, 0x2b, 0x0, 0x6b, 0xe9, 0x5f, 0xc2, 0x21, 0x2d, 0xd0, 0xf0, 0xd6, 0x7a, 0x3e, 0x46, 0x56, 0xd2, 0xe3, 0xbb, 0xb4, 0x44, 0xf9, 0xc7, 0x79, 0x81, 0x41, 0x42, 0xcc, 0xa9, 0xc3 ) # 1. 生成逆 S-Box inv_sbox = [0] * 256 for i in range(256): inv_sbox[sbox[i]] = i # 2. 逆 ShiftRows (标准AES) def inv_shift_rows(state_bytes): """ state_bytes 是一个 16 字节的列表 (行主序) """ new_state = [0] * 16 # Row 0 (no shift) new_state[0] = state_bytes[0] new_state[1] = state_bytes[1] new_state[2] = state_bytes[2] new_state[3] = state_bytes[3] # Row 1 (right shift 1) new_state[4] = state_bytes[7] new_state[5] = state_bytes[4] new_state[6] = state_bytes[5] new_state[7] = state_bytes[6] # Row 2 (right shift 2) new_state[8] = state_bytes[10] new_state[9] = state_bytes[11] new_state[10] = state_bytes[8] new_state[11] = state_bytes[9] # Row 3 (right shift 3) new_state[12] = state_bytes[13] new_state[13] = state_bytes[14] new_state[14] = state_bytes[15] new_state[15] = state_bytes[12] return new_state # 3. 逆 MixColumns (标准AES) # (Java 部分是标准 AES MixColumns 的混淆实现) INV_MIX_MATRIX = [ [0x0E, 0x0B, 0x0D, 0x09], [0x09, 0x0E, 0x0B, 0x0D], [0x0D, 0x09, 0x0E, 0x0B], [0x0B, 0x0D, 0x09, 0x0E] ] def inv_mix_columns(state_bytes): """ state_bytes 是一个 16 字节的列表 (行主序) """ new_state = [0] * 16 for c in range(4): # 提取列 col = [state_bytes[c], state_bytes[c + 4], state_bytes[c + 8], state_bytes[c + 12]] # 矩阵乘法 new_col = [0] * 4 for r in range(4): val = 0 for i in range(4): val ^= mul(INV_MIX_MATRIX[r][i], col[i]) new_col[r] = val # 写回列 new_state[c] = new_col[0] new_state[c + 4] = new_col[1] new_state[c + 8] = new_col[2] new_state[c + 12] = new_col[3] return new_state # --- 步骤 1: 运行加密以捕获轮密钥 --- def generate_round_keys(): """ 运行完整的加密模拟,以捕获所有11轮的轮密钥。 """ rr = 0 sp = 1 s = [0] * 1034 s[1023] = 3207972492 s[1024] = 1190065579 s[1025] = 4165979424 s[1026] = 2693353696 s[1027] = 3628337899 s[1028] = 1707638109 s[1029] = 1003779598 s[1030] = 2653425729 s[1031] = 795752593 s[1032] = 2469382657 m = [0] * 128 hack_m = [0] * 128 tmp = 0 round_keys = [] # 存储11轮的密钥 [ (s[1], s[2], s[3], s[4]), ... ] while True: # --- C# 部分 --- if rr == 0: s[32:36] = [0x61616161, 0x62626262, 0x63636363, 0x64646464] # 任意明文 elif rr % 2 == 1: if rr != 1: hack_m[1] ^= hack_m[0] else: hack_m[1] = 1240047111 sp += 3 s[2] = hack_m[0] s[3] = hack_m[1] if rr == 1: s[4] = 1038097261 else: s[4] ^= s[3] else: # rr % 2 == 0 且 rr > 0 # C# ColumnReorg (模拟日志中的 字节-列-重组) # 您的C# for循环模拟是错误的, 我们用一个正确的模拟来代替 # (基于日志的分析:hack_m[0:15] (行主序) -> hack_m[0:3] (列主序 dwords)) state_bytes_java = hack_m[0:16] reorg_dwords = [0] * 4 for c in range(4): reorg_dwords[c] = ( (state_bytes_java[c] << 24) | # (row 0, col c) (state_bytes_java[c + 4] << 16) | # (row 1, col c) (state_bytes_java[c + 8] << 8) | # (row 2, col c) (state_bytes_java[c + 12]) # (row 3, col c) ) hack_m[0:4] = reorg_dwords # --- 捕获密钥 (在 AddRoundKey 之前) --- # 这是在 AddRoundKey 之前生成的密钥 round_keys.append((s[1], s[2], s[3], s[4])) # --- AddRoundKey --- s[32] = hack_m[0] ^ s[1] s[33] = hack_m[1] ^ s[2] s[34] = hack_m[2] ^ s[3] s[35] = hack_m[3] ^ s[4] if rr == 22: # 加密完成, 返回捕获的密钥 return round_keys # --- Python 部分 --- if rr % 2 == 0: if rr == 0: s[sp] = 0x90a94de2 sp += 1 s[256] = 1023 else: s[sp] = s[4] s[sp] <<= 8 s[sp] &= 0xffffffff sp += 1 s[sp] = s[4] s[sp] >>= 24 s[sp - 1] = (s[sp] | s[sp - 1]) s[0] = s[sp - 1] sp -= 1 tmp = s[0] s[1] = (s[1] ^ struct.unpack("I", bytes(map(lambda x: sbox[x], struct.pack("I", tmp))))[0]) s[1] = (s[1] ^ s[s[256]]) s[256] = s[256] + 1 s[0] = s[1] m[0] = s[0] s[0] = s[2] m[1] = s[0] s[0] = s[3] m[2] = s[0] else: sp += 31 # --- SubBytes --- for i in range(4): s[0] = s[sp - 1] sp -= 1 m[3 - i] = struct.unpack("I", bytes(map(lambda x: sbox[x], struct.pack("I", s[0]))))[0] # --- Java 部分 --- if rr % 2 == 0: if rr == 0: m[1] = 1166827919 xt = lambda a: (((a << 1) ^ 0x1B) & 0xFF) if (a & 0x80) else (a << 1) ii = '>I' else: m[1] ^= m[0] hack_m[0] = m[1] hack_m[1] = m[2] else: m_cols_mix = [] # 存储 MixColumns 的输出 (4x4, 列主序) # --- MixColumns (标准AES) --- for i2 in range(4): b = list(struct.pack(ii, m[i2])) # m[0..3] from SubBytes p = b[0] ^ b[1] ^ b[2] ^ b[3] m_cols_mix.append( [b[0] ^ p ^ xt(b[0] ^ b[1]), b[1] ^ p ^ xt(b[1] ^ b[2]), b[2] ^ p ^ xt(b[2] ^ b[3]), b[3] ^ p ^ xt(b[3] ^ b[0])] ) # --- ShiftRows (标准AES) --- # (将列主序的 m_cols_mix 转换为行主序的 hack_m[0:15]) hack_m[0] = m_cols_mix[0][0] hack_m[1] = m_cols_mix[1][0] hack_m[2] = m_cols_mix[2][0] hack_m[3] = m_cols_mix[3][0] # Row 1 (shift left 1) hack_m[4] = m_cols_mix[1][1] hack_m[5] = m_cols_mix[2][1] hack_m[6] = m_cols_mix[3][1] hack_m[7] = m_cols_mix[0][1] # Row 2 (shift left 2) hack_m[8] = m_cols_mix[2][2] hack_m[9] = m_cols_mix[3][2] hack_m[10] = m_cols_mix[0][2] hack_m[11] = m_cols_mix[1][2] # Row 3 (shift left 3) hack_m[12] = m_cols_mix[3][3] hack_m[13] = m_cols_mix[0][3] hack_m[14] = m_cols_mix[1][3] hack_m[15] = m_cols_mix[2][3] rr += 1 # --- 步骤 2: 解密 --- def decrypt(ciphertext_hex, round_keys): # 将密文从 hex 转换为 4 个 32位整数 state_dwords = list(struct.unpack(">IIII", bytes.fromhex(ciphertext_hex))) # 共有 11 轮密钥,从 10 循环到 0 for r in range(10, -1, -1): keys = round_keys[r] # 1. InvAddRoundKey (C#) state_dwords[0] ^= keys[0] state_dwords[1] ^= keys[1] state_dwords[2] ^= keys[2] state_dwords[3] ^= keys[3] # 2. InvColumnReorg (C#) # (将列主序的 dwords 转换回行主序的 16 字节) state_bytes = [0] * 16 for c in range(4): d = state_dwords[c] state_bytes[c] = (d >> 24) & 0xFF # (row 0, col c) state_bytes[c + 4] = (d >> 16) & 0xFF # (row 1, col c) state_bytes[c + 8] = (d >> 8) & 0xFF # (row 2, col c) state_bytes[c + 12] = d & 0xFF # (row 3, col c) # 3. InvShiftRows (Java) state_bytes = inv_shift_rows(state_bytes) # 4. InvMixColumns (Java) state_bytes = inv_mix_columns(state_bytes) # 5. InvSubBytes (Python) state_bytes = [inv_sbox[b] for b in state_bytes] # 将 16 字节转换回 4 个 dwords 以进行下一轮 # (SubBytes 的输出是 dwords, 所以 InvSubBytes 的输出是字节) # (MixColumns 的输入是 dwords (列), 所以 InvMixColumns 的输出是字节 (列)) # 我们需要将 InvSubBytes 的 16 字节 (行主序) 转换为 dwords (列主序) dwords_for_next_round = [0] * 4 for c in range(4): dwords_for_next_round[c] = ( (state_bytes[c] << 24) | # (row 0, col c) (state_bytes[c + 4] << 16) | # (row 1, col c) (state_bytes[c + 8] << 8) | # (row 2, col c) (state_bytes[c + 12]) # (row 3, col c) ) state_dwords = dwords_for_next_round # 解密完成 # 最后一轮 (r=0) 之后, state_dwords 是 InvSubBytes (Python) 的输出 # 它应该直接对应于 s[32:35] # 最终的 state_dwords 是 python(SubBytes) -> java(Mix/Shift) -> C#(Reorg/AddKey) # 的逆运算。 # 最后一轮 (r=0) 的 `state_dwords` 是 InvSubBytes(InvMix(InvShift(InvReorg(InvAddKey(state))))) # 这就是明文的 dwords plaintext = b"" for d in state_dwords: plaintext += struct.pack(">I", d) return plaintext # --- 主程序 --- if __name__ == "__main__": with open("flag.enc", "r") as f: data = f.read() for i in range(0, len(data), 32): CIPHERTEXT = bytes.fromhex(data[i:i+32]).hex() all_round_keys = generate_round_keys() plaintext = decrypt(CIPHERTEXT, all_round_keys) print(plaintext.decode(), end="") ~~~ ## tradre 也是脑洞大开,看不懂ptrace怎么搞的,在网上找到了个ptrace hook方案,利用strace把所有ptrace调用的时候的参数数据等全部打印了出来,整出了个超大的trace文件 ~~~ strace -e trace=ptrace -o ./trace.log ./tradre ~~~ ~~~ --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_TRAPPED, si_pid=3936, si_uid=1000, si_status=SIGTRAP, si_utime=0, si_stime=0} --- ptrace(PTRACE_GETREGS, 3936, {r15=0x7f6c891c9040, r14=0, r13=0x4047cb, r12=0x7ffd83aee778, rbp=0x7ffd83aee620, rbx=0, r11=0x286, r10=0, r9=0x7f6c88f59740, r8=0xffffffff, rax=0x7ffd83aee4c0, rcx=0x7f6c8907805e, rdx=0, rsi=0, rdi=0x7ffd83aee4c0, orig_rax=0xffffffffffffffff, rip=0x400afd, cs=0x33, eflags=0x246, rsp=0x7ffd83aee430, ss=0x2b, fs_base=0x7f6c88f59740, gs_base=0, ds=0, es=0, fs=0, gs=0}) = 0 ptrace(PTRACE_PEEKTEXT, 3936, 0x400afd, [0x858bccffffffffbf]) = 0 ptrace(PTRACE_PEEKDATA, 3936, 0x400afc, [0x8bccffffffffbfcc]) = 0 ptrace(PTRACE_SETREGS, 3936, {r15=0x7f6c891c9040, r14=0, r13=0x4047cb, r12=0x7ffd83aee778, rbp=0x7ffd83aee620, rbx=0, r11=0x286, r10=0, r9=0x7f6c88f59740, r8=0xffffffff, rax=0x7ffd83aee4c0, rcx=0x7f6c8907805e, rdx=0, rsi=0, rdi=0x7ffd83aee4c0, orig_rax=0xffffffffffffffff, rip=0x4018ce, cs=0x33, eflags=0x246, rsp=0x7ffd83aee428, ss=0x2b, fs_base=0x7f6c88f59740, gs_base=0, ds=0, es=0, fs=0, gs=0}) = 0 ptrace(PTRACE_CONT, 3936, NULL, 0) = 0 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_TRAPPED, si_pid=3936, si_uid=1000, si_status=SIGTRAP, si_utime=0, si_stime=0} --- ptrace(PTRACE_GETREGS, 3936, {r15=0x7f6c891c9040, r14=0, r13=0x4047cb, r12=0x7ffd83aee778, rbp=0x7ffd83aee420, rbx=0, r11=0x286, r10=0, r9=0x7f6c88f59740, r8=0xffffffff, rax=0x7ffd83aee4c0, rcx=0x7f6c8907805e, rdx=0, rsi=0, rdi=0x404938, orig_rax=0xffffffffffffffff, rip=0x4018e2, cs=0x33, eflags=0x206, rsp=0x7ffd83aee400, ss=0x2b, fs_base=0x7f6c88f59740, gs_base=0, ds=0, es=0, fs=0, gs=0}) = 0 ptrace(PTRACE_PEEKTEXT, 3936, 0x4018e2, [0xfe58858b48ccc990]) = 0 ptrace(PTRACE_PEEKDATA, 3936, 0x4018e1, [0x58858b48ccc990cc]) = 0 ptrace(PTRACE_POKEDATA, 3936, 0x7ffd83aee3f8, 0x401254) = 0 ptrace(PTRACE_SETREGS, 3936, {r15=0x7f6c891c9040, r14=0, r13=0x4047cb, r12=0x7ffd83aee778, rbp=0x7ffd83aee420, rbx=0, r11=0x286, r10=0, r9=0x7f6c88f59740, r8=0xffffffff, rax=0x7ffd83aee4c0, rcx=0x7f6c8907805e, rdx=0, rsi=0, rdi=0x404938, orig_rax=0xffffffffffffffff, rip=0x400810, cs=0x33, eflags=0x206, rsp=0x7ffd83aee3f8, ss=0x2b, fs_base=0x7f6c88f59740, gs_base=0, ds=0, es=0, fs=0, gs=0}) = 0 ptrace(PTRACE_CONT, 3936, NULL, 0) = 0 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_TRAPPED, si_pid=3936, si_uid=1000, si_status=SIGTRAP, si_utime=0, si_stime=0} --- ptrace(PTRACE_GETREGS, 3936, {r15=0x7f6c891c9040, r14=0, r13=0x4047cb, r12=0x7ffd83aee778, rbp=0x7ffd83aee420, rbx=0, r11=0x246, r10=0x77, r9=0x9212a0, r8=0, rax=0x44, rcx=0x7f6c890708f7, rdx=0x1, rsi=0x1, rdi=0x404980, orig_rax=0xffffffffffffffff, rip=0x40125c, cs=0x33, eflags=0x202, rsp=0x7ffd83aee400, ss=0x2b, fs_base=0x7f6c88f59740, gs_base=0, ds=0, es=0, fs=0, gs=0}) = 0 ptrace(PTRACE_PEEKTEXT, 3936, 0x40125c, [0x55b60fe74588d831]) = 0 ptrace(PTRACE_PEEKDATA, 3936, 0x40125b, [0xb60fe74588d831cc]) = 0 ptrace(PTRACE_POKEDATA, 3936, 0x7ffd83aee3f8, 0x4012aa) = 0 ptrace(PTRACE_SETREGS, 3936, {r15=0x7f6c891c9040, r14=0, r13=0x4047cb, r12=0x7ffd83aee778, rbp=0x7ffd83aee420, rbx=0, r11=0x246, r10=0x77, r9=0x9212a0, r8=0, rax=0x44, rcx=0x7f6c890708f7, rdx=0x1, rsi=0x1, rdi=0x404980, orig_rax=0xffffffffffffffff, rip=0x400810, cs=0x33, eflags=0x202, rsp=0x7ffd83aee3f8, ss=0x2b, fs_base=0x7f6c88f59740, gs_base=0, ds=0, es=0, fs=0, gs=0}) = 0 ptrace(PTRACE_CONT, 3936, NULL, 0) = 0 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_TRAPPED, si_pid=3936, si_uid=1000, si_status=SIGTRAP, si_utime=0, si_stime=0} --- ptrace(PTRACE_GETREGS, 3936, {r15=0x7f6c891c9040, r14=0, r13=0x4047cb, r12=0x7ffd83aee778, rbp=0x7ffd83aee420, rbx=0, r11=0x246, r10=0x77, r9=0x9212a0, r8=0x7f6c89178a70, rax=0x44, rcx=0x7f6c890708f7, rdx=0x1, rsi=0x1, rdi=0x4049c8, orig_rax=0xffffffffffffffff, rip=0x4012b2, cs=0x33, eflags=0x202, rsp=0x7ffd83aee400, ss=0x2b, fs_base=0x7f6c88f59740, gs_base=0, ds=0, es=0, fs=0, gs=0}) = 0 ptrace(PTRACE_PEEKTEXT, 3936, 0x4012b2, [0x45c7cc00000001bf]) = 0 ptrace(PTRACE_PEEKDATA, 3936, 0x4012b1, [0xc7cc00000001bfcc]) = 0 ptrace(PTRACE_POKEDATA, 3936, 0x7ffd83aee3f8, 0x4012e7) = 0 ptrace(PTRACE_SETREGS, 3936, {r15=0x7f6c891c9040, r14=0, r13=0x4047cb, r12=0x7ffd83aee778, rbp=0x7ffd83aee420, rbx=0, r11=0x246, r10=0x77, r9=0x9212a0, r8=0x7f6c89178a70, rax=0x44, rcx=0x7f6c890708f7, rdx=0x1, rsi=0x1, rdi=0x4049c8, orig_rax=0xffffffffffffffff, rip=0x400810, cs=0x33, eflags=0x202, rsp=0x7ffd83aee3f8, ss=0x2b, fs_base=0x7f6c88f59740, gs_base=0, ds=0, es=0, fs=0, gs=0}) = 0 ptrace(PTRACE_CONT, 3936, NULL, 0) = 0 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_TRAPPED, si_pid=3936, si_uid=1000, si_status=SIGTRAP, si_utime=0, si_stime=0} --- ptrace(PTRACE_GETREGS, 3936, {r15=0x7f6c891c9040, r14=0, r13=0x4047cb, r12=0x7ffd83aee778, rbp=0x7ffd83aee420, rbx=0, r11=0x246, r10=0x77, r9=0x9212a0, r8=0x7f6c89178a70, rax=0x44, rcx=0x7f6c890708f7, rdx=0x1, rsi=0x1, rdi=0x404a10, orig_rax=0xffffffffffffffff, rip=0x4012ef, cs=0x33, eflags=0x202, rsp=0x7ffd83aee400, ss=0x2b, fs_base=0x7f6c88f59740, gs_base=0, ds=0, es=0, fs=0, gs=0}) = 0 ptrace(PTRACE_PEEKTEXT, 3936, 0x4012ef, [0x8b48fffffe30958b]) = 0 ptrace(PTRACE_PEEKDATA, 3936, 0x4012ee, [0x48fffffe30958bcc]) = 0 ptrace(PTRACE_POKEDATA, 3936, 0x7ffd83aee3f8, 0x400e55) = 0 ptrace(PTRACE_SETREGS, 3936, {r15=0x7f6c891c9040, r14=0, r13=0x4047cb, r12=0x7ffd83aee778, rbp=0x7ffd83aee420, rbx=0, r11=0x246, r10=0x77, r9=0x9212a0, r8=0x7f6c89178a70, rax=0x44, rcx=0x7f6c890708f7, rdx=0x1, rsi=0x1, rdi=0x404a10, orig_rax=0xffffffffffffffff, rip=0x400810, cs=0x33, eflags=0x202, rsp=0x7ffd83aee3f8, ss=0x2b, fs_base=0x7f6c88f59740, gs_base=0, ds=0, es=0, fs=0, gs=0}) = 0 ptrace(PTRACE_CONT, 3936, NULL, 0) = 0 ~~~ 通过观察前几行发现,每次中断后都会修改rip的值,从而实现了在碰到子进程一堆int 3干扰时能游刃有余的跑代码,所以一开始的思路就很明确,拿到整个汇编代码的trace记录。因此利用idapython和前面拿到的trace.log里的rip值对,实现了个反汇编指令trace,并保存到了文件进行分析 ~~~python import re import idc import idaapi import ida_lines import ida_ua # --- 1. 确保 trace.log 文件在 IDA 可以访问的路径 --- try: log_data = open("trace.log", "r").read() except IOError: # 如果找不到日志文件,只在 IDA 控制台打印错误 print("错误:无法打开 trace.log。请确保它与 IDB/I64 文件在同一目录。") raise Exception("Log file not found") # --- 2. 定义输出文件名 --- OUTPUT_FILENAME = "clean_asm_trace1.txt" # --------------------------------------------------- def get_disasm_line(ea): """ 获取指定地址处的反汇编指令行。 """ if ea == idaapi.BADADDR: return None line = ida_lines.generate_disasm_line(ea, 0) if not line: line = idc.get_disasm(ea) if not line: return None # 清理 IDA 生成的颜色标签 cleaned_line = ida_lines.tag_remove(line) return cleaned_line def write_clean_asm_path(start_ea, end_ea, file_handle): """ 从 start_ea 线性反汇编,并将纯指令写入文件,直到遇到 控制流指令或到达 end_ea。 """ if start_ea is None: return ea = start_ea while True: # 停止条件 if ea == idaapi.BADADDR or ea >= end_ea or ea < 0: break disasm = get_disasm_line(ea) if disasm: # 只写入清理后的汇编指令 file_handle.write(f"{disasm}\n") mnem = ida_ua.print_insn_mnem(ea) # 遇到控制流指令则停止此线性路径 if mnem.startswith('j') or mnem == 'ret' or mnem == 'call': break # 移动到下一条指令 ea = idc.next_head(ea) def parse_and_write_trace(log_content, output_filename): """ 解析日志并按顺序将纯净的汇编指令路径写入文件。 """ segments = [] # 正则表达式 get_regex = re.compile(r"PTRACE_GETREGS, \d+, .*rip=\s*(0x[0-9a-fA-F]+)") set_regex = re.compile(r"PTRACE_SETREGS, \d+, .*rip=\s*(0x[0-9a-fA-F]+)") poke_regex = re.compile(r"PTRACE_POKEDATA, \d+, 0x[0-9a-fA-F]+, (0x[0-9a-fA-F]+)\)") current_get, current_set, current_poke = None, None, None for line in log_content.splitlines(): get_match = get_regex.search(line) set_match = set_regex.search(line) poke_match = poke_regex.search(line) if get_match: if current_get is not None: segments.append((current_get, current_set, current_poke)) current_poke = None # 为新段重置 ea = int(get_match.group(1), 16) current_get = ea if 0x4009F7 <= ea <= 0x401B82 else None if poke_match and current_get is not None and current_poke is None: current_poke = int(poke_match.group(1), 16) if set_match and current_get is not None: current_set = int(set_match.group(1), 16) if current_get is not None: segments.append((current_get, current_set, current_poke)) if not segments: print("未在日志中解析出任何有效的执行段。") return # 打开文件并写入纯净的汇编代码 with open(output_filename, "w", encoding="utf-8") as f_out: for i in range(len(segments) - 1): get_A, set_A, poke_A = segments[i] get_B, _, _ = segments[i+1] end_ea = get_B - 1 # 1. 写入蹦床代码 (通常是 ret) write_clean_asm_path(set_A, end_ea, f_out) # 2. 写入主逻辑路径 (VM '调用' 的地址) write_clean_asm_path(poke_A, end_ea, f_out) print(f"纯净的汇编跟踪已成功写入到: {OUTPUT_FILENAME}") # --- 运行脚本 --- parse_and_write_trace(log_data, OUTPUT_FILENAME) ~~~ 可以发现代码量非常恐怖,展示一部分: ~~~assembly push rbp mov rbp, rsp sub rsp, 20h mov [rbp-18h], rdi lea rdi, aOooooooooooooO; "ooooooooooooo "... jmp cs:puts_ptr lea rdi, a88888888888Y88; "8' 888 `8 "... jmp cs:puts_ptr lea rdi, a888OoooD8bOooo; " 888 oooo d8b .oooo. .oooo"... jmp cs:puts_ptr lea rdi, a8888888pP88bD8; " 888 `888\"\"8P `P )88b d88'"... jmp cs:puts_ptr lea rdi, a888888Op888888; " 888 888 .oP\"888 888 "... jmp cs:puts_ptr lea rdi, a888888D8888888; " 888 888 d8( 888 888 "... jmp cs:puts_ptr lea rdi, aO888oD888bY888; " o888o d888b `Y888\"\"8o `Y8b"... jmp cs:puts_ptr mov edi, 10000h jmp cs:srand_ptr mov dword ptr [rbp+var_8], 0 cmp dword ptr [rbp+var_8], 7 mov dword ptr [rbp-4], 0 cmp dword ptr [rbp-4], 1Fh mov eax, [rbp-8] shl eax, 5 mov edx, eax mov eax, [rbp-4] add eax, edx movsxd rdx, eax lea rax, byte_606020 movzx esi, byte ptr [rdx+rax] mov eax, [rbp-4] movsxd rdx, eax mov rax, [rbp-18h] add rax, rdx movzx ecx, byte ptr [rax] mov eax, [rbp-8] shl eax, 5 mov edx, eax mov eax, [rbp-4] add eax, edx xor ecx, esi movsxd rdx, eax lea rax, byte_606020 mov [rdx+rax], cl mov eax, [rbp-8] shl eax, 5 mov edx, eax mov eax, [rbp-4] add eax, edx movsxd rdx, eax lea rax, byte_606120 movzx esi, byte ptr [rdx+rax] mov eax, [rbp-4] movsxd rdx, eax mov rax, [rbp-18h] add rax, rdx movzx ecx, byte ptr [rax] mov eax, [rbp-8] shl eax, 5 mov edx, eax mov eax, [rbp-4] add eax, edx xor ecx, esi movsxd rdx, eax lea rax, byte_606120 mov [rdx+rax], cl add dword ptr [rbp-4], 1 cmp dword ptr [rbp-4], 1Fh mov eax, [rbp-8] shl eax, 5 mov edx, eax mov eax, [rbp-4] add eax, edx movsxd rdx, eax lea rax, byte_606020 movzx esi, byte ptr [rdx+rax] mov eax, [rbp-4] movsxd rdx, eax mov rax, [rbp-18h] add rax, rdx movzx ecx, byte ptr [rax] mov eax, [rbp-8] shl eax, 5 mov edx, eax mov eax, [rbp-4] add eax, edx xor ecx, esi movsxd rdx, eax lea rax, byte_606020 mov [rdx+rax], cl mov eax, [rbp-8] shl eax, 5 mov edx, eax mov eax, [rbp-4] add eax, edx movsxd rdx, eax lea rax, byte_606120 movzx esi, byte ptr [rdx+rax] mov eax, [rbp-4] movsxd rdx, eax mov rax, [rbp-18h] add rax, rdx movzx ecx, byte ptr [rax] mov eax, [rbp-8] shl eax, 5 mov edx, eax mov eax, [rbp-4] add eax, edx xor ecx, esi movsxd rdx, eax lea rax, byte_606120 mov [rdx+rax], cl add dword ptr [rbp-4], 1 cmp dword ptr [rbp-4], 1Fh mov eax, [rbp-8] shl eax, 5 mov edx, eax mov eax, [rbp-4] add eax, edx movsxd rdx, eax lea rax, byte_606020 movzx esi, byte ptr [rdx+rax] mov eax, [rbp-4] movsxd rdx, eax mov rax, [rbp-18h] add rax, rdx movzx ecx, byte ptr [rax] mov eax, [rbp-8] shl eax, 5 mov edx, eax mov eax, [rbp-4] add eax, edx xor ecx, esi movsxd rdx, eax lea rax, byte_606020 mov [rdx+rax], cl mov eax, [rbp-8] shl eax, 5 mov edx, eax mov eax, [rbp-4] add eax, edx movsxd rdx, eax lea rax, byte_606120 movzx esi, byte ptr [rdx+rax] mov eax, [rbp-4] movsxd rdx, eax mov rax, [rbp-18h] add rax, rdx movzx ecx, byte ptr [rax] mov eax, [rbp-8] shl eax, 5 mov edx, eax mov eax, [rbp-4] add eax, edx xor ecx, esi movsxd rdx, eax lea rax, byte_606120 mov [rdx+rax], cl add dword ptr [rbp-4], 1 cmp dword ptr [rbp-4], 1Fh ~~~ 但是重复的地方非常多。直接关注特殊字符串和数组调用,发现两个256大小的sbox,最开始初始化的32字节数组,以及srand、rand等调用,一段一段的还原成伪C(大概还原了整个Addroundkey) ~~~c unsigned char byte_606020[0x100]; unsigned char byte_606120[0x100]; void process(unsigned char *buf) { for (int i = 0; i < 8; i++) { for (int j = 0; j < 32; j++) { int offset = i * 32 + j; byte_606020[offset] ^= buf[j]; byte_606120[offset] ^= buf[j]; } } } int main() { puts("ooooooooooooo ..."); puts("8' 888 `8 ..."); puts(" 888 oooo d8b .oooo. .oooo ..."); puts(" 888 `888\"\"8P `P )88b d88' ..."); puts(" 888 888 .oP\"888 888 ..."); puts(" 888 888 d8( 888 888 ..."); puts(" o888o d888b `Y888\"\"8o `Y8b ..."); srand(0x10000); int i = 0; int j = 0; process(buf); char buf[0x140]; int len = 0; char ch; setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); printf("Input your flag: "); while (len < 0x20) { if (read(0, &ch, 1) <= 0) break; if (ch == '\n') break; buf[len++] = ch; } buf[len] = 0; unsigned char random_bytes[16]; for (int i = 0; i < 16; i++) { int r = rand(); random_bytes[i] = (unsigned char)r; } unsigned char *src = (unsigned char *)(rbp - 0x180); unsigned char *dst = (unsigned char *)(rbp - 0x110); int offset = 4; // [rbp-1C8h] int len = offset << 2; // 16 int i = 0; while (i < len) { dst[i] = src[i]; i++; } unsigned char tmp[4]; int len1 = var_1C8; // 4 int len2 = var_1C4; // 0xA int i, j; for (i = 0; i < 0xA; i++) { for (j = 0; j < 4; j++) { int offset = i * 4 + j; dst[j] = src[offset]; } uint8_t tmp = dst[0]; dst[0] = dst[1]; dst[1] = dst[2]; dst[2] = dst[3]; dst[3] = tmp; for (j = 0; j < 4; j++) { tmp[j] = byte_606020[tmp[j]]; // 用查表映射 } // 异或混淆写回 for (j = 0; j < 4; j++) { int offset = i * 4 + j; dst[offset] ^= tmp[j]; // 异或回原数组 } for (int j = 0; j <= 3; j++) { int idx = (var_1C0 - var_1C8) * 4 + j; uint8_t src_byte = var_190[idx]; uint8_t xor_byte = var_184[j]; var_190[idx] = src_byte ^ xor_byte; var_1BC++; // 循环计数器 } } } ~~~ 手动把重复代码化简用数字表示重复次数,一点点扣慢慢能看出来是个AES加密,最开始两个sbox都异或完后发现互逆(其实并非完全互逆,坑死人了,sbox有两个数重复了,你去逆矩阵求出来的是错误的I_SBOX) 整体的加密流程是:先对通过一开始给出的32字节的额外key对Sbox和I_Sbox进行解密,读入32字节的输入后设置srand的seed为0x10000, 然后这个srand分两轮,第一轮生成16个rand()作为key参与AES的加密,第二轮用第17次rand出的int当作seed然后rand出32个xor key(最后调试解密脚本的时候也被这里坑了,本来应该是0x5011e654,写成了&0xff后的0x54,看了半天才看出来,也是被坑惨了) AES的实现是标准的,另外最后的密文是Congratulations!This is the correct flag!的前32字节,最后就是AES的加密结果xor上生成的xor key 然后再xor一开始给出的32字节key,然后去跟这个密文比较 对于随机数的逻辑生成一下,脚本如下: ~~~c #include <stdio.h> #include <stdlib.h> int main() { int key[17]; int b[32]; srand(0x10000); for (int i = 0; i < 17; i++) key[i] = rand(); printf("Key = "); for (int i = 0; i < 16; i++) printf("0x%02X,", key[i]&0xff); printf("\n"); srand(key[16]); for (int i = 0; i < 32; i++) b[i] = (unsigned char)rand(); printf("Rand32 = "); for (int i = 0; i < 32; i++) printf("0x%02X,", b[i]); printf("\n"); return 0; } ~~~ 得到如下两组数据: ~~~ Key = 0xF4,0x70,0xBB,0xC0,0x31,0xCA,0xEE,0x5E,0x58,0xB2,0x72,0xEA,0x02,0xF3,0xFF,0xE6, Rand32 = 0xF8,0x44,0xC6,0xBA,0xB1,0xE5,0x0E,0x3B,0xA2,0x4B,0xB5,0xAA,0x4A,0x89,0xC7,0xA0,0x19,0xBD,0xEC,0x5E,0xDE,0xC1,0xC3,0x87,0x75,0xE6,0x12,0x71,0x61,0xEA,0xF4,0x59, ~~~ 梳理清楚了加密逻辑,打印出了随机数,那么可以写出解密脚本如下: ~~~python bytes_array = [ 0xE2, 0x8B, 0x55, 0x38, 0x69, 0xFA, 0x80, 0xC2, 0x64, 0x4E, 0x7F, 0xE7, 0x13, 0x06, 0x14, 0xC5, 0xC0, 0x13, 0xD3, 0x12, 0x6B, 0xBD, 0xF2, 0xC7, 0x88, 0x44, 0x3E, 0x09, 0xE8, 0xA3, 0x83, 0x30 ] class AES: MIX_C = [[0x2, 0x3, 0x1, 0x1], [0x1, 0x2, 0x3, 0x1], [0x1, 0x1, 0x2, 0x3], [0x3, 0x1, 0x1, 0x2]] I_MIXC = [[0xe, 0xb, 0xd, 0x9], [0x9, 0xe, 0xb, 0xd], [0xd, 0x9, 0xe, 0xb], [0xb, 0xd, 0x9, 0xe]] RCon = [0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000, 0x20000000, 0x40000000, 0x80000000, 0x1B000000, 0x36000000] S_BOX = [0x81, 0xF7, 0x22, 0x43, 0x9B, 0x91, 0xEF, 0x07, 0x54, 0x4F, 0x18, 0x90, 0xED, 0xD1, 0xBF, 0xB3, 0x0A, 0x91, 0x1A, 0x6F, 0x91, 0xE4, 0xB5, 0x37, 0x25, 0x90, 0x9C, 0xA6, 0x74, 0x07, 0xF1, 0xF0, 0x55, 0x76, 0xC6, 0x1E, 0x5F, 0xC5, 0x77, 0x0E, 0x50, 0xEB, 0x9A, 0x16, 0x62, 0xDE, 0x25, 0xD0, 0xC4, 0xD4, 0xF0, 0xD1, 0x73, 0x2B, 0xF7, 0x5D, 0x8F, 0x56, 0xBE, 0xEB, 0x03, 0x84, 0x31, 0x45, 0xEB, 0x08, 0x79, 0x22, 0x72, 0x94, 0xDA, 0x62, 0x36, 0x75, 0xA9, 0x54, 0x3A, 0xE5, 0x3B, 0x41, 0x93, 0xC2, 0xD3, 0xFF, 0x4B, 0x41, 0x43, 0x9C, 0xE2, 0x8F, 0x80, 0x30, 0xA2, 0xEF, 0xDB, 0xFF, 0x32, 0x64, 0xFF, 0xC3, 0x2A, 0xB7, 0xB3, 0x47, 0x21, 0xB7, 0x7D, 0x98, 0x43, 0x3A, 0x8B, 0x6D, 0x91, 0xB0, 0x93, 0x9D, 0xF9, 0x20, 0xCA, 0x32, 0x34, 0xF2, 0xE4, 0x28, 0xF8, 0x5C, 0x70, 0xE2, 0x2F, 0x87, 0x46, 0xD4, 0x36, 0x6D, 0xC4, 0xD5, 0xA0, 0xE9, 0x01, 0xDA, 0x77, 0x5B, 0x0D, 0xB6, 0xA0, 0x92, 0x9C, 0xCE, 0x49, 0x97, 0x62, 0x4F, 0xCE, 0xAA, 0x86, 0x1D, 0x36, 0xFD, 0x88, 0xEB, 0x02, 0xB9, 0x6F, 0x32, 0x20, 0xFC, 0xA4, 0x9E, 0xA6, 0x9D, 0xD3, 0x85, 0x82, 0x93, 0xF0, 0xBC, 0x27, 0xDB, 0xE4, 0x7F, 0xE6, 0x68, 0xBC, 0x6E, 0xE4, 0x12, 0xCA, 0xE3, 0x8D, 0xD9, 0x2D, 0x38, 0x58, 0xF3, 0x70, 0x16, 0x75, 0x5C, 0x34, 0x04, 0x8C, 0x93, 0x0B, 0xF8, 0x58, 0xBB, 0x9F, 0x4F, 0xB0, 0x2D, 0x66, 0x74, 0x23, 0xBE, 0x04, 0xC9, 0xE9, 0x71, 0x69, 0xB0, 0x6E, 0x62, 0x9E, 0xAE, 0x03, 0x73, 0xCD, 0x29, 0x00, 0x23, 0x0E, 0x56, 0xFF, 0x50, 0xF8, 0x0E, 0xDD, 0x53, 0x3C, 0x1A, 0x4C, 0xB2, 0x5A, 0x1F, 0xD4, 0x5B, 0xB0, 0xAF, 0xC9, 0xDD, 0x13, 0x06, 0x58, 0xF7, 0x38, 0x26] for i in range(0, 256, 32): for j in range(32): S_BOX[i+j] ^= bytes_array[j] I_SBOX = [0xB0, 0x82, 0x3F, 0xED, 0x59, 0xCC, 0x25, 0xFA, 0xDB, 0x0E, 0xDC, 0x79, 0x92, 0xF5, 0xC3, 0x3E, 0xBC, 0xF0, 0xEA, 0x90, 0xF0, 0x92, 0x0D, 0x40, 0xBC, 0xCA, 0x7D, 0x4D, 0x2C, 0x7D, 0x6A, 0xFB, 0xB6, 0xF0, 0xC1, 0x0A, 0xCF, 0x38, 0xA3, 0xFF, 0x8A, 0x02, 0xEA, 0xEC, 0x51, 0xFC, 0xD7, 0x8B, 0xC8, 0x3D, 0x72, 0x74, 0x43, 0x64, 0xD6, 0x75, 0xFE, 0x1F, 0x9C, 0x40, 0x85, 0x28, 0x52, 0x15, 0x90, 0x73, 0xA3, 0x5C, 0xEF, 0x92, 0x18, 0xD4, 0xB0, 0xEA, 0x23, 0x2B, 0x4E, 0x63, 0xA2, 0x57, 0xAC, 0x63, 0x9B, 0x42, 0x96, 0x50, 0x4B, 0x1D, 0xD6, 0x51, 0x78, 0x5E, 0x4F, 0x2E, 0x1E, 0xB4, 0x72, 0x53, 0xFE, 0x38, 0xE5, 0x46, 0x53, 0xC8, 0x93, 0xAA, 0x27, 0xE2, 0xAB, 0xB5, 0x51, 0xC3, 0x10, 0x3F, 0xCD, 0x9D, 0xA1, 0x82, 0xFD, 0xC5, 0x49, 0xEB, 0x83, 0x0A, 0xE9, 0xB0, 0x09, 0x5B, 0xD8, 0x1A, 0x44, 0x79, 0x26, 0x9D, 0x5C, 0x28, 0xF3, 0xBC, 0xB0, 0x29, 0xE3, 0xB2, 0xF2, 0xB6, 0x56, 0xBF, 0xA7, 0x30, 0x8C, 0x10, 0xC7, 0x42, 0x6A, 0xBD, 0x09, 0xE1, 0xF4, 0xD6, 0x5C, 0x5E, 0xA5, 0x7A, 0x4F, 0x49, 0x74, 0xD3, 0x45, 0x4B, 0x0B, 0xF9, 0x1D, 0xE9, 0xB9, 0x1E, 0xAA, 0xDE, 0x3C, 0x45, 0xED, 0x59, 0xAD, 0x6F, 0x8B, 0xE7, 0x12, 0x9F, 0xFE, 0xF7, 0x90, 0x6E, 0xD9, 0xC4, 0xFD, 0x56, 0xFD, 0x0B, 0xE1, 0xFD, 0x47, 0xF3, 0xD5, 0x5C, 0x6F, 0xBE, 0x34, 0x86, 0xF8, 0x9A, 0xA0, 0x42, 0xAC, 0xBB, 0x72, 0x08, 0xB8, 0xCA, 0xA5, 0xA1, 0x44, 0x96, 0x7B, 0x6A, 0x1F, 0xDF, 0x42, 0x6B, 0x6E, 0x75, 0xC7, 0xD0, 0x75, 0x72, 0xAC, 0xA5, 0xC4, 0xDB, 0x90, 0x55, 0x8D, 0xA4, 0xD7, 0x38, 0xD7, 0x6C, 0xD1, 0xCA, 0x24, 0xE1, 0x69, 0x2D, 0x2A, 0x6A, 0xBD, 0x82, 0x8F, 0x4D, 0xDD, 0xCC, 0xBB, 0xAA][:256] for i in range(0, 256, 32): for j in range(32): I_SBOX[i+j] ^= bytes_array[j] def format_SBOX(self, isRE=False): if isRE: if len(self.I_SBOX) == 256: self.I_SBOX = [self.I_SBOX[j:j + 16] for j in range(0, 256, 16)] else: if len(self.S_BOX) == 256: self.S_BOX = [self.S_BOX[j:j + 16] for j in range(0, 256, 16)] def SubBytes(self, State): # 字节替换 return [self.S_BOX[i][j] for i, j in [(_ >> 4, _ & 0xF) for _ in State]] def SubBytes_Inv(self, State): # 字节逆替换 return [self.I_SBOX[i][j] for i, j in [(_ >> 4, _ & 0xF) for _ in State]] def ShiftRows(self, S): # 行移位 return [S[ 0], S[ 5], S[10], S[15], S[ 4], S[ 9], S[14], S[ 3], S[ 8], S[13], S[ 2], S[ 7], S[12], S[ 1], S[ 6], S[11]] def ShiftRows_Inv(self, S): # 逆行移位 return [S[ 0], S[13], S[10], S[ 7], S[ 4], S[ 1], S[14], S[11], S[ 8], S[ 5], S[ 2], S[15], S[12], S[ 9], S[ 6], S[ 3]] def MixColumns(self, State): # 列混合 return self.Matrix_Mul(self.MIX_C, State) def MixColumns_Inv(self, State): # 逆列混合 return self.Matrix_Mul(self.I_MIXC, State) def RotWord(self, _4byte_block): # 用于生成轮密钥的字移位 return ((_4byte_block & 0xffffff) << 8) + (_4byte_block >> 24) def SubWord(self, _4byte_block): # 用于生成密钥的字节替换 result = 0 for position in range(4): i = _4byte_block >> position * 8 + 4 & 0xf j = _4byte_block >> position * 8 & 0xf result ^= self.S_BOX[i][j] << position * 8 return result def mod(self, poly, mod = 0b100011011): # poly模多项式mod while poly.bit_length() > 8: poly ^= mod << poly.bit_length() - 9 return poly def mul(self, poly1, poly2): # 多项式相乘 result = 0 for index in range(poly2.bit_length()): if poly2 & 1 << index: result ^= poly1 << index return result def Matrix_Mul(self, M1, M2): # M1 = MIX_C M2 = State # 用于列混合的矩阵相乘 M = [0] * 16 for row in range(4): for col in range(4): for Round in range(4): M[row + col*4] ^= self.mul(M1[row][Round], M2[Round+col*4]) M[row + col*4] = self.mod(M[row + col*4]) return M def gen_ISBOX(self): new_contrary_sbox = [0] * 256 for i in range(256): line = (self.S_BOX[i//16][i%16] & 0xf0) >> 4 rol = self.S_BOX[i//16][i%16] & 0xf new_contrary_sbox[(line * 16) + rol] = i return new_contrary_sbox def round_key_generator(self, _16bytes_key): self.format_SBOX() self.format_SBOX(isRE=True) # 轮密钥产生 w = [_16bytes_key >> 96, _16bytes_key >> 64 & 0xFFFFFFFF, _16bytes_key >> 32 & 0xFFFFFFFF, _16bytes_key & 0xFFFFFFFF] + [0]*40 for i in range(4, 44): temp = w[i-1] if not i % 4: temp = self.SubWord(self.RotWord(temp)) ^ self.RCon[i//4-1] w[i] = w[i-4] ^ temp return [self.num_2_16bytes( sum([w[4*i] << 96, w[4*i+1] << 64, w[4*i+2] << 32, w[4*i+3]]) ) for i in range(11)] def AddRoundKey(self, State, RoundKeys, index): # 异或轮密钥 return self._16bytes_xor(State, RoundKeys[index]) def _16bytes_xor(self, _16bytes_1, _16bytes_2): return [_16bytes_1[i] ^ _16bytes_2[i] for i in range(16)] def _16bytes2num(cls, _16bytes): # 16字节转数字 return int.from_bytes(_16bytes, byteorder = 'big') def num_2_16bytes(cls, num): # 数字转16字节 return num.to_bytes(16, byteorder = 'big') def aes_encrypt(self, plaintext_list, RoundKeys): State = plaintext_list State = self.AddRoundKey(State, RoundKeys, 0) for Round in range(1, 10): State = self.SubBytes(State) State = self.ShiftRows(State) State = self.MixColumns(State) State = self.AddRoundKey(State, RoundKeys, Round) State = self.SubBytes(State) State = self.ShiftRows(State) State = self.AddRoundKey(State, RoundKeys, 10) return State def aes_decrypt(self, ciphertext_list, RoundKeys): State = ciphertext_list State = self.AddRoundKey(State, RoundKeys, 10) for Round in range(1, 10): State = self.ShiftRows_Inv(State) State = self.SubBytes_Inv(State) State = self.AddRoundKey(State, RoundKeys, 10-Round) State = self.MixColumns_Inv(State) State = self.ShiftRows_Inv(State) State = self.SubBytes_Inv(State) State = self.AddRoundKey(State, RoundKeys, 0) return State if __name__ == '__main__': aes = AES() key = int.from_bytes(bytes([0xF4,0x70,0xBB,0xC0,0x31,0xCA,0xEE,0x5E,0x58,0xB2,0x72,0xEA,0x02,0xF3,0xFF,0xE6]), byteorder="big") #print(hex(key)) RoundKeys = aes.round_key_generator(key) rand_num = [0xF8,0x44,0xC6,0xBA,0xB1,0xE5,0x0E,0x3B,0xA2,0x4B,0xB5,0xAA,0x4A,0x89,0xC7,0xA0,0x19,0xBD,0xEC,0x5E,0xDE,0xC1,0xC3,0x87,0x75,0xE6,0x12,0x71,0x61,0xEA,0xF4,0x59] cmp = b'Congratulations!This is the correct flag!'[:32] cipher = [bytes_array[i]^rand_num[i]^cmp[i] for i in range(32)] ciphertext1_bytes = aes.num_2_16bytes(int.from_bytes(bytes(cipher[:16]), byteorder="big")) plaintext1_bytes = bytes(aes.aes_decrypt(ciphertext1_bytes, RoundKeys)) ciphertext2_bytes = aes.num_2_16bytes(int.from_bytes(bytes(cipher[16:32]), byteorder="big")) plaintext2_bytes = bytes(aes.aes_decrypt(ciphertext2_bytes, RoundKeys)) full_plaintext_bytes = plaintext1_bytes + plaintext2_bytes print(full_plaintext_bytes.decode('utf-8', errors='ignore')) ~~~ 得到1629d52128ca395e4f6a0fc98712a3e1,包裹flag{} 所以flag值为:flag{1629d52128ca395e4f6a0fc98712a3e1} ## iked 流量包里关键的只有后四个包,三个ISAKMP协议包,一个ESP包,其中ESP UDP payload有大量数据 查看elf,发现通讯相关代码,定位到main,发现关键在sub_4050F0函数中,出现FLAG字眼,同时里面存在不少加解密算法。首先进入了sub_404790,里面出现HMAC+AES,HMAC我们不用管,只需关注AES密文、密钥、iv即可 核心函数如上,一个是传入了密钥,发现是第三个参数地址+4的16字节,也就是外面的dword_8498C0,交叉引用发现在main做了初始化,很明显这个位置也和序号为1的ISAKMP相关,出现了对应的字符串,一点点数据往前对应即可直到v30对应的是ISAKMP数据包前16字节  ~~~c++ char __fastcall sub_404B00(int *a1, _DWORD *a2) { int v2; // eax int v3; // r8d __int64 v4; // rdx int v5; // eax __int64 i; // rdx char result; // al v2 = *a1; a2[17] = 1; v3 = 0; v4 = 0LL; *a2 = v2; do { v5 = v3 + ((unsigned int)a1[v4 & 1] >> (3 * v4)); v3 += 17; *((_BYTE *)a2 + v4++ + 4) = __ROL1__(v5 ^ 0x37, 4); } while ( v4 != 32 ); for ( i = 0LL; i != 32; ++i ) { result = __ROL1__((i ^ ((unsigned int)a1[((_BYTE)i + 1) & 1] >> (2 * i))) - 85, 2); *((_BYTE *)a2 + i + 36) = result; } return result; } ~~~ 里面函数很简单,同构下即可得到密钥为8df5a27a8256629f1558a9d2d856a63ca87bb9f9651560b6167b74d71f5b2022,同时根据分析可知iv是udp payload第八字节开始的16字节,密文是从iv后一直到最后12字节前 ~~~python import struct, binascii, hmac from hashlib import sha1, sha256 from Crypto.Cipher import AES def rol(x, n, bits=8): """Rotate-left x by n over `bits` bits. x assumed non-negative integer.""" mask = (1 << bits) - 1 n %= bits return ((x << n) | (x >> (bits - n))) & mask def sub_404B00(a1): """ a1: list/sequence, must have at least two integers (a1[0], a1[1]). 返回 bytearray 模拟的 a2 内存块(长度 >= 72 字节)。 """ if len(a1) < 2: raise ValueError("a1 must contain at least two elements") a2_bytes = bytearray(128) # 足够空间 v2 = int(a1[0]) & 0xFFFFFFFF # a2[17] = 1 (offset 17*4) struct.pack_into("<I", a2_bytes, 17 * 4, 1) # *a2 = v2 (offset 0) struct.pack_into("<I", a2_bytes, 0, v2) # --- 第一个循环 (填充 AES 密钥 @ a2[1]...a2[8]) --- v3 = 0 for v4 in range(32): key_val = int(a1[v4 & 1]) & 0xFFFFFFFF shift = 3 * v4 # emulate x86 shift behaviour: shift count is masked to 5 bits (mod 32) shift_count = shift & 0x1F shifted_val = (key_val >> shift_count) & 0xFFFFFFFF v5 = (v3 + shifted_val) & 0xFFFFFFFF v3 = (v3 + 17) & 0xFFFFFFFF val_to_rotate = (v5 ^ 0x37) & 0xFF result = rol(val_to_rotate, 4, 8) a2_bytes[v4 + 4] = result # --- 第二个循环 (填充 HMAC 密钥 @ a2[9]...a2[16]) --- for i in range(32): key_val = int(a1[(i + 1) & 1]) & 0xFFFFFFFF shift = 2 * i shift_count = shift & 0x1F shifted_val = (key_val >> shift_count) & 0xFFFFFFFF val_32 = (i ^ shifted_val) & 0xFFFFFFFF val_sub = (val_32 - 85) & 0xFFFFFFFF val_to_rotate = val_sub & 0xFF result = rol(val_to_rotate, 2, 8) a2_bytes[i + 36] = result return a2_bytes def parse_and_try(blob_hex, a1_tuple): data = binascii.unhexlify(blob_hex) spi = struct.unpack_from(">I", data, 0)[0] seq = struct.unpack_from(">I", data, 4)[0] iv = data[8:24] # assume ICV_len = 12 (from reverse) icv_len = 12 icv = data[-icv_len:] ciphertext = data[24:-icv_len] print("SPI", hex(spi), "SEQ", hex(seq), "IVlen", len(iv), "CT len", len(ciphertext)) key_blob = sub_404B00([int(a1_tuple[0]) & 0xFFFFFFFF, int(a1_tuple[1]) & 0xFFFFFFFF]) aes_key = key_blob[4:36] hmac_key = key_blob[36:68] print("AES key:", aes_key.hex()) print("HMAC key:", hmac_key.hex()) # try hmac-sha1 and sha256 truncated 12 mac1 = hmac.new(hmac_key, data[:-icv_len], sha1).digest()[:icv_len] mac2 = hmac.new(hmac_key, data[:-icv_len], sha256).digest()[:icv_len] print("packet ICV:", icv.hex()) print("HMAC-SHA1(12):", mac1.hex(), "match?", mac1==icv) print("HMAC-SHA256(12):", mac2.hex(), "match?", mac2==icv) if len(ciphertext) % 16 != 0: print("ciphertext length not multiple of 16 -> AES-CBC can't decrypt directly. Check IV/ICV lengths or mode.") return None cipher = AES.new(aes_key, AES.MODE_CBC, iv) pt = cipher.decrypt(ciphertext) # verify padding/padlen/next header etc here (ESP layout) return pt parse_and_try("deadbeef0000000153f1c95b079add8b408a58d14e0b52e8ad888ee54da8ff38aba41196521336d34f485167227ddbc8752d492888d026a0e39bb901cc69e88e220d87c66369de77adb742bef9ba5f89e2563a34485a20c3ad93a31467b286de0d8eab51d06f225baad14f798f50e9aa751750b9a81fd2a709c9e52653a995f99f7d4aea0edddb9c213c5d87d18613a04015adcbfaf06e37d0ddf69287a568907907532abdeb9222e0379a3dce303e3bc70b988a0b4c29f7ec73451b5389bc989532f9953c5111b35a2442bd3b973c807e65b42b71c0dad710c2d653782aa29b6f2819c5c5a12171fd9e409e08044e79c8dd8b5ff7dfc1c93e81f820b92ecfdead9258b59363cc6723d684be7feb1e93cac141b237b892c8dcefd523fbf237d0e7c381d012e7541b60254aac733c9cb804cffa6e7749c55094124deeaf49271ed099f146c1270900798d533daee5fee55b3954f63742498fc5e3975f1071c560cf6a9dd8a7318ab057378a3e41bad076b4d0abb18c72b88551fd81be4042be257585801374", (0xdeadbeef, 0xcafebabe)) def compute_xmmword_849860(a0, a1): """ Compute the 16-byte value produced by sub_404B70 for inputs a1[0]=a0, a1[1]=a1. Inputs: a0, a1 are integers representing 32-bit unsigned words. Returns: bytes object of length 16 (xmmword_849860). """ # Normalize to 32-bit v7 = a0 & 0xFFFFFFFF v8 = a1 & 0xFFFFFFFF def byte_of(x): return x & 0xFF # bytes b0..b7 (lower qword) b0 = byte_of(9 * ((v7 ^ v8)) ) b1 = byte_of(9 * (((v7 >> 2) ^ (v8 >> 2))) + 1) b2 = byte_of(9 * (((v8 >> 4) ^ (v7 >> 4))) + 2) b3 = byte_of(9 * (((v8 >> 6) ^ (v7 >> 6))) + 3) # BYTE1 is (v >> 8) & 0xFF b4 = byte_of(9 * ((((v8 >> 8) & 0xFF) ^ ((v7 >> 8) & 0xFF))) + 4) b5 = byte_of(9 * (((v8 >> 10) ^ (v7 >> 10))) + 5) b6 = byte_of(9 * (((v8 >> 12) ^ (v7 >> 12))) + 6) # b7 is the same slot where the code reused v9.m128i_u8[0] as shifting chain, # but the construction in the C decomp packs the previous bytes; here we # explicitly place b0..b7 as the first 8 bytes. # (The original assembly composes them by nested ORs; this yields identical result.) b7 = b0 # in assembly b0 is re-used in shifts; but final byte ordering below matches assembly packing. # bytes b8..b15 (upper qword) b8 = byte_of(9 * ((((v8 >> 16) & 0xFF) ^ ((v7 >> 16) & 0xFF))) + 8) # BYTE2 ^ BYTE2 b9 = byte_of(9 * (((v8 >> 18) ^ (v7 >> 18))) + 9) b10 = byte_of(9 * (((v8 >> 20) ^ (v7 >> 20))) + 10) b11 = byte_of(9 * (((v8 >> 22) ^ (v7 >> 22))) + 11) # HIBYTE is (v >> 24) & 0xFF b12 = byte_of(9 * ((((v8 >> 24) & 0xFF) ^ ((v7 >> 24) & 0xFF))) + 12) b13 = byte_of(9 * (((v8 >> 26) ^ (v7 >> 26))) + 13) b14 = byte_of(9 * (((v8 >> 28) ^ (v7 >> 28))) + 14) b15 = byte_of(9 * (((v8 >> 30) ^ (v7 >> 30))) + 15) # Pack into bytes little-endian order for the 16-byte xmm (assembly used _mm_load_si128(&v9) where v9 # was constructed as m128i_i64[0] and m128i_i64[1] with bytes 0..7 then 8..15). out = bytes([b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15]) return out a0 = 0xDEADBEEF a1 = 0xCAFEBABE val = compute_xmmword_849860(a0, a1) print("xmmword_849860 (hex):", val.hex()) ~~~ 分析感觉是cbc模式,但解密没法正常padding,只好no padding得到第一层解密后的数据 ~~~ 54f6c590f050170247551cacc8be0265522a7853fe790fdd58f8556ad6cf0dda9f534c0db07b8e1d3f065f4e7f59e7db666d6629025a81faab149f70bccb6b67b7ea6b56ea3be73a04cf5c8b835e0391b6c0e22ef1e2269be74979010203040500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005d72b88b6a41daad8d691e1d09253dd3c ~~~ 接着aes出来后,看到下方多处运算,分析可知用0xCAFEBABEDEADBEEFLL初始化了密钥,同样同构下拿到96字节密钥 ~~~python import struct def rol8(x, n): x &= 0xFF n %= 8 return ((x << n) | (x >> (8 - n))) & 0xFF def compute_a2_from_a1(a1): """ a1: iterable with at least two 32-bit unsigned ints [a1[0], a1[1], ...] returns: bytearray of length 128 representing the memory region written by sub_404B70 """ if len(a1) < 2: raise ValueError("a1 must have at least two elements") a1_0 = int(a1[0]) & 0xFFFFFFFF a1_1 = int(a1[1]) & 0xFFFFFFFF a2 = bytearray(128) # allocate enough space # write a2[5].m128i_i64[0] = *(_QWORD *)a1 -> offset 5*16 = 80, write 8 bytes little-endian (a1[0], a1[1]) struct.pack_into("<II", a2, 5 * 16, a1_0, a1_1) # --- first loop: fill bytes 0..31 --- v3 = 0 for i in range(32): key_val = (a1[i & 1] & 0xFFFFFFFF) shift = (3 * i) & 0x1F # x86 shifts mask to 5 bits shifted = (key_val >> shift) & 0xFFFFFFFF if shift < 32 else 0 v5 = (v3 + shifted) & 0xFFFFFFFF v3 = (v3 + 17) & 0xFFFFFFFF val = (v5 ^ 0x37) & 0xFF a2[i] = rol8(val, 4) # --- second loop: fill bytes 32..63 --- for j in range(32): key_val = (a1[((j + 1) & 1)] & 0xFFFFFFFF) shift = (2 * j) & 0x1F shifted = (key_val >> shift) & 0xFFFFFFFF if shift < 32 else 0 val32 = (j ^ shifted) & 0xFFFFFFFF val_sub = (val32 - 85) & 0xFFFFFFFF val_byte = val_sub & 0xFF a2[32 + j] = rol8(val_byte, 2) # --- result[5].m128i_i32[2] = 1 -> offset 5*16 + 8 = 88, write 4-byte little-endian 1 --- struct.pack_into("<I", a2, 5 * 16 + 8, 1) # --- construct v9 (16 bytes) and store into a2[4] (offset 64..79) --- v7 = a1_0 v8 = a1_1 def byte_of(x): return x & 0xFF # We'll compute bytes b0..b15 according to the decompiled expressions. b0 = byte_of(9 * ((v7 ^ v8))) b1 = byte_of(9 * (((v7 >> 2) ^ (v8 >> 2))) + 1) b2 = byte_of(9 * (((v8 >> 4) ^ (v7 >> 4))) + 2) b3 = byte_of(9 * (((v8 >> 6) ^ (v7 >> 6))) + 3) b4 = byte_of(9 * ((((v8 >> 8) & 0xFF) ^ ((v7 >> 8) & 0xFF))) + 4) b5 = byte_of(9 * (((v8 >> 10) ^ (v7 >> 10))) + 5) b6 = byte_of(9 * (((v8 >> 12) ^ (v7 >> 12))) + 6) b7 = byte_of(9 * (((v8 >> 14) ^ (v7 >> 14))) + 7) b8 = byte_of(9 * ((((v8 >> 16) & 0xFF) ^ ((v7 >> 16) & 0xFF))) + 8) b9 = byte_of(9 * (((v8 >> 18) ^ (v7 >> 18))) + 9) b10 = byte_of(9 * (((v8 >> 20) ^ (v7 >> 20))) + 10) b11 = byte_of(9 * (((v8 >> 22) ^ (v7 >> 22))) + 11) b12 = byte_of(9 * ((((v8 >> 24) & 0xFF) ^ ((v7 >> 24) & 0xFF))) + 12) b13 = byte_of(9 * (((v8 >> 26) ^ (v7 >> 26))) + 13) b14 = byte_of(9 * (((v8 >> 28) ^ (v7 >> 28))) + 14) b15 = byte_of(9 * (((v8 >> 30) ^ (v7 >> 30))) + 15) v9_bytes = bytes([b0, b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13, b14, b15]) # store into a2[4] (offset 4*16 = 64) a2[4*16 : 4*16 + 16] = v9_bytes return a2 # Example usage: if __name__ == "__main__": a1 = [0xDEADBEEF, 0xCAFEBABE] a2_bytes = compute_a2_from_a1(a1) print("a2 (hex):", a2_bytes.hex()) # If you want to view each 16-byte lane like __m128i elements: for i in range(6): chunk = a2_bytes[i*16:(i+1)*16] print(f"a2[{i}] = {chunk.hex()}") ~~~ 接着就是最后加密的地方sub_4031F0  里面还是比较复杂的,第一眼以为没法逆向来着,观察到最后先乘以7再加13再异或0x53,ai分析可知7有逆元0xb7可逆,所以得开始想办法解密上面的aes明文  发现sub_403A50和sub_4031F0紧邻,里面正是逆向代码,还没被调用,出题人可能忘删了也可能故意留着。想着调试会更简单,但是不会发通信数据。所以直接疯狂拷打ai让他同构sub_403A50。其中a1是密文、a3是密钥、a2是a1长度、a4是0 ~~~python # --- 辅助函数 (模拟 C 的 8 位循环移位) --- def ROR1(byte, bits): """8位循环右移""" byte &= 0xFF return ((byte >> bits) | (byte << (8 - bits))) & 0xFF def ROL1(byte, bits): """8位循环左移""" byte &= 0xFF return ((byte << bits) | (byte >> (8 - bits))) & 0xFF # --- 占位符函数 --- def sub_4031F0_placeholder(a1_buf, a2_len, a3_buf): """ 这是对 C 函数 sub_4031F0 的占位符。 如果 a4 != 0,则会调用此函数。 """ print(f"[!] 调用 sub_4031F0 (未实现)") # 在此处实现 sub_4031F0 的逻辑 pass def sim_simd_block(a1_buf, start_index, end_index, a3_buf): """ 这是对 C 代码中 SSE/SIMD 块 (v18-v39) 的占位符。 这部分代码在 C 中用于高性能处理 128 字节的数据块。 要 1:1 翻译它,需要逆向工程其实现的具体加密算法。 """ print(f"[!] 调用 SIMD 块 (未实现) @ {start_index}-{end_index}") # 在此处实现 SIMD 块的 *算法* 逻辑 pass # --- 核心逻辑的 Python 实现 --- def scalar_8byte_block_transform(a1_buf, start_index, end_index, a3_buf): """ 实现了 C 代码中的 v40 和 v46 循环(标量 8 字节块转换)。 它在 8 字节块上对索引 i 和 i+4 的字节进行 3 轮混淆。 """ # 从 a3 获取密钥 key_b = a3_buf[34] # a3 + 34 key_c = a3_buf[33] # a3 + 33 key_d = a3_buf[32] # a3 + 32 # C 循环是 v6 < v15 (a2-7),v6 每次+8 # 这等同于处理 0, 8, 16... 直到 (a2-8) & ~7 for i in range(start_index, end_index, 8): # 对应 v41 = *v40 v41_b = a1_buf[i] # --- 第 1 轮 --- # v42 = *(v40 - 4) ^ ROL1(key_b + 3*v41, 1) # *(v40 - 4) 对应 a1[i+4] v42_b = a1_buf[i + 4] ^ ROL1((key_b + 3 * v41_b) & 0xFF, 1) # *(v40 - 4) = v41 a1_buf[i + 4] = v41_b # *(v40 - 8) = v42 a1_buf[i] = v42_b # --- 第 2 轮 --- # v44 = v41 ^ ROL1(key_c + 3*v42, 1) v44_b = v41_b ^ ROL1((key_c + 3 * v42_b) & 0xFF, 1) # *(v40 - 4) = v42 a1_buf[i + 4] = v42_b # *(v40 - 8) = v44 a1_buf[i] = v44_b # --- 第 3 轮 --- # v45 = ROL1(key_d + 3*v44, 1) v45_b = ROL1((key_d + 3 * v44_b) & 0xFF, 1) # *(v40 - 4) = v44 a1_buf[i + 4] = v44_b # *(v40 - 8) = v45 ^ v42 a1_buf[i] = (v45_b ^ v42_b) & 0xFF def sub_403A50_py(a1_buf: bytearray, a3_buf: bytes, a4: int): """ C 函数 sub_403A50 的 Python 简化版。 :param a1_buf: 必须是 bytearray (可变缓冲区) :param a3_buf: 必须是 bytes 或 bytearray (密钥/上下文) :param a4: 标志位 """ a2_len = len(a1_buf) # --- 1. `if (a4)` 分支 --- if a4 != 0: sub_4031F0_placeholder(a1_buf, a2_len, a3_buf) return # 函数结束 # --- 2. `else (a4 == 0)` 分支 --- if a2_len > 0: # --- 阶段 1:反向预处理循环 (v7, v9) --- # C 代码的循环从 a2-1 向下遍历到 0 for i in range(a2_len - 1, -1, -1): v9 = a1_buf[i] ror_val = ROR1(v9, 3) xor_val = ror_val ^ 0x5A # (val - 13) * -73,最后截断为 8-bit # Python 的 & 0xFF 模拟了 C 的 (char) 截断 sub_val = (xor_val - 13) mul_val = (sub_val * -73) & 0xFF a1_buf[i] = mul_val # --- 阶段 2:核心转换循环 --- if a2_len > 7: # C 代码中,此循环处理 8 字节块,直到索引 < (a2 - 7) # 最后一个块的起始索引是 (a2 - 8) & ~7 scalar_loop_end = (a2_len - 8) & ~7 # C 代码中的 SIMD 检查 # 我们只模拟 a2 > 135 的条件 if a2_len > 135: # --- 情况 A:SIMD --- # 计算 SIMD 会处理多少 128 字节的块 # (a2 - 8) >> 7 是 C 代码中的 simd_block_count simd_end_index = ((a2_len - 8) >> 7) * 128 if simd_end_index > 0: # 调用 SIMD 占位符 sim_simd_block(a1_buf, 0, simd_end_index, a3_buf) # C 代码的 v40 循环,处理 SIMD 之后的剩余部分 scalar_start_index = simd_end_index else: # --- 情况 B:非 SIMD --- # C 代码的 v46 循环,从头开始处理 scalar_start_index = 0 # 为情况 A(剩余部分)或情况 B(全部)运行标量循环 if scalar_start_index < scalar_loop_end: scalar_8byte_block_transform( a1_buf, scalar_start_index, scalar_loop_end, a3_buf ) # --- 阶段 3:正向后处理循环 (v12) --- # v10 = *(_DWORD *)(a3 + 88) v10 = int.from_bytes(a3_buf[88:92], 'little', signed=False) # v11 = a3 + 64 v11_base_ptr = 64 for v12 in range(a2_len): # v13 = ROR1(a1[v12], 2) v13 = ROR1(a1_buf[v12], 2) # v14 = a3[64 + (v12 & 0xF)] ^ v13 key1 = a3_buf[v11_base_ptr + (v12 & 0xF)] v14 = key1 ^ v13 # key2_index = ((v10_low_byte + v12_low_byte) & 0x1F) key2_index = ((v10 & 0xFF) + (v12 & 0xFF)) & 0x1F key2 = a3_buf[key2_index] # a1[v12] = v14 - key2 - v12 # 所有运算都隐式截断为 8 位 final_val = (v14 - key2 - (v12 & 0xFF)) & 0xFF a1_buf[v12] = final_val return bytes(a1_buf) if __name__ == "__main__": a1_data = bytearray.fromhex("54f6c590f050170247551cacc8be0265522a7853fe790fdd58f8556ad6cf0dda9f534c0db07b8e1d3f065f4e7f59e7db666d6629025a81faab149f70bccb6b67b7ea6b56ea3be73a04cf5c8b835e0391b6c0e22ef1e2269be74979") # 示例 a3_data = bytes.fromhex("8df5a27a8256629f1558a9d2d856a63ca87bb9f9651560b6167b74d71f5b2022a595518ea554627186354170c597b6de6555914e6594a231467581300657f61fd9b56f9c28ceb6b3f3bd77e4c03a170fefbeaddebebafeca0100000000000000") # 示例,a3 必须足够长 print(f"处理前: {a1_data.hex()}") # 假设 a4 = 0 (执行解密逻辑) sub_403A50_py(a1_data, a3_data, 0) print(f"处理后: {a1_data}") ~~~ 万幸后面的部分错误没影响到flag字符串,感觉还是aes那里模式选取有点问题,解密出来的明文可能不太对 flag的值为flag{5f8fe0cbc7253ww_he11obambi} 这是和gemini、gpt对话的过程 - https://gemini.google.com/share/9857c7842a01 - https://gemini.google.com/share/1eb7d5a528af - https://chatgpt.com/share/68f503a1-106c-8006-98a8-15248dc6ee10 - https://chatgpt.com/share/68f503b1-8e20-8006-b780-5c1bd8380d8f ## butterfly 拼手速和ai的 ## ABabyChal 非常像SU_ezlua,魔改了abc字节码,导致abc decompiler没法正常反编译 没什么wp参考,等一段时间再补充 最后修改:2025 年 10 月 21 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 如果觉得我的文章对你有用,请随意赞赏