文章首发于安全客
去年去北京打的bytesctf线下,质量还是挺高的。ak很爽
impersonation
Alice:
1 | from KeyExchange import KeyExchange |
中间人攻击
场景大概是Alice想与Bob通信,可以通过本地起一个socket当服务器,给他自己的ip, port后再连接Bob即可拦截与修改信息,进行中间人攻击。很有趣的背景,这个场景是有一定现实意义的,由于网站都有证书,所以Alice作为一个用户往往能够判断对方的公钥正确性,而服务器却不行,而若密钥协商协议实现有问题,则很有可能仍然有安全隐患。
Bob的代码:
1 | from KeyExchange import KeyExchange |
KeyExchange是一个修改过的ecdh,交换后Alice通过OTP发送flag,Bob再通过Alice的公钥发回。
主要的困难(与教科书最基本的中间人攻击的区别)是Alice知道Bob的公钥,无法给她自己的公钥来实现转发。
而可能的漏洞有三个:
- Bob不知道Alice的公钥
- Alice有对flag的decrypt功能,并且有回显
- Alice与Bob均不对点进行检查
对于第二个漏洞,需要能找到一个改flag一位的地方,是初赛中类似题利用的漏洞。但是这次并没有这样的漏洞,加密时用与一个随机数异或,没办法确定对或者错对应的flag位的值。
因此先考虑第一点与第三点的利用
KeyExchange的流程是
- Alice -> Bob : hash(ra * G) * AlicePublickey + ra * G
- Bob -> Alice : hash(rb * G) * BobPublickey + rb * G
1 | (hash(rb * G) * BobPrivatekey + rb) * (hash(ra * G) * AlicePublickey + ra * G) |
而这个共享密钥是通过这个点经过SM3得到,两个不同的点得到的密钥之间没有任何关系
因此在消息转发时要让Alice与Bob计算的共享密钥需相同,并且由于Alice的密钥每次更换,若不知道给Bob发送的公钥对应的私钥,也不可能解密成功。
我们可控的点为:
- 给Alice的Bob rb
- 给Bob的Alice ra
- 给Bob的Alice publickey
因为Bob的公钥无法改变,给alice发送的消息只有rb可控,但由于还要进行哈希计算,所以难以控制,所以尝试直接将Bob发送的rb转发,试图通过给Bob的ra与publickey来实现相等
则目标就是
1 | hash(ra * G) * AlicePublickey + ra * G = hash(R) * Publickey + R |
(以下为我在比赛时第一时间的想法)
将左边看作一个整体点P
若先选取R,则
1 | Publickey = (P - R) * invert(hash(R) , n) = xG |
但无法知道x的值,则最后一步Bob发送的flag无法解密。
若先选取Publickey,几乎没法确定R。
所以想到找低阶点,若Publickey的阶低,则x可以通过爆破得到,同时,也可以通过爆破来确定R。
1 | R = P - i * Publickey |
如果hash(R) % order = i,则能得到满足条件的R。
对于选取的一个低阶点,计算order次,碰撞不成功的概率为(1 - 1/order)^order > 1/e = 0.37
所以大概成功率会大于60%
但SM2使用的曲线安全性很高,阶为素数,无法在这条曲线上找低阶点。
但是Bob并不检查点的合法性,可以寻找不在曲线上的低阶点
寻找方法为将b+1,计算一个点g的阶,若阶order有小质因子k,则计算(order // k) * g 则为想要的k阶点
这样,虽然是两个不在一条曲线上的点运算,但是它也一定能算出一个结果R,只不过这个结果R在第三条曲线上,但这里的R仅仅只用到了这一次,因此不会有其他麻烦。
而Publickey还会用于下面对flag的加密,用不在曲线上的点仍能够实现加密,但是解密时就得不到k* Publickey了
原本解密时,通过privatekeyC1可以得到加密使用的点,但因为我们的Publickey根本就不是从G得到的,因此无法直接得到加密使用的点。但由于Publickey的阶并不大,k Publickey的可能值仅有order个,爆破order次,即可从中找到明文。
exp:
1 | import socketserver |
但这种思路看着太乱了,完全不按题目正常的流程走,比赛时打了一整页草稿才写出来。 但是赛后跟V师傅交流了一下,发现上面有一步出了问题 当先任意选取R时,
1 | Publickey = (P - R) * invert(hash(R) , n) = xG |
这条式子其实是可以算出x的 最初认为P – R是一个未知的值,但是完全可以取R = P – G 则x = invert(hash(R) , n) 接着直接解密即可。
agid
1 | import agid |
题目中用的gmssl并不是pip直接下载的那个,需要去github找另一个
题目是一个认证系统,如果有identity即可多得到两个数据
上面check_auth为正常的认证流程
我们需要做的是没有identity的情况下认证成功
看上去需要给12个变量,满足5个方程。十分复杂。
但实际上全写出来,真的很简单
1 | eq1_left = ate.pairing(U2, U1) |
虽然有双线性对,一开始还以为需要用到双线性对的限制,但是因为懒,就直接U2 = G2 , U1 = G1就完事了
1 | eq2_left = ate.pairing(S2, S1) |
第二个同理,S2 = V , S1 = G1
下面的则是要求
1 | T1 = (s1 + s2*sm + h*s3)G1 - c * U3 |
由于预先不知道c的值,所以在T的构造里必须把c全消掉。
点为0可能出现一些神秘问题(上一道题是这样的,这题库不一样,也不知道会不会)。但是不为0完全也能构造
首先T3处,s未知,需要用s2 * Qpub 得到 s * s2 * G1,取s1 = c抵消c。s2可任意
因此T3 = s2 * Ppub
T2处方便起见U3 = G1,s3 * h G1 用 s3 H得到,同样让s4 = c抵消c,s3可任意
因此T2 = s3 * H
T1处几个参数已固定
T1 = s3 * H + s2 * Qpub
然后任意取值即可
exp中任意参数全取了1
1 | import gmssl.optimized_curve as ec |