Skip to content
🎉 Welcome! Enjoy your reading, and I hope you will learn something new.
FCSC 2026 - Crypto Write-Ups

FCSC 2026 - Crypto Write-Ups

May 2, 2026

Hi 👋 ! In this post, I’ll present my write-ups for all the challenges listed in the crypto category that I’ve solved, ordered by difficulty rating.

Those challenges are :

  1. This is fine
  2. Code breaker
  3. Little d - Big trouble
  4. Fully Homomorphic RSA

This is fine

PointsDifficultySolves
151194

Description

This is fine !

Code

The following source code was provided :

code-breaker.py
 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
L = [
    lambda x: 7356954580121243762*x**16 - 67917758999022629485319994263285229323*x**15 - 126557346997785504703303593285520174611*x**14 - 79196433701250933379099644305803773692*x**13 + 64127295017451262308591178117921719607*x**12 + 30348988932379286893278017713168381811*x**11 + 103217349796193762180113460134492309958*x**10 - 154876828044950977759573152639253989872*x**9 - 83291919960546378198292499838313325081*x**8 + 13843350859630151992361239852646634059*x**7 - 102720501418550210355972956242885213268*x**6 - 155819885470062394096388278625509744351*x**5 - 71889517112932522335238158706394974633*x**4 - 102947773924158754256136745869287671436*x**3 - 123078785304424416159367972716706676890*x**2 + 52344605551728127581536635264341776866*x + 142567267056755772849168679088948045314,
    lambda x: -1919394743776313599*x**16 + 33531946481116524314458346374871477551*x**15 + 141700664496035506125403192322286180537*x**14 + 298802448218233530042455767052063360891*x**13 + 268153191400039594168618354294588659838*x**12 - 177409918865848988274008323987508058221*x**11 + 317553455470007336526195832769356594745*x**10 - 6741958807805473103041921028774102156*x**9 + 196231061867387846560308288938031028856*x**8 + 225310188760333755871133730481999560635*x**7 + 39114557004759887411790720245202940968*x**6 + 314242837455720452318000606011119103142*x**5 + 177905507644891522142111942733903774887*x**4 - 258653385787047546556625076790715434004*x**3 - 149927724937383561949353847868212606057*x**2 - 49270775291382477095377877815814551939*x + 176934557189303823953245156086357596867,
    lambda x: 17785626125600180987*x**16 - 68438067879541516418086818983252046686*x**15 + 1232863896613783506602005284217729755*x**14 + 42667830809143825376895770456436632977*x**13 - 4616291169416290699275147934336598718*x**12 - 11918939535167095042045140261971407376*x**11 + 58712983826688440242683931142422822917*x**10 + 49624707074164252406625999617048442005*x**9 - 21043066265085458289032879527389755637*x**8 - 64784446919240330287732413566446566723*x**7 - 34554950792814697650493035669351010825*x**6 + 2330896254435687291716031104985875756*x**5 + 15576083897223557418501484574439678388*x**4 - 39085763943328213655171101083159696160*x**3 + 58826830810048480503145896317642367767*x**2 - 67402332550860395319599822550137306307*x + 7222789046145818967498004078910770603,
    lambda x: -11545323918808458648*x**16 - 42969151411751701726158425047168970491*x**15 + 43682093727960126963522862985006165469*x**14 - 17383825963868975094938598605545674692*x**13 + 17007604084745354060272395247372943106*x**12 + 38332130268694646991664649879227966968*x**11 - 54750960902840137645899159989600302750*x**10 + 65114172257795777169217701898345100261*x**9 - 7416914002524894997967281751598144325*x**8 - 30123608046633276032845028532438160901*x**7 - 67608744769650013305991351496752811863*x**6 - 58603558451835698916868603869075193086*x**5 - 25990583520089834458667048073668445813*x**4 - 39806744620997161017360007054678123282*x**3 + 53947273064812751379162898919246599941*x**2 - 51081121913209678345237911836106371582*x - 41759839642573198534167946630598524925,
    lambda x: 8725360072272666090*x**16 - 5913155235364199382268092353290310580*x**15 - 11112567438555301804445729367094030312*x**14 + 4855980993364457582636238946823933503*x**13 + 12330382206185432709201185565311442074*x**12 - 4961818728540525693420089397657846622*x**11 - 8342446808738778143857695283564636789*x**10 - 11651785966426150191480732004322669786*x**9 - 1310175363016768989844164972719050864*x**8 - 5864365606366843350730192956143997378*x**7 + 3801766065481524168713079392746333867*x**6 - 11218674205159637379322515354678178886*x**5 - 3192131496001084505995214665300735448*x**4 - 385732639682223217757117863068071384*x**3 - 8079232175755916490900255202155114166*x**2 + 3218877303080268722958660163492900661*x + 7792540110945790878478452414055848319,
    lambda x: -7579038256921038458*x**16 + 135003312103265030613173322165205236344*x**15 + 214996870299785292166719763627578595853*x**14 - 142886949340585999341253646167880496743*x**13 + 86058883542821437611199192116716752457*x**12 + 130656737245289049489823022480045879223*x**11 - 196691243225736368279609888943521518507*x**10 - 118880869877522954925412065072032087533*x**9 - 158174829526453772221378781665665664987*x**8 + 133846893183119250850386217740806270928*x**7 - 286582795147418488544465606425962988058*x**6 + 31872416852446756500554566026257680414*x**5 + 273702372018418685089163342107879854769*x**4 - 129947409194325842840605172345629527092*x**3 + 34867548911736644990896439847002263848*x**2 - 320466674481053506126390278702053828976*x + 313509983334655869809815829867511605320,
    lambda x: 16248035746673159442*x**16 + 201679500401512642734513704860185222201*x**15 - 115212880192269254962036423385569915458*x**14 - 151460491430220098073655705958397630861*x**13 + 223895793305507660495741817814594844697*x**12 + 92181223856255013978108787813396676843*x**11 + 117710976480247464007259381740456519088*x**10 + 148501386400619641713135888038294412218*x**9 - 228102818208210306107612860125287202270*x**8 - 62318659190228005799475371861595970218*x**7 - 181050387277137686895678322666732118997*x**6 + 36199290817719275527146429392428795435*x**5 - 93175011130687511905313809511803519405*x**4 + 41612319964461064857925614462966636845*x**3 + 3271847349803477440252764345835791606*x**2 + 226369087843651582502998466833951026495*x - 106247806007764255286475969754212060472,
    lambda x: 10862539730610837494*x**16 + 166870047722992928649188182102330990391*x**15 + 260454743094168825188655730972390196712*x**14 - 238886091600309222981162517259190221209*x**13 - 121338346677133143669129732797084897819*x**12 - 17748247303283689735309230201482706111*x**11 - 125287440565314223991284365768812608930*x**10 + 257362671928451934240857062139711715751*x**9 - 283060948854953112467869319019255578879*x**8 + 281698004510687242431675716294609458699*x**7 + 281082887108012051861704320475749350433*x**6 + 34458717682500858694298008597002245490*x**5 - 144771306691109249740623374851827901878*x**4 + 69364886209421534641499069479004651304*x**3 + 223419557504416773896109551182384778788*x**2 + 155407846779462762765209718913262278593*x - 94299683756455943916625519763071490071,
    lambda x: -16207909608988638988*x**16 + 136254850025634363006360672026960304056*x**15 - 119288465962872853871395129246983440816*x**14 + 146245852654230655705262382901422127247*x**13 + 115315521056703708998357992238892252208*x**12 - 36548627005209380152004276110350472499*x**11 + 109868344727151599531560706053482300819*x**10 + 15418211015992293722166972304486282675*x**9 + 62444221732557334698050885511450446135*x**8 + 19752099681885421186154023350190832520*x**7 - 146287248427165093851564697465320192007*x**6 + 117174192257623498556856418370171574965*x**5 - 121858569394261850262980921568922114359*x**4 + 118839827376547737734740359053172860131*x**3 - 120118942888652575021877617614134531853*x**2 + 90468717381643065091830760914145264120*x + 7766737089630039147293577289842794848,
    lambda x: 8815265029275498172*x**16 + 59770695876892290336267036577396194456*x**15 + 83042609464636837422671145833248287061*x**14 - 34083106692740828625923853525302195424*x**13 + 115230737749216064467226964915171380596*x**12 + 60638507398981429271333179563404139170*x**11 + 47870909557425747731962150766243104171*x**10 + 66654456639288521395694162762174564977*x**9 - 10280645749643516961716354842070944700*x**8 - 87151438856725530812507227724556730510*x**7 - 78606774466095022487060081591111940511*x**6 + 17391335018689744878234538295279671949*x**5 + 16769735021025845708792995088573066533*x**4 - 33492827804904131365420908225343231311*x**3 - 50531089838196464582380974259891416893*x**2 - 26561450237176579947814911441006042985*x + 94990440021939257379854740086520324588,
    lambda x: 14549453715583576903*x**16 - 171377188124991867873818450126849371617*x**15 - 103127430231240217662117561906478178347*x**14 - 137892573196079485775232364686107668673*x**13 - 58781831986224581825246754646167995705*x**12 + 45103100477845192957263417437127528663*x**11 + 78470010296211370100302474355027103860*x**10 - 184449325334832245490935242770054642472*x**9 - 101086208179396694633742637024534647418*x**8 - 187957741802552359089663253974931604511*x**7 + 174023508488392464664440643798884847119*x**6 - 137637126589403692125715589222896858999*x**5 + 122729410713859335533186018806306916986*x**4 - 148219605840529123774770643743387451289*x**3 - 180227792110108532228837889734497286722*x**2 - 66338078739879209595312110471593646850*x - 110035094853851701041426812659727306200,
    lambda x: 16154809215815899109*x**16 - 76766066800502971347262021999519718369*x**15 + 87204716007097820641793481513328937927*x**14 - 30508725462548183804347769147845020130*x**13 + 35070698964482321833741590401116373697*x**12 + 5581522958913175160699932286535936446*x**11 + 18891403401752629123869269518070510436*x**10 + 64824107022928994142718534703494857129*x**9 - 4197452058528981747184891548891541741*x**8 + 73157812215303694011298368031219891378*x**7 + 22817816254888927896619184773261106387*x**6 - 86492847201683470300287339688311890946*x**5 + 48653586121978337016277562590525399844*x**4 - 45282029023845537276679663877859175543*x**3 - 50970929332429489344516118005741381635*x**2 + 34381876861099930227217722319157300810*x - 72272806044212311108033277122750131653,
    lambda x: 12463821256946283563*x**16 - 70321321915220200041807460872840193059*x**15 + 73349847460211577171117789707799348301*x**14 + 45589975128265674686923656585801147966*x**13 + 84488991392660743555407383042700955760*x**12 + 24918875299303316870638662274133118467*x**11 - 30530759462075287732708490537310523750*x**10 + 12709500435933438918952496624564019042*x**9 + 38743520360090212836288598415555434925*x**8 - 66497893115202385939353360312435091461*x**7 + 59206573860945754428470351804821611747*x**6 + 58944153465167061505004597920992231321*x**5 + 15116195257429140323135681762501756680*x**4 - 97614865428250747542143462883511155799*x**3 - 35341796223578340629779469487127323027*x**2 + 51258115378730509499540623605428182024*x - 84085872204266886090420127027162551496,
    lambda x: 6322859967256120582*x**16 + 72629259044568243709265873522516413503*x**15 - 40594859692913882342835149624800513756*x**14 + 140413339730076686686429006861051788148*x**13 - 53822102490037073435031564980559617336*x**12 + 54751635231417846637864571700165011450*x**11 + 164867927047065556907904476033655321190*x**10 + 157011325726162476843583439859508334250*x**9 + 201304833208315484595785796888046284626*x**8 + 49031949827375909758816382954808343445*x**7 + 136092168845466375731636207812792379367*x**6 + 72291944960652434354747631536410609603*x**5 + 130612821940302367131226279877865030357*x**4 - 140844270483815426393949254497245700147*x**3 + 64426365991788524125807506663656709592*x**2 + 101616296144920579526865444156102855397*x - 23276337623052557283980693011357761368,
    lambda x: -10050456793762207544*x**16 + 6733931524826857092751727548520740135*x**15 - 10118956547237519528300048299990507837*x**14 - 132825726583283647441021799149572511*x**13 - 4644720994909387347879529274075624012*x**12 - 1837232998312427528095401650136377678*x**11 - 11748435225507644343854507217351567302*x**10 + 5666468443903563016067614958257307902*x**9 - 2224777356028021049361808270996577758*x**8 - 4195812121284733520004560592139247125*x**7 - 915245031526388272889931873646180996*x**6 - 7775941331381433941370001133720407395*x**5 + 6588538396582289695598101631151125536*x**4 - 9180264531430869004811422032428143129*x**3 + 7074782385495611846315974545853589763*x**2 - 7850341602787054263763967177553938483*x + 10972971964674631910651981179681300253,
    lambda x: 6783261132797195933*x**16 - 5315233885043974475525594611145477666*x**15 + 1314672795075121883926360449610209380*x**14 + 6929073611831556407326913402879032765*x**13 - 7220404549091418535062107308963176635*x**12 + 3508187885806965617317257772190252909*x**11 - 9015827443038138887659708590410630804*x**10 + 13514523569001693001335428889585211478*x**9 - 10025929242587521912210560567597455365*x**8 - 3457395388044062085458472013513563374*x**7 - 8017061993173473382239753641462985792*x**6 + 11875994504801385284335290468299982864*x**5 - 3500884317543680638030369588710706196*x**4 - 1506449891706600301404050126885124775*x**3 + 8019932465572586868756334365179182606*x**2 + 9073177726086797636068691127880018799*x - 1420135943226131541580816469366499494,
    lambda x: 6218888926184322089*x**16 - 19865092430417870864271423730003357642*x**15 + 30244769445939814210086439082370560316*x**14 + 40271230158116595346797002454739068849*x**13 + 42212416615130016754776360958427842269*x**12 + 29178204784224606941393654638560152693*x**11 - 47322795656499196186503588787480074470*x**10 - 23579313653305303951370640014503122976*x**9 - 3980313786421360285705965895789373808*x**8 - 41039919822973106824911573339532122281*x**7 - 2586780059859740680555008760929605388*x**6 - 47299167746982722510521035207747830544*x**5 + 17963501072765687267637923735611433461*x**4 + 9214894662076346221383391194736137781*x**3 - 16977860869391956178462742702880989995*x**2 + 15316235730848807997584495118245456839*x + 30745376742026131612916328126501014432,
    lambda x: -17293875146745899695*x**16 + 105487353636599093683047607947319694084*x**15 - 82434797364684893410036859211459400049*x**14 - 61628247273040608022037000552619137568*x**13 + 75852202757026187409519596821173517350*x**12 - 42916844741155660072706134558876242845*x**11 - 101339961234605056757351408741674994939*x**10 - 79707035270010181794405975971824815545*x**9 + 90957818729657981985118670509278499929*x**8 + 94360821249262620506725943932110184306*x**7 + 71142576449187668499539897853590694170*x**6 + 24177920437921921489860775042470750584*x**5 - 86098360773254446034181062124035475733*x**4 + 58322757072189483267092729509961926891*x**3 - 77857605607736844474561545598910865160*x**2 - 54899876854553710460202311856495475988*x + 42681452258863950422248035634244481429,
    lambda x: 14093079749684153825*x**16 + 93704982061943974150213041754776840608*x**15 - 89285399205999234389254186966935898284*x**14 + 2003283059755230256923765200659057440*x**13 - 18914954308377403399191022121577140121*x**12 + 93230339416717690795912828200298468105*x**11 + 107392101561690419047420453164556183621*x**10 - 21206430681318465928638296765471938246*x**9 - 18715880472033957264090814593570847256*x**8 + 23284355803818582758809655568716481512*x**7 + 40354200082707699456516363929883323677*x**6 - 32975924501598130892546250531820194309*x**5 + 30377527908206847963614443582677554833*x**4 + 62930465891076236768786474812916470782*x**3 - 76849016843247023597395004071048817389*x**2 - 113723315675728840400486091127259268145*x - 52425744465534109031142924159028384890,
    lambda x: 1819333684148875614*x**16 + 5248141005812696304603058711795030673*x**15 + 41261576774353053028434304059700065877*x**14 - 5252002166950947815074572944102203362*x**13 + 43050511163933216384850985137852052289*x**12 + 23552743530884566540585001533987852515*x**11 - 27319707173061613484891942723166822987*x**10 + 749512252846916246750358865459530028*x**9 - 46831737778697149123875248964387114466*x**8 + 16916792705272341629818758715988973160*x**7 + 48316869907391294132797451047868318289*x**6 + 6824331682474811814731355860041180208*x**5 - 32271893683178459010209564274490873576*x**4 - 44455946599830234098365897686406704244*x**3 - 46670379701637033134957762625682295997*x**2 + 37623850690417515687356054240047952457*x - 9262973983766398079736839028918437038,
    lambda x: -7827480105209468776*x**16 - 4596820834300665397411275128545772950*x**15 + 6612277891485116285880889965222672967*x**14 + 4753667647575445520992753956860077041*x**13 + 10631800613676602382428278554146838680*x**12 - 2390351738044808477132611596775837752*x**11 - 4991514458665393628511362150792522129*x**10 + 9766430092794140288872395923388939976*x**9 + 4117320413053308554303322680606286155*x**8 - 7000454625841790139704139910799455964*x**7 + 3359117285162220970876244153239786890*x**6 + 3270655014803393927519997826859613798*x**5 - 7508009443936521646976777894470494189*x**4 + 3054682123536878878327503001223999086*x**3 - 8774709089950100573915378054846154364*x**2 - 3844756295762233797105357275132523577*x - 8240126548289528120220859407237670908,
    lambda x: -10841654172016639059*x**16 + 13546221767465121732846007338963817946*x**15 - 8136824119750071948774050124173475687*x**14 + 17715816929888326954397592639426629618*x**13 + 436576355103627112736246055714236676*x**12 + 15492448055678882018116791999670155497*x**11 - 18354068568466837680018466011649614924*x**10 + 11918494527442903491716988035750511417*x**9 - 218502123299984118730689943206697950*x**8 + 8461247343732872688780310737706594511*x**7 + 4997929422505582188625606982276064524*x**6 - 7457247352434840427806509174517106299*x**5 + 2951894165712144817560868746582833333*x**4 - 4834538460445493199904909903410806587*x**3 - 17214417625827928777931647118522588106*x**2 + 14755120087692176930657941345778494849*x + 13060416238835978277749456647044347481,
    lambda x: -10151998049633407027*x**16 - 84986097287888187336958855924179536941*x**15 - 130063125023824083063447597565213635653*x**14 + 37363068443322064254372241933393640797*x**13 - 149688255448789375914084974522583972176*x**12 + 113096778454858636208875619623322688463*x**11 - 12450884522150157313874937645859366592*x**10 + 77040650993648624238510285791900888995*x**9 + 97568943270559349599258735672627156197*x**8 - 65888371006269118759656282676429828133*x**7 + 137296140112547296265625155747154772409*x**6 + 146226295568102064145490542918707951056*x**5 - 37903265295941092897161472806577507255*x**4 - 4068515564101752906679068724773558347*x**3 + 422569991132581747660984963953104514*x**2 - 108367305905585367746488466103855382425*x - 98846982959238510702634294825423564428,
    lambda x: 4021700324268753815*x**16 - 65461236372324592501681860938435829531*x**15 - 13523428148407936327851633266552162317*x**14 - 209830699624677126324380054387035830537*x**13 - 101248172834467459297666469748556659781*x**12 + 267522540788140183043655569473232363818*x**11 - 11232165878953426773421826505807881435*x**10 - 108563114520013423426739617659643917109*x**9 + 265040712017833474493692472967057910301*x**8 - 225847173130280019621849479955877265304*x**7 + 152391643962207415846037077002012883441*x**6 - 190824042222042421235586848809154849870*x**5 + 92789238294433448718768269058878339398*x**4 + 144858239113728655849367399430078767048*x**3 + 224025321712075422720213629750820492469*x**2 - 105737211482354045301587181174132570657*x + 103912095327334237953240211955464683708,
    lambda x: 1578155806669080788*x**16 + 4729239042567396364467445135764822744*x**15 - 12032348551708598000769950525966621624*x**14 - 2406206408521447352215269328298318913*x**13 - 44037844568262287813558558521382991207*x**12 + 50831389088394075985950250291258433582*x**11 - 52982818063028168965214582640812304*x**10 - 49046043937887103699122453060213359011*x**9 + 34186867757771762302105831449650475797*x**8 - 34003399885510711161360089237759793090*x**7 - 38026206455373932156179561996122575819*x**6 - 17695875807593028674136056493759398307*x**5 + 47763746406337633685434496514388490782*x**4 + 34261989253121596521148203233955086201*x**3 - 13403418523103451381973071167760745523*x**2 - 33306127639418616036713120118117708368*x - 646762574920154381093248906277717714,
    lambda x: -7030584535921167743*x**16 + 76479535327403091417861846703939209635*x**15 - 18940676738128254440398127751552381511*x**14 + 156231181192509761479775428933678284513*x**13 - 136921327568894868306540194824296788907*x**12 + 83630946879073083306621727865960353923*x**11 - 22100904747903703940562510055989452999*x**10 - 26074333962047710265305614480791012518*x**9 + 81406226308411090178149936486112804458*x**8 - 62747015635235933362066475302662598969*x**7 + 178055587373472554361211176911332033781*x**6 + 23682693176591251571210097071119556789*x**5 + 134857206289519843245648986564841741207*x**4 + 166033957550560923569717116043438781817*x**3 - 76951726449068301516966335676333670959*x**2 - 13489940867106649220020889558219354280*x + 168301640050052037359810508236646160300,
    lambda x: 10818541742160428478*x**16 + 114066679534525091822012082587547784484*x**15 - 180601194988567212786901667709735940861*x**14 - 159720054918785216538258960848641359196*x**13 + 182417597102206357991071213130416907610*x**12 + 63593885902072851099465459207883028741*x**11 + 184845980591813751048337092962040760877*x**10 - 6474357048255098392312500066767174561*x**9 + 173274176699884337245790612086722437132*x**8 - 176330348940999465366270379301687904506*x**7 - 170096228049554389407771186635621190634*x**6 - 108045704646882696918557128646675781106*x**5 + 45448858789782542158462293834510181965*x**4 + 48993534548779596758300575781185396729*x**3 + 151848618388269177555088918761490601943*x**2 + 33572442988673678325721521617746678593*x - 109148639005621835735233709548653498261,
    lambda x: 13206479430865627681*x**16 - 125168145904774458142443437075721780930*x**15 + 124783679582724248319264668989541968842*x**14 - 89681758909264918322885592648505754700*x**13 - 91859256942990228547461574958339304598*x**12 + 144718751685933592726982089125616225553*x**11 + 134663138568073265289358997824076597116*x**10 - 106891597743564115930108364708705884420*x**9 - 108635915962554616341887647849680650067*x**8 + 158899932451312173749278126966494344921*x**7 + 76460495259670544770992468427725210982*x**6 - 13680844811667401532995163575810325094*x**5 - 12844593222779023844817150043258303785*x**4 - 139796905162516446527386309559844379106*x**3 - 13949357912318912028342356362778798540*x**2 - 98081211323723747475732717602438008607*x + 136065643380763220873728067273078546859,
    lambda x: 5005160072649318138*x**16 - 35585396448632403668277439332855424589*x**15 - 110360275540905379431232273731941553613*x**14 + 29567008997033681510590092494987301254*x**13 + 66747234530078067031832981188545054885*x**12 - 50173618960048112633708047355373116601*x**11 + 56539710277119407226207746528155015704*x**10 - 13800601175213053697045296455076683014*x**9 + 38060743939567899542432804151807452169*x**8 - 36637864343355756083753924327423387157*x**7 + 58279171727586798718633539723428783780*x**6 - 37013999757539367517578116630457627805*x**5 - 77290387703883597140423280406369085399*x**4 - 20323234207710895082506874679599471772*x**3 - 100544133128835622746932112267058287569*x**2 - 554532185302243348725060441904967502*x - 39912022865702114110652931214946503131,
    lambda x: -9657689417758138526*x**16 - 145511730518171677959666986056708947652*x**15 - 187690166628703476941982941357153218322*x**14 + 71457759328425175374882281304675373052*x**13 - 119896669230155953477389214818884205023*x**12 + 183070764855706696080237925248493137836*x**11 - 86779247934315556400682906624611601564*x**10 - 25858929950879667371353087173466127284*x**9 + 14356466714102138983945766854903005717*x**8 - 227252449702264207910255957032292402225*x**7 - 75812085434166132134994517285321231271*x**6 + 134099841275101076055574825922230720157*x**5 - 194521188389976937700380146442650709724*x**4 - 260252190724894463611819077545511026520*x**3 + 179092006152897859048380870661962756764*x**2 + 231894695860103644217338621006860010290*x + 157205768642602220065510195048622092194,
    lambda x: -9889695002955056764*x**16 + 69645189545172168106126256849856132168*x**15 + 15119515167126906754058919515617240097*x**14 - 25686000124082708391600348830588805986*x**13 + 16726279632051305058799124091675671787*x**12 - 91450142767317217874602131223697007903*x**11 + 33672904809798147353249262672576500457*x**10 + 32887773998332710311443183630560297882*x**9 - 95720086739405059601535387362051751386*x**8 - 49362604416761119316232566374928280990*x**7 + 16709509423056687344108558585091114718*x**6 + 31074802126752048795165141614976586051*x**5 + 55841832433854873151356743498592849731*x**4 + 16375362150806888200117344066111249482*x**3 - 37294641222637500418240594020686802462*x**2 - 90174516415729101103976980680583967775*x + 66857540448671464653614748707245256374,
    lambda x: -3818728192449811294*x**16 + 24782638789434236804136764364547701980*x**15 - 118303163680418992274347195085314798776*x**14 - 103379589350051271267149337649638818644*x**13 + 61741119136062606270694280237232665114*x**12 - 63197300366903511011531199906437400236*x**11 - 65193577429387945599072143835803906205*x**10 - 67362777664557556502161883239065708916*x**9 + 19082765257831259825158105200367562726*x**8 - 60139509237315743349584432966399085173*x**7 - 118186084850517752360446458831008752534*x**6 - 103671639420774847546723788884464109607*x**5 - 118210519667892596453419733641601756384*x**4 + 113370829328143742323759675145988285049*x**3 - 7467885533348941144621230588240054935*x**2 - 31207570848624358181247145661158223565*x + 49110766958408406151995425022607619255,
    lambda x: -6881335651551863410*x**16 - 47260742074596722912013116035618844263*x**15 + 53197411488297913487788530699228215197*x**14 + 71217959285316229160798619438090083427*x**13 + 53673658832711447727908579679917742914*x**12 + 34206606295707371105628649465226315846*x**11 - 23171463500482704319956618280787055982*x**10 - 120220418180518296295711760042777986846*x**9 - 87661565874520115233465749876724212691*x**8 - 13310834048679278741272000821394618627*x**7 + 40205306864138578742934298098986308995*x**6 - 98981883174611062533536692935045958074*x**5 - 70667701255142813004772709402835441499*x**4 - 39156626108885859918680071002664565509*x**3 + 66812238690489819778047644762474696342*x**2 + 40320415059626054340160893408228644979*x + 107643058574785984617298840186306615205,
    lambda x: 3977702663799179755*x**16 - 24189549313809149241114739608491037569*x**15 + 35173904037922364938794203778054291718*x**14 - 97888124297677293708611606008079012128*x**13 + 85114339874261399076018808880780155191*x**12 + 54781953782625696252754043568175103196*x**11 + 10206123556545383994982246559698945295*x**10 - 7789124313168153934545752858537227765*x**9 + 81348907779470266842257361160249077043*x**8 - 81647760760535555704537989819455231165*x**7 - 63197269853439851553289219033011351460*x**6 + 93060244910383459370053793301713939771*x**5 - 109358397569130734089577367411342086611*x**4 - 76313133248788946745139048764625247314*x**3 + 111832045203338662789806277337340750027*x**2 - 90688991722894763827943690104391183796*x - 23510188560639101530535192058396785501,
    lambda x: 4862210655465979359*x**16 + 70877975742469664027857246764735433963*x**15 + 190780080800167166521712575228881496890*x**14 - 193887521429054198767813155182183148752*x**13 - 117167738806797225813121559333786362923*x**12 - 96510476740224664120323718013968411889*x**11 - 190467569509079532101979288255167985556*x**10 - 235199162842145764856351809519501821116*x**9 + 56085151408283255373270501572954835108*x**8 - 43258409811389086685259122154845321547*x**7 - 51664568624071065847108236057388503986*x**6 + 256650466723031610442859699476087040531*x**5 - 247679308363138891472615055829883677278*x**4 - 253943488361539475839404690332078350544*x**3 + 247408932110928078134561509185439393979*x**2 - 149983491872967547449084229183665217913*x + 171856488729694190427645254900133565898,
    lambda x: 253286317013082961*x**16 - 4042515988467291284562820749749052801*x**15 - 41473719271560882674788211574506221617*x**14 + 75857940572192211543282082625802182149*x**13 + 248093373963768163265328261269074928114*x**12 - 252186187974545074574341462892659268000*x**11 + 273356105044278388216597890494201497488*x**10 + 201579832238267818752802997169140107743*x**9 -129692778873181280074277882538820129175*x**8 + 67664159601742436544576540228334065127*x**7 - 32421324434952271255913926234406339466*x**6 + 256708380536843030726236109487502940652*x**5 + 181248122282886800197826566153687030501*x**4 - 36989261005767981569554361221690945802*x**3 + 271692579279859491583810166792524288008*x**2 + 72224794737171460128017621980553968769*x - 74170628299460371862801549363142411056,
    lambda x: 6751835056037632930*x**16 + 94131408434005259388156464549612039076*x**15 - 23436583025177056247114222275602039928*x**14 - 104133618672471483348443535086219124687*x**13 + 35042473228232814448615623388051616170*x**12 + 207260567062799948574759379139809337132*x**11 - 245773468323434618767868799062139233544*x**10 - 82499196428175507723759316138194481498*x**9 + 117959910204557324214592384220678945407*x**8 - 249760598731126988900010152658341990117*x**7 - 179521770390746144371541881019937634319*x**6 - 54180168040459049639442182553559789818*x**5 - 180104166600302637558556781869605899900*x**4 - 218279666905192942041828442676311930087*x**3 - 168508978531632718477734487921855092360*x**2 - 54063267895985620231558058923189553905*x - 201068230063131559746111480867222235714,
    lambda x: 15632129288115972793*x**16 + 5531679265421934604676297791216692820*x**15 + 5254881363121972013281681014373831608*x**14 - 1640555876147204975519067214389036428*x**13 + 1809257968300152950960828801196256306*x**12 - 4241899763000076389714440222744442187*x**11 + 5275642112082336222310421109704827439*x**10 - 5259052293449850246028353616461371395*x**9 - 3527001177612210023596240280885179044*x**8 - 2949967733684882946992360901624318542*x**7 - 1449825027747408088724061233096459345*x**6 - 4040909213678056904334897682266441241*x**5 + 4566003232236628765791846413622147916*x**4 + 4730150921050001241902596659925105235*x**3 - 3454333079046777221049015613731816871*x**2 + 2652955729097729070610104106977490395*x + 212989510304826740697987860213947515,
]
try:
    flag = []
    for y in L:
        this = int(input(">>> "))
        fine = int(input(">>> "))
        assert this == fine
        this = y(this)
        fine = y(fine)
        assert this is fine
        flag.append(this | fine)
    print(bytes(flag).decode())
