My personal goal this year is to learn more about pentesting and thanks to a few friends at work, I've now entered 2 capture the flags. The Down Under Capture the Flag is the first one where I've been able to solve a challenge and their shufflebox is the first challenge I solved.
I've learned that if you shuffle your text, it's elrlay hrda to tlle htaw eht nioiglra nutpi aws.
Find the text censored with question marks in output_censored.txt and surround it with DUCTF{}.
Author: hashkitten
We get two files, one with the script for shuffling the text and the second with 2 examples with input and output and a third with just the output. We're supposed to figure out the input for the third and that will be the value for the flag.
import random
PERM = list(range(16))
random.shuffle(PERM)
The first 3 lines of shufflebox.py set up our random order. For those who don't know python, the first references a module/library we'll be using for randomizing the list. The second line makes a list of 16 numbers and the third shuffles them.
The hardcoded 16 is important here because all strings will have 16 characters.
If we print out PERM twice, once just before the shuffle and once after, we'll see something like this.
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
[5, 14, 1, 10, 2, 13, 6, 8, 9, 0, 4, 7, 11, 3, 15, 12]
Next we have a function that applies the above defined shuffle to the letters.
def apply_perm(s):
assert len(s) == 16
return ''.join(s[PERM[p]] for p in range(16))
The assert ensures we have the correct length string. The return does a few things:
It steps through the PERM array in order to get the index for the new character array
It takes the character at the correct position in the string, creating a new temporary array
That new temporary array is then joined with an empty string to form the new shuffled string
The function returns the new string
for line in open(0):
line = line.strip()
print(line, '->', apply_perm(line))
This asks the user for input and applies the shuffling.
Since we only set the shuffled order once at the beginning, every invocation in this for loop will use the same ordering, which means we can use multiple invocations with different strings to figure out information that might not be clear on the first run. More on that in a moment.
Let's take a simpler example at first with just 8 characters.
In the first section, we would do a shuffle.
So 1 2 3 4 5 6 7 8 would become 3 7 4 1 8 2 6 5.
If we were to enter the string "abcdefgh" when prompted, it would become "cgdahbfe."
Since we used all unique letters, the new order would be fairly obvious. Let's try again with repeated characters like in the examples given.
If we enter the string "aabbccdd" when prompted, with the same shuffle order, we'd get "bdbadacc."
What we see right away is if we didn't know the shuffle order, with repeated characters we can't use one instance to figure it out.
This is why the censored text provides two lines. Let's see what happens if we add a second one, "abcdabcd." The shuffled string becomes "ccdadbba."
Now let's take those two inputs and outputs and see if we can figure out the shuffle order. The values are the possible locations in the string for the character.
Next we substitute these into the order. Where both rows have a match on order, that is the order that the character appeared in the original string.
This gives us the order 3 7 4 1 8 2 6 5 which is the same order we started with so it works. Let's try this now with the challenge examples.
First, we create our possible values matrix for each letter.
And then we need figure out where the common numbers are to get us our shuffled order.
Our shuffled order is then: 10 11 1 9 12 14 4 7 16 6 15 8 5 3 13 2
Let's now apply this to our last shuffled string and get the original, censored, text.
The flag was DUCTF{udiditgjwowsuper}