Mirage#
Helpers#
import numpy as np
import os
import requests
from dotenv import load_dotenv
from PIL import Image
from string import ascii_lowercase as ASCII
load_dotenv()
API_KEY = os.environ["API_KEY"]
URL_QUERY = "https://%s.crucible.dreadnode.io/score"
URL_FLAG = "https://crucible.dreadnode.io/api/challenges/%s/submit-flag"
CHALLENGE = "mirage"
def query(prompt):
response = requests.post(
URL_QUERY % CHALLENGE,
headers={ "X-API-Key": API_KEY },
json={ "data": prompt }
)
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#
For this challenge we’re given a fairly innocuous image:
By running ExifTool against it we can find a hint that indicates we need to use a Caesar Cipher with a rotation of 3 (or -3).
!exiftool ./data/mirage.png
ExifTool Version Number : 11.88
File Name : mirage.png
Directory : ./data
File Size : 1494 kB
File Modification Date/Time : 2024:11:05 07:21:07+00:00
File Access Date/Time : 2024:11:25 18:19:11+00:00
File Inode Change Date/Time : 2024:11:25 18:17:42+00:00
File Permissions : rw-rw-r--
File Type : PNG
File Type Extension : png
MIME Type : image/png
Image Width : 1024
Image Height : 1024
Bit Depth : 8
Color Type : RGB
Compression : Deflate/Inflate
Filter : Adaptive
Interlace : Noninterlaced
Author : Captain Codebeard
Hint : Thar be secrets in the pixels that must be decoded!
Key : Caesar Cipher 3
Image Size : 1024x1024
Megapixels : 1.0
We can extract of the least significant bit of each of the three colour channels for the first chunk of pixels and convert them to a binary string. We can then convert the bytes in that binary string into ASCII characters.
# Extract LSB of the first 200 pixel values and convert them to a binary string
str_binary = "".join([
str(value & 1)
for value in np.array(Image.open("./data/mirage.png")).flatten()[:200]
])
# Convert the binary string to ASCII characters
str_ascii = "".join([
chr(int(str_binary[i:i+8], 2))
for i in range(0, len(str_binary), 8)
])
print(str_ascii)
{Q0w_Zk@w_Lw_Vh3pv}¶Ûm¶Û¶
The above string includes angle brackets which probably enclose the flag. We can decode the characters using a Caesar Cipher with a rotation of -3.
# Extract the flag using a Caesar cipher (accounting for upper/lowercase)
flag = ""
for char_in in str_ascii:
char_lower = char_in.lower()
if char_lower in ASCII:
char_out = ASCII[(ASCII.index(char_lower) - 3) % 26]
flag += char_out if char_in.islower() else char_out.upper()
else:
flag += char_in
if char_in == "}":
break
print(flag)
print("Flag accepted:", submit(query(flag)["flag"]))
{N0t_Wh@t_It_Se3ms}
Flag accepted: True