except:
    print("Nope!")

The first notable observation is an array \(L\) containing \(38\) polynomials of degree \(16\) which is used to calculate the bytes of the flag.

Solving

Analyzing this file in more details, it appears that we need to find, for each polynomial \(y\) in \(L\), two numbers this and fine such that

this-is-fine.py
46
47
48
49
assert this == fine
this = y(this)
fine = y(fine)
assert this is fine

What is interesting is that the first check adds a constraint that forces the numbers to be equal (i.e., this = fine). So the question now becomes: what must this number be ?

After experimenting with the second assert test, I found out that it passes only when y(this) and y(fine) refer to the same object in memory. How can this occur ?

Spoiler
When y(fine) is a small enough number, specifically when y(fine) \(\in [-5, 256]\)

After some research, I came across this comment on this answer, which I found while reading the Stack Overflow question on the subject. The question provides a clear explanation. Further investigation led me to the Python C API documentation, which confirms that the result of y(this) must be a number between \(-5\) and \(256\) for the test to pass.

This implies that for each polynomial \(y\) in \(L\), we need to find an input value \(x\) such that \(y(x) \in [-5, 256]\). Since \(y(x)\) is used directly as a byte of the flag, it must be in the range \([32, 127]\) to be a valid printable ASCII character. Given the small size of this range and the assumption that there is only one solution, we can find \(x\) for each polynomial by brute-forcing all possible outputs \(c \in [32, 127]\) and solving \(y(x) - c = 0\). If a solution is found, it means we have the correct guess for \(c\). By repeating this process for all polynomials in \(L\), we can recover the flag.


