[Crypto] copperstudy - CirQ xp0int Posted on May 27 2019 一系列的基于 coppersmith attack 的RSA问题,让人不禁联想到去年强网杯也是出了连续10道RSA的操作,算是综合性题目,去年的出题人说的也是,出题思路是把市面上有的rsa攻击都考一遍,这次就是更具体了一点,把现有的coppersmith攻击技术都考了一遍,这些类型在[ctfwiki](https://ctf-wiki.github.io/ctf-wiki/crypto/asymmetric/rsa/rsa_coppersmith_attack/)上都能找到,把它们实践一遍,也算是对得起study这个名字了。 POW完进入题目后,要求的是输入token,后面生成的题目每次都是相同的,所以猜测是根据token生成一系列伪随机数,但不管怎样,对我们来说题目就是固定的了,所以可以直接用独立的sage脚本去解出答案,直接发过去就行了。 ### 第一关 已知明文的高位,要得到后面72位,典型的Known High Bits Message Attack ``` n = 0x1e4c23321ee5005085faa926e98104dfb1ce0070a1c423e86909f7e2e822ef331c0610aaeb1fd790a950e4786d69572d76713abe28b2554605c150b186b034d0f337a1cee0759bd023915788c619bde453ab5c02eaff5ef429663d335675cb6f09db2297aa03fcfd176ee4c8eca57e4489a8177ac8556e50990dfda65df85157 e = 3 m = randrange(n) c = pow(m, e, n) beta = 1 epsilon = beta^2/7 nbits = n.nbits() kbits = floor(nbits*(beta^2/e-epsilon)) mbar = 0x48ec711a1b4994739e0a365949d2b455e9a30390c5285897b95f7d3974340d7be798c7325727bd78cc51b8dfe035abd9228405cfe0b70c000000000000000000 c = 0xe16b1bc6933849c47e5bd738f240070038626409ad1b8a2babf7ee07ef59fe805b212c4acf5e1d741e4a2b6dd37f591d0a39e0532e3ae52edc5371f84f95d97fd21875f8b5972ea6f19ad91bdb52a25a14b2da1e399494520a9fffb4227472fe7ee1c35fe6146df38b75770be6a9273e6f45bd9e0ec445dba1b719732f8957c print "upper %d bits (of %d bits) is given" % (nbits-kbits, nbits) PR.<x> = PolynomialRing(Zmod(n)) f = (mbar + x)^e - c print m x0 = f.small_roots(X=2^kbits, beta=1)[0] # find root < 2^kbits with factor = n print mbar + x0 print x0 ``` ### 第二关 已知一个质数的高位,可以分解大整数,Factoring with High Bits Known ``` n = 0x522200507383701f2548b767b6f6f32f72e09cf357a14464a0683b992034b3e404e6edc824366570c6454ae73205b18923866415734454d94133fb417843c4d1f12f24c6f40749f8ff694dd2ca3b6d4eb11eee35306862589565e7fe253a428d4214b5daa602c2fc775f17ce39569f2c4f5277e34cfd0be0e411e2f34ebbeef p4 = 0xb5b7af661beb9284150f6b7fc7d87bb4fab25ebfbfa87395396c17ee2939174502a12aa31286b35e48435b8abf6a225700000000000000000000000000000000 cipher = 0x267e90249177b4f61f45271e8d461afe817affab2029026f0ce5a0804d2ce91d9240220f5f3f4a47fb7940e4228a3f8250d56ce987763ea47671aa7167d7e1621825e1a46de0e0c47cb1b859834a1c87155e76256027bd114f50a350d30ce8c24c8b822268f87c846d80f2bc83f74dfee62626449c5cc86ea1069e8cfd04a8b e2 = 0x10001 pbits = 512 kbits = pbits - p4.nbits() p4 = p4 << kbits PR.<x> = PolynomialRing(Zmod(n)) f = x + p4 roots = f.small_roots(X=2^128, beta=0.4) if roots: p = p4+int(roots[0]) print "p: ", hex(int(p)) assert n % p == 0 q = n/int(p) print "q: ", hex(int(q)) phin = (p-1)*(q-1) d = inverse_mod(e2,phin) m = pow(cipher,d,n) print m ``` ### 第三关 已知解密指数d的一半信息,可以分解出两个质数,partial key recovery ``` def partial_p(p0, kbits, n): PR.<x> = PolynomialRing(Zmod(n)) nbits = n.nbits() f = 2^kbits*x + p0 f = f.monic() roots = f.small_roots(X=2^(nbits//2-kbits), beta=0.3) # find root < 2^(nbits//2-kbits) with factor >= n^0.3 if roots: x0 = roots[0] p = gcd(2^kbits*x0 + p0, n) return ZZ(p) def find_p(d0, kbits, e, n): pq = [] P = var('P') for k in xrange(1, e+1): results = solve_mod([e*d0*P - k*(n-P+1)*P + k*n == P], 2^kbits) for x in results: p0 = ZZ(x[0]) p = partial_p(p0, kbits, n) if p: pq.append(p) return pq n = 0x3418685d14e094fc651aba43efc8d4a0cac593fb2f66ba21385c7570b995587074a0da2a983f8d1312e01577b6825e156bbe7655c4dbc68de84e66af8a8f7c3e99f5e2b362bb91f2fc641032d0999578d96434f345f109613dfba7ede562bfc51927ffb685fc4ae55b975a3d92f1c1bd77cec8d0cf29a2c051d8c34391a8233d e = 3 d0 = 0x77533a5407a1d65b5957239efd786da3a165926fa7a89f32918939861c4056b840edce04480bce23bd2a703f0cf49feb1cb3b9c385936cb03561cdc3d5cd8993 p, q = find_p(d0, 511, e, n) c = 0x2719982c2960a610151f02510c3ff99efe8cdc25deb9508113edc350d27dddcb1762fbfb7fe6a769212da3556c3b8ade9451b29eff27e1c667c8f093fa0efaa51c04a0042a9de45a5fe66cd29ea9260720dace054932742f0796877722026fd85559feaaac31b5e438e074ddc9431831298f4f62075b90d43a2dc580a61ff6c7 d = inverse_mod(e, (p-1)*(q-1)) m = pow(c, d, n) print m ``` ### 第四关 广播攻击,用中国剩余定理解 ``` import gmpy import gmpy2 def modinv(a, m): return int(gmpy2.invert(a, m)) def chinese_remainder(n, a): sum = 0 prod = reduce(lambda a, b: a * b, n) for n_i, a_i in zip(n, a): p = prod / n_i sum += a_i * modinv(p, n_i) * p me = int(sum % prod) return gmpy.mpz(me).root(len(n))[0] ns = [ 60251109231613677370939813609730977000030074916024918223136126157970436255733754961165565830072305110638595339006958686139705038872676776489457725496212432148240971246042954683443565313116825446888077209186367148546261344739812120886035439872164705670693721330574744081336264037182569526587173996696257059309, 77360389049420368329122014562169473553938163275998543639621737038888595873291513978674152052501226088257772960485303815176910088771837268603963441943772531562652176962107314194475805909183621124477032161686570902300625031459337451809355267774916893154019881221059367301486777190814773476175208914261214798991, 68107100051937608458855487732080682226492944409297809789875105937604953174654455622786585340048308931417619045989411204696017590331536956634968095599954589991201885486142901279014679062266708195782407691885325397556625756371761723438488626288085462081701893161235167217289374509429707770690263506953160482477 ] cs = [ 5794833766692263375172235703507067551154301344528604172672336082112509253935499740442315069155967387859977585850252763036329790249766926402382130657297073136029631799036480187068204024292582885526291766245024527485711161345557192959439453988085600185983500112983412591893005001275020587637520034208841200523, 38820571856653658290125035563999172375428306113014105748954010329811980484911668467967689232322555566009462951299704084229578417422797556505483075401210408862662849709052788166338013273783181783847662629651138275178821337103493234049335492786025088547795148609647173127646853309123852801619466352994405027640, 50735047650586785075426268390623560328940566081242598797595934655561753138508009016684302510040711851846204822963801962456343470261413333444820834267229809962164966903109318422569122774760692298659706012745262777697367606253163793011035296280038484440402103601839030347232766839384254753226192301620107364097 ] m = chinese_remainder(ns, cs) print m ``` ### 第五关 注意到e很小,相减能够得到一个二次方程,直接求就行了,一个坑是要加上一次N,但总归不用做很多次尝试 ``` import gmpy sn = 79942733742148641433031187047719706021400358968658347092817516062427685456487540160865253643819503734251035977834623609479979911986657256928904629118231082156556176313988888837672903414486129841814226419123415535382069492490120944012033817196908264538648983439078409100890635462040495530272504197667830406806 d = gmpy.mpz((1+4*(sn))).root(2)[0] m = (d-1) / 2 print m ``` ### 第六关 低解密指数,还很贴心的提醒$d\lt N^{0.25}$,用Boneh-Durfee attack ``` """ Setting strict to true will stop the algorithm (and return (-1, -1)) if we don't have a correct upperbound on the determinant. Note that this doesn't necesseraly mean that no solutions will be found since the theoretical upperbound is usualy far away from actual results. That is why you should probably use `strict = False` """ strict = False """ This is experimental, but has provided remarkable results so far. It tries to reduce the lattice as much as it can while keeping its efficiency. I see no reason not to use this option, but if things don't work, you should try disabling it """ helpful_only = True dimension_min = 7 # stop removing if lattice reaches that dimension ############################################ # Functions ########################################## # display stats on helpful vectors def helpful_vectors(BB, modulus): nothelpful = 0 for ii in range(BB.dimensions()[0]): if BB[ii,ii] >= modulus: nothelpful += 1 print nothelpful, "/", BB.dimensions()[0], " vectors are not helpful" # display matrix picture with 0 and X def matrix_overview(BB, bound): for ii in range(BB.dimensions()[0]): a = ('%02d ' % ii) for jj in range(BB.dimensions()[1]): a += '0' if BB[ii,jj] == 0 else 'X' if BB.dimensions()[0] < 60: a += ' ' if BB[ii, ii] >= bound: a += '~' print a # tries to remove unhelpful vectors # we start at current = n-1 (last vector) def remove_unhelpful(BB, monomials, bound, current): # end of our recursive function if current == -1 or BB.dimensions()[0] <= dimension_min: return BB # we start by checking from the end for ii in range(current, -1, -1): # if it is unhelpful: if BB[ii, ii] >= bound: affected_vectors = 0 affected_vector_index = 0 # let's check if it affects other vectors for jj in range(ii + 1, BB.dimensions()[0]): # if another vector is affected: # we increase the count if BB[jj, ii] != 0: affected_vectors += 1 affected_vector_index = jj # level:0 # if no other vectors end up affected # we remove it if affected_vectors == 0: print "* removing unhelpful vector", ii BB = BB.delete_columns([ii]) BB = BB.delete_rows([ii]) monomials.pop(ii) BB = remove_unhelpful(BB, monomials, bound, ii-1) return BB # level:1 # if just one was affected we check # if it is affecting someone else elif affected_vectors == 1: affected_deeper = True for kk in range(affected_vector_index + 1, BB.dimensions()[0]): # if it is affecting even one vector # we give up on this one if BB[kk, affected_vector_index] != 0: affected_deeper = False # remove both it if no other vector was affected and # this helpful vector is not helpful enough # compared to our unhelpful one if affected_deeper and abs(bound - BB[affected_vector_index, affected_vector_index]) < abs(bound - BB[ii, ii]): print "* removing unhelpful vectors", ii, "and", affected_vector_index BB = BB.delete_columns([affected_vector_index, ii]) BB = BB.delete_rows([affected_vector_index, ii]) monomials.pop(affected_vector_index) monomials.pop(ii) BB = remove_unhelpful(BB, monomials, bound, ii-1) return BB # nothing happened return BB """ Returns: * 0,0 if it fails * -1,-1 if `strict=true`, and determinant doesn't bound * x0,y0 the solutions of `pol` """ def boneh_durfee(pol, modulus, mm, tt, XX, YY): """ Boneh and Durfee revisited by Herrmann and May finds a solution if: * d < N^delta * |x| < e^delta * |y| < e^0.5 whenever delta < 1 - sqrt(2)/2 ~ 0.292 """ # substitution (Herrman and May) PR.<u, x, y> = PolynomialRing(ZZ) Q = PR.quotient(x*y + 1 - u) # u = xy + 1 polZ = Q(pol).lift() UU = XX*YY + 1 # x-shifts gg = [] for kk in range(mm + 1): for ii in range(mm - kk + 1): xshift = x^ii * modulus^(mm - kk) * polZ(u, x, y)^kk gg.append(xshift) gg.sort() # x-shifts list of monomials monomials = [] for polynomial in gg: for monomial in polynomial.monomials(): if monomial not in monomials: monomials.append(monomial) monomials.sort() # y-shifts (selected by Herrman and May) for jj in range(1, tt + 1): for kk in range(floor(mm/tt) * jj, mm + 1): yshift = y^jj * polZ(u, x, y)^kk * modulus^(mm - kk) yshift = Q(yshift).lift() gg.append(yshift) # substitution # y-shifts list of monomials for jj in range(1, tt + 1): for kk in range(floor(mm/tt) * jj, mm + 1): monomials.append(u^kk * y^jj) # construct lattice B nn = len(monomials) BB = Matrix(ZZ, nn) for ii in range(nn): BB[ii, 0] = gg[ii](0, 0, 0) for jj in range(1, ii + 1): if monomials[jj] in gg[ii].monomials(): BB[ii, jj] = gg[ii].monomial_coefficient(monomials[jj]) * monomials[jj](UU,XX,YY) # Prototype to reduce the lattice if helpful_only: # automatically remove BB = remove_unhelpful(BB, monomials, modulus^mm, nn-1) # reset dimension nn = BB.dimensions()[0] if nn == 0: print "failure" return 0,0 # check if determinant is correctly bounded det = BB.det() bound = modulus^(mm*nn) if det >= bound: print "We do not have det < bound. Solutions might not be found." print "Try with highers m and t." if strict: return -1, -1 else: print "det(L) < e^(m*n) (good! If a solution exists < N^delta, it will be found)" BB = BB.LLL() # transform vector i & j -> polynomials 1 & 2 found_polynomials = False for pol1_idx in range(nn - 1): for pol2_idx in range(pol1_idx + 1, nn): # for i and j, create the two polynomials PR.<w,z> = PolynomialRing(ZZ) pol1 = pol2 = 0 for jj in range(nn): pol1 += monomials[jj](w*z+1,w,z) * BB[pol1_idx, jj] / monomials[jj](UU,XX,YY) pol2 += monomials[jj](w*z+1,w,z) * BB[pol2_idx, jj] / monomials[jj](UU,XX,YY) # resultant PR.<q> = PolynomialRing(ZZ) rr = pol1.resultant(pol2) # are these good polynomials? if rr.is_zero() or rr.monomials() == [1]: continue else: print "found them, using vectors", pol1_idx, "and", pol2_idx found_polynomials = True break if found_polynomials: break if not found_polynomials: print "no independant vectors could be found. This should very rarely happen..." return 0, 0 rr = rr(q, q) # solutions soly = rr.roots() if len(soly) == 0: print "Your prediction (delta) is too small" return 0, 0 soly = soly[0][0] ss = pol1(q, soly) solx = ss.roots()[0][0] return solx, soly # the modulus N = 0xbadd260d14ea665b62e7d2e634f20a6382ac369cd44017305b69cf3a2694667ee651acded7085e0757d169b090f29f3f86fec255746674ffa8a6a3e1c9e1861003eb39f82cf74d84cc18e345f60865f998b33fc182a1a4ffa71f5ae48a1b5cb4c5f154b0997dc9b001e441815ce59c6c825f064fdca678858758dc2cebbc4d27 # the public exponent e = 0x11722b54dd6f3ad9ce81da6f6ecb0acaf2cbc3885841d08b32abc0672d1a7293f9856db8f9407dc05f6f373a2d9246752a7cc7b1b6923f1827adfaeefc811e6e5989cce9f00897cfc1fc57987cce4862b5343bc8e91ddf2bd9e23aea9316a69f28f407cfe324d546a7dde13eb0bd052f694aefe8ec0f5298800277dbab4a33bb # the hypothesis on the private exponent (the theoretical maximum is 0.292) delta = .25 # this means that d < N^delta # # Lattice (tweak those values) # # you should tweak this (after a first run), (e.g. increment it until a solution is found) m = 4 # size of the lattice (bigger the better/slower) # you need to be a lattice master to tweak these t = int((1-2*delta) * m) # optimization from Herrmann and May X = 2*floor(N^delta) # this _might_ be too much Y = floor(N^(1/2)) # correct if p, q are ~ same size # # Don't touch anything below # # Problem put in equation P.<x,y> = PolynomialRing(ZZ) A = int((N+1)/2) pol = 1 + x * (A + y) # boneh_durfee solx, soly = boneh_durfee(pol, e, m, t, X, Y) # found a solution? if solx > 0: print "=== solution found ===" if False: print "x:", solx print "y:", soly d = int(pol(solx, soly) / e) print "private key found:", d else: print "=== no solution was found ===" c = 0xe3505f41ec936cf6bd8ae344bfec85746dc7d87a5943b3a7136482dd7b980f68f52c887585d1c7ca099310c4da2f70d4d5345d3641428797030177da6cc0d41e7b28d0abce694157c611697df8d0add3d900c00f778ac3428f341f47ecc4d868c6c5de0724b0c3403296d84f26736aa66f7905d498fa1862ca59e97f8f866c m = pow(c, d, N) print(m) ``` ![图片标题](https://leanote.com/api/file/getImage?fileId=5ceb4905ab64412e23001413) 打赏还是打残,这是个问题 赏 Wechat Pay Alipay [Web] 高明的黑客 - LanceaKing [Pwn] trywrite -cpt.shao
没有帐号? 立即注册