-
-
[原创]KCTF2025 day3 wp
-
发表于: 2025-8-19 22:39 4911
-
以下分析由于是做完题后进行的, 部分符号已经手动恢复
程序为32位程序, 核心加密和校验在0x0402380.
由于题目不用写注册机, 只需要求name为KCTF时的序列号, 所以有关name的处理全部无视, 根据格式化字符串可以定位到在这里进行了序列号的初步处理:

以十六进制字符串形式将对应数据读取到内存后先进行了一个反转操作, 然后进入以下函数进行了按30bit进行分割:

有python逆向经验的都可以察觉到这和python底层中Py_Long类型储存大数字的形式是一样的, 结合下面某个函数中的报错:

应该是将序列号作为一个大数字利进行了一些运算, 对于这些处理大数字的函数最好的分析方法就是调试猜测功能.
下面的处理还有几百行伪代码, 反正最后解密也要从尾开始, 接下来将从校验开始向上开始分析加密流程.
在最后校验处下断点, 修改用户名可以发现只有密文发生变化, 也就是说整个加密流程由序列号单独完成:

并且最后一步加密是反转然后第一个_DWORD自增, 因为最后的密文第一个字节固定是b'J', 这里直接当是第一个字节自增就行了.
继续向上查找加密结果的来源, 在from_bignum上下断点, 最后的密文明显来自第二个参数:

结合上面对序列号初步处理的分析, 应该是将一个大数转化为字节串, 用以下函数来验证(由于程序用到多种大数, 这里给出我用到的几种转化):

成功验证猜想.
接下来就是通过调试和拷打AI不断还原这个大数是通过什么运算得到的, 最终恢复了3个最重要的符号:

分别是大数相乘, 取模, 求模逆操作, 用伪代码来表示这个阶段的加密逻辑就是:
其中key是上阶段加密的结果, 并且所有相乘都化简对应的乘方, c是一个函数开头初始化的常数0x56f67550f16a00390dcf0b2715708e61c5b3f23101862fc1, 接下来就是拷打AI怎么解出上一阶段的key(不然呢, 我又不是密码手).

这里放出怎么得到的37次方是方便后面还要解类似问题时能快速上手使用解密函数, 根据c可以分解为两个质因数, AI给出了以下通用解法:
参数的含义分别是: 最后取模的结果, 模数, 模数的第一个质因数, 第二个质因数, k次方
上facordb分解一下c就能得到两个常数, 对于上面37次方的解法就是:
求模逆是一个可逆操作, 再对c做一次就能求回来, 然后solve函数求解得到上一阶段的结果key.
这里验证结果的方法是找到一开始通过字节串转化为大数的地方将字节串替换为求出的大数的小端序字节串, 然后让程序运行到最后校验处看看是否匹配密文.
现在基本上可以确定序列号就是作为大数不断进行运算得到最终结果, 实际上并没有针对序列号字节串的加密, 但是在最后一个阶段恢复的符号前面并没有任何一处调用, 猜测题目可能使用了多种数论库进行大数运算.
而中间常见的自增1再反转的操作可以通过调试来分析实际对一个大数的影响, 这里以倒数第二阶段到上面那个阶段为例探索这个操作的实际效果, 倒数第二阶段用到的是bignum1at:

可以看到实际上就是最高一个_DWORD自增, 那么下面就不探索大数和字节串之间的转化了, 只会越绕越晕, 只把它当成一个数处理就行.
倒数第二阶段也能恢复出乘, 取模, 模逆的符号:

运算对应的伪代码:
也能转化为和最后一个阶段一样的问题, 能用一样的解法, 这里对应的是10 * 3 + 1 = 31次
接下来继续向上分析也是一样的方法, 分析到倒数第三阶段还是一样的加密方式, 这时候已经开始不需要每次都验证每个函数的作用了, 因为上面说了程序用到了多种数论库(也可能是一个库的同样效果不同实现的函数?), 每一阶段都有自己的乘, 取模, 模逆, 这里直接通过这个加密模式来猜测每一个函数对应的运算即可, 其中有几个函数IDA识别参数有问题手动调一下参数个数能分别结果放在哪个参数即可, 最终一共是有9个阶段, 全部是同类型加密, 解密代码:
from idc import get_bytesdef origin_data2bignum(data): bignum = 0 for i in range(len(data)): r = data[i] & 0x3FFFFFFF bignum |= r << (30 * i) return bignumdef bignumat(addr): digtsLen = int.from_bytes(get_bytes(addr, 4), 'little') if digtsLen >= 0x1000: return 0 digtsAddr = addr + 4 data = [] for i in range(digtsLen): data.append(int.from_bytes(get_bytes(digtsAddr + i * 4, 4), 'little')) return origin_data2bignum(data)def bignum1at(addr): digitsAddr = int.from_bytes(get_bytes(addr, 4), 'little') digitsLen = int.from_bytes(get_bytes(addr + 4, 4), 'little') data = get_bytes(digitsAddr, digitsLen * 4) return int.from_bytes(data, 'little')def bignum3at(addr): digtsLen = int.from_bytes(get_bytes(addr, 4), 'little') digtsAddr = int.from_bytes(get_bytes(addr + 0xc, 4), 'little') data = [] for i in range(digtsLen): data.append(int.from_bytes(get_bytes(digtsAddr + i * 4, 4), 'little')) return origin_data2bignum(data)from idc import get_bytesdef origin_data2bignum(data): bignum = 0 for i in range(len(data)): r = data[i] & 0x3FFFFFFF bignum |= r << (30 * i) return bignumdef bignumat(addr): digtsLen = int.from_bytes(get_bytes(addr, 4), 'little') if digtsLen >= 0x1000: return 0 digtsAddr = addr + 4 data = [] for i in range(digtsLen): data.append(int.from_bytes(get_bytes(digtsAddr + i * 4, 4), 'little')) return origin_data2bignum(data)def bignum1at(addr): digitsAddr = int.from_bytes(get_bytes(addr, 4), 'little') digitsLen = int.from_bytes(get_bytes(addr + 4, 4), 'little') data = get_bytes(digitsAddr, digitsLen * 4) return int.from_bytes(data, 'little')def bignum3at(addr): digtsLen = int.from_bytes(get_bytes(addr, 4), 'little') digtsAddr = int.from_bytes(get_bytes(addr + 0xc, 4), 'little') data = [] for i in range(digtsLen): data.append(int.from_bytes(get_bytes(digtsAddr + i * 4, 4), 'little')) return origin_data2bignum(data)from sympy import mod_inversedef solve(result, c, p, q, k): d_p = mod_inverse(k, p-1) d_q = mod_inverse(k, q-1) x_p = pow(result, d_p, p) x_q = pow(result, d_q, q) n = c n1, n2 = q, p m1, m2 = mod_inverse(n1, p), mod_inverse(n2, q) code = (x_p * n1 * m1 + x_q * n2 * m2) % n return codefrom sympy import mod_inversedef solve(result, c, p, q, k): d_p = mod_inverse(k, p-1) d_q = mod_inverse(k, q-1) x_p = pow(result, d_p, p) x_q = pow(result, d_q, q) n = c n1, n2 = q, p m1, m2 = mod_inverse(n1, p), mod_inverse(n2, q) code = (x_p * n1 * m1 + x_q * n2 * m2) % n return codec = 0x56f67550f16a00390dcf0b2715708e61c5b3f23101862fc1p1 = 45424490472579293708671645907p2 = 46942075831425428541187578011enc = inverse(enc, c)keys = solve(enc, c, p1, p2, 37)c = 0x56f67550f16a00390dcf0b2715708e61c5b3f23101862fc1p1 = 45424490472579293708671645907p2 = 46942075831425428541187578011enc = inverse(enc, c)keys = solve(enc, c, p1, p2, 37)from Crypto.Util.number import inversec = 0x56f67550f16a00390dcf0b2715708e61c5b3f23101862fc1p1 = 45424490472579293708671645907p2 = 46942075831425428541187578011enc = [0x4B, 0x43, 0x54, 0x46, 0x32, 0x30, 0x32, 0x35, 0x7A, 0x1A, 0xB1, 0xC6, 0xA2, 0x99, 0x9F, 0x97, 0x97, 0xF5, 0xAB, 0xD5, 0xB4, 0x9F, 0xD9, 0xA0]def b2hex(b): print(' '.join([f"{x:02x}" for x in b]))def n2hex(n): n = n.to_bytes(24, 'little') b2hex(n)from sympy import mod_inversedef solve(result, c, p, q, k): d_p = mod_inverse(k, p-1) d_q = mod_inverse(k, q-1) x_p = pow(result, d_p, p) x_q = pow(result, d_q, q) n = c n1, n2 = q, p m1, m2 = mod_inverse(n1, p), mod_inverse(n2, q) code = (x_p * n1 * m1 + x_q * n2 * m2) % n return codeenc[0] -= 1enc = enc[::-1]enc = int.from_bytes(enc, 'little')enc = inverse(enc, c)keys = solve(enc, c, p1, p2, 37)print("found: ", hex(keys))enc = bytearray(keys.to_bytes(24, 'big'))enc[0] -= 1enc = int.from_bytes(enc, 'big')enc = inverse(enc, c)code = solve(enc, c, p1, p2, 31)print("found: ", hex(code))enc = bytearray(code.to_bytes(24, 'big'))enc[0] -= 1enc = int.from_bytes(enc, 'big')enc = inverse(enc, c)ser = solve(enc, c, p1, p2, 29)print("found: ", hex(ser))enc = bytearray(ser.to_bytes(24, 'big'))enc[0] -= 1enc = int.from_bytes(enc, 'big')enc = inverse(enc, c)enc = solve(enc, c, p1, p2, 23)print("found: ", hex(enc))n2hex(enc)enc = bytearray(enc.to_bytes(24, 'big'))enc[0] -= 1enc = int.from_bytes(enc, 'big')enc = inverse(enc, c)enc = solve(enc, c, p1, p2, 19)print("found: ", hex(enc))enc = bytearray(enc.to_bytes(24, 'big'))enc[0] -= 1enc = int.from_bytes(enc, 'big')enc = inverse(enc, c)enc = solve(enc, c, p1, p2, 17)print("found: ", hex(enc))enc = bytearray(enc.to_bytes(24, 'big'))enc[0] -= 1enc = int.from_bytes(enc, 'big')enc = inverse(enc, c)enc = solve(enc, c, p1, p2, 11)print("found: ", hex(enc))enc = bytearray(enc.to_bytes(24, 'big'))enc[0] -= 1enc = int.from_bytes(enc, 'big')enc = inverse(enc, c)enc = solve(enc, c, p1, p2, 7)print("found: ", hex(enc))enc = bytearray(enc.to_bytes(24, 'big'))enc[0] -= 1enc = int.from_bytes(enc, 'big')enc = inverse(enc, c)enc = solve(enc, c, p1, p2, 3)print("found: ", hex(enc)[2:].upper())from Crypto.Util.number import inversec = 0x56f67550f16a00390dcf0b2715708e61c5b3f23101862fc1p1 = 45424490472579293708671645907赞赏
- [原创]KCTF2025 day10 wp 487
- [原创]KCTF2025 day9 wp 4809
- [原创]KCTF2025 day8 wp 4781
- [原创]KCTF2025 day7 wp 4587
- [原创]KCTF2025 day6 wp 4678