Description
This challenge extends Some Assembly Required 2 by adding a rotating XOR key. The WASM module encodes the flag with a 5-byte XOR key applied cyclically, then stores the ciphertext in the data segment. Find the key and the ciphertext, then XOR them together to recover the flag.
Setup
Open the challenge URL with DevTools, download the WASM file from the Network tab.
wasm2wat xSAR3.wasm -o xSAR3.watSolution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1Decompile the WASM and locate the encoding logic
ObservationThe description mentions a rotating XOR key and a WASM module. Decompile with wasm2wat and wasm-decompile to read the copy_char function, which holds both the key bytes and the XOR index formula.Convert the WASM to WAT (or use wasm-decompile for readable pseudocode), then inspect the copy_char function. Compared to Some Assembly Required 2, you will see a new variable that tracks a key index and an XOR operation applied to each character before it is stored. The key index cycles using the expression 4 - (position % 5), which walks the 5-byte key in reverse order.bashwasm2wat xSAR3.wasm -o xSAR3.watbashwasm-decompile xSAR3.wasm -o xSAR3.dcmpbashgrep -n 'xor\|key\|offset' xSAR3.wat | head -40What didn't work first
Tried: Use strings on the WASM binary to find the key and flag directly
strings extracts only printable ASCII, so it shows neither the key bytes, which are values like 0xf1 and 0xa7, nor the ciphertext, which is equally non-printable. wasm2wat or wasm-decompile reads the data segment as raw hex and lets you trace the encoding logic in copy_char.
Tried: Skip wasm-decompile and rely solely on the raw WAT output from wasm2wat to find the key index formula
Raw WAT expresses the XOR and modulo as low-level stack operations spread over many lines with no variable names, which makes the index formula very hard to spot. wasm-decompile produces named pseudocode where the cyclic key pattern is obvious; skipping it costs real analysis time.
Learn more
The WASM data segment holds two important regions. The ciphertext (the XOR-encoded flag) starts at memory offset 1024 and is 43 bytes long. The key is stored as 5 bytes beginning at offset 1067 in the order
0xf1, 0xa7, 0xf0, 0x07, 0xed.The encoding function XORs each input byte at position i with
key[4 - (i % 5)]before comparing. That index formula just walks the key bytes backwards (4, 3, 2, 1, 0, 4, 3, ...), which is equivalent to using the reversed key[0xed, 0x07, 0xf0, 0xa7, 0xf1]in the normal forward direction. Because XOR is its own inverse, decryption is identical to encryption.Step 2Extract the ciphertext and key, then XOR to recover the flag
ObservationThe WAT puts the 43-byte ciphertext at memory offset 1024 and the 5-byte key at 1067. XOR is its own inverse, so running the same cyclic XOR over the ciphertext with the reversed key recovers the flag.Read the 43 ciphertext bytes from the data segment starting at offset 1024, and the 5 key bytes from offset 1067. Apply the same cyclic XOR the WASM uses: for each byte at position i, XOR with key[4 - (i % 5)]. The result is the flag.pythonpython3 - <<'EOF' # Ciphertext bytes from data segment at offset 1024 (43 bytes) ciphertext = bytes([ 0x9d, 0x6e, 0x93, 0xc8, 0xb2, 0xb9, 0x41, 0x8b, 0x9f, 0x90, 0x8c, 0x62, 0xc5, 0xc3, 0x95, 0x88, 0x34, 0xc8, 0x93, 0x92, 0x88, 0x3f, 0xc1, 0x92, 0xc7, 0xdb, 0x3f, 0xc8, 0x9e, 0xc7, 0x89, 0x31, 0xc6, 0xc5, 0xc9, 0x8b, 0x36, 0xc6, 0xc6, 0xc0, 0x90, 0x00, 0x00, ]) # Key bytes from data segment at offset 1067 (5 bytes, stored reversed) key_raw = [0xf1, 0xa7, 0xf0, 0x07, 0xed] # The WASM indexes the key as key[4 - (i % 5)], which is the reversed key forward. key = [key_raw[4 - (i % 5)] for i in range(len(ciphertext))] flag = bytes(c ^ k for c, k in zip(ciphertext, key)) print(flag.decode('ascii', errors='replace')) EOFExpected output
picoCTF{8aae5dde...}What didn't work first
Tried: XOR the ciphertext with the raw key in forward order (key[i % 5]) using the bytes as stored at offset 1067
The WASM indexes the stored key as 4 minus i mod 5, which reverses the stored byte order. Index it forward against the raw stored bytes and every key byte is misaligned, so the output is garbage. Either reverse the key array first, or keep the same index formula the WASM uses.
Tried: Treat the ciphertext as a single-byte XOR and try all 256 possible keys with a brute-force loop
A single-byte brute force assumes one key byte repeats across the whole ciphertext, but this cipher rotates a 5-byte key. Any single candidate decrypts only one position in five, so all 256 guesses score as noise and the flag never appears. You need the key length and the actual key bytes from the data segment.
Learn more
Why the reversed key? The WASM stores the key bytes in reverse order in memory and then accesses them via
key[4 - (i % 5)]. When i = 0, the index is 4, which picks the last stored byte (0xed). When i = 1, the index is 3, picking 0x07, and so on. The net effect is identical to storing the key as[0xed, 0x07, 0xf0, 0xa7, 0xf1]and accessing it askey[i % 5]. Both formulations produce the same decryption.Why XOR works here: XOR is symmetric, meaning
plaintext XOR key = ciphertextandciphertext XOR key = plaintext. The decryption script is literally the same as the encryption loop in the WASM - you just run it on the ciphertext instead of the user input.
Interactive tools
- XOR CipherXOR-decrypt hex or text ciphertext with a known key, or brute-force the single-byte key automatically.
Flag
Reveal flag
picoCTF{8aae5dde...}
The WASM encodes the flag with a rotating 5-byte XOR key (cyclic index 4 - (pos % 5)). XOR is its own inverse, so running the same operation on the ciphertext recovers the plaintext.