2023.1.30,退役篇,最后随缘更新 20 题了
# 0x0B from rec matrix_e 9.13
附件
1 | #!/usr/bin/env sage |
总能从 rec 大佬的题里学到很多!
题目分为三部分,都是涉及到矩阵乘法,一部分一部分分别看吧
第一部分,32 长度的向量 m 乘上 32*32 的矩阵 M 得到 32 长度的向量 c
没啥说法,直接矩阵求逆即可
1 | #chall1 |
第二部分,32 长度的向量 m 乘上 32*31 的矩阵 M 得到 31 长度的向量 c
只差了一位,可以直接爆破向量 m 的最后一位,在 (0,256) 的范围,之后将这一位所作的运算减到向量 c 里面,再对 31*31 的矩阵求逆,用得到的向量 m 是否都为 (0,256) 来进行判断即可
1 | #chall2 |
第三部分,32 长度的向量 m 乘上 32*16 的矩阵 M 得到 16 长度的向量 c
有点像背包算法,将等式写出可以发现,m 的范围在 (0,256),p 和 c 已知,m 很小,构造 49*49 的格子得到
M 为题目给出的 32*16 的矩阵,p 放置斜对角,c 放最后一行,这样右边可以得到 0,格基规约后就可以得到我们想要的向量
1 | data = {"p": p, "M": M.list(), "c": c.list()} |
格基规约第一行就是向量
最后按照题目逻辑 sha512 完了异或即可
1 | import hashlib |
# 0x0A 2023bluehat DHRSA 8.27
附件
1 | # sage |
观察题目,变量都是通过以下函数生成
1 | def gen_DH_key(g,r): |
通过后面的 try 给出的 c,有式子 可以进行尝试构造格子,只需找到一组合适的系数 使得,其中系数 k 有正有负。这样就有,同过格基规约可以找到我们想要的系数,格子如下
1 | from sage.all import * |
得到系数之后,下一步将其放在指数位上,由于没有取模,直接运算的指数中有正有负,得到的就不是一个整数,会得到结果,也就是说,分子分母做差就是 k 倍的 n,得到两组进行 GCD 即可得到我们的模数,也就是我们的 r。
1 | l = len(ee) |
得到 r 之后可以共模攻击得到 g,取两组没有公约数的 e 即可,这样可以得到 g。得到的 r 发现 r-1 光滑,也就是说指数位可解
之后分析 p 和 q 的生成,可以看出
其中 t1-t2
的范围只有三个值 -1,0,1
,这样可以得到三个 C 的可能值,逐个尝试即可,之后按照原逻辑生成即可。完整 exp 为
1 | from sage.all import * |
# 0x09 2023HWS Random 7.15
附件
1 | from os import urandom |
算是我见过最恶心的 AMM 了。
首先要先分解 n,观察 n 的生成函数,可以看到是一个公共的 base,通过这个 base 来生成素数 p,偏差只有 randint(1,500000)
,素数 p 还有一个 randint(1,5)
的次方,综上 n 的分解应该为
尝试直接对 n 开 es 次方,es 为 的乘积,如果 es 是正确的,那得到开方结果的高位应该是 形式。所以直接用一个 table
变量打表出范围内 的所有值,之后进行爆破就可以得到可能满足条件的所有 base
1 | import gmpy2 |
这样就得到了所有可能的 base,之后再对这些 base 进行爆破,得到了正确的 base,也就成功地将 n 进行了分解
1 | base = 47890485652059026823698344598447161988085597568237568 |
得到了 n 的分解之后,观察 e 的生成,都是一些小因数的乘积,肯定会有模不互素。意思是要批量 amm,而且素数都有次方,还得 hensel lift。在 sage 里面发现一个很厉害的有限域开方函数 Zmod(p ** i)(c).nth_root(e,all=True)
,也自带了 hensel lift,这个函数跑出来的结果就是 c 在 的有限域下开 e 次方的列表集合,功能非常强大。用这个函数打这个题也很快了,但是跑一次 flag 得花半个钟头
1 | from Crypto.Util.number import * |
# 0x08 2023ichunqiu cisticola 5.22
附件
1 | #!/usr/bin/env sage |
抽代的内容了,研究的不多,比较粗俗的理解就是不涉及进位的进制,例如
这题最后是用非预期做出来的,非预期的方法也是值得学习的。
首先仔细观察 hint 的生成,poly 的量为待求量,,其中 Q,pos 是已知量,poly 是待求量但是相对模数 来说较小。预期解应该是找关系打格子,可惜才疏学浅。
仔细分析可得,Q 的最高位是系数为 1 的,即后续的 pos 若小于 430,则模除 之后还是原始值,若大于则会发生复杂的取模运算。分析 hint 的组成,在每一项中,运算的优先级可以这样考虑,则括号内的内容就是可以运算的量,hint 的长度为 430,pos 的长度为 18,也就是说有 18 个变量,430 个同余式,解方程就好了。
1 | from Crypto.Cipher import AES |
# 0x07 2023ichunqiu ecdsa 5.22
附件
1 | import os |
用的第三方包 ecdsa
来做的,首先要清楚签名的流程。
观察变量定义的代码
1 | sk = ecdsa.SigningKey.from_secret_exponent( |
说明其中私钥用的是 key
, 曲线用的是固定的曲线 ecdsa.SECP256k1
。其中用 ecdsa 签名的曲线都是相对固定的,对应的参数也都可以找到。以下是一个签名的流程
1 | #sage |
可以看出,自己签名和第三方包签名是一致的,分别解释一下就是
参数 | 含义 |
---|---|
k | 随机整数,用于计算 |
G | 对应曲线中的基点 |
n | 基点 G 的阶 |
d | 私钥 |
z | ,其中 hash 签名算法根据具体的曲线算法而定 |
r,s | 签名的结果 |
签名的流程如下
1. 选择一个随机整数 k,计算出,签名 即为点 的横坐标
2. 计算出,在本题中用的曲线为 SECP256k1
,跳转到源码中查看发现 hash 算法是 sha1
,则得到 z = int(hashlib.sha1(msg1).hexdigest(),16)
3. 计算出
4. 签名值即为
在本题中,两个签名所用的私钥 是一样的,不同的是随机整数,但是差值不会很大,范围是可以爆破的,理清楚同余式的逻辑之后就很好写 exp
1 | import hashlib |
# 0x06 2023miniL curvesignin 5.11
附件
1 | from Crypto.Util.number import * |
首先得通过加法函数推出原曲线,加法函数如下
1 | def add(P,Q): |
可以看出斜率就是 t,当两个相同的点相加时,切线的斜率为 t = ((3*c*P[0]*P[0]+a) * inverse(2*b*P[1],q))%q
,也就是,由此可推出原曲线的格式为,简化计算可令 为 1,其他系数跟着变化即可。
1 | PR.<a,b,d> = PolynomialRing(ZZ) |
这样就求出了各个系数,由于 flag2 是作为加密系数,加密方式为,求 m,可以进行一个映射。
我们得到的曲线为,稍作变换可写成形如 的格式,即
已知的点 P 和点 G 映射到新的曲线上即可,就可以用 sage 中的函数求解系数,完整 exp 如下
1 | from Crypto.Util.number import * |
# 0x05 2023nkctf raven 3.28
附件
1 | #!/usr/bin/env sage |
这个格子算是我目前调过最复杂的格子了,但是原理不太难,理清楚关系即可。
理解 raven 中的函数,观察到函数 f 的函数的系数都是经过 sha256 的,不太可能通过逆向或者是正向爆破得解。可以确定的是 sha256 之后的数都不太大。本地调试的时候可以直接 print(f)
可以看到 f 大概是等于,而其中系数 就是我们要求的量。我们有以下关于 x 和 y 的关系
i 的范围从 0-3 共 4 组,t 为 256 位的素数,由此我们可以构造格 (中间加一列常数 是为了让格子成为一个方阵)
造好中间这个格子之后,LLL 出来第一行就是我们要的变量,通过 5 个系数可以求出最后的 secret,之后便可求得 flag
最后的 exp 为
1 | from Crypto.Util.number import * |
# 0x04 2023nkctf fake_MT 3.28
附件
1 | import random |
题目不难,一直没有在 blog 里记过 mt19937 的题目。
题目一共 4 个不同的挑战,通过 20 次即可拿到 flag。
guess_number_1
是预测后一位随机数, guess_number_2
是预测之前的随机数, guess_number_3
是根据 mt19937 的原理逆向函数预测之前的数, guess_number_4
也是逆向函数。
mt19937 一直有一个很好用的包,https://github.com/kmyk/mersenne-twister-predictor,不建议初学者使用,过于方便不适合在了解原理的阶段使用。难度不大,就直接放 exp 了
1 | from pwn import * |
# 0x03 2022 羊城杯 NovelSystem2 3.23
附件
1 | from Crypto.Util.number import * |
赛后才知道考点是二元 cop,只能说当时被题目唬住了,看到类似于曲线里面的 mul 和 add 函数之后就直接放弃了。
其实这题主要在 psi 里面,,化简得到
,再根据公私钥生成的式子得到
,令 写成等式就是
观察一下数据量的大小,在模 e 的情况下可以用二元 cop 求出其中较小的 k 和 s。其中一直不太了解二元 cop 的原理,一直把它当作普通的 cop 来做,就是比普通一元的 cop 的界更紧。以下是二元 cop 的过程
1 | import itertools |
得到 s 之后后面的内容都很简单了,顺着原来类里面的函数逆回去就好了
1 | import gmpy2 |
# 0x02 2022 羊城杯 linearAlgebra 3.23
附件
1 | from secret import flag |
数据量太大就不放了,复现的话自己生成一个 flag 即可。参考 zima👴的 blog,再膜一下膜一下
这题拿来练手 LLL 挺好的,当时比赛的时候不会格子呜呜呜,回来复现的时候发现挺好做的。
M 是要求的矩阵,pad 函数就是把 flag 数据 pad 成一个矩阵。给了 C 和 Ac,Ac 就是矩阵 A 混淆过后的矩阵。
混淆是用 corrupt
函数,其实就是在 32*32 的矩阵中随机选 32 个数据替换成一个大小差不多的数据。这个随机就很讲究,随机是纯随机,也就是说并不是这个矩阵中每一行都有数据被替换。写个循环找到某一行没有被替换的数据构造矩阵。
其中 m 的数据是特别小的,矩阵中的数据 Ac 是特别大的,可以规约出 m 来。
首先读取矩阵,然后测试 Ac 中哪些行的数据是没有被改过的
1 | import re |
之后再随便以某一行为基准再 LLL 出其他的数据即可
完整 wp 如下
1 | import re |
# 0x01 2022ichunqiu 勇者山峰 bob_enc 3.3
附件
1 | from secret import * |
题目分为两部分,意思很好懂。
第一部分就是正常的矩阵求逆,取 flag 长度就可以
1 | prime = 2141 |
第二部分是一个 lwe 问题,是在第一部分的基础上,在结果上加了噪声,也就是
1 | noise = [random.randint(0,6) for i in range(row)] |
lwe 的原理也不懂,记个板子就好。板子来自 lazzaro
1 | from sage.modules.free_module_integer import IntegerLattice |
# 0x00 2022 赣网杯 lattice 1.30
附件
1 | from Crypto.Util.number import * |
给了生成公钥的函数,其中变量的比特大小需要特别注意。首先是用扩展欧几里得生成了 和,等式为。给了两组,其中 E 是固定的,然后再分别得到 p 和 q,总结已知量如下
其中 E 已知,要求得到 b。注意各个量的大小分别为
a | E | b | k |
---|---|---|---|
400 | 1024 | 1024 | 400 |
参考 zima👴的 blog,化简相当麻烦
首先令,原式变为
代入相乘得到
不做代入化简得到
代入 得到
最后得到式子
之后可以构造格子
观察我们预期规约出的向量,大小分别为
400 | 800 | 800 |
显然这不应该是格中最短向量,我们需要适当增大格中最短向量的界即可,格中第一列中每一个数乘上 即可 (不唯一,只要能规约出向量即可)。之后可以利用已知的 和 求得,后续也就分别得到了 n 的分解,完整 exp 为
1 | #sage |