湾区杯
re1 minigame
用wedecode解包
微信开发小助手打开解包后的app.wxss
$dbg = typeof $dbg != 'undefined' ? $dbg : {
$: function(fn) {
return function() {
return fn.apply(this, arguments);
};
},
__: function() {},
_: function() {}
};
var _ = $dbg._;
var $ = $dbg.$;
var __ = $dbg.__;
__(function(_e12) {
return eval(_e12);
});
_(2, 1, 0);
_(2, 1, 13);
var e = require("../../@babel/runtime/helpers/regeneratorRuntime"),
t = require("../../@babel/runtime/helpers/asyncToGenerator"),
r = require("../../utils/validator.js");
_(2, 1, 175);
Page({
data: {
input: "",
resultIcon: "",
resultText: "",
resultClass: "",
loading: !1,
wasmReady: !1
},
onLoad: function() {
var _args = arguments;
return $(function() {
var arguments = _args;
__(function(_e) {
return eval(_e);
});
_(2, 1, 282);
this.initWasm();
_(2, 1, 1);
}.bind(this), 0, 2)();
},
initWasm: function() {
var _args4 = arguments;
return $(function() {
var arguments = _args4;
__(function(_e2) {
return eval(_e2);
});
_(2, 1, 319);
var a = this;
_(2, 1, 330);
var _tmp;
return _tmp = (_tmp = t((_(2, 1, 339), (_tmp = e(), _(2, 1, 343), _tmp).mark(function t() {
var _args3 = arguments;
return $(function() {
var arguments = _args3;
__(function(_e3) {
return eval(_e3);
});
var s;
_(2, 1, 368);
return _tmp = (_tmp = e(), _(2, 1, 379), _tmp).wrap(function(e) {
var _args2 = arguments;
return $(function(e) {
var arguments = _args2;
__(function(_e4) {
return eval(_e4);
});
for (;;) {
_(2, 1, 404);
switch (e.prev = e.next) {
case 0:
_(2, 1, 433);
return _tmp = (_(2, 1, 440), e.prev = 0, _(2, 1, 449), e.next = 3, _(2, 1, 458), r.init()), _(2, 1, 467), _tmp;
case 3:
_(2, 1, 474), console.log("WASM初始化成功"), _(2, 1, 499), a.setData({
wasmReady: !0,
resultText: "就绪,请输入字符串",
resultClass: "ready"
}), _(2, 1, 568), e.next = 14;
_(2, 1, 578);
break;
case 7:
_(2, 1, 591), e.prev = 7, _(2, 1, 600), e.t0 = e.catch(0), _(2, 1, 616), console.error("WASM初始化失败:", e.t0), _(2, 1, 649), s = "WASM加载失败", _(2, 1, 662), e.t0.errMsg && e.t0.errMsg.includes("file not found") ? s = "WASM文件未找到" : e.t0.message && e.t0.message.includes("WebAssembly") && (s = "浏览器不支持WASM"), _(2, 1, 797), a.setData({
resultText: s,
resultClass: "error"
}), _(2, 1, 843), wx.showToast({
title: s,
icon: "none",
duration: 3e3
});
case 14:
_(2, 1, 900);
return _tmp = (_(2, 1, 907), e.prev = 14, _(2, 1, 917), a.setData({
loading: !1
}), _(2, 1, 941), e.finish(14)), _(2, 1, 954), _tmp;
case 17:
case "end":
_(2, 1, 972);
return _tmp = e.stop(), _(2, 1, 987), _tmp;
}
}
_(2, 1, 1);
}.bind(this), 1, 2)(e);
}, t, null, [
[0, 7, 14, 17]
]), _(2, 1, 1012), _tmp;
_(2, 1, 1);
}.bind(this), 2, 2)();
}))), _(2, 1, 1016), _tmp)(), _(2, 1, 1018), _tmp;
_(2, 1, 1);
}.bind(this), 3, 2)();
},
onInput: function(e) {
var _args5 = arguments;
return $(function(e) {
var arguments = _args5;
__(function(_e5) {
return eval(_e5);
});
_(2, 1, 1040);
this.setData({
input: e.detail.value
});
_(2, 1, 1);
}.bind(this), 4, 2)(e);
},
onValidate: function() {
var _args8 = arguments;
return $(function() {
var arguments = _args8;
__(function(_e6) {
return eval(_e6);
});
_(2, 1, 1100);
var a = this;
_(2, 1, 1111);
var _tmp2;
return _tmp2 = (_tmp2 = t((_(2, 1, 1120), (_tmp2 = e(), _(2, 1, 1124), _tmp2).mark(function t() {
var _args7 = arguments;
return $(function() {
var arguments = _args7;
__(function(_e7) {
return eval(_e7);
});
var s, n, i, u, o;
_(2, 1, 1157);
return _tmp2 = (_tmp2 = e(), _(2, 1, 1168), _tmp2).wrap(function(e) {
var _args6 = arguments;
return $(function(e) {
var arguments = _args6;
__(function(_e8) {
return eval(_e8);
});
for (;;) {
_(2, 1, 1193);
switch (e.prev = e.next) {
case 0:
if (_(2, 1, 1225), !a.data.loading) {
_(2, 1, 1242);
e.next = 2;
_(2, 1, 1251);
break;
}
_(2, 1, 1257);
return _tmp2 = e.abrupt("return"), _(2, 1, 1283), _tmp2;
case 2:
if (_(2, 1, 1293), s = a.data.input) {
_(2, 1, 1309);
e.next = 6;
_(2, 1, 1318);
break;
}
_(2, 1, 1324);
return _tmp2 = (_(2, 1, 1331), wx.showToast({
title: "请输入内容",
icon: "none"
}), _(2, 1, 1373), e.abrupt("return")), _(2, 1, 1392), _tmp2;
case 6:
if (_(2, 1, 1402), 38 === s.length) {
_(2, 1, 1417);
e.next = 9;
_(2, 1, 1426);
break;
}
_(2, 1, 1432);
return _tmp2 = (_(2, 1, 1439), a.showResult("✗", "长度错误", "fail"), _(2, 1, 1471), e.abrupt("return")), _(2, 1, 1490), _tmp2;
case 9:
try {
_(2, 1, 1501), a.setData({
loading: !0
}), _(2, 1, 1525), n = new TextEncoder(), _(2, 1, 1543), i = n.encode(s), _(2, 1, 1557), u = r._malloc_wrapper(i.length + 1), _(2, 1, 1589), r.HEAPU8.set(i, u), _(2, 1, 1607), r.HEAPU8[u + i.length] = 0, _(2, 1, 1630), o = r._validateString(u), _(2, 1, 1653), r._free_wrapper(u), _(2, 1, 1672), o ? a.showResult("✓", "校验成功", "success") : a.showResult("✗", "校验失败", "fail");
} catch (e) {
__(function(_e9) {
return eval(_e9);
});
_(2, 1, 1750), console.error("校验出错:", e), _(2, 1, 1775), a.showResult("⚠", "校验异常", "fail");
} finally {
_(2, 1, 1815);
a.setData({
loading: !1
});
}
__(function(_e10) {
return eval(_e10);
});
case 10:
case "end":
_(2, 1, 1857);
return _tmp2 = e.stop(), _(2, 1, 1872), _tmp2;
}
}
_(2, 1, 1);
}.bind(this), 5, 2)(e);
}, t), _(2, 1, 1878), _tmp2;
_(2, 1, 1);
}.bind(this), 6, 2)();
}))), _(2, 1, 1882), _tmp2)(), _(2, 1, 1884), _tmp2;
_(2, 1, 1);
}.bind(this), 7, 2)();
},
showResult: function(e, t, r) {
var _args9 = arguments;
return $(function(e, t, r) {
var arguments = _args9;
__(function(_e11) {
return eval(_e11);
});
_(2, 1, 1913);
this.setData({
resultIcon: e,
resultText: t,
resultClass: r
});
_(2, 1, 1);
}.bind(this), 8, 2)(e, t, r);
}
});
_(2, 1, 1972);
分析知主要加密逻辑在validator.wasm中
拖入ghidra分析
int export::c(uint *param1)
{
uint *puVar1;
uint *puVar2;
int iVar3;
uint uVar4;
uint uVar5;
int iVar6;
iVar6 = 0;
puVar2 = param1;
if (((uint)param1 & 3) == 0) {
code_r0x80000072:
do {
puVar1 = puVar2;
puVar2 = puVar1 + 1;
} while (((0x1010100 - *puVar1 | *puVar1) & 0x80808080) == 0x80808080);
do {
puVar2 = puVar1;
puVar1 = (uint *)((int)puVar2 + 1);
} while (*(char *)puVar2 != '\0');
}
else {
if (*(char *)param1 == '\0') {
iVar3 = 0;
goto code_r0x800000b6;
}
do {
puVar2 = (uint *)((int)puVar2 + 1);
if (((uint)puVar2 & 3) == 0) goto code_r0x80000072;
} while (*(char *)puVar2 != '\0');
}
iVar3 = (int)puVar2 - (int)param1;
code_r0x800000b6:
if (iVar3 != 0x26) {
return 0;
}
do {
uVar4 = (uint)*(byte *)(iVar6 + 0x400) ^ (int)*(char *)(iVar6 + (int)param1);
uVar5 = (uint)(uVar4 == 0x99);
if (uVar4 != 0x99) {
return uVar5;
}
iVar6 = iVar6 + 1;
} while (iVar6 != 0x26);
return uVar5;
}
分析得知其将iVar6的一块区域异或0x99
跟进找到此区域
ram:00000400 ff ?? FFh
ram:00000401 f5 ?? F5h
ram:00000402 f8 ?? F8h
ram:00000403 fe ?? FEh
ram:00000404 e2 ?? E2h
ram:00000405 ff ?? FFh
ram:00000406 f8 ?? F8h
ram:00000407 fc ?? FCh
ram:00000408 a9 ?? A9h
ram:00000409 fb ?? FBh
ram:0000040a ab ?? ABh
ram:0000040b ae ?? AEh
ram:0000040c fa ?? FAh
ram:0000040d ad ?? ADh
ram:0000040e ac ?? ACh
ram:0000040f a8 ?? A8h
ram:00000410 fa ?? FAh
ram:00000411 ae ?? AEh
ram:00000412 ab ?? ABh
ram:00000413 a1 ?? A1h
ram:00000414 a1 ?? A1h
ram:00000415 af ?? AFh
ram:00000416 ae ?? AEh
ram:00000417 f8 ?? F8h
ram:00000418 ac ?? ACh
ram:00000419 af ?? AFh
ram:0000041a ae ?? AEh
ram:0000041b fc ?? FCh
ram:0000041c a1 ?? A1h
ram:0000041d fa ?? FAh
ram:0000041e a8 ?? A8h
ram:0000041f fb ?? FBh
ram:00000420 fb ?? FBh
ram:00000421 ad ?? ADh
ram:00000422 fc ?? FCh
ram:00000423 ac ?? ACh ? -> ram:00e4aaac
ram:00000424 aa ?? AAh ? -> ram:0000e4aa
ram:00000425 e4 ?? E4h ? -> ram:000000e4
异或0x99后得到flag{fae0b27c451c728867a567e8c1bb4e53}
re2 strangeApp
寻找合适环境运行程序
用frida-dexdump,dump下解密后的dex
package com.swdd.strangeapp;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import androidx.appcompat.app.AlertDialog.Builder;
import androidx.appcompat.app.AppCompatActivity;
import java.nio.charset.StandardCharsets;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class MainActivity extends AppCompatActivity {
private static final byte[] TARGET;
static {
MainActivity.TARGET = new byte[]{0x76, 17, 7, 0x7C, -99, 51, 23, (byte)0x85, -78, 23, -53, 1, 42, 109, -77, 5, -87, 10, -77, 106, 78, 100, 0x7B, (byte)0x8A, (byte)0xD1, 0x1F, 19, 56, 0x73, -105, -11, -38, -18, -72, 12, 42, 17, 55, (byte)0x87, -44, 0x77, -41, 87, 0x76, 0x5F, -76, -84, 69};
}
public static String a(String algo) {
return algo == null || (algo.isEmpty()) ? algo : ((char)(algo.charAt(0) ^ 5)) + algo.substring(1);
}
private byte[] aa(String input) throws Exception {
SecretKeySpec secretKey = new SecretKeySpec("1234567891123456".getBytes(StandardCharsets.UTF_8), MainActivity.a("DES"));
IvParameterSpec iv = new IvParameterSpec("1234567891123456".getBytes(StandardCharsets.UTF_8));
Cipher cipher0 = Cipher.getInstance(MainActivity.a("DES/CBC/PKCS5Padding"));
cipher0.init(1, secretKey, iv);
return cipher0.doFinal(input.getBytes(StandardCharsets.UTF_8));
}
private boolean compareBytes(byte[] a, byte[] b) {
if(a != null && b != null && a.length == b.length) {
int i;
for(i = 0; i < a.length; ++i) {
if(a[i] != b[i]) {
return false;
}
}
return true;
}
return false;
}
@Override // androidx.fragment.app.FragmentActivity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(layout.activity_main);
EditText inputText = (EditText)this.findViewById(id.inputText);
((Button)this.findViewById(id.checkButton)).setOnClickListener((View v) -> {
String s = inputText.getText().toString();
try {
if(this.compareBytes(this.aa(s), MainActivity.TARGET)) {
this.showDialog("Good");
return;
}
this.showDialog("NO");
}
catch(Exception e) {
this.showDialog("加密失败: " + e.getMessage());
}
});
}
private void showDialog(String msg) {
new Builder(this).setMessage(msg).setPositiveButton("OK", null).show();
}
}
“D”被异或成了“A”,AES加密
脚本:
from Crypto.Cipher import AES
TARGET = bytes([
0x76, 17, 7, 0x7C, 0x9D, 51, 23, 0x85,
0xB2, 23, 0xCB, 1, 42, 109, 0xB3, 5,
0xA9, 10, 0xB3, 106, 78, 100, 0x7B, 0x8A,
0xD1, 0x1F, 19, 56, 0x73, 0x97, 0xF5, 0xDA,
0xEE, 0xB8, 12, 42, 17, 55, 0x87, 0xD4,
0x77, 0xD3, 87, 0x76, 0x5F, 0xB4, 0xAC, 69
])
def decrypt_aes_ignore_padding():
# 密钥和IV(16字节)
key = "1234567891123456".encode('utf-8')
iv = "1234567891123456".encode('utf-8')
cipher = AES.new(key, AES.MODE_CBC, iv)
decrypted_data = cipher.decrypt(TARGET)
results = {
"原始字节": decrypted_data,
"UTF-8解码": decrypted_data.decode('utf-8', errors='replace'),
"十六进制": decrypted_data.hex()
}
return results
if __name__ == "__main__":
try:
results = decrypt_aes_ignore_padding()
print("解密结果(忽略填充验证):")
for name, value in results.items():
print(f"{name}: {value}")
# 尝试手动去除可能的填充
if len(results["原始字节"]) > 0:
last_byte = results["原始字节"][-1]
if 1 <= last_byte <= 16: # AES块大小为16
manual_unpad = results["原始字节"][:-last_byte]
print(f"\n手动去除{last_byte}字节填充后(UTF-8):{manual_unpad.decode('utf-8', errors='replace')}")
except Exception as e:
print(f"解密失败:{str(e)}")
解出的16进制转为字符串,得到flag{just_easy_strange_app_right?}
re3 hardtest
ida打开
__int64 __fastcall main(int a1, char **a2, char **a3)
{
unsigned int seed; // eax
unsigned __int64 v5; // rax
void *v6; // rsp
__int64 v7; // r8
__int64 v8; // r9
__int64 n16; // rcx
unsigned __int64 v10; // rax
char *v11; // rsi
void *v12; // rsp
char v13; // al
__int64 i_3; // [rsp+8h] [rbp-F0h] BYREF
__int64 v15; // [rsp+10h] [rbp-E8h]
__int64 i_2; // [rsp+18h] [rbp-E0h]
__int64 v17; // [rsp+20h] [rbp-D8h]
char v18; // [rsp+29h] [rbp-CFh] BYREF
char v19; // [rsp+2Ah] [rbp-CEh]
char v20; // [rsp+2Bh] [rbp-CDh]
int j; // [rsp+2Ch] [rbp-CCh]
int i; // [rsp+30h] [rbp-C8h]
int i_1; // [rsp+34h] [rbp-C4h]
__int64 v24; // [rsp+38h] [rbp-C0h]
__int64 *v25; // [rsp+40h] [rbp-B8h]
__int64 v26; // [rsp+48h] [rbp-B0h]
__int64 *n16_1; // [rsp+50h] [rbp-A8h]
char s[104]; // [rsp+58h] [rbp-A0h] BYREF
unsigned __int64 v29; // [rsp+C0h] [rbp-38h]
v29 = __readfsqword(0x28u);
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
seed = time(0LL);
srand(seed);
v20 = rand() % 255 + 1;
printf("input your number(1-255): ");
if ( (unsigned int)__isoc99_scanf("%d", &v18) == 1 && v20 == v18 )
{
while ( getchar() != 10 )
;
printf("flag: ");
fgets(s, 100, stdin);
s[strcspn(s, "\n")] = 0;
i_1 = strlen(s);
v24 = i_1 - 1LL;
i_2 = i_1;
v17 = 0LL;
i_3 = i_1;
v15 = 0LL;
v5 = 16 * ((i_1 + 15LL) / 0x10uLL);
while ( &i_3 != (__int64 *)((char *)&i_3 - (v5 & 0xFFFFFFFFFFFFF000LL)) )
;
v6 = alloca(v5 & 0xFFF);
if ( (v5 & 0xFFF) != 0 )
*(__int64 *)((char *)&i_3 + (v5 & 0xFFF) - 8) = *(__int64 *)((char *)&i_3 + (v5 & 0xFFF) - 8);
v25 = &i_3;
((void (__fastcall *)(char *, __int64 *))sub_1492)(s, &i_3);
v26 = i_1 - 1LL;
n16 = 16LL;
v10 = 16 * ((i_1 + 15LL) / 0x10uLL);
v11 = (char *)&i_3 - (v10 & 0xFFFFFFFFFFFFF000LL);
while ( &i_3 != (__int64 *)((char *)&i_3 - (v10 & 0xFFFFFFFFFFFFF000LL)) )
;
v12 = alloca(v10 & 0xFFF);
if ( (v10 & 0xFFF) != 0 )
*(__int64 *)((char *)&i_3 + (v10 & 0xFFF) - 8) = *(__int64 *)((char *)&i_3 + (v10 & 0xFFF) - 8);
n16_1 = &i_3;
for ( i = 0; i < i_1; ++i )
{
v13 = sub_13E1(*((unsigned __int8 *)v25 + i), v11, v25, n16, v7, v8, i_3, v15, i_2, v17);
n16 = (__int64)n16_1;
*((_BYTE *)n16_1 + i) = v13;
}
v19 = 1;
for ( j = 0; j < i_1; ++j )
{
if ( *((_BYTE *)n16_1 + j) != byte_2120[j] )
{
v19 = 0;
break;
}
}
if ( v19 )
puts("right");
return 0LL;
}
else
{
puts("error");
return 1LL;
}
}
跟进sub1492
__int64 __fastcall sub_1492(const char *s, __int64 a2)
{
__int64 i_1; // rax
signed int i; // [rsp+14h] [rbp-1Ch]
int i_2; // [rsp+18h] [rbp-18h]
i_2 = strlen(s);
for ( i = 0; ; ++i )
{
i_1 = (unsigned int)i;
if ( i >= i_2 )
break;
*(_BYTE *)(i + a2) = sub_12A9((unsigned __int8)s[i], (unsigned int)(i % 7 + 1));
}
return i_1;
}
跟进 sub_13E1
int64 __fastcall sub_13E1(unsigned __int8 a1)
{
unsigned __int8 v1; // al
unsigned __int8 v3; // [rsp+15h] [rbp-3h]
v1 = sub_12A9(a1 ^ 0x5Au, 3LL);
v3 = sub_1313((unsigned __int8)((16 * ((3 * (v1 >> 4)) & 0xF)) | (5 * (v1 & 0xF)) & 0xF));
return byte_2020[(unsigned __int8)sub_12DE(v3, 2LL)];
}
分析可得,明文经过以下步骤:
按位置左旋转,异或与多次旋转,有限域运算,S盒查找
得到密文
写出脚本:
from typing import Optional
byte_2120 = [
0x97,0xD5,0x60,0x43,0xB4,0x10,0x43,0x73,0x0F,0xDA,0x43,0xCD,
0xD3,0xE8,0x73,0x4A,0x94,0xC3,0xCD,0x71,0xBD,0xDC,0x97,0x1A
]
byte_2020 = [
0x63,0x7C,0x77,0x7B,0xF2,0x6B,0x6F,0xC5,0x30,0x01,0x67,0x2B,0xFE,0xD7,0xAB,0x76,
0xCA,0x82,0xC9,0x7D,0xFA,0x59,0x47,0xF0,0xAD,0xD4,0xA2,0xAF,0x9C,0xA4,0x72,0xC0,
0xB7,0xFD,0x93,0x26,0x36,0x3F,0xF7,0xCC,0x34,0xA5,0xE5,0xF1,0x71,0xD8,0x31,0x15,
0x04,0xC7,0x23,0xC3,0x18,0x96,0x05,0x9A,0x07,0x12,0x80,0xE2,0xEB,0x27,0xB2,0x75,
0x09,0x83,0x2C,0x1A,0x1B,0x6E,0x5A,0xA0,0x52,0x3B,0xD6,0xB3,0x29,0xE3,0x2F,0x84,
0x53,0xD1,0x00,0xED,0x20,0xFC,0xB1,0x5B,0x6A,0xCB,0xBE,0x39,0x4A,0x4C,0x58,0xCF,
0xD0,0xEF,0xAA,0xFB,0x43,0x4D,0x33,0x85,0x45,0xF9,0x02,0x7F,0x50,0x3C,0x9F,0xA8,
0x51,0xA3,0x40,0x8F,0x92,0x9D,0x38,0xF5,0xBC,0xB6,0xDA,0x21,0x10,0xFF,0xF3,0xD2,
0xCD,0x0C,0x13,0xEC,0x5F,0x97,0x44,0x17,0xC4,0xA7,0x7E,0x3D,0x64,0x5D,0x19,0x73,
0x60,0x81,0x4F,0xDC,0x22,0x2A,0x90,0x88,0x46,0xEE,0xB8,0x14,0xDE,0x5E,0x0B,0xDB,
0xE0,0x32,0x3A,0x0A,0x49,0x06,0x24,0x5C,0xC2,0xD3,0xAC,0x62,0x91,0x95,0xE4,0x79,
0xE7,0xC8,0x37,0x6D,0x8D,0xD5,0x4E,0xA9,0x6C,0x56,0xF4,0xEA,0x65,0x7A,0xAE,0x08,
0xBA,0x78,0x25,0x2E,0x1C,0xA6,0xB4,0xC6,0xE8,0xDD,0x74,0x1F,0x4B,0xBD,0x8B,0x8A,
0x70,0x3E,0xB5,0x66,0x48,0x03,0xF6,0x0E,0x61,0x35,0x57,0xB9,0x86,0xC1,0x1D,0x9E,
0xE1,0xF8,0x98,0x11,0x69,0xD9,0x8E,0x94,0x9B,0x1E,0x87,0xE9,0xCE,0x55,0x28,0xDF,
0x8C,0xA1,0x89,0x0D,0xBF,0xE6,0x42,0x68,0x41,0x99,0x2D,0x0F,0xB0,0x54,0xBB,0x16
]
def rotl8(x: int, n: int) -> int:
n &= 7
return ((x << n) & 0xFF) | ((x & 0xFF) >> (8 - n))
def ror8(x: int, n: int) -> int:
n &= 7
return ((x & 0xFF) >> n) | ((x << (8 - n)) & 0xFF)
def sub_12A9(a1: int, a2: int) -> int:
return rotl8(a1 & 0xFF, a2 & 0xFF)
def sub_12DE(a1: int, a2: int) -> int:
return ror8(a1 & 0xFF, a2 & 0xFF)
def sub_1313(a1: int) -> int:
a1 &= 0xFF
if a1 == 0:
return 0
return pow(a1, 255, 257)
def sub_13E1(a1: int) -> int:
v1 = sub_12A9((a1 ^ 0x5A) & 0xFF, 3)
high = (3 * (v1 >> 4)) & 0xF
low = (5 * (v1 & 0xF)) & 0xF
combined = ((16 * high) | low) & 0xFF # fits in 0..255
v3 = sub_1313(combined & 0xFF)
idx = sub_12DE(v3 & 0xFF, 2) & 0xFF
return byte_2020[idx]
def find_preimage_for_target(t: int) -> Optional[int]:
cands = []
for x in range(256):
if sub_13E1(x) == t:
cands.append(x)
if len(cands) == 1:
return cands[0]
else:
print(f"[!] target 0x{t:02X} 有 {len(cands)} 个前像: {cands}")
return None
def recover_flag() -> Optional[str]:
L = len(byte_2120)
res_bytes = []
for i in range(L):
t = byte_2120[i]
y = find_preimage_for_target(t)
if y is None:
print(f"无法唯一确定位置 {i} 的中间字节 (sub_13E1 前像)。")
return None
shift = (i % 7) + 1
orig = ror8(y, shift) & 0xFF
res_bytes.append(orig)
try:
s = bytes(res_bytes).decode('utf-8')
except:
s = ''.join(f'\\x{b:02x}' for b in res_bytes)
return s
if __name__ == "__main__":
flag = recover_flag()
if flag is None:
print("NO")
else:
print(flag)
flag{Bl@st1ng_1s_a_g00d_Way!!}