In this task, we explore the realm of cryptographically secure random generators, where predicting the next output is deemed impossible. Are you ready to test your luck and skill?
Solution:
TMP is ignored, enc is simply flag XOR'd with KEY, which is reversible since we're given KEY and enc.
Again, in this task, we explore the realm of cryptographically secure random generators, where predicting the next output is deemed impossible. Are you ready to test your luck and skill this time?
This requires 624 32-bit values which we are almost exactly given. We just need to be sure to give the last 624 values so we skip the first two entries in TMP.
from Crypto.Util.number import long_to_bytesfrom randcrack import RandCrackTMP = [0, 22330693840234311255135949029444484409546667648719176405826663892267656641027, 127168478027482847709328807841325386271927515479937061237117195618823278578116, 182258311374053859620888699680212168010665323374548870180038645090147843867373, 1120044041165490856498692287111236626472260308631093314161690677868431277653536, 1983473421395194676263973602935227753154638099492341714205203280778040675593450, 1574768551732085861078069762534699936995654652684634077104498873387111232412816, 4988773041677976257517254491234335651753610239922582254283447205154548743632904, 869738033317159039287197189670964123964466628318970710545560734535418094431872, 716771557072892076589368879721160406613516964478389692662921907034616035095047, 2841054733362182186252458286741823726277405165099408732758691872324732479956600, 6200268989316199565071790593244237980113705529543497656127585449937778556282311, 10670728743047162087774896911955052588177734200772863764402582886370432879158720, 7713906922622752752151916696524419287963819641354815269293605765422900017233866, 13689077681405838115291939958594572280593102467042881661528817316126253635857444, 23677404931618939684375357302211056316481456538100460743428412550112769975941300, 22334702277647520331031971258896634990832479997228972554803329027443498276011264, 24695994670269108821474844143270568317378271123560130717104045624895774803117988, 10726839246587772223823222881528936091917884797218227418638385365176143122217812, 1312747277711228023681888222399668996816715931126782050057534166588569071642948, 14829434912751138825019062212374862054511849430113519894438429231649766515851600, 4917180643387964007287001238070594020985844865025196727991425387470641537875518, 50772176246766546694026388399540445347088279634906123947563600159509306535585300, 20680598744337311676861190641592800456437920078216405214477640693225317242487078, 57560623230262776939750106414721715686651269149245752162663251361023294801081600, 63941301709699592129851769466238327968731332723117779339939586823464299930335000, 30248094445348087425063737332624900285689080519537666953907462011122884602991780, 9774708715683840095021685805936771586028623975773332766526807054152590972465402, 61228751294246951869891671407294469506401133460669313068369993608651062307301536, 41981261972157910420555352577742115252749734931422260886610665615142932761250238, 92332289648534120255700799585162857690611895814212622902006472593032842219422300, 102090694836612045964656351247645673041342905792690679450732518780700786595757872, 8465306744686231379969736050689382339949995071265316552433666241539252681451520, 114072153081233359084524715014825650254537286682603109151986752844288607088786066, 39946361462751138749261511325777846481011288953117931061771127396007551287911208, 51243479474799144289518571031495536096625532453885999576052634625243425716758700, 132356504405092579871543186323238530972479261975470487510352508943760068475015440, 109077835346013498228568867183016137777644328620298812835459712256002833220195417, 52635267919343130972014005273289555808336337947193348140410148289978267235415648, 9568343438735227407132147420705807168258684366618511035784505242511446472528193, 136103745592722122037143341370556407561964415802887285393102934361453911394982400, 15501324115571167305412632833471884183641743683875758176471163573103721210677697, 124579054262159655532164017523017564697199759561416452868759873217906475930663652, 69331433672201876294056448428159828327113921951663941374636039203754564050923557, 134825790087045765574290263555594553874136924161813224135475519279020442040026864, 127098236196925756090074171499128508507461799729629969599917408442298996799214250, 120716315173788627251671396349879537684221828425501013413665864262612928100844788, 246230945837378885729579613348413794121875158206606559652651668292953179058653508, 182436930868427241575608788617950343128628563937798409868187047670441481734494464, 2216307326510769061988701188806623458793041637834505792592287312459658319545700, 217778196427604121810125555838576095983026719310491477185297193068203986197977500, 200042153662024093707446037685450040433674498805614787040971237961725493946807124, 55096521527758008435839474651130444687406648424301616531387151625485823586357376, 315911207494925949742212443025101639383551363855632617410391325922132118052280432, 160608721274889447938606989650810386105243008009388938737103600719751998405695052, 80485718020426913778898398898436382386718914865993732581279132006386834763843750, 175256027423949464821148437330609889703365513530429385704635213979205690543187968, 312494592697141143680238564093947039458907138790072672218576868913190841311490441, 12551558878313236197845748627693664902436846005140074555532691630477757920400492, 368163678666609325358026149200535116090648801749210267074911311082497122727619418, 132244486142872991925346591101049195464960273281071718729683433268064480383763200, 187524820546739515326467479985404725103464284941528452333038247179114024353648176, 283320427018968981710753682470612392210145925235229015984823155988278867852342424, 273076274412276025537791810337835157311632197268182698230310819989050497776963263, 327014096802403962955714851262399814244813548393488285833127238998882721132883968, 206832690482752439833856322955815020186765387390104398292271480795930880106073325, 104167288428075991079921385804154376915444422785935287020330329091692992364020356, 468442878028756757484855000070722747267796721762231179211069666438706434848755245, 13006681553773847728990900149289800641720551387610802780788594468812438984199760, 199716185379958028413200192962692404940513822154864483463050473557869065589649168, 412558417168152436059170177108518481504104909389966119467224740980715361039084900, 379013360598848524426838307544021120793535763669172279637583374247930017257612752, 79510803625960975136293110699095743477640774841480691165531320726532279504009152, 119246467719878286004186703543298639812649580965124121805161153548472942538790653, 44235048729597559877492812430806736314711896199059487848598597142896457753232432, 453319033816285234767354843915966019736243075972507643199351036007057824008570000, 300975897791737470999557383409844137620736489995632513055593286593028252152372832, 488688724028459389993054497130088474659149461722402520817247390457263798063265080, 98311703485802819685121101139900586756957739352203591545958914778011243453808576, 503894794312461918204750180188338003935699664049776370432270755067603639622480931]
rc =RandCrack()# Use values from TMP to reconstruct the state of the PRNGfor i, val inenumerate(TMP):if i >1:# Skip the first two values to provide exactly 624 32-byte chunks (79 * 8) extracted_val = val // (i **2)# Split the 256-bit number into eight 32-bit chunksfor shift inrange(0, 256, 32): chunk = (extracted_val >> shift) &0xFFFFFFFF rc.submit(chunk)# Predict the outputs of the PRNG to determine the KEYpredicted_key_parts = [rc.predict_getrandbits(256>> _)**2for _ inrange(8)]KEY =sum(predicted_key_parts)# Given value for encenc = 1954128229670403595826293823451515985816812578139791173172421160740653397416251058891670696398940725266238000104900728729829302299509397650740333416176077
# Decrypting the messagedecrypted = enc ^ KEYflag =long_to_bytes(decrypted)print("Decrypted flag:", flag)
The MAPNA CERT team has identified an intrusion into the plant's PLCs, discovering a covert message transferred to the PLC. Can you uncover this secret message?
Flag is visible, simply assemble 1:MAPNA{y through 6:d1n9!!}.
MAPNA{y0U_sHOuLd_4lW4yS__CaR3__PaADd1n9!!}
Tampered (65 solves)
Description:
Our MAPNA flags repository was compromised, with attackers introducing one invalid flag. Can you identify the counterfeit flag?
Note: Forgot the flag format in the rules pages, just find the tampered one.
You are not allowed to brute-force the flag in scoreboard, this will result in your team being blocked.
Solution:
We get a text file with many flags in it. We need to see which was tampered so my first instinct is to scroll through and see if there are any with a different length. 30 seconds later holding ctrl+d in vim, I find it.
MAPNA{Tx,D51otN\eUf7qQ7>ToSYQ;5P6jTIHH#6TL+uv}
Web
Flag Holding (317 solves)
Description:
Hopefully you know how web works...
http://18.184.219.56:8080/
Solution:
Navigating to the site, you see:
You are not coming from "http://flagland.internal/".
A quick google search to find you just need to intercept the request in burp and add:
Referer: http://flagland.internal/
Next, it says Unspecified "secret".
There are multiple ways to specify a variable, easiest is in the url adding ?=
Next, it says Incorrect secret.
Press ctrl+u to see the page source which has a hint that the secret is the protocol the server and browser agree on, which ends up being http.
Next, it says Sorry we don't have "GET" here but we might have other things like "FLAG".
This means instead of a GET request, we just make a FLAG request. Finally:
Reverse
Compile Me! π¨ (141 solves)
Description:
Compile the given code and execute the resulting binary, passing the source code file as an argument, to obtain the flag.
Pretty straight-forward, seems like we just gcc file.c -o file; ./file < file.c right?
MAPNA{1427__11__31583_!}
But wait, it's not working. Let's not talk about how much time I wasted on this, here's what worked.
After running, stdout shows MAPNA{1426__11__31582_!}
Heaverse (42 solves)
Description:
Heaverse, a paradoxical binary that defies logic: reverse it without reversing it. Can you navigate its enigmatic depths?
Flag format:MAPNA{CAPITAL_WORDS_THAT_YOU_FIND}
Solution:
Run Heaverse with gdb (I have pwndbg set up), ctrl-c when a beep is played, see this:
We see morse code in RBX, when continuing and ctrl+c, we see the morse passed in is slowly iterated. Therefore we just need to print out the memory at that location to see the full string. Printing at an address slightly above we get:
Copy paste into cyberchef to decode the morse, and you get JUS7LIST3NN0TREV3RSE but it doesn't work directly, follow the instructions in the description to get:
MAPNA{JUS7_LIST3N_N0T_REV3RSE}
Solution:
The given binary reads a flag and allows the user to either gather information or confirm the flag. Decompiling it doesn't help much and with simple trial and error, it can be seen that only a single character input is allowed. Manually trying each likely character and copy-pasting/cleaning up the result gets the following:
We notice that { is 25 and } is 8649, which happen to be 5 and 93 squared. Since we know the flag likely starts with MAPNA{, we can assume the numbers refer to the 0-indexed position. It's not that simple though since most numbers aren't a perfect square. Here's where things get a bit messy.
We know we didn't miss any characters since these sum to what the sum of squares is.
Next, we can see what the flag looks like if we assume perfect squares go in their correlated position.
import mathdefis_perfect_square(n):return n ==int(math.sqrt(n))**2char_values ={"0":21279,"1":2704,"2":0,"3":4373,"4":5805,"5":484,"6":0,"7":1296,"8":400,"9":0,"!":8296,"@":0,"#":0,"$":0,"%":0,"^":0,"&":0,"*":0,"(":0,")":0,"-":0,"_":43159,"=":0,"{":25,"}":8649,"\\":0,"|":0,"/":0,"?":16745,".":0,",":0,"<":0,">":0,"a":2098,"b":6241,"c":2930,"d":10001,"e":5573,"f":4096,"g":1849,"h":6970,"i":0,"j":5329,"k":4437,"l":0,"m":0,"n":7145,"o":8586,"p":0,"q":0,"r":8704,"s":5929,"t":4689,"u":8077,"v":2116,"w":0,"x":4356,"y":12962,"z":4900,"A":458,"B":0,"C":1024,"D":0,"E":0,"F":1156,"G":7225,"H":0,"I":0,"J":0,"K":0,"L":4705,"M":6301,"N":850,"O":0,"P":6564,"Q":2500,"R":0,"S":1444,"T":3305,"U":0,"V":0,"W":6724,"X":0,"Y":0,"Z":0}flag_length =94# As positions are 0-indexed# Initialize the flagflag = ['*'] * flag_length# Lists to keep track of characters with single and multiple occurrencessingle_occurrences ={}multiple_occurrences ={}# Assign characters to positions or list them as candidatesfor char, value in char_values.items():ifis_perfect_square(value): position =int(math.sqrt(value)) flag[position]= char single_occurrences[char]= positionelse:# If the value is not a perfect square, list the character for further analysisif value !=0: multiple_occurrences[char]= value# Display the partially filled flag and characters with multiple occurrencespartial_flag =''.join(flag)print("Single Occurrences:", single_occurrences)print("Multiple Occurrences or Uncertain Positions:", multiple_occurrences)print("Partially Filled Flag:", partial_flag)
Running this script to fill out positions that are a perfect square, keeping in mind some of these may be incorrect and perfect squares can be made up of sums of squares, we get the following:
Ignore the Z at the start since the first position corresponds to 0.
We can manually add MAPNA at the beginning and subtract the squares from the candidates (e.g., A - 1, P -4, N-9, A-16). Next we see what it looks like when we try to fill by calculating sums of squares.
import mathfrom itertools import combinationsdefis_perfect_square(n):return n ==int(math.sqrt(n))**2flag_length =94# As positions are 0-indexed# The partially filled flagpartial_flag ="MAPNA{**************8A5*********C*F*7*S****g**v***Q*1***********f*x***z**j***s*b**W**G*******}"# Updated character values after manual cleanupupdated_char_values ={'3':4373,'4':5805,'!':8296,'_':43159,'?':16745,'a':2098,'c':2930,'d':10001,'e':5573,'h':6970,'k':4437,'n':7145,'o':8586,'r':8704,'t':4689,'u':8077,'y':12962,'L':4705,'M':6301,'N':841,'P':6560,'T':3305}# Convert the partial flag to a list for easier manipulationflag_list =list(partial_flag)# Calculate the squares of all possible positionspossible_positions =list(range(flag_length))# Exclude positions that are already filled (not a '*' or '?')excluded_positions = [i for i, char inenumerate(flag_list)if char notin ['*','?']]# Update possible positions to exclude the filled positionsupdated_possible_positions = [pos for pos in possible_positions if pos notin excluded_positions]deffind_combinations_for_value(value,possible_positions,max_combination_length):""" Find combinations of positions that match the given value up to a maximum number of squares. """for r inrange(2, max_combination_length +1):# Start from 2 as we're looking for sums of squaresfor combo incombinations(possible_positions, r):ifsum([p **2for p in combo])== value:return comboreturn ()# For now only allow sum of two squaresmax_combination_length =2# Finding combinations for remaining characters with updated valuesfor char, value in updated_char_values.items():if value >0:# Only consider characters with positive adjusted values combos =find_combinations_for_value(value, updated_possible_positions, max_combination_length)if combos:# Fill in the flag positions based on the found combinationsfor pos in combos:if flag_list[pos]in ['*','?']: flag_list[pos]= char# Convert the flag list back to a stringupdated_flag_str =''.join(flag_list)print(updated_flag_str)
From here we can already see a lot of the flag, CtF, 7aSk, and a scrambled "The quick fox jumped over the lazy dog". Everything so far seems mostly right except d and h which we can manually reverse.
After fixing the above phrase and adding _ where obvious, there wasn't much left. I created this utility that allows me to determine all sums of squares resulting in a number, while capping the minimum square allowed.
from math import isqrtdefsum_distinct_squares_solutions(n,min_square=1): min_square_root =isqrt(min_square)# Initialize the list of solutions for each sum up to n W = [[] for _ inrange(n +1)] W[0]= [[]]for i inrange(isqrt(n), min_square_root -1, -1): i2 = i * ifor j inrange(i2, n +1):for solution in W[j - i2]:# Check if i is not in the solution and all numbers are above or equal to min_square_rootif i notin solution andall(num >= min_square_root for num in solution): W[j].append(solution + [i])return W[n]n =int(input("Enter a number to find solutions of distinct squares: "))min_square =int(input("Enter the minimum square value to be used: "))solutions =sum_distinct_squares_solutions(n, min_square)print(f"Distinct square solutions for {n}, starting from square {min_square}:")for solution in solutions:print(solution)