Here is the final Sage script to solve this challenge :

solve-this-is-fine.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# [...]
# L defined above

from sage.all import *

R.<x> = PolynomialRing(ZZ)
for y in L:
    for c in range(32, 128):
        roots = (y(x) - c).roots()
        if roots:
            print(chr(c), end="")

FCSC{dqcb7dVhWa9hunProTfJMsurwboPkT3V}

Code breaker

PointsDifficultySolves
29194

Description

Code breaker

Code

The source code was provided :

code-breaker.py
 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
61
62
63
64
import os
import json
import numpy as np
from galois import GF
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad

def random_matrix(Fq, m, n, rng):
    return Fq.Random((m, n), seed = rng)

def random_permutation_matrix(Fq, n, rng):
    p = rng.permutation(n)
    P = Fq.Zeros((n, n))
    P[np.arange(n), p] = Fq(1)
    return P

def random_grs(Fq, k, n, rng):
    while True:
        a, v = Fq.Random(n, seed = rng), Fq.Random(n, seed = rng)
        if np.all(v) and np.unique(a).size == a.size:
            break
    j = np.arange(k)
    return a[None, :] ** j[:, None] * v[None, :]

def generate(Fq, G, rng):
    k, n = G.shape
    S = random_matrix(Fq, k, k, rng)
    P = random_permutation_matrix(Fq, n, rng)
    return S @ G @ P

