Description
What is in this web assembly? Find the flag hidden in the WASM module loaded by the challenge page.
Setup
Open the challenge URL in your browser with DevTools open.
# Open DevTools with F12 and navigate to the Network tab before loading the pageSolution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1
Download the WASM file from the Network tabObservationI noticed the challenge page loads a WebAssembly module at runtime, which suggested intercepting the network request in DevTools to capture the .wasm binary before analyzing it offline.In DevTools > Network, reload the page and look for a request with Content-Type application/wasm or a .wasm extension. Right-click and save the file.Learn more
Unlike Some Assembly Required 1, the flag in this challenge is not stored as a plaintext string. Instead, it is XOR-encoded in the WASM data section. A runtime function decodes it on the fly during the validation check. You need to identify both the encoded data and the XOR key.
Step 2
Decompile the WASM to WATObservationI noticed that the flag was not visible as a plaintext string in the binary, which suggested the bytes were encoded and that decompiling to WAT with wasm2wat would expose the encoding logic and the XOR key as readable instructions.Use wasm2wat to convert the binary to readable text format. Search for data segments and the XOR logic in the validation function.bashwasm2wat xSAR2.wasm -o xSAR2.watbashgrep -n 'i32.xor\|data\|pico' xSAR2.watWhat didn't work first
Tried: Run strings on the .wasm binary to find the flag directly
strings xSAR2.wasm produces no picoCTF output because the flag bytes are XOR-encoded before being stored in the data segment. The raw bytes do not form printable ASCII, so strings skips them entirely. You need wasm2wat to disassemble the binary and then locate both the encoded data segment and the XOR key in the validation function.
Tried: Search the WAT output for 'pico' with grep expecting a plaintext match
grep -n 'pico' xSAR2.wat returns nothing because the flag is stored as escaped byte literals like \41\42 rather than ASCII text. The grep for i32.xor is the productive search - it locates the XOR instruction, and the i32.const immediately before it in the instruction stream is the key.
Learn more
In the WAT output, look for a
datasegment near the bottom of the file - this is the encoded flag. Then find the validation function (often named something like$checkor indexed asfunc 2). Trace through the logic to find where the XOR operation occurs and what value is used as the key.WAT XOR pattern: A XOR loop in WAT looks like a series of
i32.load8_u,i32.const <key>,i32.xor, andi32.store8instructions. The key is thei32.constimmediately precedingi32.xor(ori64.xor). If the key is not an inline constant, trace backwards from the XOR instruction - the value being XORed comes from somewhere (a local, another load, a function argument), and walking the data flow reveals the source.Data segment offset. A WAT data segment looks like
(data (i32.const 1024) "...")- the encoded bytes start at memory offset 1024 in this example. Inside the segment, byte literals are written as\41\42\43for the bytes[0x41, 0x42, 0x43]. Match the offset to where the validation function reads from to confirm you are looking at the right segment.Step 3
Decode the XOR-encoded flagObservationI noticed the WAT disassembly contained an i32.xor instruction preceded by an i32.const value, which identified the single-byte XOR key and the encoded data segment needed to recover the plaintext flag.Extract the encoded bytes from the data segment and XOR each byte with the discovered key to recover the plaintext flag.pythonpython3 - <<'EOF' # Paste the encoded bytes from the WAT data segment here encoded = bytes([0x??, 0x??, ...]) # Replace with actual bytes key = 0x?? # Replace with XOR key from WAT flag = bytes(b ^ key for b in encoded) print(flag.decode()) EOFExpected output
picoCTF{6f3bd183...}What didn't work first
Tried: Try all 256 possible XOR keys in a brute-force loop expecting the one that starts with 'picoCTF{' to be obvious
Brute-forcing 256 keys works in principle but is unnecessary here because the key is a plaintext constant visible as i32.const in the WAT disassembly. Reading the key directly from the WAT saves a step and avoids ambiguity if multiple outputs happen to start with printable characters.
Tried: XOR the bytes against the key as a multi-byte repeating key rather than a single byte
If you assume the key is multi-byte and repeat it across the encoded buffer, the output is garbled. The WAT shows a single i32.const value used in every iteration of the loop - there is only one key byte, and it is applied uniformly to each encoded byte. Treating it as a repeating-key XOR misreads the loop structure.
Learn more
XOR encoding is trivially reversible: if
encoded = plaintext XOR key, thenplaintext = encoded XOR key. Applying the same key twice returns the original value, making XOR its own inverse. This makes XOR useless as an encryption scheme when the key is static and embedded in the binary - the "obfuscation" only protects against a raw strings search, not actual analysis.
Interactive tools
- Regex TesterTest regular expressions against a string with live match highlighting, flag toggles, and common CTF pattern shortcuts.
Flag
Reveal flag
picoCTF{6f3bd183...}
The flag was XOR-encoded in the WASM data segment - decompiling to WAT reveals both the encoded bytes and the XOR key, making decoding trivial.