Genigma#
DISCLAIMER: our team did not solve this challenge during the live competition; we first solved it while preparing for this write-up.
Helpers#
import numpy as np
import os
import requests
import time
from dotenv import load_dotenv
from string import ascii_uppercase, digits
load_dotenv()
API_KEY = os.environ["API_KEY"]
URL_QUERY = "https://%s.crucible.dreadnode.io/%s"
URL_FLAG = "https://crucible.dreadnode.io/api/challenges/%s/submit-flag"
CHALLENGE = "genigma"
CHARS = ascii_uppercase + digits
def query(data, route="score"):
response = requests.post(
URL_QUERY % (CHALLENGE, route),
headers={ "X-API-Key": API_KEY },
json={ "data": data }
)
return response.json()
def submit(flag):
response = requests.post(
URL_FLAG % CHALLENGE,
headers={ "X-API-Key": API_KEY },
json={ "challenge": CHALLENGE, "flag": flag }
)
return False if response.status_code != 200 else response.json().get("correct")
Solution#
If we submit some random values to the endpoint we can see that it returns 16 logits.
data = np.zeros((100, 1))
query(data.tolist(), route="predict")["prediction"]
[[0.13794109654006212],
[0.14017329184141292],
[0.18642894700537402],
[0.1637307606500884],
[0.13963116830454997],
[0.16077281815261674],
[0.13729851190502865],
[0.13969673425944382],
[0.18523813603837574],
[0.13947802411484708],
[0.1383049461891841],
[0.13842525959608593],
[0.13832571431083493],
[0.16387797054251438],
[0.16311711035024368],
[0.1879446894428261]]
For each of the 16 logits we can submit 100 different queries where each query contains 99 zeros and a single large value. If we take the index which maximizes the logit and then index into the alphabet (including digits) we can find the flag.
Note: for convenience, the below code only submits 36 different queries as it turns out that the logit is always maximized within the first 36 values.
flag_chars = []
for logit in range(16):
max_value = 0.0
max_option = 0
for option in range(36):
data = np.zeros((100, 1))
data[option][0] = 100
response = query(data.tolist(), route="predict")
value = response["prediction"][logit][0]
if value > max_value:
max_value = value
max_option = option
time.sleep(0.1)
flag_chars.append(CHARS[max_option])
flag = "".join(flag_chars)
print(flag)
print("Flag accepted:", submit(query(flag)["flag"]))
GEN0M1CINV3RS10N
Flag accepted: True