if __name__ == "__main__":

    q = 2 ** 32 - 5
    k = 20
    n = 64
    Fq = GF(q)
    rng = np.random.default_rng()

    d = {}
    key = []
    for i in range(256):
        b = rng.integers(0, 2)
        key.append(int(b))

        if b: G = random_matrix(Fq, k, n, rng)
        else: G = random_grs(Fq, k, n, rng)

        C = generate(Fq, G, rng)
        d[i] = [ int(_) for _ in C.flatten() ]

    iv = os.urandom(16)
    key = sum(b << i for i, b in enumerate(key)).to_bytes(32)
    flag = open("flag.txt", "rb").read()
    E = AES.new(key, AES.MODE_CBC, iv = iv)
    c = E.encrypt(pad(flag, 16))

    d["params"] = (q, k, n)
    d["enc"] = {
        "c": c.hex(),
        "iv": iv.hex(),
    }

    with open("output.txt", "w") as fp:
        fp.write(json.dumps(d, indent = 4))

Along with its output.txt file

The program encrypts the flag using AES-128 in CBC mode. The goal is to recover the key, which is constructed as follows:

key = sum(b << i for i, b in enumerate(key)).to_bytes(32)

Given that key was already constructed here:

code-breaker.py
41
42
43
44
45
46
47
for i in range(256):
    b = rng.integers(0, 2)
    key.append(int(b))
    if b: G = random_matrix(Fq, k, n, rng)
    else: G = random_grs(Fq, k, n, rng)
    C = generate(Fq, G, rng)
    d[i] = [ int(_) for _ in C.flatten() ]

In output.txt, we are provided with the contents of \(d\), which include the values of \(C\), the ciphertext, and the IV used. Therefore, we must start from \(d\) to recover the values of \(b\), giving us the key and thus the flag.

Note that, because of the function

