Description
We give you the PowerShell transformation script and the output file. Can you reverse it to find the input?
Setup
Download the PowerShell script and the output file.
wget <url>/script.ps1wget <url>/output.txtSolution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1
Read the script to understand the transformationObservationI noticed that we were given both the transformation script (script.ps1) and the output file, which suggested reversing the process by first understanding exactly what operations the script applies and in what order.Open script.ps1. It reads an input file as UTF-8, verifies each block of 6 characters consists of only '0' and '1', generates a list of pseudo-random seeds (one per block), then for each block calls a scramble function and XORs the result cumulatively, writing each cumulative XOR to output.txt. The output.txt has 264 lines.bashcat script.ps1What didn't work first
Tried: Treat the output.txt values as plain binary strings and convert them directly to ASCII without reversing the XOR.
The output.txt contains cumulative XOR results across all 264 blocks, not the raw encoded blocks. Converting those lines directly gives meaningless bytes because each line incorporates the XOR of all previous blocks. You must first XOR adjacent lines to isolate per-block data before any further decoding.
Tried: Assume the scramble function is a Caesar cipher or simple substitution and try to brute-force the shift value.
The scramble function is not a Caesar cipher - it uses a seeded pseudo-random permutation that reorders bits within each 6-character block differently for every block. Brute-forcing a fixed shift produces no consistent plaintext because each block's scramble key changes based on its position in the seed sequence.
Learn more
The transformation has three stages:
- The input is 264 blocks of 6 binary digits (ones and zeros).
- Each block is scrambled using a seeded random number generator (the seed sequence is deterministic given the block count).
- Results are XORed cumulatively and written to output.txt.
To reverse it, undo the cumulative XOR first (XOR adjacent lines), then unscramble each block, then interpret each 6-bit block as a binary digit.
Step 2
Reverse the cumulative XORObservationI noticed the script accumulates XOR results across all blocks before writing to output.txt, which suggested that isolating each individual block's contribution requires XORing each adjacent pair of output lines rather than using the raw values directly.The script accumulates XOR results across blocks. The first line of output is the XOR of block 0 alone. The second line is that XOR block 1. To undo, XOR each line with the previous line to isolate individual block results. Write a PowerShell (or Python) script that reproduces the same seed sequence and unscramble function.bash# Reproduce the seed generation (copy from script.ps1): # RandomGen generates the same seed list given blocks.count = 264 # Run the unscramble logic in PowerShell: pwsh -File reverse.ps1What didn't work first
Tried: XOR every line with the first line instead of XORing each line with the one immediately before it.
The script accumulates XOR progressively: line N is the XOR of blocks 0 through N. To recover block N individually, you XOR line N with line N-1, not with line 0. XORing everything against line 0 produces the XOR of blocks 1 through N for each position, which is still a combined result rather than an isolated block.
Tried: Skip regenerating the seed sequence and try to unscramble each block using a fixed permutation order.
The RandomGen function produces a distinct seed per block position, meaning every block is scrambled with a different permutation. A single fixed permutation only correctly unscrambles block 0 and produces garbage for all 263 remaining blocks. You must reproduce the full seed list from script.ps1 using blocks.count = 264 to get the right permutation for each block.
Learn more
The seed sequence is deterministic: copy the
RandomGenfunction directly from script.ps1 withblocks.count = 264. The unscramble function is the inverse of scramble: where scramble replaces one binary character with a longer pattern, unscramble maps those patterns back to0or1.Step 3
Interpret the unscrambled blocks as binary and convert to ASCIIObservationI noticed each unscrambled line contained exactly two distinct 6-character patterns (one composed entirely of '11' pairs and one of '00' pairs), which suggested the scramble function was a bit-substitution encoding and that mapping these two patterns to bits 1 and 0 then grouping into 8-bit ASCII values would recover the flag.After reversing, each line of the recovered data contains only two distinct 6-character patterns. One pattern maps to bit 0 and the other to bit 1. This is a fixed substitution, not a vote: the scramble function encodes each input bit as a 2-char pair ('00' for 0, '11' for 1) at a seed-shuffled position. To decode, collect the two unique patterns per line, determine which is which by checking their 2-bit pairs, assign bits, then decode 8-bit groups as ASCII.pythonpython3 << 'EOF' # Read the unscrambled blocks output from the PowerShell reverse step with open("unscrambled.txt") as f: lines = f.read().splitlines() bits = "" for line in lines: parts = line.split() # Each line has exactly two distinct 6-char patterns. # The scramble encodes bit-1 as "11" pairs, bit-0 as "00" pairs. # Identify which of the two patterns encodes a 1 by checking inner pairs. seen = list(dict.fromkeys(parts)) # preserve order, deduplicate # A pattern encoding 1 will have all "11" 2-bit pairs (e.g. "111111") # A pattern encoding 0 will have all "00" 2-bit pairs (e.g. "000000") # After unscrambling, pairs are either "11" or "00"; count "11" pairs def is_one_pattern(p): pairs = [p[i:i+2] for i in range(0, len(p), 2)] return all(pair == "11" for pair in pairs) one_pat = seen[0] if is_one_pattern(seen[0]) else seen[1] for block in parts: bits += "1" if block == one_pat else "0" # Decode 8 bits at a time to ASCII flag = "" for i in range(0, len(bits), 8): byte = bits[i:i+8] if len(byte) == 8: flag += chr(int(byte, 2)) print(flag) EOFExpected output
picoCTF{2018highw@y_2_pow3r$hel!}What didn't work first
Tried: Decode each 6-character block directly as a 6-bit binary number and convert groups of bits to ASCII.
The blocks are not raw binary - each block is a scrambled encoding where bit values are represented as 2-character pairs ('11' for 1, '00' for 0) placed at shuffled positions. Reading the six characters as a literal 6-bit number gives a numeric value that has no correspondence to the original bit, because the meaningful signal is in which 2-char pair is present, not the positional value of each character.
Tried: Take the first of the two distinct patterns per line as bit 0 and the second as bit 1 based on appearance order.
Appearance order depends on how the scramble permutation happened to arrange patterns for that block and is not a reliable indicator of which pattern encodes 0 versus 1. The correct approach is to inspect the 2-character pairs inside each pattern - a pattern made entirely of '11' pairs encodes bit 1 and a pattern made entirely of '00' pairs encodes bit 0, regardless of which appeared first in the line.
Learn more
The scramble function encodes each input bit as a 2-character pair:
"11"for a 1 bit and"00"for a 0 bit, placed at a seed-scrambled position in the output. Because of this, each line of the reversed output contains exactly two distinct 6-character patterns - one made entirely of"11"pairs (representing 1) and one made entirely of"00"pairs (representing 0). Decoding means identifying which pattern is which per line and mapping to bits, then reading 8-bit groups as ASCII characters to reveal the flag.
Interactive tools
- Strings ExtractorPull printable text from any binary, library, or image. ASCII and UTF-16 detection, configurable minimum length, flag-like highlight, no command line needed.
- Hex ViewerView text or raw hex bytes as a xxd-style hex dump with byte offset, hex columns, and ASCII sidebar. Highlights printable characters and null bytes.
Flag
Reveal flag
picoCTF{2018highw@y_2_pow3r$hel!}
Reverse the cumulative XOR, unscramble each block using the same seed sequence, decode the two-pattern substitution encoding (each line has exactly two distinct 6-char patterns, one for bit 0 and one for bit 1), then convert 8-bit groups to ASCII.