-
-
[原创] KCTF 2026 第五题
-
发表于: 2026-8-24 01:21 196
-
首先把程序拖入IDA,可以看到提示加载符号文件,里面有RSA字样,给了我们一点提示。

main里面我们可以看到作者留下的混淆视听的字符串

对调用0x403C60跟进去,

再看sub_403180,后续没有任何操作了, 完全是混淆视听的代码,不嘻嘻。

回到main,真正路径是,先通过gets_s读取输入,接着调用sub_402100解析为BigNum

随后开始循环检查字符,这说明程序要求解析结果有11个32位limb,也就是44个字节。

然后程序从最后一个逻辑limb开始倒序读取,每个limb按大端字节顺序写入
再对44个字节xor,下面的SIMD指令是编译器优化的产物,等价于bytes[0] ^ bytes[1] ^ ... ^ bytes[43]

最后判断结果是否为-113,不是的话直接fail。
然后是一段滚动校验,可以理解为:
v59 = 0;
for (int i = 0; i < 44; i++) {
v59 += bytes[i] * ((v59 & 0x7F) + 1);
}
if (v59 != 0xBEFF)
fail;
两个校验都完成后,调用sub_403500进行RSA(开头提示加载PDB信息,不嘻嘻),分析RSA,可以拿到:
n = 0x8C91CB79EC693F3ED6519C945DD67371
e = 0x10001
丢factordb直接分解出来:
p = 13636154180376482939
q = 13702465297157554691
计算得到
d = 0x721A022D849DE5CC5AD9BBB701F9FB65
然后回到main,对44个字节逐个查表,然后和目标字符串比较

编写IDA脚本
import ida_bytes
u32 = ida_bytes.get_dword
u8 = ida_bytes.get_byte
base = 0x4263B0
offsets = 0x4163B0
target = b"Welcome to KCTF2026! Come and give it a try."
table = {}
for x in range(1, 256):
table[x] = u8(base + u32(offsets + 4 * (x - 1)))
inverse = {}
for x, value in table.items():
inverse.setdefault(value, []).append(x)
preimages = [inverse.get(value, []) for value in target]
assert all(len(values) == 1 for values in preimages)
candidate = bytes(values[0] for values in preimages)
print("candidate =", candidate.hex().upper())
print("prefix28 =", candidate[:28].hex().upper())
print("cipher16 =", candidate[28:].hex().upper())输出:
prefix28 = 323C47184B0D3C44254B445842552F365C362C1144424B0D3C441643
cipher16 = 0F4439374E3C44372544164425151D1B
编写计算脚本
p = 13636154180376482939
q = 13702465297157554691
d = 0x721A022D849DE5CC5AD9BBB701F9FB65
prefix = "323C47184B0D3C44254B445842552F365C362C1144424B0D3C441643"
cipher = "0F4439374E3C44372544164425151D1B"
n = p * q
suffix = f"{pow(int(cipher, 16), d, n):032X}"
flag = prefix + suffix
print(flag)Flag为:323C47184B0D3C44254B445842552F365C362C1144424B0D3C4416433B0DD6B12A0D3D95FA65B5E0ADE5E11B