code-breaker.py
25
26
27
28
29
def generate(Fq, G, rng):
    k, n = G.shape
    S = random_matrix(Fq, k, k, rng)
    P = random_permutation_matrix(Fq, n, rng)
    return S @ G @ P

the matrix \(C\) is only a random permutation of the matrix \(G\).

Solving

After a quick look at the script, I spotted the two important lines that will guide our entire solution.

code-breaker.py
42
43
44
45
46
47
48
b = rng.integers(0, 2)
key.append(int(b))

if b: G = random_matrix(Fq, k, n, rng)
else: G = random_grs(Fq, k, n, rng)

C = generate(Fq, G, rng)

From this, we see that \(b\) takes the bits of the value of the key, which is what we’re looking for to decrypt the flag. Depending on its value (\(0\) or \(1\)), we generate a different matrix. The goal of this challenge is then to find a way to differentiate between these two cases to recover the correct value of \(b\) for this output.

By taking a closer look at these functions, we can see that the functions random_matrix and random_permutation_matrix, as their names suggest, only generate a random matrix and a permutation matrix, respectively. Meanwhile, the function random_grs generates a random special matrix with a particular form.

To be more formal, we can find through some research that it is a Generalized Reed-Solomon (GRS) code matrix representation.

To determine the value of the bit, we need to distinguish a Generalized Reed-Solomon (GRS) code from a random matrix. The Schur product (or hadamard product) is such a distinguisher for codes with algebraic structure.

More precisely, as stated in Distinguishing and Recovering Generalized Linearized Reed–Solomon Codes (2023, Section 4.1)

The main observation for distinguishing a random linear code in \(\mathbb{F}_{q^m}^n\) from a GRS code \(\mathcal{C}\) is that the squared GRS code has dimension dim(\(\mathcal{C} \star \mathcal{C}\)) = min(\(n, 2k − 1\)), which is small compared to the expected dimension of a squared random linear code

Multiplying \(G\) by \(S\) and \(P\) does not change this property because it only applies a permutation to the matrix, which does not alter its algebraic structure.


Time

It takes about 1 or 2 minutes for the script to finish on my machine, mainly because the output.txt file is quite large, I suppose.

Here is the final Python solve script

solve-code-breaker.py
 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
import json

import numpy as np
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from galois import GF


def is_grs_disguised(flat, k, n, Fq):
    M = Fq(flat).reshape((k, n))

    products = []
    for i in range(k):
        for j in range(i, k):
            products.append(M[i] * M[j])

    P = Fq(products)

    # Compute matrix's rank
    P.row_reduce()
    dim = np.linalg.matrix_rank(P)

    return dim <= 2 * k


with open("output.txt") as f:
    data = json.load(f)

iv = bytes.fromhex(data["enc"]["iv"])
ciphertext = bytes.fromhex(data["enc"]["c"])

q = int(data["params"][0])
k = int(data["params"][1])
n = int(data["params"][2])

Fq = GF(q)

key = []
for _k, v in data.items():
    if _k not in ["enc", "params"]:
        if is_grs_disguised(v, k, n, Fq):
            key.append(0)
        else:
            key.append(1)

# Recover flag bytes
key = sum(b << i for i, b in enumerate(key)).to_bytes(32)
print(unpad(AES.new(key, AES.MODE_CBC, iv=iv).decrypt(ciphertext), 16))

FCSC{949f94d858ef6ad1333164d796a0d777fd82f9155ece7d6fad68c0b992f0e7af}

Little d - Big trouble

PointsDifficultySolves
410★ ★37

Description

