# TRAINING WCO4 #1 - Mathematics in Cryptography ## Lý thuyết https://giapppp.notion.site/Mathematic-in-Cryptography-01a27fda51a943d2b04fbe840e5dd26b ## Bài tập CryptoHack ### Greatest Common Divisor Đề bài yêu cầu tự code GCD( 66528, 52920 ). #### Script giải : ```= python def GCD( a, b ): if( a < b ): a, b = b, a while( b ): a = a % b a, b = b, a return a a = 66528 b = 52920 print( GCD( a, b ) ) ``` ### Extended GCD https://wiki.vnoi.info/algo/algebra/euclid ECD cho phép ta tính được phương trình 2 ẩn a, b $$ u * a + v * b = gcd( u, v ) $$ Đề bài yêu cầu tìm u, v với p=26513, q=32321 thỏa phương trình $$ p⋅u+q⋅v=gcd(p,q) $$ Trả kết quả là số nhỏ hơn trong 2 số u, v. #### Script giải : ```= python def egcd(a, b): x,y, u,v = 0,1, 1,0 while( a ): q, r = b // a, b % a m, n = x - u * q, y - v * q b, a, x, y, u, v = a, r, u, v, m, n gcd = b return gcd, x, y p = 26513 q = 32321 _, x, y = egcd( p, q ) print( min (x , y) ) ``` ### Modular Arithmetic 1 #### Đề : ![image](https://hackmd.io/_uploads/Bk8gUEBzbl.png) Đây là một hệ quả của lí thuyết đồng dư $$ a \equiv b \pmod{c} \;\Longleftrightarrow\; b \equiv a \pmod{c} $$ #### Script giải : ```= python x = 11 % 6 y = 8146798528947 % 17 print( min(x, y) ) ``` ### Modular Arithmetic 2 #### Đề : ![image](https://hackmd.io/_uploads/SJYQwNrGbe.png) Theo định lí Fermat nhỏ ( Little Fermat Theorem ) $$a^p \equiv a \pmod{p} $$ $$a^{p-1} \equiv 1 \pmod{p}$$ Vậy nên đáp án là 1 ### Modular Inverting #### Đề : ![image](https://hackmd.io/_uploads/BkYxT4BGbe.png) Áp dụng Little Fermat Theorem $$ 3^{p-1} \equiv 1 \pmod{p}$$ Nhân 2 vế cho 3^(-1) ta được $$3^{p-2} \equiv 3^{-1} \pmod{p} $$ Vậy d = 3^(p-2) với p = 13 ### Quadratic Residues #### Đề : ![image](https://hackmd.io/_uploads/SJ9rMUBGZx.png) Tìm số là thặng dư bậc 2 trong danh sách ints, sau đó tìm căn bậc 2 của số đó, trả về kết quả nhỏ hơn. Thặng dư bậc 2 ( quadratic residue ) là số q có nghiệm x thỏa $$ x^{2} \equiv q \pmod{n}$$ Tìm số thặng dư bậc 2 bằng cách tìm từ 1 đến n, nếu tồn tại 1 nghiệm x thỏa điều kiện thì q là thặng dư bậc 2 #### Script giải : ```= python p = 29 ints = [ 14, 6, 11 ] root = [] for i in ints: for j in range(p): if( pow(j,2,p) == i ): root.append(j) print(min(root)) ``` ### Legendre Symbol #### Đề : ![image](https://hackmd.io/_uploads/HkgBNLBM-x.png) File output : https://cryptohack.org/static/challenges/output_479698cde19aaa05d9e9dfca460f5443.txt Thay vì brute-force để tìm thặng dư bậc 2, ta có một cách nhanh hơn đó là Legendre, với 2 số a và p, xét $$(a/p) = a^{(p-1)/2} \pmod{p}$$ Nếu $$(a/p) = 1 \Rightarrow \; a \; là \; quadratic \; residue, \; a \not\equiv 0 \pmod{p}$$ $$(a/p) = -1\Rightarrow \; a \; không \; phải \; là \; quadratic \; residue $$ $$(a/p) = 0 \Rightarrow a \equiv 0 \pmod{p} $$ Ta có một tính chất khi kết hợp 2 số ![image](https://hackmd.io/_uploads/B1KmzPHfWl.png) Đề yêu cầu tìm thặng dư bậc 2, sau đó tìm căn của nó, trả kết quả là số lớn hơn. Đề bài giống với câu trước nhưng độ khó cao hơn khiến brute-force tốn nhiều thời gian. Vì p = 3 mod 4 nên ta có công thức tìm căn bậc 2 nhanh $$ x = a^{(p+1)/4} \pmod{p} $$ #### Script giải : ```= python p = 101524035174539890485408575671085261788758965189060164484385690801466167356667036677932998889725476582421738788500738738503134356158197247473850273565349249573867251280253564698939768700489401960767007716413932851838937641880157263936985954881657889497583485535527613578457628399173971810541670838543309159139 ints = [25081841204695904475894082974192007718642931811040324543182130088804239047149283334700530600468528298920930150221871666297194395061462592781551275161695411167049544771049769000895119729307495913024360169904315078028798025169985966732789207320203861858234048872508633514498384390497048416012928086480326832803, 45471765180330439060504647480621449634904192839383897212809808339619841633826534856109999027962620381874878086991125854247108359699799913776917227058286090426484548349388138935504299609200377899052716663351188664096302672712078508601311725863678223874157861163196340391008634419348573975841578359355931590555, 17364140182001694956465593533200623738590196990236340894554145562517924989208719245429557645254953527658049246737589538280332010533027062477684237933221198639948938784244510469138826808187365678322547992099715229218615475923754896960363138890331502811292427146595752813297603265829581292183917027983351121325, 14388109104985808487337749876058284426747816961971581447380608277949200244660381570568531129775053684256071819837294436069133592772543582735985855506250660938574234958754211349215293281645205354069970790155237033436065434572020652955666855773232074749487007626050323967496732359278657193580493324467258802863, 4379499308310772821004090447650785095356643590411706358119239166662089428685562719233435615196994728767593223519226235062647670077854687031681041462632566890129595506430188602238753450337691441293042716909901692570971955078924699306873191983953501093343423248482960643055943413031768521782634679536276233318, 85256449776780591202928235662805033201684571648990042997557084658000067050672130152734911919581661523957075992761662315262685030115255938352540032297113615687815976039390537716707854569980516690246592112936796917504034711418465442893323439490171095447109457355598873230115172636184525449905022174536414781771, 50576597458517451578431293746926099486388286246142012476814190030935689430726042810458344828563913001012415702876199708216875020997112089693759638454900092580746638631062117961876611545851157613835724635005253792316142379239047654392970415343694657580353333217547079551304961116837545648785312490665576832987, 96868738830341112368094632337476840272563704408573054404213766500407517251810212494515862176356916912627172280446141202661640191237336568731069327906100896178776245311689857997012187599140875912026589672629935267844696976980890380730867520071059572350667913710344648377601017758188404474812654737363275994871, 4881261656846638800623549662943393234361061827128610120046315649707078244180313661063004390750821317096754282796876479695558644108492317407662131441224257537276274962372021273583478509416358764706098471849536036184924640593888902859441388472856822541452041181244337124767666161645827145408781917658423571721, 18237936726367556664171427575475596460727369368246286138804284742124256700367133250078608537129877968287885457417957868580553371999414227484737603688992620953200143688061024092623556471053006464123205133894607923801371986027458274343737860395496260538663183193877539815179246700525865152165600985105257601565] quad = [] for i in ints: x = pow( i , (p - 1) // 2 , p ) if( x == 1 and i % p != 0): quad.append(i) print("int: ", i) roots = [] for num in quad: root = pow( num , (p + 1) // 4 , p ) roots.append(root) roots.append(p - root) print( max(roots) ) ``` ### Modular Square Root #### Đề : ![image](https://hackmd.io/_uploads/Bk5M7vrGbe.png) Ở bài này, p không ở dạng đặc biệt như câu trước nên ta dùng Tonelli-Shanks, điều kiện là p phải là số nguyên tố. #### Script giải : ```= python import math a = 8479994658316772151941616510097127087554541274812435112009425778595495359700244470400642403747058566807127814165396640215844192327900454116257979487432016769329970767046735091249898678088061634796559556704959846424131820416048436501387617211770124292793308079214153179977624440438616958575058361193975686620046439877308339989295604537867493683872778843921771307305602776398786978353866231661453376056771972069776398999013769588936194859344941268223184197231368887060609212875507518936172060702209557124430477137421847130682601666968691651447236917018634902407704797328509461854842432015009878011354022108661461024768 p = 30531851861994333252675935111487950694414332763909083514133769861350960895076504687261369815735742549428789138300843082086550059082835141454526618160634109969195486322015775943030060449557090064811940139431735209185996454739163555910726493597222646855506445602953689527405362207926990442391705014604777038685880527537489845359101552442292804398472642356609304810680731556542002301547846635101455995732584071355903010856718680732337369128498655255277003643669031694516851390505923416710601212618443109844041514942401969629158975457079026906304328749039997262960301209158175920051890620947063936347307238412281568760161 def check_quadratic_residue( a , p ): return pow(a, (p - 1) // 2, p) == 1 def split_QS(p:int): n=p-1 np= n & -n S= np.bit_length() -1 Q=n >> S return Q,S def tonelli_shanks(a, p, z): Q, S = split_QS(p) M = S c = pow(z, Q, p) t = pow(a, Q, p) x = pow(a, (Q + 1) // 2, p) if t == 0: return 0 while t != 1: i = 0 temp = t while temp != 1: temp = pow(temp, 2, p) i += 1 if i == M: raise ValueError("No solution") b = pow(c, 1 << (M - i - 1), p) x = (x * b) % p t = (t * b * b) % p c = (b * b) % p M = i return x, p - x z=2 while (z<=(p-1)): if pow(z,(p-1)//2,p) == p-1: break z+=1 #z=29 if( check_quadratic_residue( a, p )): print(min( tonelli_shanks(a,p,z))) ``` ### chinese Remainder Theorem #### Đề : ![image](https://hackmd.io/_uploads/BkIjHwBzZx.png) CRT là cách giải hệ nhiều phương trình cùng 1 ẩn,với các modul đôi một cùng nguyên tố, từ đó ta xác định được duy nhất 1 nghiệm thỏa tất cả các phương trình #### Script giải : ```= python def Crt(a,n): N = 1 for i in n: N *= i x = 0 for i,j in zip(a,n): Ni = N // j Yi = pow(Ni,-1,j) x += i * Ni * Yi return x % N n= [ 5 , 11 , 17 ] a= [ 2 , 3 , 5 ] print(Crt(a,n) ) ``` ### Adrien's Symbol #### Đề : ```= python from random import randint a = 288260533169915 p = 1007621497415251 FLAG = b'crypto{????????????????????}' def encrypt_flag(flag): ciphertext = [] plaintext = ''.join([bin(i)[2:].zfill(8) for i in flag]) for b in plaintext: e = randint(1, p) n = pow(a, e, p) if b == '1': ciphertext.append(n) else: n = -n % p ciphertext.append(n) return ciphertext print(encrypt_flag(FLAG)) ``` output.txt https://pastebin.com/52UYTRZa Bài này có đặc điểm là p = 3 mod 4, và a là một thặng dư bậc 2 ( (a|p) = 1 ). Qua đó ta thấy với mọi số mũ e thì cũng sẽ trở thành thặng dư bậc 2. Vậy (-a|p) = -1, không là thặng dư bậc 2. Đề bài mã hóa từng bit của flag bằng cách: nếu bit là 1 thì thêm vào n là một số thặng dư bậc 2 mod p, nếu bit là 0 thì thêm vào -n là một số không phải thặng dư bậc 2 mod p. Từ các điều trên ta có thể suy ra được bit là 1 hay 0 chỉ nhờ vào Legendre symbol chứ không cần suy ngược ra là n hay -n mod p. #### Script giải : ```= python encFlag = [67594220461269, 501237540280788, 718316769824518, 296304224247167, 48290626940198, 30829701196032, 521453693392074, 840985324383794, 770420008897119, 745131486581197, 729163531979577, 334563813238599, 289746215495432, 538664937794468, 894085795317163, 983410189487558, 863330928724430, 996272871140947, 352175210511707, 306237700811584, 631393408838583, 589243747914057, 538776819034934, 365364592128161, 454970171810424, 986711310037393, 657756453404881, 388329936724352, 90991447679370, 714742162831112, 62293519842555, 653941126489711, 448552658212336, 970169071154259, 339472870407614, 406225588145372, 205721593331090, 926225022409823, 904451547059845, 789074084078342, 886420071481685, 796827329208633, 433047156347276, 21271315846750, 719248860593631, 534059295222748, 879864647580512, 918055794962142, 635545050939893, 319549343320339, 93008646178282, 926080110625306, 385476640825005, 483740420173050, 866208659796189, 883359067574584, 913405110264883, 898864873510337, 208598541987988, 23412800024088, 911541450703474, 57446699305445, 513296484586451, 180356843554043, 756391301483653, 823695939808936, 452898981558365, 383286682802447, 381394258915860, 385482809649632, 357950424436020, 212891024562585, 906036654538589, 706766032862393, 500658491083279, 134746243085697, 240386541491998, 850341345692155, 826490944132718, 329513332018620, 41046816597282, 396581286424992, 488863267297267, 92023040998362, 529684488438507, 925328511390026, 524897846090435, 413156582909097, 840524616502482, 325719016994120, 402494835113608, 145033960690364, 43932113323388, 683561775499473, 434510534220939, 92584300328516, 763767269974656, 289837041593468, 11468527450938, 628247946152943, 8844724571683, 813851806959975, 72001988637120, 875394575395153, 70667866716476, 75304931994100, 226809172374264, 767059176444181, 45462007920789, 472607315695803, 325973946551448, 64200767729194, 534886246409921, 950408390792175, 492288777130394, 226746605380806, 944479111810431, 776057001143579, 658971626589122, 231918349590349, 699710172246548, 122457405264610, 643115611310737, 999072890586878, 203230862786955, 348112034218733, 240143417330886, 927148962961842, 661569511006072, 190334725550806, 763365444730995, 516228913786395, 846501182194443, 741210200995504, 511935604454925, 687689993302203, 631038090127480, 961606522916414, 138550017953034, 932105540686829, 215285284639233, 772628158955819, 496858298527292, 730971468815108, 896733219370353, 967083685727881, 607660822695530, 650953466617730, 133773994258132, 623283311953090, 436380836970128, 237114930094468, 115451711811481, 674593269112948, 140400921371770, 659335660634071, 536749311958781, 854645598266824, 303305169095255, 91430489108219, 573739385205188, 400604977158702, 728593782212529, 807432219147040, 893541884126828, 183964371201281, 422680633277230, 218817645778789, 313025293025224, 657253930848472, 747562211812373, 83456701182914, 470417289614736, 641146659305859, 468130225316006, 46960547227850, 875638267674897, 662661765336441, 186533085001285, 743250648436106, 451414956181714, 527954145201673, 922589993405001, 242119479617901, 865476357142231, 988987578447349, 430198555146088, 477890180119931, 844464003254807, 503374203275928, 775374254241792, 346653210679737, 789242808338116, 48503976498612, 604300186163323, 475930096252359, 860836853339514, 994513691290102, 591343659366796, 944852018048514, 82396968629164, 152776642436549, 916070996204621, 305574094667054, 981194179562189, 126174175810273, 55636640522694, 44670495393401, 74724541586529, 988608465654705, 870533906709633, 374564052429787, 486493568142979, 469485372072295, 221153171135022, 289713227465073, 952450431038075, 107298466441025, 938262809228861, 253919870663003, 835790485199226, 655456538877798, 595464842927075, 191621819564547] flag = "" a = 288260533169915 p = 1007621497415251 for ch in encFlag: if pow(ch,(p-1)//2,p)==1: flag += '1' else: flag += '0' ls = [chr(int(flag[i:i+8],2)) for i in range(0,len(flag),8)] result = "" for i in ls: result += i print( result ) ``` ### Modular Binomials #### Đề : ![image](https://hackmd.io/_uploads/SkEprI8Gbg.png) data.txt https://pastebin.com/BDX0t4Cy Ta có dạng tổng quát $$ c_1 = (a_1 \cdot p + b_1 \cdot q)^{e_1} \pmod{N} $$ $$ c_2 = (a_2 \cdot p + b_2 \cdot q)^{e_2} \pmod{N} $$ Mũ e2 cho c1, và e1 cho c2 để 2 vế bên phải đều có mũ e1e2. $$ c_1^{e_2} = (a_1 * p + b_1 * q)^{\,e_1 e_2} \pmod{N} $$ $$ c_1^{e_2} = (a_1 * p)^{\,e_1 e_2} + (b_1 * q)^{\,e_1 e_2} \pmod{N} $$ $$ a_1^{-e_1 e_2} * c_1^{e_2} = a_1^{-e_1 e_2} * (a_1 * p)^{\,e_1 e_2} + a_1^{-e_1 e_2} * (b_1 * q)^{\,e_1 e_2} \pmod{N} $$ $$ a_1^{-e_1 e_2} * c_1^{e_2} = p^{\,e_1 e_2} + a_1^{-e_1 e_2} * (b_1 * q)^{\,e_1 e_2} \pmod{N} $$ Và $$ c_2^{e_1} = (a_2 * p + b_2 * q)^{\,e_2 e_1} \pmod{N} $$ $$ c_2^{e_1} = (a_2 * p)^{\,e_2 e_1} + (b_2 * q)^{\,e_2 e_1} \pmod{N} $$ $$ a_2^{-e_2 e_1} * c_2^{e_1}= a_2^{-e_2 e_1} * (a_2 * p)^{\,e_2 e_1}+ a_2^{-e_2 e_1} * (b_2 * q)^{\,e_2 e_1} \pmod{N} $$ $$ a_2^{-e_2 e_1} * c_2^{e_1}= p^{\,e_2 e_1}+ a_2^{-e_2 e_1} * (b_2 * q)^{\,e_2 e_1} \pmod{N} $$ Trừ 2 vế với nhau $$ a_2^{-e_2 e_1} * c_2^{e_1} \;-\;a_1^{-e_1 e_2} * c_1^{e_2}=p^{\,e_2 e_1}+ a_2^{-e_2 e_1} * (b_2 * q)^{\,e_2 e_1}- p^{\,e_1 e_2}- a_1^{-e_1 e_2} * (b_1 * q)^{\,e_1 e_2}\pmod{N} $$ $$ =a_2^{-e_2 e_1} * (b_2 * q)^{\,e_2 e_1}\;-\;a_1^{-e_1 e_2} * (b_1 * q)^{\,e_1 e_2}\pmod{N} $$ Gọi $$ A =a_2^{-e_2 e_1} * c_2^{e_1} \;-\;a_1^{-e_1 e_2} * c_1^{e_2} $$ $$ \Rightarrow A =a_2^{-e_2 e_1}\; * \;(b_2 * q)^{\,e_2 e_1}\;-\;a_1^{-e_1 e_2}\; * \;(b_1 * q)^{\,e_1 e_2}\pmod{N} $$ $$ = q^{\,e_1 e_2}\; * \;(\; a_2^{-e_2 e_1}\;*\;b_2^{\,e_1 e_2} \; - \; a_1^{-e_1 e_2}\;*\;b_1^{\,e_1 e_2} ) \pmod{N}$$ Xét lần lượt trên GF( p ) và GF( q ) Vì $$ q^{\,e_1 e_2} = 0 \pmod{q }$$ $$ \Rightarrow A = 0 \pmod{q} $$ mà $$ N = \pmod{q} $$ Nên **q là ước chung của N và A** Tương tự với GF( p ) Vì q và p là 2 số cùng nguyên tố ( coprime ) nên GCD( p, q ) = 1, mà A = 0 ( mod q ) $$ \Rightarrow A \not= 0 \pmod{p} $$ Vậy **p không phải là ước chung của N và A** **Suy ra q là ước chung lớn nhất của N và A** Từ đó ta tìm được q, p = N // q. #### Script giải : ```= python from math import gcd n = 14905562257842714057932724129575002825405393502650869767115942606408600343380327866258982402447992564988466588305174271674657844352454543958847568190372446723549627752274442789184236490768272313187410077124234699854724907039770193680822495470532218905083459730998003622926152590597710213127952141056029516116785229504645179830037937222022291571738973603920664929150436463632305664687903244972880062028301085749434688159905768052041207513149370212313943117665914802379158613359049957688563885391972151218676545972118494969247440489763431359679770422939441710783575668679693678435669541781490217731619224470152467768073 e1 = 12886657667389660800780796462970504910193928992888518978200029826975978624718627799215564700096007849924866627154987365059524315097631111242449314835868137 e2 = 12110586673991788415780355139635579057920926864887110308343229256046868242179445444897790171351302575188607117081580121488253540215781625598048021161675697 c1 = 14010729418703228234352465883041270611113735889838753433295478495763409056136734155612156934673988344882629541204985909650433819205298939877837314145082403528055884752079219150739849992921393509593620449489882380176216648401057401569934043087087362272303101549800941212057354903559653373299153430753882035233354304783275982332995766778499425529570008008029401325668301144188970480975565215953953985078281395545902102245755862663621187438677596628109967066418993851632543137353041712721919291521767262678140115188735994447949166616101182806820741928292882642234238450207472914232596747755261325098225968268926580993051 c2 = 14386997138637978860748278986945098648507142864584111124202580365103793165811666987664851210230009375267398957979494066880296418013345006977654742303441030008490816239306394492168516278328851513359596253775965916326353050138738183351643338294802012193721879700283088378587949921991198231956871429805847767716137817313612304833733918657887480468724409753522369325138502059408241232155633806496752350562284794715321835226991147547651155287812485862794935695241612676255374480132722940682140395725089329445356434489384831036205387293760789976615210310436732813848937666608611803196199865435145094486231635966885932646519 a1, b1, a2, b2 = 2, 3, 5, 7 q = gcd(pow(a2,(-e2 * e1),n) * pow(c2, e1, n) - pow(a1, (-e1 * e2), n) * pow(c1, e2, n), n) p = n // q print( p ) print( q ) ``` ### Lo-Hi card #### Đề : ```= python from functools import total_ordering from Crypto.Random import random from utils import listener FLAG = 'crypto{?????????????????????????????}' VALUES = ['Ace', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine', 'Ten', 'Jack', 'Queen', 'King'] SUITS = ['Clubs', 'Hearts', 'Diamonds', 'Spades'] class RNG: mul = random.getrandbits(60) inc = random.getrandbits(60) mod = 2**61 - 1 # 9th mersenne prime def __init__(self, seed): self.state = seed def next(self): self.state = (self.state * self.mul + self.inc) % self.mod return self.state @total_ordering class Card: def __init__(self, value, suit): self.value = value self.suit = suit def __str__(self): return f"{self.value} of {self.suit}" def __eq__(self, other): return self.value == other.value def __gt__(self, other): return VALUES.index(self.value) > VALUES.index(other.value) class Game: def __init__(self): self.rng = RNG(random.getrandbits(60)) self.deck = [Card(value, suit) for suit in SUITS for value in VALUES] self.num_deals = self.shuffle() def rebase(self, n, b=52): if n < b: return [n] else: return [n % b] + self.rebase(n//b, b) def shuffle(self): self.deals = self.rebase(self.rng.next()) return len(self.deals) def deal_card(self): index = self.deals.pop() if self.deals == []: self.num_deals = self.shuffle() return self.deck[index] class Challenge(): def __init__(self): self.no_prompt = True self.game = Game() self.funds = 20 self.dollars = self.funds self.round = 0 self.hidden = self.game.deal_card() def play_round(self, msg=None): self.round += 1 self.hand = self.hidden # move last round's hidden card to player's hand self.hidden = self.game.deal_card() # deal new hidden card self.shuffle_msg = "" if self.game.num_deals: # dealer shuffled deck self.shuffle_msg = f"I will reshuffle the deck after {self.game.num_deals} rounds. " self.game.num_deals = None if self.round == 1: msg = f"Welcome to my virtual casino! You are sitting down for a game of lo-hi. {self.shuffle_msg}Your hand is the {self.hand}. Lower or higher?" self.shuffle_msg = "" return { "round": self.round, "$": self.dollars, "hand": str(self.hand), "msg": msg } def win(self): self.dollars += 1 msg = f"Correct! Hidden card was {self.hidden}. {self.shuffle_msg}Lower or higher?" return self.play_round(msg) def lose(self): self.dollars -= 2 # house edge msg = f"Incorrect! Hidden card was {self.hidden}. {self.shuffle_msg}Lower or higher?" return self.play_round(msg) def challenge(self, your_input): if self.round == 0: return self.play_round() elif self.dollars <= 0: self.exit = True return {"error": "You're broke!"} elif self.round == 200: self.exit = True if self.dollars >= 130: return {"msg": f"You pulled a 21! Here's a flag: {FLAG}"} elif self.dollars > self.funds: return {"msg": f"Nice, you beat the house!"} else: return {"msg": f"Aww, have a free drink!"} elif not 'choice' in your_input: self.exit = True return {"error": "You must make a choice"} else: if self.hidden == self.hand: return self.lose() # house edge choice = your_input['choice'] if choice.lower().startswith('l'): if self.hidden < self.hand: return self.win() else: return self.lose() elif choice.lower().startswith('h'): if self.hidden > self.hand: return self.win() else: return self.lose() else: self.exit = True return {"error": "Invalid input"} import builtins; builtins.Challenge = Challenge # hack to enable challenge to be run locally, see https://cryptohack.org/faq/#listener listener.start_server(port=13383) ``` Tóm tắt đề: Đây là 1 trò chơi dự đoán giá trị lá bài hidden và chọn "higher" hoặc "lower", nếu hidden có giá trị lớn hơn hand ( lá bài đang ở tay ) thì là "higher", ngược lại là "lower", nếu bằng là house edge. Đoán đúng +1 dollar, sai -2 dollars, house edge -2 dollards. Mỗi lần sẽ có 1 deck bài được bốc dần, khi hết deck sẽ tráo lại bộ bài và lấy một deck khác. Mỗi deck có khoảng 10 - 11 lá bài ( log256(2^61) ~ 10 - 11 ). Điều kiện để thắng là chơi hết 200 ván và số tiền kiếm được trên 130 dollards. #### Phân tích : Đề có 1 điểm đặc biệt là sau khi biết được kết quả, đề luôn cho biết lá hidden là gì và lá hand tiếp theo của mình luôn là lá hidden của vòng trước. Để ý ở class Game, khi bắt đầu sẽ gọi class RNG random với seed dài 60 bit, gọi hàm shuffle 1 lần để lấy số lượng bài trong deck đầu. Hàm rebase là hàm biến 1 số từ hệ 10 sang hệ cơ số 52. Hàm deal_card trả về lá bài từ deck, nếu deck đó hết bài thì shuffle để đổi lại deck khác. Trong class RNG có hàm next liên quan mật thiết với class Game. State lúc đầu là seed, sau đó được tinh theo công thức $$ state = (state * mul + inc )\; \% \; mod $$ Tức là nếu ta biết được vài state thì có thể xây dựng lại công thức để tạo lại mul và inc, cuối cùng là seed. Khi biết seed thì toàn bộ game đã bị phá. Ta có thể đọc trước các kết quả để tính toán và gửi đi. Gọi X1, X2, X3 lần lượt là 3 state đầu. Công thức xây lại seed $$ X3 - X2 = mul * (X2-X1)\; \% \;mod$$ $$ \Rightarrow mul = (X3-X2)*(X2-X1)^{-1} \; \% \; mod$$ $$inc = (X3-X2*mul) \; \% \; mod$$ $$seed = (X1-inc)*mul^{-1} \; \% \; mod $$ Sau khi tìm seed thì chạy game local và đọc kết quả, sau đó gửi cho sv #### Script giải : ```= python from pwn import * import re import json HOST = "socket.cryptohack.org" PORT = 13383 r = remote( HOST , PORT ) def recv_json(): return json.loads(r.recvline().decode()) def send_json(msg): data = json.dumps(msg) r.sendline(data.encode()) receive = recv_json() money = receive["$"] hand = receive["hand"] msg = receive["msg"] m = re.search(r"after (\d+) rounds", msg) if m: num_deals = int(m.group(1)) class Card: def __init__(self, value, suit): self.value = value self.suit = suit def __str__(self): return f"{self.value} of {self.suit}" def __eq__(self, other): return self.value == other.value def __gt__(self, other): return VALUES.index(self.value) > VALUES.index(other.value) VALUES = ['Ace', 'Two', 'Three', 'Four', 'Five', 'Six', 'Seven', 'Eight', 'Nine', 'Ten', 'Jack', 'Queen', 'King'] SUITS = ['Clubs', 'Hearts', 'Diamonds', 'Spades'] deck = [Card(value, suit) for suit in SUITS for value in VALUES] def value_index_from_name( name ) : value_str, _, _ = name.partition(" of ") return VALUES.index(value_str) def choose( hand ): value = value_index_from_name( hand ) if( value > 6 ): return "lower" else: return "higher" def index_from_name( name ) : value_str, _, suit_str = name.partition(" of ") v = VALUES.index(value_str) s = SUITS.index(suit_str) return s * 13 + v mapping = [[] for _ in range(3)] for i in range(3 * num_deals): idx = index_from_name(hand) print( idx, hand) mapping[ i // num_deals ].append(idx) choice = choose(hand) send_json({"choice": choice}) receive = recv_json() money = receive["$"] hand = receive["hand"] msg = receive["msg"] mod = 2**61 - 1 def reconstruct_state( digits ): s = 0 k = len( digits ) for i in range(k): s = (s + digits[k - 1 - i] * pow(52, i, mod)) % mod return s states = [] for deck_idx in range(3): st = reconstruct_state(mapping[deck_idx]) states.append(st) mul = (states[2] - states[1]) * pow(states[1] - states[0], -1, mod) % mod inc = (states[1] - mul * states[0]) % mod seed = (states[0] - inc) * pow(mul, -1, mod) % mod class RNG: def __init__(self, seed, mul, inc, mod): self.state = seed self.mul = mul self.inc = inc self.mod = mod def next(self): self.state = (self.state * self.mul + self.inc) % self.mod return self.state class Game: def __init__(self, seed, mul, inc, mod): self.rng = RNG(seed, mul, inc, mod) self.deck = [Card(value, suit) for suit in SUITS for value in VALUES] self.num_deals = self.shuffle() def rebase(self, n, b=52): if n < b: return [n] else: return [n % b] + self.rebase(n//b, b) def shuffle(self): self.deals = self.rebase(self.rng.next()) return len(self.deals) def deal_card(self): index = self.deals.pop() if self.deals == []: self.num_deals = self.shuffle() return self.deck[index] game = Game(seed, mul, inc, mod) send = [] for i in range( 201 ): name = game.deal_card() if( i == 0 ): send.append("higher") last = value_index_from_name( str(name) ) continue value = value_index_from_name( str(name) ) if( value > last ): send.append("higher") else: send.append("lower") last = value for i in range( 3 * num_deals + 1, 201 ): send_json({"choice": send[i]}) print(recv_json()) ``` ### ASCII #### Script giải : ```= python m = [99, 114, 121, 112, 116, 111, 123, 65, 83, 67, 73, 73, 95, 112, 114, 49, 110, 116, 52, 98, 108, 51, 125] for i in m: print(chr(i), end="") ``` ### Hex #### Script giải : ```= python ct = "63727970746f7b596f755f77696c6c5f62655f776f726b696e675f776974685f6865785f737472696e67735f615f6c6f747d" print(bytes.fromhex(ct).decode()) ``` ### Base64 #### Script giải : ```= python import base64 ct = "72bca9b68fc16ac7beeb8f849dca1d8a783e8acf9679bf9269f7bf" ct = bytes.fromhex( ct ) print( base64.b64encode( ct ).decode()) ``` ### Bytes and Big Integers #### Đề : ```= python #!/usr/bin/env python3 from Crypto.Util.number import bytes_to_long, long_to_bytes from utils import listener # this is cryptohack's server-side module and not part of python import base64 import codecs import random FLAG = "crypto{????????????????????}" ENCODINGS = [ "base64", "hex", "rot13", "bigint", "utf-8", ] with open('/usr/share/dict/words') as f: WORDS = [line.strip().replace("'", "") for line in f.readlines()] class Challenge(): def __init__(self): self.no_prompt = True # Immediately send data from the server without waiting for user input self.challenge_words = "" self.stage = 0 def create_level(self): self.stage += 1 self.challenge_words = "_".join(random.choices(WORDS, k=3)) encoding = random.choice(ENCODINGS) if encoding == "base64": encoded = base64.b64encode(self.challenge_words.encode()).decode() # wow so encode elif encoding == "hex": encoded = self.challenge_words.encode().hex() elif encoding == "rot13": encoded = codecs.encode(self.challenge_words, 'rot_13') elif encoding == "bigint": encoded = hex(bytes_to_long(self.challenge_words.encode())) elif encoding == "utf-8": encoded = [ord(b) for b in self.challenge_words] return {"type": encoding, "encoded": encoded} # # This challenge function is called on your input, which must be JSON # encoded # def challenge(self, your_input): if self.stage == 0: return self.create_level() elif self.stage == 100: self.exit = True return {"flag": FLAG} if self.challenge_words == your_input["decoded"]: return self.create_level() return {"error": "Decoding fail"} import builtins; builtins.Challenge = Challenge # hack to enable challenge to be run locally, see https://cryptohack.org/faq/#listener listener.start_server(port=13377) ``` Chỉ cần sử dụng pwntool để đọc type và encode, sau đó decode theo từng loại và đưa ra đúng chuỗi sau khi decode theo từng type #### Script giải : ```= python from pwn import * # pip install pwntools import json import base64 from Crypto.Util.number import long_to_bytes import codecs r = remote('socket.cryptohack.org', 13377, level = 'debug') def json_recv(): line = r.recvline() return json.loads(line.decode()) def json_send(hsh): request = json.dumps(hsh).encode() r.sendline(request) def decoded( ct, type ): if( type == "base64"): return base64.b64decode( ct ).decode() elif( type == "hex" ): return bytes.fromhex( ct ).decode() elif( type == "rot13" ): return codecs.decode( ct, 'rot_13' ) elif( type == "bigint" ): ct = int( ct, 16 ) return long_to_bytes( ct ).decode() elif( type == "utf-8" ): pt = "" for i in ct: pt += chr(i) return pt for i in range(100): received = json_recv() type = received["type"] encoded = received["encoded"] print("Received type: ") print(type) print("Received encoded value: ") print(encoded) pt = decoded( encoded , type ) to_send = { "decoded":pt } json_send(to_send) print( json_recv() ) ``` ### XOR Starter #### Script giải : ```= python def xor(a, b): return "".join(chr(ord(x) ^ ord(y)) for x, y in zip(a, b)) string = "label" pt = "" for i in string: pt += xor( i, chr(13) ) print( pt ) ``` ### XOR Properties #### Đề : ![image](https://hackmd.io/_uploads/rJ9MJw_G-g.png) Dựa theo tính chất Commutative và Associative để giải ![image](https://hackmd.io/_uploads/rkhsJPdfWe.png) #### Script giải : ```= python def xor(a, b): return bytes([x ^ y for x, y in zip(a, b)]) KEY1 = "a6c8b6733c9b22de7bc0253266a3867df55acde8635e19c73313" KEY21 = "37dcb292030faa90d07eec17e3b1c6d8daf94c35d4c9191a5e1e" KEY23 = "c1545756687e7573db23aa1c3452a098b71a7fbf0fddddde5fc1" FLAG123 = "04ee9855208a2cd59091d04767ae47963170d1660df7f56f5faf" key1 = bytes.fromhex( KEY1 ) key2 = xor( key1 , bytes.fromhex( KEY21 )) key3 = xor( key2 , bytes.fromhex( KEY23 )) flag = xor( bytes.fromhex( FLAG123 ), xor( key1, xor( key2, key3 ) ) ) print( flag.decode() ) ``` ### Favourite byte #### Đề : ![image](https://hackmd.io/_uploads/SJkqzvuMZe.png) Ta chỉ cần xor string đề cho với từng byte từ 0 - 256, nếu có chuỗi "crypto" trong đó thì in ra. #### Script giải : ```= python def xor( a, b ): return bytes([ x ^ y for x, y in zip( a, b )]) ct = "73626960647f6b206821204f21254f7d694f7624662065622127234f726927756d" ct = bytes.fromhex( ct ) for i in range( 256 ): pt = b"" for char in ct : pt += xor( bytes([ char ]), bytes([ i ]) ) if( b"crypto" in pt ): print( pt.decode() ) break ``` ### You either know, XOR you don't #### Đề : ![image](https://hackmd.io/_uploads/ByDu2Duz-x.png) Biết được format của flag bắt đầu là "crypto{", ta có thể tìm được đoạn key đầu bằng cách xor đoạn flag đã biết với ciphertext, được kết quả là "myXORke", đoán vội key là "myXORkey". Từ đó xor cho đến hết đoạn ciphertext #### Scrip giải : ```= python def xor( a, b ): return bytes([ x ^ y for x, y in zip( a, b )]) ct = "0e0b213f26041e480b26217f27342e175d0e070a3c5b103e2526217f27342e175d0e077e263451150104" ct = bytes.fromhex( ct ) known = b"crypto{" key = xor( known, ct ) print( key ) key = b"myXORkey" pt = b"".join( xor( key, ct[ i: i + len(key) ]) for i in range(0, len( ct ), len( key )) ) print(pt.decode()) ``` ### Lemur XOR #### Đề : ![image](https://hackmd.io/_uploads/rJrGAwdzZg.png) lemur.png https://cryptohack.org/static/challenges/lemur_ed66878c338e662d3473f0d98eedbd0d.png flag.png https://cryptohack.org/static/challenges/flag_7ae18c704272532658c10b5faad06d74.png Xor 2 ảnh lại với nhau theo RGB bytes, sau đó tạo thành 1 ảnh mới. ```= python from PIL import Image def xor_images(img1_path, img2_path, out_path): img1 = Image.open(img1_path).convert("RGB") img2 = Image.open(img2_path).convert("RGB") w, h = img1.size px1 = img1.load() px2 = img2.load() out = Image.new("RGB", (w, h)) px_out = out.load() for x in range(w): for y in range(h): r1, g1, b1 = px1[x, y] r2, g2, b2 = px2[x, y] px_out[x, y] = ( r1 ^ r2, g1 ^ g2, b1 ^ b2 ) out.save(out_path) print(f"Saved XOR image -> {out_path}") xor_images( "flag.png", "lemur.png", "solve.png" ) ``` ### Privacy-Enhanced Mail? #### Đề : ![image](https://hackmd.io/_uploads/SJsFRP_fZe.png) file .pem https://cryptohack.org/static/challenges/privacy_enhanced_mail_1f696c053d76a78c2c531bb013a92d4a.pem #### Script giải : ```= python from Crypto.PublicKey import RSA with open("privacy.pem", "rb") as f: key = RSA.import_key(f.read()) print( key.d ) ``` ### CERTainly not #### Đề : ![image](https://hackmd.io/_uploads/Syh5mddMWx.png) file.der https://cryptohack.org/static/challenges/2048b-rsa-example-cert_3220bd92e30015fe4fbeb84a755e7ca5.der Chạy lênh openssl để đọc modul từ file, sau đó chuyển sang int ``` # openssl x509 -inform DER -in 2048b-rsa-example-cert.der -modulus -noout n = "B4CFD15E3329EC0BCFAE76F5FE2DC899C67879B918F80BD4BAB4D79E02520609F418934CD470D142A0291392735077F60489AC032CD6F106ABAD6CC0D9D5A6ABCACD5AD2562651E54B088AAFCC190F253490B02A29410F55F16B93DB9DB3CCDCECEBC75518D74225DE49351432929C1EC669E33CFBF49AF8FB8BC5E01B7EFD4F25BA3FE596579A2479491727D7894B6A2E0D8751D9233D068556F858310EEE81997868CD6E447EC9DA8C5A7B1CBF24402948D1039CEFDCAE2A5DF8F76AC7E9BCC5B059F695FC16CBD89CEDC3FC129093785A75B45683FAFC4184F6647934351CAC7A850E73787201E72489259EDA7F65BCAF8793198CDB7515B6E030C708F859" print( int( n, 16 )) ``` ### SSH Keys ### Big - CryptoCTF 2023 #### Đề : ```= python #!/usr/bin/env sage from Crypto.Util.number import * from hashlib import sha512 from flag import flag def genkey(nbit): while True: p = getPrime(nbit) if p % 4 == 3: q = int(str(p)[::-1]) if isPrime(q): return p * q, (p, q) def setup(msg, pkey): hid = sha512(msg).digest() while True: a = bytes_to_long(hid) if kronecker(a, pkey) == 1: return a else: hid = sha512(hid).digest() def encrypt(msg, pkey): a, m = setup(msg, pkey), bytes_to_long(msg) B, C = bin(m)[2:], [] for b in B: while True: t = randint(1, pkey) if kronecker(t, pkey) == 2 * int(b) - 1: C.append((t - a * inverse(t, pkey)) % pkey) break return (a, C) pkey, privkey = genkey(512) E = encrypt(flag, pkey) print(f'pkey = {pkey}') print(f'E = {E}') ``` output.txt https://pastebin.com/pbA1SyDL #### Phân tích : Hàm genkey tạo một p là số nguyên tố 522 bit, đảm bảo p mod 4 = 3, tạo một q là số đảo của p, đảm bảo q cũng là số nguyên tố. Hàm setup tạo một biến hid là giá trị băm sha256 của msg, chuyển thành số nguyên a, đảm bảo kronecker của a với pkey bằng 1. Hàm quan trọng nhất là encrypt, tạo biến a theo hàm setup, m là bytes của flag, B là chuỗi bit của flag, và mảng C trống. Xét từng bit của m, tạo giá trị t ngẫu nhiên từ 1 đến pkey, đảm bảo kronecker( t, pkey ) = 2 * int( bit ) - 1. Sau đó thêm vào C một giá trị theo công thức $$c=t-a*t^{-1} \pmod{pkey}$$ Xét phần đảm bảo của hàm encrypt: kronecker( t, pkey ) = 2 * int( bit ) - 1, nếu bit = 1 thì kronecker( t, pkey ) = 1, nếu bit = 0 thì kronecker( t, pkey ) = -1 Công thức trên có thể biến đổi $$ c*t = t^2-a \pmod{pkey}$$ $$t^2-c*t-a = 0 \pmod{pkey}$$ Vậy ta được phương trình bậc 2 theo ẩn t $$\Rightarrow t=(c \pm \sqrt{c^2+4*a})/2 \pmod{pkey}$$ Ta có thể tách modul pkey ra thành 2 modul p và q, sau đó gộp lại tính Crt để tìm t. Từ t có thể tìm lại từng bit của flag bằng cách kiểm tra lại kronecker( t, pkey ) Về phần tính căn bậc 2, vì p được đảm bảo p = 3 mod 4 nên ta dùng công thức $$ x = a^{(p+1)/4} \pmod{p}$$ Với x là căn bậc 2 của a Còn q không chắc chắn sẽ có dạng mod 4 = 3 nên dùng tonelli shanks. #### Script giải : https://pastebin.com/ZnPyaeuf > Flag : CCTF{_Cocks_18e_5chEm3} >