这次 hard 分类也有 5 题,虽然思维方面肯定比 medium 要难些,但也还中规中矩。
Vinefruit 题目描述 题目定义了一个函数 $f$:输入一个消息 $m$,记该消息的每个字节的内容分别为 $m_1, m_2, \ldots, m_l$。$v_0$ 已知,然后满足一个递推关系:
其中 $p$ 已知。根据这个迭代式一直计算,返回 $f(m) = v_l$。
给定 $l$,需要给出 $m, m’$ 使得 $f(m) = f(m’)$,且 $f(m)$ 在之后的关卡中不能和之前已经用过的 $f(m)$ 相等。
我的解答 可以把那个迭代式展开,得:
把 $m’$ 的各项也代入到上式,两式相减,并记 $d_i = m_i - m_i’$,我们便有:
且有 $d_i \in \{-255, -254, \ldots, 255\}$。
那么由于 $d_i$ 较小,这就是经典的格基规约问题了,可构造如下格(把同余式写成等式→把系数非 1 的项都往等号右边丢→写成线性组合):
上面这个公式好像手机 app 端会渲染出问题,用 PC 的 web 端能正常显示,检查了几遍都没发现有语法问题,奇怪奇怪真奇怪
我已经没耐心去调这公式的写法以迎合 app 端的 markdown 解析逻辑了,大家自行前往 web 端看吧
然后规约即可。然后再设 $m_i$ 和 $m_i’$ 的值:
当 $d_i < 0$ 时,设 $m_i = 0, m_i’ = -d_i$
当 $d_i \ge 0$ 时,设 $m_i = d_i, m_i’ = 0$
然后其实,如果能对于最小的 $l$ 有解 $(m, m’)$ 的话,可以直接把更长的解构造为 $(m || 0, m’ || 0)$,这样就能完成剩下的构造,不过注意每一组 $p$ 和 $v_0$ 会在某几组里面挑
规约出结果的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 def my_solve (v0, p ): l = 18 M = [[0 for j in range (l)] for i in range (l)] for i in range (l-1 ): M[i][i] = 1 M[i][l-1 ] = p^(l-i-1 ) M[l-1 ][l-1 ] = 2 ^128 M = Matrix(ZZ, M) v = M.LLL()[0 ] vlist = v.list () vlist[-1 ] = -vlist[-1 ] print (vlist) P = [16777619 , 1099511628211 , 309485009821345068724781371 ] O = [2166136261 , 14695981039346656037 , 144066263297769815596495629667062367629 ] for v0, p in zip (O, P): my_solve(v0, p)
提交答案的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 from pwn import *context.log_level = 'debug' class Gao : def __init__ (self ): self.conn = remote('04.cr.yp.toc.tf' , 31777 ) self.ans = [ [40 , -13 , 23 , 37 , -8 , -10 , -81 , -14 , 17 , 8 , 44 , -39 , -23 , 2 , 3 , -28 , 85 , -11 ], [-20 , 18 , 31 , -36 , -35 , -53 , 19 , 6 , -37 , -56 , 14 , 79 , -29 , 17 , 0 , -2 , -69 , 29 ], [-2 , -6 , 31 , 20 , 24 , -8 , -49 , 36 , 50 , 2 , 120 , -22 , -18 , -11 , -17 , 17 , 57 , 40 ] ] def gao_one (self, l ): self.conn.recvuntil('| Submit two different string such that ' ) s = self.conn.recvline().decode()[14 ] problem_id = int (s) m1, m2 = [0 ] * l, [0 ] * l print (f'{problem_id = } ' ) for i, di in enumerate (self.ans[problem_id]): if (di < 0 ): m1[i], m2[i] = 0 , -di else : m1[i], m2[i] = di, 0 self.conn.sendline(bytes (m1).hex ()) self.conn.sendline(bytes (m2).hex ()) def gao (self ): self.conn.sendline('S' ) for level in range (18 ): self.gao_one(35 - level) self.conn.interactive() if __name__ == '__main__' : g = Gao() g.gao()
得到 flag 为:
1 CCTF{fINd1n9_cOlL!s10n_f0r___FNV2___!}
Shevid 题目描述 题目基于 SIDH,其中椭圆曲线为 $E_0: y^2 = x^3 + 6x^2 + x$,SIDH 素数为 $p=2^a 3^b - 1$。
并且知道 $E_0[2^a]$ 中的生成元 $P_0, Q_0$、$E_0[3^b]$ 中的生成元 $R_0, S_0$、isogeny $\varphi$ 的 codomain $E/\mathbb{F}_{p^2}$、生成元 $P=\varphi(P_0), Q=\varphi(Q_0)$
试破解 Bob 的私钥。
我的解答 众所周知,去年 Castryck 和 Decru 弄出了个大新闻,把 SIDH 给破解了。
因为我对 SIDH 相关一窍不通,所以直接找到对应的仓库地址并进行一个 git clone
:
https://github.com/GiacomoPope/Castryck-Decru-SageMath
开源真是好文明,赞美开源
在这期间其实先找了另一个 repo:
https://github.com/Breaking-SIDH/direct-attack
这个攻击好像是今年发表的,但是实际上并跑不起来,会报个错:
1 2 3 4 5 6 File "/xxx/yyy/zzz/direct-attack/xonly.py", line 106, in <listcomp> return ntl.ZZ_pEX([ntl.ZZ_pE(c.list(), self.ctx) for c in poly]) File "sage/structure/element.pyx", line 494, in sage.structure.element.Element.__getattr__ (build/cythonized/sage/structure/element.c:4831) File "sage/structure/element.pyx", line 507, in sage.structure.element.Element.getattr_from_category (build/cythonized/sage/structure/element.c:4943) File "sage/cpython/getattr.pyx", line 361, in sage.cpython.getattr.getattr_from_other_class (build/cythonized/sage/cpython/getattr.c:2702) AttributeError: 'sage.rings.finite_rings.element_pari_ffelt.FiniteFieldElement_pari_ffelt' object has no attribute 'list'
不是很懂……
然后就是把题目的数据给接上就行了。因为他那个 attack 函数实际上会有一些全局变量的传入,譬如 $P_3, Q_3, a, b$ 等,所以最好不要去动他 repo 里面的一些命名:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 Local imports import public_values_auxfrom public_values_aux import *load('castryck_decru_shortcut.sage' ) p = 4651852759096127491733667803074539573102288457512521355046899661762757394431 a = 142 b = 69 Fp2.<x> = GF(p^2 , modulus=x^2 +1 ) E_start = EllipticCurve(Fp2, [0 ,6 ,0 ,1 ,0 ]) E_start.set_order((p+1 )^2 ) two_i = generate_distortion_map(E_start) P = (3799366067639160994711391413511701264777392349807255916259320256251336249666 *x + 633884628131689177031068067897867565283026098415356709331881575255526844055 , 3967348484864888240941807454988077684669074109524399477973520952727771366997 *x + 2712354532382043232096058211997452093712477916671679907467703464009558475387 ) Q = (560486392227142181240528415381730657098132581407794833013161975335122628946 *x + 3215971074127995409351672272900519761566156375365764079386358523254177565572 , 2231746347912007096335360835242707156884468521076802738444724241125548841199 *x + 1147378568798166954853288670809304301194478550602719730593160186622788033023 ) R = (2656280316115888204975052029900945854050191685154131031859911335618240136443 *x + 1127449111197960889758916770042950976852995868310602942974825430779982061546 , 3477289737920472771668877366806058236254602770820629911886593813749930497839 *x + 4646016633241840360901490323351236375375727836768954121794139000985805816564 ) S = (2403139149412141532587482679318245468078604585804423116505024414375742701912 *x + 2772488504607240668919423445309052101443116827322741849326656561794480962717 , 3356599382898540527962106232860239304405841217130774924490318752448476310798 *x + 2818004736373436361527915593539097434287090434842750370886675729711731978922 ) P, Q, R, S = map (E_start, (P, Q, R, S)) _phi_dom = EllipticCurve(Fp2, [ 0 , 6 , 0 , (2070374075904221348548347954672740119972290047985052548426161483408084160515 *x+896749506444795652787374405713981306103783874504413776724865996633074195878 ), (2497300913991521538985990865799426081199023429830552981773916386651958830387 *x+4243320791854592301388975795466391442631117041175807728844738724691845270777 ) ]) _phi_dom.set_order((p+1 )**2 , num_checks=0 ) _phi_P = (76437828586489590038329193939006186966443918785230388533883401536928551274 *x + 1854701339851606878866613257086348330205980107370562791121360193846610915298 , 3614996124089236146025194675467338095830005859290616536023140003816221458491 *x + 1308394538776387115295908857102539180825411698539070801598965381200026966383 ) _phi_Q = (2350346337927935568113772636838467488287314266120334638991371449749383548230 *x + 3389994457403704259291228848441313337916860864318549296438418403582347527289 , 3514523396038039657181009561828298688334341559779027220238590888980836781356 *x + 1132784636339236588815425424619354807485262558088269015122405460656452137103 ) _phi_P, _phi_Q = map (_phi_dom, (_phi_P, _phi_Q)) P2, Q2, P3, Q3 = P, Q, R, S EB, PB, QB = _phi_dom, _phi_P, _phi_Q def RunAttack (num_cores ): return CastryckDecruAttack(E_start, P2, Q2, EB, PB, QB, two_i, num_cores=num_cores) if __name__ == '__main__' and '__file__' in globals (): if '--parallel' in sys.argv: num_cores = os.cpu_count() print (f"Performing the attack in parallel using {num_cores} cores" ) else : num_cores = 1 recovered_key = RunAttack(num_cores)
就这样就可以弄出 Bob 的私钥。然后就是朴实无华的解密:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from Crypto.Util.number import *from Crypto.Cipher import AESfrom hashlib import md5skey = 35588822058809282635150734794357 enc = '50d8ed352ccf3ce6d64b25e50ed67b832dcbcd94dcb7dc8293e813e0c83ace541abb61618d5f971ff5d24fab8a2e' enc = bytes .fromhex(enc) key = md5(long_to_bytes(skey)).digest() iv = md5(str (skey).encode()).digest() cipher = AES.new(key, AES.MODE_CFB, iv=iv) msg = cipher.decrypt(enc) print (msg)
得到 flag 为:
1 CCTF{3FfiC!En7_k3Y_R3c0vErY_4tTacK_ON_SIDH!!!}
找到了个 MSR 出品的介绍 SIDH 怎么玩的文章:
https://eprint.iacr.org/2019/1321.pdf
有空去补一下