Le standard FIPS 186-5 impose dans la génération d’une clef RSA que l’exposant privé \(d\) soit plus grand que \(2^{(size//2)}\). Le standard précise d’ailleurs que dans le cas extrêment rare où \(d\) serait plus petit, il faut générer de nouveaux nombres premiers \(p\) et \(q\).

Testez votre chance en soumettant un de ces cas extrêment rares.

Little d - Big trouble

nc challenges.fcsc.fr 2155

Code

The service’s source code was provided :

little-d-big-trouble.py
 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
61
62
63
64
65
66
67
68
69
from random import randrange

import gmpy2


def correctness(p, q, e, d):
    if e.bit_length() > 256:
        print("Error: e is too big.")
        return False

    if e < 65537:
        print("Error: e is too short.")
        return False

    if not gmpy2.is_prime(p):
        print("Error: p is not prime.")
        return False

    if not gmpy2.is_prime(q):
        print("Error: q is not prime.")
        return False

    n = p * q

    if p.bit_length() != q.bit_length():
        print("Error: p and q have not the same bit length.")
        return False

    if p.bit_length() < 512:
        print("Error: prime bit length is too small.")
        return False

    if n.bit_length() != p.bit_length() + q.bit_length():
        print("Error: public modulus has not the correct bit length.")
        return False

    if abs(p - q) < 2 ** (p.bit_length() - 100):
        print("Error: primes not compliant with FIPS 186-5.")
        return False

    for _ in range(4):
        m = randrange(n)
        if gmpy2.powmod(m, e * d, n) != m:
            print("Error: message is not of order e*d - 1.")
            return False

    if d.bit_length() >= p.bit_length():
        print("Error: private exponent is too big for this challenge.")
        return False

    if (e * d).bit_length() - p.bit_length() < 128:
        print("not safe enough for this challenge")
        return False

    return True


if __name__ == "__main__":
    try:
        print("Please, enter your lucky RSA key as : p, q, e, d.")
        p, q, e, d = [int(input(">>> ")) for _ in "pqed"]

        print("[+] Testing correctness...")
        if correctness(p, q, e, d):
            flag = open("flag.txt").read().strip()
            print(f"[+] Congrats! Here is the flag: {flag}")

    except:
        print("Please check your inputs")

First, before solving, I must admit in plain sight that I spend (and lost 😿) way too much time thinking there was a flaw in the way the checks ware made in the correctness function, but there isn’t, because it’s not the goal of this challenge.

I probably should have read the description more carefully this time. That being said, let’s solve this challenge!

Solving

Our goal is to find prime numbers \(p\) and \(q\) such that the corresponding private key \(d\) is “small”. If you know a bit about RSA, you should know that this happens when the chosen \(e\) is very large (about the same order as \(N\)), which is how challenges that aim to introduce Wiener’s attack are created.

The main difficulty here is that, as we can see in the first lines of the check function, \(e\) can’t be too big, so we can’t use the easy way.

little-d-big-trouble.py
6
7
8
9
def correctness(p, q, e, d):
    if e.bit_length() > 256:
        print("Error: e is too big.")
        return False

Let’s get back to the basics.

We want \(d\) to be small, and we know that \(d\) is calculated as

\[ d = e^{-1} \mod \phi(n) \]

with \(\phi(n) = (p - 1)(q - 1)\).

It means that in most cases, \(d\) is of the order of \((p - 1)(q - 1) \approx n = p \cdot q\), but because of the challenge, we need to loosely verify \(p, \ q \ge 2^{512} \ \Longleftrightarrow \ n \ge 2^{1024} \) and \(d \lesssim p\). So this is not going to cut it with what we’ve said before.

At this point, I was stuck. I was until, while reading for the \(100\)-th time the RSA Wikipedia page, I finally realized that the following information, which I already knew, would be relevant this time. The private key can also be computed as

\[ d = e^{-1} \mod lcm(p - 1, q - 1) \]

with \(lcm(p-1, q-1)\) calculating the least common multiple of \(p-1\) and \(q-1\). What it means is that if \(p-1\) and \(q-1\) share common big factor, then their \(lcm\) will be not much larger than them.

To generate such numbers, we will try to build prime numbers of the form

\[ \begin{aligned} p &= 2 g a + 1 \\ q &= 2 g b + 1 \\ \end{aligned} \]

with \(g\) a big common number and \(a, \ b\) two small numbers. If we find such primes, we know that \(lcm(p - 1, q - 1) = 2 g a b\), which is small enough to brute-force a number \(e\) that satisfies the conditions and allows us to calculate \(d\) such that it also satisfies the conditions.

Here is the final Python solve script that implements those steps :

Note

The value of \(N\) in this script is arbitrary. It is a good value because the script finds primes fast enough and because the primes it generates satisfy the required conditions to proceed.

The script runs forever ?!

Because there aren’t many 10-bit primes, about half of the time the script gets stuck in the first infinite loop. Just re-run it a few times until it gets out fast. In the right conditions, this step should be instantaneous.

solve-little-d-big-trouble.py
 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
import math
from random import randrange

import gmpy2
from Crypto.Util.number import (
    getPrime,
    getRandomNBitInteger,
)

N = 10

while True:
    g = getPrime(512 - N)
    b = a = 0
    p = q = 0
    while not gmpy2.is_prime(p):
        a = getRandomNBitInteger(N)
        p = 2 * a * g + 1

    while not gmpy2.is_prime(q):
        b = getRandomNBitInteger(N)
        q = 2 * b * g + 1

    if p.bit_length() != q.bit_length():
        continue
    if (p*q).bit_length() != p.bit_length() + q.bit_length():
        continue
    if abs(p - q) < 2 ** (p.bit_length() - 100):
        continue

    break

lcm = math.lcm(q - 1, p - 1)
while True:
    e = 2*getRandomNBitInteger(255) + 1
    try:
        d = pow(e, -1, lcm)
    except:
        continue
    if (e * d).bit_length() - p.bit_length() > 128 and d.bit_length() < 512:
        if correctness(p, q, e, d):
            print("p = ", p)
            print("q = ", q)
            print("e = ", e)
            print("d = ", d)
            exit()

FCSC{bef584620e83319d1e64cdcb57346b1fa3cd1a1b6875aa6591958a3dd7e6cd6f}

Fully Homomorphic RSA

PointsDifficultySolves
415★ ★35

Description

Il est bien connu que la primitive plain RSA est multiplicativement homomorphe : le chiffré du produit de deux clairs est égal au produit des deux chiffrés. À l’issue d’un travail de plusieurs années, nos chercheurs sont parvenus à un résultat révolutionnaire : ils ont trouvé comment calculer le chiffré de la somme de deux clairs uniquement à partir des chiffrés initiaux et de la clé publique. Cela rend le système RSA complètement homomorphe !

Les détails de leur résultat sont encore confidentiels. Cependant, nous sommes heureux de fournir à la communauté un émulateur de ce “Fully Homomorphic RSA” afin qu’elle puisse dès maintenant en explorer toutes les possibilités.

nc challenges.fcsc.fr 2150

Code

The service’s source code was provided :

fully-homomorphic-rsa.py
 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
from Crypto.PublicKey import RSA


def hom_prod(k, c1, c2):
    m1 = pow(c1, k.d, k.n)
    m2 = pow(c2, k.d, k.n)
    return pow(m1 * m2, k.e, k.n)


def hom_sum(k, c1, c2):
    m1 = pow(c1, k.d, k.n)
    m2 = pow(c2, k.d, k.n)
    return pow(m1 + m2, k.e, k.n)


k = RSA.generate(1024)

flag = int.from_bytes(open("flag.txt", "rb").read())
assert flag < k.n

print(k.n)
print(k.e)
flag_enc = pow(flag, k.e, k.n)
print(flag_enc)

while True:
    try:
        c1 = int(input(">>> c1 = "))
        c2 = int(input(">>> c2 = "))
        choice = input(">>> prod or sum? ")
        if choice == "sum":
            print(hom_sum(k, c1, c2))
        elif choice == "prod":
            print(hom_prod(k, c1, c2))
    except:
        pass

First things first, we are dealing with an RSA cryptosystem, and the goal is to recover the encrypted flag that is given to us when we connect to the service using its two other functionalities.

Solving

Let’s note our encrypted flag \(c_f\), such that

\[ c_f = m_f^e \mod n \]

After a more thorough analysis, we can determine that the functions hom_sum let us calculate :

\[ \begin{aligned} c &= (c_1^d + c_2^d \mod n)^e &\mod n \\ &= (m_1 + m_2)^e &\mod n \\ \end{aligned} \]

while the function hom_prod let us calculate :

\[ \begin{aligned} c &= (c_1^d \cdot c_2^d \mod n)^e &\mod n \\ &= (m_1 \cdot m_2)^e &\mod n \\ \end{aligned} \]

From there, we know that we can calculate, with the help of the service, any linear combination of the encrypted flag. How is this helpful ?

Well, having faced RSA cryptanalysis quite a few times, I remembered it was something familiar. But even if you didn’t know, with a quick search as I did about common RSA attacks, you might stumble upon the Franklin-Reiter related-message.

And as it turns out, it is exactly what we need !

The key point here, as mentioned in my notes, is that \(b \neq 0\), which is what the function hom_sum will be useful for. So here is the plan :

  1. Choose two numbers \(A\), \(B < n\)
  2. Connect to the service, retrieve \(c_f\), \(e\) and \(N\).
  3. Using the public key \((n, e)\), compute
\[\begin{aligned} c_{f1} &= c_f \cdot A^e &\mod n \\ &= (A \cdot m_f)^e &\mod n \\ \end{aligned} \]
  1. Calculate \(c_b = B^e \mod n\)
  2. Then, send \((c_{f1}, c_b)\) to the service and use hom_sum to compute
\[ \begin{aligned} c_{f2} &= (c_{f1}^d + c_b^d \mod n)^e &\mod n \\ &= (A \cdot m_f + B)^e &\mod n \\ &= f(m_f)^e &\mod n \\ &= (m_{f2})^e &\mod n \\ \end{aligned} \]

with \(m_{f2} = f(m_f) = Am_f + B\)

  1. Now, we’re in the exact case described by the attack, that we can then use to recover \(m_f\) using the two following encrypted messages
\[ \begin{aligned} c_{f2} &= (m_{f2})^e &\mod n \\ c_f &= (m_f)^e &\mod n \end{aligned} \]

Important

In fact, the hom_prod function is useless here because, as stated in the challenge’s description, RSA is multiplicatively homomorphic by definition. This means we can perform this operation on our own since we know the public key used to encrypt the flag (provided by the service).


Compute Time

It takes this script about 15 to 30 minutes to recover the encrypted flag because \(e = 65537\) is not quite small.

Given this last piece of knowledge, here is the final Sage solve script :

solve-fully-homomorphic-rsa.py
 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
61
62
63
64
65
66
67
68
69
70
from sage.all import *
from Crypto.PublicKey import RSA
from Crypto.Util.number  import getRandomNBitInteger, long_to_bytes, bytes_to_long
from pwn import *

def hom_prod(k, c1, c2):
    m1 = pow(c1, k.d, k.n)
    m2 = pow(c2, k.d, k.n)
    return pow(m1 * m2, k.e, k.n)


def hom_sum(k, c1, c2):
    m1 = pow(c1, k.d, k.n)
    m2 = pow(c2, k.d, k.n)
    return pow(m1 + m2, k.e, k.n)

def gcd(a, b):
    while b:
        a, b = b, a % b
    return a.monic()

# ADDENDUM !!
# The below implementation of the attack is MUCH FASTER than mine
# https://github.com/jvdsn/crypto-attacks/blob/master/attacks/rsa/related_message.py

def franklinreiter(C1, C2, e, N, a, b):
    P.<X> = PolynomialRing(Zmod(N))
    g1 = (a*X + b)^e - C1
    g2 = X^e - C2
    result = gcd(g1, g2).coefficients()
    print(result)
    return -result[0]

def solve_chall():
    B = getRandomNBitInteger(512)
    A = getRandomNBitInteger(512)
    log.info("A = " + str(A))
    log.info("B = " + str(B))

    p = remote("challenges.fcsc.fr", 2150)
    n = int(p.recvline().decode().split("\n")[0])
    e = int(p.recvline().decode().split("\n")[0])
    c1 = int(p.recvline().decode().split("\n")[0])

    c2 = pow(A, e, n) * c1
    b_plus = pow(B, e, n)

    log.info("N = " + str(n))
    log.info("E = " + str(e))
    log.info("C1 = " + str(c1)) # flag

    p.recvuntil(b">>> c1 = ")
    p.sendline(str(c2).encode())
    p.recvuntil(b">>> c2 = ")
    p.sendline(str(b_plus).encode())
    p.recvuntil(b">>> prod or sum? ")
    p.sendline(b"sum")
    c2 = int(p.recvline().decode().split("\n")[0])
    log.info("C2 = " + str(c2))
    p.close()

    print("Attack ...")
    m2 = franklinreiter(c2, c1, e, n, A, B)
    m1 = (A * int(m2) + B) % n

    print(b"M1 = " + long_to_bytes(m1))
    print(b"M2 = " + long_to_bytes(int(m2 % n)))

if __name__ == '__main__':
    solve_chall()

FCSC{4d7b3ef7300acf70c892d8327db8272f54434adbc61a4e130a563cb59a0d0f47}

Not Solved

In this section, there will be post-CTF write-ups about challenges I attempted but didn’t solve during the competition. For each, I am now writing a detailed solution, given the time to think more and the hints, solutions, and ideas from people who solved them and didn’t make a write-up but talked about it on Discord.

This means what you’ll read here is ultimately a combination of both my personnal progress during the CTF (and after) and the solutions given on the Discord channel.

I will mention under each following write-up the people or solutions that helped me understand better and wrote what you’ll read. Thanks to them ✨.

Those challenges are :


I love permutations

Important

The solution exposed here and the explanations are based on this writeup, from @Maxime, posted on the Discord server of the event.

PointsDifficultySolves
372★ ★54

Description

Moi, j’aime les permutations. Alors j’ai décidé de créer mon propre chiffrement par bloc.

I love permutations

nc challenges.fcsc.fr 2153

Code

The service’s source code was provided :

i-love-permutations.py
 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
import os
import random


class i_love_permutations:
    def __init__(self, n=64, r=101):
        self.n = n
        self.r = r
        self.k = os.urandom(16)  # 128-bit key

    def branch_to_bits(self, branch):
        bits = []
        for b in branch:
            for i in range(8):
                bits.append((b >> i) & 1)
        return bits

    def bits_to_branch(self, bits):
        assert len(bits) == self.n
        branch = []
        for i in range(0, self.n, 8):
            branch.append(sum((bits[i + j] & 1) << j for j in range(8)))
        return bytes(branch)

    def encrypt(self, m):
        assert len(m) == 2 * self.n // 8, "Invalid message length"
        l = self.branch_to_bits(m[: self.n // 8])
        r = self.branch_to_bits(m[self.n // 8 :])
        for _ in range(self.r):
            random.seed(self.bits_to_branch(l))
            random.shuffle(r)
            random.seed(self.k)
            random.shuffle(r)
            random.seed(self.bits_to_branch(r))
            random.shuffle(l)
            random.seed(self.k)
            random.shuffle(l)
        return self.bits_to_branch(l) + self.bits_to_branch(r)


if __name__ == "__main__":
    print("Hello, I love permutations!")
    ILP = i_love_permutations()
    flag = open("flag.txt", "rb").read().strip()
    assert len(flag) == 32

    print(f"Flag hex : {ILP.encrypt(flag[:16]).hex() + ILP.encrypt(flag[16:32]).hex()}")

    try:
        queries = 6
        while queries > 0:
            print("Which message should I permute?")
            m = bytes.fromhex(input(">>>"))
            if not m:
                break
            c = ILP.encrypt(m)
            print(f"Encryption: {c.hex()}")
            queries -= 1
    except:
        print("Please check your inputs.")

When connecting to the service, we’re given th encrypted flag and the ability to perform 6 encryptions using the same key for chosen ciphertexts of our own.

i-love-permutations.py
43
44
45
46
47
ILP = i_love_permutations()
flag = open("flag.txt", "rb").read().strip()
assert len(flag) == 32

print(f"Flag hex : {ILP.encrypt(flag[:16]).hex() + ILP.encrypt(flag[16:32]).hex()}")

Solving

Let’s define:

  • \(P_0\): the permutation generated by the seed \(0\)
  • \(P_k\): a random unknown permutation generated from the key
  • \(R = 101\): the number of rounds for the encryption process
  • \(M = (l, r)\): our message, with \(l\) and \(r\) respectively the left and right branch
  • \(E(x)\): our encryption function
  • \(E_f\): our encrypted flag
  • \(n=64\): the size of the permutations

The key point here is to observe that for a carefully crafted message \(M_c = (l, 0)\), with the right branch filled with \(0\), we have:

\[ E(M_c) = (l \cdot (P_k P_0)^R, 0) \]

with \(\sigma = (P_k P_0)^R\) an unknown permutation, and \(P_k\) the key-based permutation we’re looking for.

Remark

These permutations apply to the bit representation and ordering of the numbers we are working with.

From there, if we can recover the permutation \(\sigma\) on its own, we can then take the \(R\)-th root to find \(P_k P_0\), and since we know \(P_0\), we finally have \(P_k\), which allows us to revert the encryption of the flag.

This is where I was stuck during the CTF. I knew that by constructing the \(l\) part of the message in a special way, I would be able to recover \(\sigma\), but I didn’t know how.

The trick is to create 6 messages \(M_i = (l_i, 0)\) with \(i \in [0, 5]\) and

\[ l_i = \sum_{j=0}^{64} \left(\left\lfloor \frac{j}{2^i} \right\rfloor \mod 2\right) \cdot 2^j \]

Meaning

This formula is just a fancy mathematical way to say that we set the value of the \(j\)-th bit of \(l_i\) to the value of the \(i\)-th bit of \(j\).

Because of this formulation, we know that if

\[ \begin{aligned} E_i &= l_i \cdot (P_k P_0)^R \\ &= l_i \cdot \sigma \\ \end{aligned} \]

then we have

\[ \begin{equation} \sigma = \left\{ \sum^{5}_{i=0} \left[\left(\left\lfloor \frac{E_i}{2^j} \right\rfloor \mod 2\right) \cdot 2^i \right] \mid j \in \{0, 1, \cdots, 64\} \right\} \tag{3} \end{equation} \]
Proof

Here is the proof for (3) :

To be continued …

We can now take the \(R\)-th root of this permutation, and because \(R = 101\) is prime, we have a unique solution. I didn’t find many resources on how to do that, so you can read those Stack Exchange Math discussions (1, 2 and 3) or read the Sage’s source code of the function I will use.

Now that we have \(\beta = P_k P_0\) such that \(\beta^R = \sigma\), we can calculate

\[ P_k = \beta \cdot P_0^{-1} \]

since we know the permutation \(P_0\).

Finally, we can recover our unencrypted flag \(M_f\) by running the encryption algorithm backward, as we now know \(P_k\), meaning we can undo it.


Tip

For future reference, I’m using Sage’s built-in permutation objects to find the \(101\)-th root of the full \(101\) rounds permutation.

Here is the final solve script using Sage :

  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
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
# SOLUTION BASED ON
# https://discord.com/channels/570360372209385473/706896953820315678/1492922641210802217

class i_love_permutations:
    # [...]
    # Previously defined

class i_love_permutations_decrypt(i_love_permutations):
    def decrypt(self, m):
        l = self.branch_to_bits(m[: self.n // 8])
        r = self.branch_to_bits(m[self.n // 8 :])
        for _ in range(101):
            l = apply_permutation(l, reverse_key_permutation)
            l = reverse_shuffle(l, self.bits_to_branch(r))
            r = apply_permutation(r, reverse_key_permutation)
            r = reverse_shuffle(r, self.bits_to_branch(l))
        return self.bits_to_branch(l) + self.bits_to_branch(r)


# Connect to remote service
io = remote("challenges.fcsc.fr", 2153)
io.recvline()
flag_hex = io.recvuntil(b"Flag hex : ").decode().strip()

##########################################
# HELPERS
##########################################

def send_message(m):
    io.sendlineafter(b">>>", b"ff" * 8 + m)
    io.recvuntil(b"Encryption: ")
    return io.recvline().decode().strip()


def bits_to_hex(b):
    result = []
    for i in range(0, len(b), 8):
        byte = sum(int(b[i + j]) << j for j in range(8))
        result.append(f"{byte:02x}")
    return "".join(result)


def hex_to_bits(h):
    result = []
    for byte in bytes.fromhex(h):
        for i in range(8):
            result.append(str((byte >> i) & 1))
    return result


def apply_permutation(l, perm):
    return [l[perm[i]] for i in range(len(l))]


def reverse_permutation(p):
    reverse_p = [0] * len(p)
    for i, x in enumerate(p):
        reverse_p[x] = i
    return reverse_p


def reverse_shuffle(l, seed):
    r = list(range(len(l)))
    random.seed(seed)
    random.shuffle(r)

    return apply_permutation(l, reverse_permutation(r))

##########################################
# SOLVING
##########################################

n = 64
shuffles = []

for i in range(6):
    msg = []
    for x in range(n):
        msg.append(f"{x:06b}"[i])
    hex_message = bits_to_hex(msg)
    response = send_message(hex_message.encode())
    response_final_bits = hex_to_bits(response[16:])
    shuffles.append(response_final_bits)

perm_101 = []
for j in range(n):
    perm_index = "".join([shuffles[i][j] for i in range(6)])
    perm_101.append(int(perm_index, 2))

perm = Permutation([e + 1 for e in perm_101])
sigma = [int(e) - 1 for e in list(perm.nth_roots(101))[0]]


key_permutation = apply_permutation(reverse_shuffle(list(range(n)), b"\x00" * 8), sigma)
reverse_key_permutation = reverse_permutation(key_permutation)


ILP = i_love_permutations_decrypt()
print(
    ILP.decrypt((bytes.fromhex(flag_hex[:32]))).decode()
    + ILP.decrypt((bytes.fromhex(flag_hex[32:]))).decode()
)

FCSC{af56d1a25c88e9ebe515958e32}

Shor

Important

I didn’t solve this challenge during the CTF, but managed to do so now after a simple message from @nikost on the Discord server of the event, which revealed the solution to me.

PointsDifficultySolves
437★ ★26

Description

Après des années de rêve, c’est enfin arrivé : on a mis la main sur un ordinateur quantique de compétition. Entre deux essais pour plier l’espace-temps (et miner du Bitcoin quantique), on s’est dit que casser du RSA serait plus rigolo.

nc challenges.fcsc.fr 2154

Code

The service’s source code was provided :

dixvision.py
 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
# Homemade RSA key generation for this challenge,
# but you don't need to know the details to solve.
# you don't have access to the implementation details of our quantum computer
import quantum_computer
import rsa

print("Generating RSA key ...")
e = 2**16 + 1
n, p, q, iq, dp, dq, d = rsa.keygen(1024, e)

print("Here is the public modulus:")
print(f"n = {int(n)}")

base = 3
c = quantum_computer.QFT(n)
print(f"Here is the output of the quantum computer for the base {base}:")
print(f"c = {int(c)}")

print("Please provide any non-trivial factor of the public modulus:")
try:
    factor = int(input(">>> "))
    if factor == p or factor == q:
        print("Congrats! Here is the flag:")
        print(open("flag.txt").read())
    else:
        print("Nope!")
except:
    print("Please check your input.")

As the name of the challenge suggests, it is going to be about Shor’s algorithm for factorization.

From the source code, we know that \(a = 3\), which is the base in Shor’s algorithm, and that \(e = 2^{16} + 1 = 65537\). If we connect to the server, we are given the public modulus \(N\) and the number \(c\), which is the result of the Quantum Fourier Transform. This is the last step of the quantum order-finding subroutine in Shor’s algorithm for \(N\) to the base \(a\).

The goal, given \(N\), \(c\), and \(a\), is to find one of the factors of \(N = pq\).

Solving

Let’s query the service one time to get example values :

1
2
3
a = 3
N = 94712998809351678755619220752350241725498534675390182553454205573347271952964418820122160853491032527086337825397383880134791451067145637384050537862462387038715960692114256621542256650258772937389505859589566837358812527843973517559251137287542190118026531050058902022837758276367473396465911504541945838261
c = 12215394701968053338275071083105233716563740576479408226414614768198032753204143722321319331470885325012821459560725569206085649565790576880581734076527518833549685685076024659341881240082790917675504818913820728373181225904477158553037671736909646363711629207220924488228398269085384844143489657296789975121884216907450715974702131993836908109884108826484791778736757108054985702386287345083523611633712459913202284585570226650191463560793164566736267829689789594315668480810785133054384978677627459795319852251257795372537215166883261992456439420362224381857832730509438906234003335382402756962612726830294239310215

What is this number \(c\) ? If we read the Wikipedia page, as it is the result of the quantum order-finding subroutine, it should be the order \(r\), such that

\[ \begin{equation} a^r \equiv 1 \mod N \tag{1} \end{equation} \]

But if you try these values, you’ll see that this is not the case, so \(c\) can’t be the order. So what is it ?

Reading the page again, we can find the answer in this section and the paragraph just above it. That is,

\[ c = 2^{2n} \frac{j}{r} \]

for random \(j = 0, 1, \dots, r-1\).

But we also read that we can use continued fractions to extract the period \(r\) (what we’re looking for) from the decimal approximation of

\[ \frac{j}{r} = \frac{c}{2^{2n}} \]

Since we know from the source code that \(N\) is generated from the product of two 1024-bits prime numbers, we can assume that the algorithm was implemented as stated and thus that \(n = 1024\).

Okay, now let’s do this using Sage

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# N and c defined above

# Take the approximation by continued fractions and then convergents
conv = continued_fraction(c / (2**(2*1024))).convergents()
# We keep only the denominators, as r should be there
denum = [int(e.denominator()) for e in conv]

# Iterate over all denominators
for r in denum:
    # Check if r is the order of the base a (i.e a^r % n == 1)
    if power_mod(3, r, n) == 1:
        print(r)
        break

# >>> 47356499404675839377809610376175120862749267337695091276727102786673635976482209410061080426745516263543168912698691940067395725533572818692025268931231183785972153497445734867167854150970340284615466562841710704683870397792564798315083702938491939124425702861842851591348057486457894111682521232889651103686

We successfully find the order \(r\) such that \(a^r \equiv 1 \mod N\) ! We can now do the last part of shor’s algorithm to recover the factors of \(N\).

1
2
3
4
5
6
7
8
9
from math import gcd

r = 47356499404675839377809610376175120862749267337695091276727102786673635976482209410061080426745516263543168912698691940067395725533572818692025268931231183785972153497445734867167854150970340284615466562841710704683870397792564798315083702938491939124425702861842851591348057486457894111682521232889651103686

t = pow(a, r // 2, N)
p = gcd(t + 1, N)
print(p)

# >>> 1

and it doesn’t work …

This is where I got stuck until the end of the CTF 😭

After the CTF ended, I was looking through the crypto channel on Discord to find some insights and saw this message (in French):

A good message indeed
@nikost message

And indeed, you really do have \(\phi(N) = \left\lfloor \frac{N}{r} \right\rfloor \cdot r\), because if you now try to recover the factors using this method, you successfully get back \(p\) and \(q\).

1
2
3
4
5
6
7
phi = (N // j) * j
p_plus_q = N + 1 - phi
p = abs(-p_plus_q + isqrt(p_plus_q**2 - 4*N)) // 2
q = N // q

assert 1 < p < N   # True
assert p * q == N  # True

which finally solves our challenge …

But hey, let’s be honest, I don’t have any idea why \(\phi(N) = \left\lfloor \frac{N}{r} \right\rfloor \cdot r\) is correct. So is there another way ? YES

Addendum - Explanations

As told to me just after the release of this post by none other than @nikost himself (again), here is the explanation of why it works.

We have

\[ \begin{aligned} \phi(N) &= (p - 1)(q - 1) \\ &= pq - p - q + 1 \\ &= N - p - q + 1 \end{aligned} \]

So

\[ \frac{N}{r} = \frac{\phi(N)}{r} + \frac{p + q - 1}{r} \]

Now, because of something we will discuss below, \(\phi(N)\) is a multiple of \(r\), thus we have \(k \cdot \phi(N) = r\) and

\[ k = \frac{\phi(N)}{r} \]

is an integer.

Moreover, because with very high probability for a randomly chosen base \(a\), we have \(r \gg p + q - 1\), it means that

\[ 0 < \frac{p + q - 1}{r} < 1 \]

In the end, it all comes down to

\[ \begin{aligned} \left\lfloor \frac{N}{r} \right\rfloor \cdot r &= \left\lfloor \frac{\phi(N)}{r} + \frac{p + q - 1}{r} \right\rfloor \cdot r \\ &= \left\lfloor \frac{\phi(N)}{r} \right\rfloor \cdot r \\ &= \frac{\phi(N)}{r} \cdot r \\ &= \phi(N) \end{aligned} \]

For this specific example, we can actually find that \(\phi(N) = 2r\), which means that in many cases, \(\phi(N)\) must be a multiple of \(r\). But why ?

One part of the answer lies in the Euler’s theorem, which states that if \(a\) and \(N\) are coprime, then

\[ \begin{equation} a^{k \cdot \phi(N)} \equiv 1 \mod n \quad \forall k \in \mathbb{N}^* \tag{2} \end{equation} \]

Now, because \(a\) should be coprime to \(N\) for shor’s algorithm, and following from equation (1), we should have, for \(k = 1\)

\[ \phi(N) = r \]

If we take one last look at what is written on the Wikipedia page, we can read the last part of our answer under the previously mentioned section :

However, while \(b\) and \(c\) are coprime, it may be the case that \(j\) and \(r\) are not coprime. Because of that, \(b\) and \(c\) may have lost some factors that were in \(j\) and \(r\).

What does it mean ?

What this means is that we found our \(r\). However, due to the method we used (continued fractions), we lost a leading factor (\(2\) in our case) of the “true” value of \(r\).

We now have \(r = k \cdot r^{'}\) with \(r^{'}\) the actual order we found, which gives us

\[ \phi(N) = k \cdot r^{'} \]

In the end, for a given \(r\) such that \(a^r \equiv 1 \mod n\), we must try to factor with multiples and factors of \(r\) if it doesn’t work with \(r\) directly.

So there it is. Now the strategy is to loop over many multiples until we find the correct one. It shouldn’t be too long, as this one is often small, and if it takes too much time, then retry from the beginning with new values.

One final point

In a normal case of shor’s algortihm implementation, because \(k \neq 1\), then we have from (2)

\[ k \cdot \phi(n) = r \]

which means that \(r\) is a multiple of \(\phi(n)\).

To factor \(N\) from there, you should, as first discussed in this paper and as implemented in this attack, try to divide \(r\) by it’s divisors to find \(\phi(n)\)


Here is the final Sage solve script for this challenge :

solve-shor.py
 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
from sage.all import *
from pwn import *

def solve(c, n):
    conv = continued_fraction(c / (2**(2*1024))).convergents()
    denum = [int(e.denominator()) for e in conv]

    for r in denum:
        if power_mod(3, r, n) == 1:
            m = 0
            while True:
                m += 1
                phi = r * m
                p_plus_q = N + 1 - phi
                p = abs(-p_plus_q + isqrt(p_plus_q**2 - 4*N)) // 2
                if 1 < p < n and n % p == 0:
                    return p

                # If m divides r, we try the quotient
                if r % m != 0:
                    continue

                phi = r // m
                p_plus_q = N + 1 - phi
                p = abs(-p_plus_q + isqrt(p_plus_q**2 - 4*N)) // 2
                if 1 < p < n and n % p == 0:
                    return p

    return False

while True:
    conn = remote("challenges.fcsc.fr", 2154)
    data = conn.recvuntil(b">>> ").decode()
    n = int(data.split("n = ")[1].split("\n")[0])
    c = int(data.split("c = ")[1].split("\n")[0])
    log.info(f"n = {n} ({n.bit_length()})")
    log.info(f"c = {c} ({c.bit_length()})")
    fact = solve(c, n)
    if fact:
        conn.sendline(str(fact).encode())
        log.info(conn.recvline())
        log.info(conn.recvline())
        break
    conn.close()

FCSC{e4d93d2d05e78587e736078953345d52f00f6b8ee16e7913f40fe53518452bbb}

End word

It was my first time parcticipating at FCSC CTF and I must stay I was suprised by the level of diffuclty of the challenges. I’m a bit frustated because I spent a lot time on some challenges which I didn’t solve in the end but discovered I was close to the solution after reading others. Guess I’ll have to train more and become better 😆.

Anyway, it was quite enjoyable and educational. Looking forward to next year to try my chance again to qualify for ECSC, as I’m still young enough to be a candidate for the two years to come !

Last updated on