๐Ÿ•DawgCTF 2026

AI-generated writeups for all but the two 0-solve challenges (well Rubiya got warmth right before the end)

Maybe missing some other trivial ones. Let me know and I can add it.

crypto

Grecian Battleship

Description

Can you beat the Ancient Greeks?

Solution

The provided ancientbattleship binary is a PyInstaller-packed Python/Tkinter game. Reversing the embedded battleship.pyc shows that the AI is not making decisions dynamically. Its moves are fully hard-coded:

move_script = [
    (2, 4), (2, 3), (2, 1), (0, 0), (1, 1),
    (3, 1), (3, 4), (2, 2), (0, 4), (3, 3)
]

The challenge hint, Consider why the AI behaves so predictably.., points directly at this fixed script.

Grecian suggests a Polybius square, and Battleship gives 5x5 coordinates. Using a standard 5x5 Polybius square with I/J combined and treating the hard-coded pairs as 0-indexed (row, col) coordinates:

#!/usr/bin/env python3

move_script = [
    (2, 4), (2, 3), (2, 1), (0, 0), (1, 1),
    (3, 1), (3, 4), (2, 2), (0, 4), (3, 3)
]

polybius = [
    ["A", "B", "C", "D", "E"],
    ["F", "G", "H", "I", "K"],
    ["L", "M", "N", "O", "P"],
    ["Q", "R", "S", "T", "U"],
    ["V", "W", "X", "Y", "Z"],
]

flag_text = "".join(polybius[r][c] for r, c in move_script)
print(flag_text)

Running that script prints:

That raw decode is the intended answer. The accepted flag is:

I Hate Physics!

Description

The local description.md only contained a link to the actual challenge file in the public challenge repo. The relevant file was STUDYME.txt.

The text is mostly decoy physics notes. The intended signal is in the structure of the lines, not in the formulas themselves.

Solution

Taking the first and last character of each non-empty line reveals the message. The recovered string starts with the flag and then continues with filler text:

DawgCTF{therm0dyn4mic5sucks!}Thisisn0tpartoftheflag!...

So the flag is:

DawgCTF{therm0dyn4mic5sucks!}

Solution code:

Equivalent one-liner:

Vault Breaker

Description

The challenge provides a PDF note full of odd glyphs. Visual decoding points toward a pigpen-style substitution, but the faster path is to inspect the PDF itself.

Each glyph is embedded as a tagged /Figure object with an accessibility alt string of the form /Alt (char\(NN\)), where NN is the ASCII code for the underlying plaintext character.

Reading those numeric codes in order recovers:

EXTREMELYLONGPASSWORD

So the flag is:

DawgCTF{EXTREMELYLONGPASSWORD}

Solution

Solution code:

Run it:

Expected output:

Six Seven

Description

The challenge implements a stream cipher:

The ciphertext is provided in output.txt, and the flag format is known to start with DawgCTF{.

Solution

Because this is XOR stream encryption, ciphertext ^ plaintext = keystream. The known prefix DawgCTF{ reveals the first 8 keystream bytes immediately.

From the first byte:

So the initial state is 0xdb. Applying gen once gives 0x4f, and applying it again gives 0xda. After that, 0xda is a fixed point:

That means the keystream becomes constant very quickly, so the full plaintext is recovered by extending the keystream with repeated calls to gen and XORing it with the ciphertext.

Full solve script:

Running it prints:

Sussy Friend

Description

We are given 12 Among Us screenshots and the hint:

Think about what all the pictures have in common...

The right idea is the Hexahue cipher. Each screenshot is a 2-column by 3-row symbol built from the same six recurring crewmates.

Solution

In the cafeteria screenshots, the six Hexahue colors are explicit:

  • R = red (Sussy CTF)

  • G = green (balloon)

  • B = blue (soldier hat)

  • Y = yellow (bunny ears)

  • C = cyan (cowboy hat)

  • M = magenta/pink (devil tail)

Read each image as a Hexahue block in row-major order:

  1. top row, left to right

  2. middle row, left to right

  3. bottom row, left to right

The standard Hexahue alphabet is:

The 12 screenshots decode to these Hexahue symbols in numeric filename order:

A complete decoder:

Output:

So the accepted flag is:

What's your Zodiac Sign?

Description

The PDF contains:

  • a legend page mapping custom Zodiac-like symbols to A-Z

  • a second page with a 17 x 20 grid of those symbols

After transcribing the symbol grid with the legend, a normal row-wise read does not produce plaintext. The 17 x 20 = 340 layout is the important clue: this challenge is modeled after Zodiac Z340, so the text needs a Zodiac-style transposition readout.

Solution

After manually transcribing the second page, the decoded letter grid was:

A pure toroidal 1,2 knight-move read gave strong English fragments, which suggested the intended route was very close to the real Z340 transposition. The useful version was to split the grid into row segments 9 / 9 / 2, then apply 1,2 decimation to the first two 9-row segments.

This script reproduces the important readout:

Output:

Even with a few remaining transcription/route imperfections, the clue is clear:

  • ... name of the hotel across the street from the sf chronicle ...

  • ... only fifteen minutes from where he mailed a cryptogram ...

  • ... was called z three forty ...

  • ... it took fifty one years to solve ...

So the task is to identify the hotel across the street from the San Francisco Chronicle building at Fifth and Mission. That hotel is the Pickwick Hotel.

Flag:


fwn

Gen-Z Found My Registry

Description

We are given a Windows registry export of HKLM\SYSTEM\CurrentControlSet\Services in chal.reg. The description says the attacker turned the registry into "String Cheese" and asks for all changes made.

The first obvious anomalies are two fake services:

  • HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\+7

  • HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\-6

Their Parameters subkeys contain only:

  • "evens"=""

  • "odds"=""

There are also two Linkage\Export values that were converted from registry hex data into quoted strings, matching the String Cheese hint:

  • .NET Data Provider for Oracle\Linkage\Export

  • .NET Data Provider for SqlServer\Linkage\Export

Those clues point to a parity-based character transform.

Solution

The actual payload is hidden in many root service keys as extra values with numeric names and single-character string data. Example:

  • DeviceAssociationService contains "5"="I"

  • CmBatt contains "7"="L"

  • WinRM contains "1"="J"

Collecting all root-level numeric-name single-character string values gives positions 1..26. Ordering by the numeric name produces:

The fake services tell us how to decode it:

  • apply +7 to even positions

  • apply -6 to odd positions

Applying that transform yields:

Solver:

Flag:

I Love Bacon!

Description

We are given a DNS capture. The local challenge directory was missing the attachment, but description.md linked the official repo path, which contained dns_c2.pcap.

The traffic is 1000 DNS queries from 10.67.0.2 to 10.1.1.53, each with a matching TXT response under *.dawg.cwa.sec.

Solution

The query labels and TXT answers are uppercase base32-like strings. A useful anomaly is that only 3 query/response pairs have the exact same encoded value in both the request and the response TXT. Those are the suspicious packets.

Packet pairs:

  • frames 533/534

  • frames 909/910

  • frames 1823/1824

The intended decode is per-record, not one giant concatenated stream:

  1. strip .dawg.cwa.sec

  2. treat each character as a 5-bit base32 symbol using A-Z2-7

  3. keep only full bytes from that record

  4. decode the 3 echoed records

  5. concatenate the resulting ASCII fragments in capture order

Code:

Output:

Flag:

Modem Metamorphosis

Description

Such a sad little routerarrow-up-right, assumed obsolete, cast-off and condemned to the dusty scrap heap... but who says this must be the end? Come with us, and we'll transform you into something beautiful~

Flag format: DawgCTF{Manufacturer_Model_OldFirmwareVersion_NewFirmwareName_NewFirmwareVersion}

Solution

The provided artifact for this challenge is the packet capture repo/Modem Metamorphosis/morph.pcap. The solve is to recover:

  • the original router manufacturer

  • the original router model

  • the original firmware version

  • the new firmware name

  • the new firmware version

The PCAP shows a user logging into a router web UI, browsing to the upgrade page, and uploading new firmware.

First, inspect the HTTP requests:

The interesting request is:

The stock router identity is visible in the extracted HTTP pages and in the HTTP auth realm:

Relevant hits:

That gives:

  • model family: WRT610N

  • stock firmware shown by UI: 1.00.00 B18

  • backup filename strongly suggests hardware revision V1

  • stock firmware version string itself is 1.00.00

Next, inspect the firmware upload stream:

The multipart upload contains:

This immediately reveals the new firmware:

  • new firmware name: OpenWrt

  • new firmware version: 24.10.0

  • target device slug: linksys_wrt610n-v1

To confirm the flashed image, extract the uploaded file from the POST body and inspect it. The extracted file in this solve was saved as firmware.bin.

Output:

Then verify the OpenWrt release from the unpacked rootfs:

Relevant values:

So the intended normalized flag components are:

  • Manufacturer: Linksys

  • Model: WRT610N_V1

  • Old firmware version: 1.00.00

  • New firmware name: OpenWrt

  • New firmware version: 24.10.0

Final flag:

Let's Avoid Doing Math

Description

A GitHub repository contains threat_depth_analysis.log with 120 labeled malware samples. Each has a "Known Threat Depth" (ground truth) and "Detected Threat Depth" (prediction), classified as minor, medium, or major. We need to report per-class accuracy, false positive rate, and false negative rate for each classification in growing order of importance (minor, medium, major), formatted with a single leading zero and no trailing zeros, comma-separated.

Solution

The critical parsing insight is that "how accurate it was, and what our false positive and false negatives rates were for each classification" means ALL three metrics (accuracy, FPR, FNR) are computed per class using one-vs-rest binary classification, not overall accuracy + per-class FPR/FNR.

Parse the log, build the confusion matrix, and compute per-class binary metrics:

Flag: DawgCTF{0.95,0.05,0.05,0.85,0.1,0.25,0.85,0.1125,0.225}

The Step After the PCAP

Description

The challenge provides an LLM-generated flow report instead of the original PCAP. The description says the analyzer lost the timestamps and also failed to identify where the interesting traffic was going. We need to find the correct destination, recover the relevant payload fragments, sort them chronologically, and join them with underscores.

Solution

The useful clue is in the header:

  • Repeated TLS JA3 hash observed in multiple flows to the same IP address.

That means the interesting traffic should be the set of flows sharing both:

  • one destination IP

  • one repeated TLS JA3 hash

  • non-empty payload fragments

Parsing the log shows exactly one such channel:

  • Dst IP: 45.76.123.45

  • TLS JA3 Hash: d2b4c6a8f0e1d3c5b7a9f2e4d6c8b0a1

There are 41 records in that channel with real payload fragments. Sorting those records by Timestamp gives the payloads in the intended order. Joining those fragments with underscores produces the accepted flag.

Solution code:

Running it prints:

Stomach Bug

Description

The challenge only gave a URL:

https://stomachbug.umbccd.net

Fetching / returned an endless attachment stream named spew.txt. The stream alternated between:

  • A sliding printable ASCII line

  • A numbered hex fragment like |000|89504e47...

The hex fragments contained a full PNG, then repeated in a loop.

Solution

One full cycle of the numbered hex lines reconstructed a valid 625x625 grayscale PNG. That PNG was itself a QR code. Scanning it produced another PNG as raw QR payload. Scanning that second PNG produced a base64 string, which decoded to the flag.

Full solve script:

Running it prints:

TeleLeak

Description

The app exposed Spring Boot Actuator and, critically, /actuator/heapdump.

The intended bug was that the heap dump leaked live application objects, including the seeded admin account. The login flow hashes the password in JavaScript before sending it, so the stored SHA-256 hex digest is enough to authenticate if it is submitted directly as the password form value.

Solution

The solve path was:

  1. Download the heap dump from the exposed actuator endpoint.

  2. Parse com/example/TeleLeak/User instances out of the HPROF.

  3. Recover the admin row:

    • username = admin

    • role = ROLE_ADMIN

    • password = f374e70b2d71eb7188c0eda0b6a13d47ca5abd681118de48354f003d8af534f5

  4. Submit that leaked hash directly to /login.

  5. Visit /admin/dashboard and read the flag.

Download:

Targeted HPROF extractor:

Running that script printed the seeded admin object:

Then authenticate by sending the leaked hash directly, not the plaintext password:

That returned:


misc

An Italian Penguin

Description

The provided file was attachments/Penguin_Steg.jpg. The challenge text says the image has something hidden in it and that "the key is the last word." A later hint was:

That clue reads as copy + pasta, so the intended search term is Linux copypasta.

Solution

First, confirm the image is actually a steghide container:

This reports embedded data, so the remaining problem is the passphrase.

The hint points to the well-known GNU/Linux copypasta ("I'd just like to interject for a moment..."). The challenge says "the key is the last word", and the last word of that copypasta is GNU/Linux.

Use that as the steghide password:

Equivalent direct extraction:

The extracted payload contains the flag:

Frequency 3000

Description

The local challenge directory only contained description.md, which linked to the actual challenge files on GitHub. That folder contained:

  • Space Pilot 3000 Transcript.txt

  • flag.txt

flag.txt was not the real flag. It contained hex bytes that decoded to:

The challenge hint said the message could be solved by "frequenting" Futurama's pilot episode, so the intended approach was frequency analysis against the pilot transcript.

Solution

Count character frequencies in Space Pilot 3000 Transcript.txt, then map each number in the decoded payload to the closest matching character frequency.

Several values match exactly:

  • 390 -> w

  • 1002 -> h

  • 580 -> y

  • 191 -> 0

  • 1589 -> t

  • 33 -> z

  • 141 -> !

  • 762 -> d

  • 352 -> b

  • 88 -> 3

  • 50 -> ?

The remaining values are off by only 1-2 from nearby transcript character counts:

  • 1314 -> n because n appears 1315 times

  • 1526 -> o because o appears 1528 times

  • 1293 -> r because r appears 1295 times

  • 379 -> g because g appears 380 times

That reconstructs:

Final flag:

Solver used:

Hiding in Plain Sight

Description

We are given a single image, hello.webp, and told that something is strange about it. The flag format hint says the answer is the name of the person or object found in the image.

Solution

The file itself was a normal WebP image with no useful metadata or appended payload, so this was not a container-stego challenge. The intended trick was visual: the image hides a recognizable face in plain sight.

I first confirmed there was no obvious embedded data:

Then I generated a few forensic transforms to make any hidden visual structure easier to see:

After applying the filters and inspecting the image as a whole rather than focusing on the fountain/statue details, the hidden face is Barack Obama.

So the flag is:

Beeps and Boops

Description

The challenge provided a note-to-character mapping in Old_Notes.txt and a WAV file, RandomSong.wav. The obvious intent was to recover a note sequence from the audio, then translate each note through the mapping.

The main complication was that the WAV is strongly harmonic, so naive pitch detection often locks onto overtones instead of the fundamental, especially for low notes. A direct decode produced text close to the answer, but not the exact capitalization and leet substitutions.

Solution

The working path was:

  1. Read Old_Notes.txt as the note-to-character lookup.

  2. Notice the audio has about 22 seconds of signal followed by silence.

  3. Fit the signal to a regular symbol grid. The best fit was 33 equally spaced symbols at about 0.6645 seconds each, which matches DawgCTF{...} length.

  4. For each symbol window, compute a spectrum and score every mapped note using a harmonic-comb style scorer.

  5. Because low notes were still ambiguous, score whole candidate phrase variants against the per-position note likelihoods instead of trusting per-note top-1 choices.

  6. The best-supported candidate was:

Accepted flag:

Solution code used for scoring candidate flags:

The top-scoring candidate was the accepted flag.

HAZMAT

Description

"I saw this CRAZY looking truck driving home. Can you figure out what it's carrying?"

An image of a highway with a hazmat truck is provided.

Solution

The image shows an Airgas tube trailer truck on a highway near UMBC/Catonsville, Maryland.

On the rear panel of the truck there is:

  1. A red diamond DOT hazmat placard with a flame symbol (Class 2.1 - Flammable Gas) containing UN number 1049

  2. Text below the placard reading HYDROGEN COMPRESSED

UN 1049 corresponds to "Hydrogen, compressed" in the DOT hazardous materials table.

The solution required cropping and enhancing the photo to read the placard number and descriptive text on the rear of the truck, then identifying the material using DOT HAZMAT placard standards.

Flag: DawgCTF{COMPRESSED_HYDROGEN}

HAZMAT III

Description

Apparently corporate says I have to deliver this really weird looking green vat somewhere, can you help me figure out what number I should put on my placard when I transport this? Your flag will look like DawgCTF{5661}.

Solution

The local challenge directory did not include the image, so I searched the synced organizer repo already present elsewhere in the workspace and recovered the missing asset:

  • /home/ubu/ctf/competitions/dawg26/fwn/01_gen_z_found_my_registry/repo/HAZMAT (I,II,III)/HAZMAT III/goop.jpg

The image shows a gray vat filled with fluorescent yellow-green liquid and a label reading UCARTHERM and SEE MSDS.

That identifies the product family as Dow UCARTHERM heat-transfer fluid. The fluorescent yellow-green dyed variant matches the Dow/UCARTHERM ethylene-glycol heat-transfer fluid line. The transport information for this fluid lists the DOT bulk identification number as NA3082, which is the placard number shown as 3082.

Submitted flag:

  • DawgCTF{3082}

crazy? i was crazy once! they locked me in a

Description

The challenge source only contained a single file, crazy.txt, which repeats the same sentence over and over with progressively more leetspeak substitutions.

Each repetition ends with a 3-character fragment after the transformed it drove me ... phrase. Those fragments are the flag split into 3-byte chunks, written in reverse chunk order, with each chunk itself reversed.

One chunk in the file is inconsistent with the standard DawgCTF prefix, but the intended prefix is obvious and the corrected flag is what the scoreboard accepted.

Solution

Extract the 3-character fragments, reverse the fragment list, then reverse each fragment:

This prints:

The entire payload is clearly readable except for the broken prefix chunk. Replacing the malformed prefix with the standard DawgCTF prefix gives the accepted flag:

Hiding in Plain Sight 2

Description

We are given a single image, attachments/ps2.png, and told that โ€œsomething here seems a little off.โ€ The flag is the name of the hidden person or object.

Solution

This is an image-steganography challenge. The PNG looks like a normal landscape at first glance, but the low bitplanes contain hidden data.

The quickest reliable path was:

  1. Split the PNG into RGB channels.

  2. Extract each bitplane from each channel.

  3. Recombine the least significant bit of each RGB channel into a new RGB image.

That LSB composite clearly reveals a hidden portrait of John Cena on the left side of the image, which matches the joke/theme of โ€œhiding in plain sight.โ€

Flag:

Solution code:

Run it with:

The important output is:

Opening that file reveals John Cena directly.

ZAP!

Description

The challenge gives three photos of an insulator and points to the NIA suspension catalog. The goal is to identify the correct ST-* style number and submit it as DawgCTF{ST-XXXX}.

Solution

The object is a porcelain suspension insulator. The NIA catalog made it clear the challenge piece belonged to the 10-inch suspension family around ST-4625 / ST-4626.

The useful path was:

  1. Compare the shell shape and underside rings against nearby NIA candidates.

  2. Read as much of the shell marking as possible from the close-up.

  3. Restrict the candidate set to styles whose published NIA markings actually fit what was visible.

The challenge photos were:

  • zap1.jpg

  • zap2.jpg

  • zap3.jpg

I used a few light crops to make the stamped areas easier to inspect:

The important read from the marking was:

  • 20000

  • LOCKE

  • 1840 visible above on the cap area

That immediately killed the UK and Lapp branches and left the Locke-family 10-inch styles as the only serious candidates.

I then pulled the relevant NIA pages:

That gave the key published markings:

  • ST-4625

    • additional Locke reports:

      • LOCKE / year / USA

      • LOCKE / 15000 TEST / 30000 M&E

  • ST-4626

    • LOCKE / 20000 TEST / 10000 M&E // 43 84 / U.S.A.

  • ST-4626F

    • LOCKE / 10000 TEST / 20000 M&E

ST-4626 looked strongest at first because it contains the exact LOCKE + 20000 pair, but that submission was wrong. After that, the best remaining fit was ST-4626F:

  • same 10-inch Locke shell family

  • same general shell geometry as the challenge piece

  • still contains the visible 20000 and LOCKE

  • explains why only a partial read from the blurry mark was available

Final correct submission:

Accepted flag:

ZAP! II

Description

Part II asks for the model number of the same insulator from ZAP! I. The sample flag format is DawgCTF{30S255}.

Solution

The key point was to use the result from ZAP! I first:

  • ZAP! I accepted DawgCTF{ST-4626F}

So the real task in part II was not โ€œguess from the photos againโ€, but:

  1. take the accepted style ST-4626F

  2. identify what manufacturer/model that style corresponds to

  3. submit the Locke model number

I pulled the ST-4626F style data and reference images:

That gave the important published ST-4626F entry:

  • size: 10" / 254mm

  • manufacturer shown: Locke

  • shell marking: LOCKE / 10000 TEST / 20000 M&E

Then I downloaded the official manufacturer cross-reference and drawings:

The useful table from the REA materials list was:

  • ANSI 52-3 -> Locke 20S840

  • ANSI 52-4 -> Locke 20S580

  • ANSI 52-5 -> Locke 30S255

  • ANSI 52-6 -> Locke 30S257

The deciding step was the hardware type:

  • ST-4626F reference photos show the ball-and-socket branch, not the clevis branch

  • the official 2325230 drawing is the 20k ball-and-socket unit

  • the official 2325240 drawing is the 20k clevis unit

  • ST-4626F matches the ball-and-socket side, so it maps to the Locke 52-3 family, not 52-4

That leaves the Locke model number:

  • 20S840

Accepted flag:

HAZMAT II

Description

I saw another crazy looking truck! This one looks even scarier... can you identify the type of storage container being used here?

Your answer will look like DawgCTF{INTERMODAL_CONTAINER}

Hint used:

If you're having trouble finding info, consider that this is clearly in the US, and the US highly regulates what's on that trailer. Also note this is NOT the model of container, but the classification type that the container falls into. It should be concise, so only the name and the word type, e.g "TYPE_QUADRO" or "TITANIUM_TYPE", not "TYPE_CHARLIE_FISSILE" or "FISSILE_TYPE_DOE_UMBRA".

Solution

The image shows several nuclear-material transport packages on a trailer. Cropping the label on the front of the package makes the important text readable:

  • RADIOACTIVE MATERIAL

  • URANIUM HEXAFLUORIDE

  • FISSILE UN 2977

  • MODEL UX-30

  • ... TYPE B(U)

The misleading part is MODEL UX-30: that is the package model, but the hint explicitly says the flag is not the model and instead asks for the classification type.

For radioactive-material transport in the US, Type B is the regulatory package classification. The U in B(U) is the approval subtype marking, but the actual concise classification name is Type B.

So the flag is:

  • DawgCTF{TYPE_B}

Through the Looking Bit

Description

A certain university hosts a mirror. If you interact with it the right way, it will greet you. Just remember: reflections aren't always true.

Solution

The challenge hints at a university mirror (software repository mirror) that should be interacted with "the right way." Since this is DawgCTF (run by UMBC), the target is the UMBC Linux User's Group mirror at mirror.lug.umbc.edu.

Step 1: Connect via rsync

Connecting with rsync reveals a custom MOTD/banner containing binary digits (0s and 1s) arranged in a circular shape, with a UMBC ASCII art logo in the center:

The banner contains rows of 0 and 1 characters forming a diamond/ellipse pattern, with the UMBC logo overlaid in the center obscuring some bits.

Step 2: Extract and decode the binary data

The key insight is that the 0 and 1 characters in the banner ARE the data. To decode:

  1. Extract only the actual 0/1 characters from the banner, ignoring spaces (background padding) and the ASCII logo area

  2. Invert all bits (0 -> 1, 1 -> 0) - as hinted by "reflections aren't always true"

  3. Read the resulting bitstream as 8-bit ASCII

The decoded message is a repeating 34-character string: DawgCTF{R3ync_1s_b3tt3r_th5n_http}

The bits behind the UMBC logo are simply skipped - since the flag repeats, we have enough visible bits to reconstruct the full message without needing to guess the hidden values.

Flag: DawgCTF{R3ync_1s_b3tt3r_th5n_http}

The message "Rsync is better than HTTP" references the fact that the flag was only accessible via the rsync protocol (through the MOTD banner), not through HTTP.

Mr. Worldwide

Description

The server sends a weighted adjacency matrix for a graph and asks for the minimum tour distance. The graph is complete and the correct interpretation is the Traveling Salesman Problem on a closed tour: start at one node, visit every node exactly once, and return to the start.

The remote instance is time-sensitive, so a native solver is the safest approach.

Solution

I used Held-Karp dynamic programming for exact TSP. To reduce states, I fixed node 0 as the starting node and only tracked subsets of the other n-1 nodes.

State:

dp[mask][j] = minimum cost to start at node 0, visit exactly the nodes in mask, and end at node j

Transition:

dp[mask | (1 << (k-1))][k] = min(dp[mask][j] + dist[j][k])

Final answer:

min(dp[full_mask][j] + dist[j][0])

Because the server starts timing immediately, I wrote the socket client and solver in the same C++ program so it could parse the matrix, solve it, and reply without shell or subprocess overhead.

Solution code:

Build and run:

Flag:

DawgCTF{wh4t_l4ngu4ag3_d1d_y0u_us3?}


proto

Protocol Analysis 1: Can You Hear Me?

Description

The local description.md only links to the shared Protocol Analysis PDF. In challenge 1, Bob expects a plaintext message with a fixed format:

"Hello", bob, "this is", alice, "give me the flag"

If he receives that message, he replies in plaintext with:

"here it is", [FLAG]

Since there is no authentication or encryption, the attacker can send Bob the expected message directly and read the flag from his response.

Solution

Create a challenge instance, then send Bob the exact content he expects:

Example response:

Use that conn_id in a request to Bob:

Response:

Flag:

Protocol Analysis 2: Liar

Description

Bob only releases the flag if the speaker identifies themself as charlie, but Aliceโ€™s script says alice. The protocol has no authentication, so the attacker can relay Aliceโ€™s first message to Bob after changing only the claimed sender name.

Solution

Start a fresh instance for model 2, ask Alice for her first outbound message, replace n:alice with n:charlie, and forward the modified message to Bob. Bob accepts the forged identity claim and returns the flag.

Recovered flag:

Protocol Analysis 3: Missing

Description

The shared protocol manual shows that for challenge 3, Alice has no actions at all, while Bob only waits for a single plaintext message:

"Hello", B, "this is", A, "give me the flag"

After receiving that message, Bob responds with:

"here it is", [FLAG]

That means there is no authentication, no prior session state from Alice, and no cryptography to bypass. We can create a challenge instance and send Bob the exact message he expects while claiming to be Alice.

Solution

Create a model 3 instance, take the returned conn_id, and send Bob this content:

t:Hello|n:bob|t:this is|n:alice|t:give me the flag

Bob returns the flag directly.

Solution code:

Returned response:

Protocol Analysis 4: Real Security!

Description

Alice sends Bob a symmetric key and nonce in plaintext and asks him to encrypt the flag with them. Because the attacker can read Alice's first message, the attacker also learns the exact key and nonce Bob will use.

Solution

Start a challenge instance, ask Alice for her first outbound message, extract the symmetric key and nonce from that message, forward the exact message to Bob, then decrypt Bob's ciphertext with the provided utility endpoint.

This returns:

Protocol Analysis 5: Is This Real?

Description

Alice asks Bob to send the flag encrypted under Alice's asymmetric key. Bob checks that the sender name is alice, but he does not verify that the supplied public key actually belongs to Alice.

Solution

Generate a fresh asymmetric keypair, obtain Alice's opening message from /alice, replace the final key field with our own public key, and forward that forged message to /bob. Bob encrypts the flag to our key, and we decrypt it with the matching private key via /util/asym_decrypt.

Recovered flag: DawgCTF{C3RT1F13D_1NS3CUR3}

Protocol Analysis 6: Sneedham-Chucker

Description

This challenge is a Needham-Schroeder-style public-key protocol between Sneed and Chuck. The bug is the classic man-in-the-middle issue: Chuck accepts Sneed's encrypted first message as long as it decrypts correctly under Chuck's key, but the protocol does not bind the responder's identity strongly enough to stop relaying through an attacker-controlled keypair.

Solution

Generate an attacker keypair and cert for a harmless name such as cowboy.

  1. Ask Bob/Chuck for his public key and cert.

  2. Send Alice/Sneed the attacker public key and cert.

  3. Alice responds with {nA, pubA, A, certA} encrypted to the attacker key. Decrypt it to recover nA and Sneed's public key.

  4. Re-encrypt that exact plaintext to Chuck's public key and forward it to Bob/Chuck.

  5. Chuck responds with {nA, nB} encrypted to Sneed's public key. Forward it unchanged to Alice/Sneed.

  6. Alice replies with {nB} encrypted to the attacker key. Decrypt it to recover nB.

  7. Re-encrypt nB to Chuck's public key and send it to Bob/Chuck.

  8. Chuck returns the final symmetric ciphertext.

The final symmetric parameters are:

  • Key: sha256((nA + nB).encode()).hexdigest()

  • Nonce: the first 24 hex characters of that key

Decrypting yields the flag:

DawgCTF{FORM3RLY_S3CUR3}

Solver:

Protocol Analysis 7: Mediation

Description

Challenge 7 uses this protocol:

  • Alice sends pubA, A, certA, nA

  • Bob replies with pubB, B, certB, nB, {B, nB, nA}privB

  • Alice later expects pubX, X, certX, nX, {X, nX, nA}privX

  • Alice responds with {A, nX, nA}privA

  • Bob accepts {A, nB, nA}privA and sends the flag

The flaw is that Bob does not require the second message to come from Bob specifically. He only needs a valid certificate for some identity X and a valid signature over (X, nX, nA). That lets an attacker choose X, set nX = nB, and then use Alice as a signing oracle. Alice will sign (A, nB, nA), which is exactly what Bob wants in the next step.

Solution

Attack flow:

  1. Start a challenge instance.

  2. Ask Alice for her first message and capture nA.

  3. Relay that message to Bob and capture nB.

  4. Generate our own keypair and a valid cert for a non-reserved name such as mallory.

  5. Sign n:mallory|d:nB|d:nA with our private key.

  6. Send Alice pubMallory, mallory, certMallory, nB, {mallory, nB, nA}privMallory.

  7. Alice returns {alice, nB, nA}privAlice.

  8. Forward that signature to Bob.

  9. Bob sends the flag.

Full solver:

Recovered flag:

Protocol Analysis 8: Reflection

Description

The challenge references the shared protocol-analysis PDF. For challenge 8, the protocol is:

Alice:

  • send: pubA, A, certA

  • recv: pubX, X, certX, nX1

  • send: nA, {X, nX1, nA}privA

  • recv: nX2, {A, nA, nX2}privX

Bob:

  • send: pubB, B, certB

  • recv: pubA, A, certA, nA

  • send: nB, {A, nA, nB}privB

  • recv: nA2, {A, nB, nA2}privA

  • send: [FLAG]

The mistake is that Alice will sign attacker-chosen identity material in step 3, and Bob will accept a valid Alice signature in his final step. The working transcript is a reflection variant:

  1. Start a challenge instance.

  2. Receive Aliceโ€™s initial message pubA|alice|certA.

  3. Receive Bobโ€™s initial message pubB|bob|certB.

  4. Send Bob pubA|alice|certA|nonce.

  5. Bob responds with nB|sigB(alice, nonce, nB).

  6. Send Alice pubB|bob|certB|nB.

  7. Alice responds with nA2|sigA(bob, nB, nA2).

  8. Forward Aliceโ€™s response to Bob.

  9. Bob sends the flag.

The key point is that Bob accepts Aliceโ€™s response produced over Bobโ€™s own identity bundle and Bobโ€™s nonce.

Solution

Running the script returned:

Protocol Analysis 9: Oracle

Description

Challenge 9 gives Bob a flag encrypted twice to Alice:

{{FLAG}pubA, B}pubA

Alice will then repeatedly accept messages of the form:

pubX, X, certX, {{m}pubA, X}pubA, A

and answer with:

pubA, A, certA, {{m}pubX, A}pubX

This makes Alice a re-encryption oracle for anything encrypted to pubA.

Solution

The attack is a two-step unwrap:

  1. Start a fresh instance and ask Alice for her initial message.

  2. Forward that message to Bob and capture Bob's ciphertext {{FLAG}pubA, B}pubA.

  3. Generate an attacker keypair and valid certificate for a non-reserved name such as mallory.

  4. Wrap Bob's full outer ciphertext as the inner payload of a new message to Alice: {{ {{FLAG}pubA, B}pubA , mallory }pubA Alice decrypts Bob's outer layer and re-encrypts the plaintext d:{FLAG_cipher_for_A}|n:bob to us.

  5. Decrypt Alice's reply with the attacker private key to recover {FLAG}pubA.

  6. Send that recovered ciphertext back through Alice again, wrapped for mallory: {{ {FLAG}pubA , mallory }pubA

  7. Alice decrypts the flag and re-encrypts it to us.

  8. Decrypt the result with the attacker private key and read the flag.

Recovered flag:

DawgCTF{ST4R3_1NTO_TH3_VO1D}

Solver used:


pwn

Stacking Flags

Description

The local challenge only provided a remote host and a link to the source. The source is a 64-bit non-PIE binary compiled without stack canaries:

vulnerable_function() reads unbounded input into a 64-byte stack buffer with gets(), so this is a standard ret2win.

Solution

Because the code was compiled with -no-pie, the address of win() is fixed. Rebuilding the provided source locally produced:

The stack layout is:

  • 64 bytes for buffer

  • 8 bytes for saved rbp

  • then the saved return address

So the overwrite offset is 72 bytes. Sending 72 junk bytes followed by the little-endian address of win() redirects execution into the flag-reading function before main() can continue.

Exploit:

Running the exploit against the remote service returned:

Just Print It

Description

The service reads one line with fgets() and passes it directly to printf():

There is also a hidden win() function that opens flag.txt and prints it.

Solution

This is a straightforward format-string exploit.

Key facts:

  • The binary is compiled -no-pie, so code addresses are fixed.

  • puts@GOT is writable because the binary has partial RELRO.

  • win() already exists, so code execution is unnecessary.

  • After printf(buffer), the program immediately calls puts().

Exploit strategy:

  1. Use the format string to overwrite puts@GOT with win().

  2. Let execution continue normally.

  3. The next call to puts() jumps to win() and prints the flag.

On amd64, the input buffer appears at format-string argument offset 6, so fmtstr_payload(6, ...) works directly.

Relevant addresses from the locally reproduced binary:

  • win = 0x401196

  • puts@got = 0x404000

Exploit code:

Running it against the remote service returned:

Stacking Melodies

Description

A music parser/scorer binary with source provided. Connect to nc.umbccd.net:8929 and exploit vulnerabilities to read the flag.

Solution

The binary has two key vulnerabilities:

  1. Integer signedness bug in validate_size(): Returns (int)aligned where aligned is size_t. Large uint32_t values for d_len produce negative int results, bypassing the > 2048 check.

  2. Format string vulnerability: printf(title) at line 82 uses user-controlled input as the format string.

The heap overflow approach (using d_len = 0xFFFFFFC0 to make malloc(d_len + 0x40) wrap to malloc(0), then overflowing into the adjacent session_context) worked locally but failed remotely due to different heap layouts.

The format string approach was more reliable:

  • Leak ctx pointer: Position 9 on printf's argument list contains the heap address of ctx (the session_context struct). ctx->server_logging is the first field - a function pointer initially set to log_event.

  • Leak remote log_event address: Using %9$s to dereference ctx and read the function pointer bytes, revealing remote log_event = 0x4011e6 (vs local 0x4011d6).

  • Find remote win address: The remote binary differs (e.g., calculate_rating() returns rand() instead of 0), shifting addresses. By scanning %Nc%9$hn with values around the estimated win offset, the correct lower 2 bytes were found: 0x124e, giving remote win = 0x40124e.

  • Overwrite function pointer: %4686c%9$hn prints 0x124e characters then writes that count (as uint16_t) to *ctx, overwriting ctx->server_logging's lower 2 bytes from log_event to win. When ctx->server_logging("Rating", rating) executes, it calls win() which prints the flag.

Flag: DawgCTF{A_H34ping_helping}


recon

Gateway to the Turnpike

Description

We are given a road-trip photo and asked for the ZIP code of the place where it was taken.

Solution

The local directory only contained description.md, so the first step was to recover the inline challenge image from the live MetaCTF challenge JSON using the session cookie already stored in the competition config.

Inspecting the image shows several useful clues:

  • a green street sign reading 5 Breezewood Rd

  • I-70 East/West signage

  • the dense motel / gas-station strip that is famous in Breezewood

  • nearby brands like Sheetz, Days Inn, McDonald's, and BP matching that interchange area

That identifies the location as Breezewood, Pennsylvania.

The ZIP code for Breezewood is:

So the flag is:

The Temple of Doom

Description

We were given a photo of a distinctive stepped building and told the flag was the building's nickname.

Solution

The challenge directory did not contain the image locally, but the provided image URL was:

I downloaded the image and inspected it:

The photo showed a large gold stepped-pyramid style office building with a broad parking lot in front and hills behind it. That matched the Chet Holifield Federal Building in Laguna Niguel, California.

This building is commonly nicknamed The Ziggurat Building. The shorter form The Ziggurat was rejected, so the full nickname was required.

Final flag:

ะ”ะผะธั‚ั€ะธะน-ัˆะตัั‚ัŒ

Description

OSINT challenge. Given an image (dmetri6.jpeg) showing underground metro tunnels with a vasi.net watermark. The description states a friend from Ukraine sent this picture claiming it's "the key to a secret treasure room underground." The flag is the official name of the location, 6 capital letters.

Solution

The challenge title "ะ”ะผะธั‚ั€ะธะน-ัˆะตัั‚ัŒ" translates to "Dmitri-six," which is the Russian phonetic alphabet expansion of D-6 (ะ”-6) -- the KGB codename for Moscow's secret underground metro system. The filename dmetri6.jpeg reinforces this (dmetri + 6 = D-6).

The images are well-known photographs of this clandestine metro system, sourced from the Russian entertainment site vasi.net. They show:

  • Two old Soviet-era trains in an underground tunnel

  • Dark flooded tunnels with rail tracks

  • Curved platform/tunnel sections

The system's commonly known name is Metro-2 (ะœะตั‚ั€ะพ-2), an informal designation for the officially-unacknowledged deep underground metro built during Stalin's era. It connects the Kremlin with key government facilities including the FSB headquarters and government airport at Vnukovo-2.

The "official name" in 6 characters, all caps: METRO2.

Flag: DawgCTF{METRO2}

Better Call AT&T!

Description

We need the real phone number for the parking garage seen in Better Call Saul. Flag format: DawgCTF{##########}.

Solution

There were no local attachments, so this was pure OSINT.

First, identify the exact garage used in the show:

This gives:

Then pull the coordinates from a second location source:

Relevant result:

Resolve those coordinates to the actual garage:

Relevant result:

So the filming location is the Albuquerque Convention Center parking garage.

Now get the garage phone number from public parking listings:

Relevant result:

That yields the flag:

Computer Repair I

Description

We are given a photo of the underside of a Dell laptop and asked to determine the RAM size and speed it was sold with, along with the hard drive size and model. The flag format is:

DawgCTF{RAMSIZE_RAMSPEED_DRIVESIZE_DRIVEMODEL}

Solution

The image shows a Dell Latitude 5500 and the underside label reveals the service tag FZGXPV2.

Using the service tag, the original-configuration data can be recovered from Dell support. The useful rows were:

  • Memory: R4GT0 : MOD,DIMM,16GB,1X16G,2667,N-ECC | CRXJ6 | Dual In-Line Memory Module,16GB,2666,2RX8,8G,DDR4,Ss | 1

  • Storage: 2HMFM | INFO,C DRIVE,PCIESSD | 1 35PK2 | Solid State Drive,256G,P32,30S3,TOSHIBA,BG3 | 1

From that:

  • RAM size: 16GB

  • RAM speed: 2666MHZ The accepted value uses the Dell part description speed (2666) rather than the shorthand module label (2667).

  • Drive size: 256GB

  • Drive model: 35PK2 The challenge expected the Dell part number as the drive โ€œmodelโ€, not the OEM family name such as BG3.

Flag:

DawgCTF{16GB_2666MHZ_256GB_35PK2}

Commands used:

Locksmith

Description

Identify the lock series from the challenge image and determine the lock body height. The required format was DawgCTF{SERIES_HEIGHT}.

Solution

The local description.md did not include the image, but the live MetaCTF challenge page had it embedded inline. I pulled the raw challenge JSON, extracted the image URL, and downloaded the lock photo:

The lock face is distinctive:

  • teardrop-shaped escutcheon

  • five round pushbuttons in a circle

  • Roman numerals around the buttons

  • a SIMPLEX turnpiece, visible upside down in the challenge photo

That identifies it as a Simplex 900 Series mechanical pushbutton lock.

I then checked the official/retail spec sheet for the 900 Series:

Page 2 shows the Simplex 900 Series auxiliary lock exterior height as 3 3/4" (95 mm).

So the flag is:

Computer Repair II

Description

We are given a photo of the front of a Dell laptop and asked for the laptop's screen size. The expected flag format is DawgCTF{18.9IN}.

Solution

The local challenge directory only contained description.md, so the first step was to recover the missing attachment from the public challenge repository referenced by the related Computer Repair III challenge.

From the public repository, the Computer Repair II asset is r2.jpg. The photo shows a Dell laptop from the front, but not enough text is visible on that image alone to read the exact model.

To identify the model cleanly, I checked the corresponding asset for Computer Repair I, which appears to be the same laptop photographed from the bottom. That image clearly shows the model text Latitude 5500.

Once the model was known, the screen size could be verified from Dell's official Latitude 5500 specifications. Dell lists the display as 15.6 in..

Therefore the flag is:

DawgCTF{15.6IN}

Commands used:

The Lookout's Legend

Description

High above the birthplace of the MTO, this mountain offers a view that spans six counties. What do the locals call this spot?

Solution

The clue points to central Pennsylvania.

MTO is a strong reference to Sheetz's "Made-To-Order" branding, which points at Altoona, Pennsylvania, where Sheetz is based and strongly associated with the MTO concept.

From there, the mountain clue fits Wopsononock Mountain above Altoona:

  • Local/history sources refer to the mountain and lookout area as Wopsy.

  • Historical descriptions of the Wopsononock resort/lookout say the view extended across six counties.

So the locally used name is:

Wopsy

Flag:

Computer Repair III

Description

OSINT challenge (135 pts). Given photos of a disassembled Dell device, identify the exact Dell product model. The flag is 6 characters (capital letters and numbers).

Solution

Two images were provided showing a Dell device taken apart:

  1. cr3_1.jpg: Shows a black rectangular Dell-branded case (the outer shell) and the internal PCB removed from it. The PCB has a cooling fan assembly, multiple port connectors along the edges, a host module connector slot, and a QR/data matrix code.

  2. cr3_2.jpg: Close-up of a PCB corner showing a Microchip PIC microcontroller (identifiable by the "PIC" copyright marking), LED indicators, and various board silkscreen labels.

Key identification steps:

  1. Form factor: The elongated rectangular case with Dell logo and the internal PCB layout (fan, multiple video/USB ports, modular cable connector) identified this as a Dell WD19-series docking station.

  2. PIC microcontroller: The Microchip PIC chip visible in the close-up matches the PIC32MX40F128H used in WD19 docks for fan/system management, as documented in public teardowns of WD19/WD22TB4 docks.

  3. 6-character constraint: Only two WD19 variants have exactly 6-character model names: WD19TB (Thunderbolt) and WD19DC (Dual USB-C). The WD19TB is the more commonly deployed Thunderbolt variant.

  4. Context from series: Computer Repair I showed a Dell Latitude 5500 laptop, confirming this series involved identifying Dell enterprise hardware and accessories.

Flag: DawgCTF{WD19TB}

Plane Spotting Pt. 1

Description

A photo (20260301_160018.jpg) was transmitted from a "cyberdawg" documenting their travel before going missing. The task is to identify the airport where the photo was taken. Flag format: DawgCTF{IATA}.

Solution

The photo shows a Southwest Airlines Boeing 737 on the tarmac with a fuel truck and flat terrain with bare trees in the background.

The critical clue is the fuel truck which has "USAirports" branding on its tank. USAirports is a family-owned FBO (Fixed Base Operator) that operates exclusively at Frederick Douglass/Greater Rochester International Airport in Rochester, New York.

The IATA code for Rochester is ROC.

Additional confirming details:

  • Southwest Airlines serves ROC with multiple weekly flights

  • The flat terrain matches the Lake Ontario plain around Rochester

  • Bare deciduous trees are consistent with upstate New York in early March

  • EXIF data was stripped (no GPS), so visual identification was required

Flag: DawgCTF{ROC}

Plane Spotting Pt. 2

Description

You saw this plane approaching; what airport was it coming from? Use the flag from Plane Spotting Pt. 1 to unlock the image. Flag format: DawgCTF{IATA}. Limit of six attempts.

Solution

This challenge is part of a three-part series. Solving Pt. 1 (DawgCTF{ROC}) unlocks the Pt. 2 image (planespotting2.jpg).

The unlocked image shows a plane on final approach, photographed from the ground looking up through trees.

EXIF analysis of planespotting2.jpg provided critical metadata:

  • GPS: 39ยฐ8'24.52"N, 76ยฐ38'28.91"W (directly under the BWI approach path)

  • Timestamp: 2026-04-05 15:22:55 EDT

  • Camera: Samsung Galaxy S23+

The GPS coordinates place the photographer near Baltimore-Washington International Airport (BWI). The plane is a Southwest Airlines 737 on final approach.

Flight identification: Searching historical Southwest arrivals at BWI around 15:22-15:25 EDT on April 5, 2026 revealed flight WN1868 from NAS (Nassau, Bahamas) arriving at 15:24 -- a near-exact match to the photo timestamp (2 minutes before landing = on final approach).

Flag: DawgCTF{NAS}

Plane Spotting Pt. 3

Description

We are given a photo of an aircraft just after takeoff and need the aircraft registration number.

Solution

The cleanest path was:

  1. Read the photo metadata to get the exact timestamp.

  2. Use the local Sea-Tac noise monitoring dataset to identify which departure matched that time and corridor.

  3. Use the official BTS on-time performance dataset for July 2023 to map that exact flight to its tail number.

The image EXIF timestamp was 2023-07-18 06:54:49 -07:00.

The local Seattle noise workbook contained a Hyper extract with flight/noise events. Querying the few minutes around the photo time showed only one departure in the correct window:

Relevant result:

So the aircraft in the photo was AS195 / ASA195, departing SEA.

Next, use the official BTS July 2023 on-time data, which includes Tail_Number:

Output:

That is the exact flight, so the registration is N609AS.

Flag:

owo?

Description

Find the ZIP code of the town containing the Pizza Hut shown in the challenge photo.

Solution

The decisive clue was the blue rooster near the sign. Once that was identified as carrying a WVU-style mark, the search narrowed back to West Virginia instead of Pennsylvania or Kentucky.

The final match is the Pizza Hut at 444 Virginia Ave, Petersburg, WV 26847. The Street View pano matches the challenge scene, including the old Pizza Hut pole sign, the fenced utility-style lot, the nearby banner, and the roadside layout.

Clean map links:

  • Place: https://www.google.com/maps/place/Pizza+Hut,+444+Virginia+Ave,+Petersburg,+WV+26847/

  • Street View: https://www.google.com/maps/@38.9937571,-79.1137107,3a,75y,328.84h,91.83t

Final flag:

Andy Martin

Description

We are given an OSINT target, Andy Martin, described as a Londoner who travels a lot, with mention of Mauritius and Portugal. The question asks:

Where did he go out to eat in his hometown on Thursday July 12, 2018?

Solution

The solve path was:

  1. Identify the correct Andy Martin Google Maps contributor profile.

  2. Use the live Google Maps contribution timeline, especially old photos around July 2018, to recover food venues near the target date.

  3. Try candidate venue names in the flag format until the accepted place string is confirmed.

The key identity pivot was the Google Maps contributor:

  • https://www.google.com/maps/contrib/101832575045909613341

This established that the target was a London-area traveler and that the hometown clue should still be interpreted broadly enough to include London venues, not just Bromley/Swanley/Sevenoaks.

I also parsed the saved hidden Google ratings dump to understand his historical activity and home-area cluster. This was useful for confirming identity and ruling out some false directions, even though the July 2018 answer itself was not present in the saved dump.

Code used to extract dated ratings from andy_ratings_full.txt:

That produced early 2018 local activity such as:

  • Castle Farm, Kent

  • The Mens Room - Barber Shop Dartford

  • Caffรจ Nero in Sevenoaks

This confirmed the SE London / Kent-border footprint, but there was still no saved July 2018 rating entry for the target meal. The answer instead came from the live Google Maps photo history.

Manual review of Andy Martinโ€™s July 2018 Google Maps photos (edit: scrolling down for like 30 minutes while watching youtube) recovered several venues from the period immediately before Portugal travel:

  • Nando's Whitechapel

  • Starbucks

  • Costa Coffee

  • Poppies Fish & Chips

  • West Cornwall Food Company Canterbury

From there:

  • West Cornwall Food Company Canterbury looked like a travel/transit stop, not hometown dining.

  • The generic coffee entries were weak because branch names were unclear.

  • Poppies Fish & Chips was a clean named food venue in London and matched the broad โ€œLondoner / hometownโ€ reading better than the transit stop.

Several alternative guesses were tested and rejected, including:

  • DawgCTF{whitechapel}

  • DawgCTF{nandos_whitechapel}

  • DawgCTF{poppies}

  • DawgCTF{poppies_fish_and_chips}

The accepted flag preserved the venueโ€™s displayed punctuation and spacing:

Final flag:


reven

Machine Learnding

Description

The public repo originally pointed at a broken Google Drive folder artifact, but the repo history showed the challenge was fixed on April 10, 2026. The corrected link in repo/Machine Learnding/gdrivelink.txt pointed to a new silly_fella.zip containing a full merged_qwen_model/ bundle.

The intended solve was behavioral, matching the challenge hint:

This AI is pretty stupid, try playing around with it and see what you can uncover :)

After downloading and extracting the fixed ZIP, the model loaded cleanly and directly revealed the flag when prompted.

Flag: DawgCTF{Astr4l_Pr0j3ct_Th1s!}

Solution

  1. Check the repo history and use the fixed Drive link, not the old truncated folder artifact.

  2. Download silly_fella.zip.

  3. Extract it:

  1. Load the extracted model and ask it for the flag:

Observed output:

The model consistently converged on the same flag across multiple prompts, including:

The stable flag string was:

Cheater Cheater...

Description

There's this game called Hac-Manarrow-up-right and I've been trying really hard to beat this guy's high score but I swear it's impossible! Can you help? The flag will be in the format DawgCTF{Anyth1ngIsP0ss1bl3!}

Solution

The local challenge directory only contained description.md, so the actual artifact had to be pulled from the linked GitHub challenge folder. That folder contains one file:

Decompiling the jar with javap -c -p shows the intended trick:

  1. SimplePacMan.actionPerformed() sets winner = true once score >= 6942069.

  2. In paintComponent(), the win path sets the panel name to the decimal score string and then calls getComponents()[0].revalidate().

  3. JTextBasket.revalidate() uses the parent component name as a BigInteger, computes:

  1. The decimal result is treated as a hex string for the AES key, and the reversed decimal string is treated as a hex string for the IV.

  2. It decrypts the hardcoded Base64 ciphertext:

So there is no need to play the game. We can reproduce the decryption directly using the winning score 6942069.

Exact recovery code:

Running it prints:

Flag:

Checkmate, Liver King

Description

Reverse the provided chess binary and recover the flag. The challenge title and runtime behavior point at the Fried Liver line, but the real trick is that the binary only prints a compact destination-only move blob before the GUI applies one final scripted reply.

Solution

The useful patch is in the engine reply path, not the GUI. The binary stores several XOR-encrypted strings with repeating key xnasff3wcedj. Decrypting the blobs around 0x131900 gives four checkpoint board states, a compact move string, and the success message.

The encrypted data can be recovered with:

That prints:

Those checkpoints correspond to the Fried Liver line:

The important detail is that the message prints immediately after White reaches Nxf7, but the patched reply path still returns one more hardcoded Black move: e8f7 (...Kxf7). So the printed blob is the right format, but it is missing the final on-screen move destination.

The intended destination-only sequence from the actual move list on screen is therefore:

Concatenated:

Final flag:

Data Needs Splitting

Description

The challenge description only gave a domain: data-needs-splitting.umbccd.net.

Direct HTTP/DNS A lookups did not return a host, but querying TXT records revealed the actual payload: a Base64-encoded JAR split across numbered DNS TXT chunks.

Solution

data-needs-splitting.umbccd.net had TXT records prefixed 00 through 16. Concatenating the chunk bodies in numeric order and Base64-decoding them produced a JAR containing:

Main loads /assets/file.dat through Loader.defineClass(...). That file is another Java class, Validator.

Validator.validate():

  1. Reads one input line.

  2. Uses two long constants:

    • 2194307438957234483

    • 148527584754938272

  3. For each character at index i, computes:

    • a = (key1 >> ((i % 4) * 16)) & 0xffff

    • b = (key2 >> ((i % 4) * 16)) & 0xffff

    • appends the decimal string of ord(ch) ^ a ^ b

  4. Compares the concatenated decimal output against:

That gives four repeating XOR masks:

Then the target decimal stream can be parsed character by character using the standard DawgCTF{...} flag format.

Self-contained solve script:

Recovered flag:

Dust to Dust

Description

We are given encoder.c and output.txt. The encoder only implements level 1 compression, so the task is to reverse that step and reconstruct the original bitmap.

Solution

encoder.c reads input.txt as rows of 0/1 characters. It requires:

  • each row length minus the newline to be a multiple of 3

  • the total number of rows to be a multiple of 2

Compression then takes each 2x3 block:

It interprets those six bits as a binary number and stores it as:

Then each compressed row is written followed by } and the whole file ends with ~.

So decompression is:

  1. Split output.txt on } and ignore the trailing ~.

  2. For each character, compute value = ord(ch) - 0x20.

  3. Convert value back to 6 bits.

  4. Expand those bits back into a 2x3 block.

Solver:

Running that reconstructs the original 198 x 100 monochrome bitmap. Rendering the PBM reveals the flag visually:

DawgCTF{Th1s_w4s_1nspIr3d_By_UND3RT4L3!}

Last updated