sCTF is now concluded, and I had a fantastic time competing in this challenge with my team. This CTF presented a broad range of challenges, ranging from simple reverse engineering challenges to more difficult and obscure challenges (such as decoding a video of binary data encoded via flashing LEDs – I thought I got close, but wasn’t able to complete this one).

The most fun challenge for me was the “Obfuscation” challenge, because it required some out of the box thinking.

This challenge presented users with an HTML page, on which users could enter a key, in the format of sctf{xxx}

If the entered key was incorrect, the text box would rest to “wrong” for another attempt. Inspection of the source code quickly revealed the key code to be as follows (this file also relied upon “mt.js” and “sha1.js”, but a quick search indicated that they did what the label on the tin specified – Mersenne Twister PRNG + Hashing):

All the cryptograhpic functionality may look scary at first, but line 64 gives the game away without further ado: the Mersenne Twister PRNG is seeded with “parseInt(btoa(answer[_[$[6]]](0, 4)), 32)”, which is a static value based on the first 4 characters of the answer, which we know to be “sctf”. From there, every variable which relies on the same “source” with no user input can be considered static.

Our first clue is line 68: “o = answer.split(“_”);”. Inspecting the source code, we notice that o[3] is the highest “chunk” referenced, so we can safely assume that the answer contains three underscores. Our next clue is line 71 (and 72): the return statements indicate an initial check for o[1] and o[2], and if we fail, we proceed no further.

These checks are made easier by the fact that ‘e’ and ‘s’ are static (as above, based on a statically seeded MT PRNG). We can simplify this down to:

e =- eval((hexlify(o[1]))) ^ s[0], e needs to be $[21]

The only user-controlled value in this entire check is o[1]: and knowing the value of s[0] and $[21], it is trivial to deduce what e might be. Be careful of the trickery in blue: e is set to an arbitrary value before this, but it is summarily discarded at this line. The almost-same check is repeated at Line 72, just with different values (and the value is xor’ed with ‘e’).

We can then trivially deduce the following with a bit of Python:

o[1] = "iz" o[2] = "mai"

From here, the challenge gets slightly more difficult. o[0] and o[3] are now stripped of their padding (sctf{ and }). A simple length check is at line 79, the next logic check is at line 75 (in blue, relevant lines above provided for context):

```
a = parseInt(_[$[23]]("1", Math.max(o[0].length, o[3].length)), 3) ^ eval(_[$[31]](o[0]));
...
a += _[$[31]](o[3].substring(o[3].length - 2)).split("x")[1];
...
a = parseInt(_[$[23]]("1", Math.max(o[0].length, o[3].length)), 3) ^ eval(_[$[31]](o[0]));
if (parseInt(a.split("84")[1], $.length/2) != 0x4439feb) return false
```

This one is slightly more difficult. From here, we can deduce that:

- ‘a’ is based off a function of o[0] (xor o[0] with a string of “1”‘s, the length of which is the greater of the length of o[0] or o[3])
- ‘a’ must contain 84, and must contain characters after 84
- These characters must be a valid base25(!) number
- They must, when decoded back to an integer, be equal to 0x4439feb

The base25 representation of 0x4439feb is 84783f3f, meaning that the last two characters of o[3] are ‘??’. We don’t have enough information to solve the rest of ‘a’ at this point, so we can move on – just patch out the check and continue down the file (let me know if you found another way to do this).

Continuing down the file, the ‘d’ check an help us identify the length of ‘a’. A bit of trial and error shows that d will look approximately like 0x174814c6ef4b3f, which is 7 hex bytes.

We know from above that:

- ‘a’ must equal this
- ‘a’ is strcat(o[0]^something,’??’)

Based on this (and an earlier check that fails of o[0] is longer than 5), we can assume the length of o[0] is 5. Again, this leaves us with a constraint on what the possible key could be, but does not provide too many clues on exactly what the key is.

Continuing down the file, we find a few more interesting snippets:

```
n = (p = (f = _[$[23]](o[3].charAt(o[3].length - 4), 3)) == o[3].substring(1, 4));
...
t = _[$[23]](o[3].charAt(3), 3) == o[3].substring(5, 8) && (o[3].charCodeAt(1)-2) * o[0].charCodeAt(0) == 0x32ab;
...
h = ((g ^ e ^ 96) & i).toString(16);
i = o[3].split(f).join("");
s = i.substring(0, 2) == h // 0xf0
```

This tells us a few things:

- o[3] at a minimum must contain 8 characters
- o[3][1:4] must be o[3][len(o[3])-4] * 3 (as per the repeat function at the start of ‘n’)
- o[3][5:8] must be o[3][1] * 3 (as per the “repeat character” function at the start of ‘t’)
- therefore, o[1-4] must equal o[5-8]
- o[3][1] – 2 * o[0][0] == 0x32ab
- o[3] must be in the format f***0***?????, where the red is unknown, but must be the same character, and blue is unknown characters.

At this point, a little guesswork was in order (one of the clues in the challenge was that there were multiple possible solutions, but only one which made sense) – we needed a 5-letter word, which would fit into the structure “?_iz_mai_f???0?????”, and I figured it would be “where” – it needed to be a “question word”, and they all start with ‘w’ anyway, and ‘where’ is the only one that’s 5 characters in length.

On this assumption, we could use the ‘t’ check above to determine o[3][1]: ‘o’. From there, we can determine the structure of o[3] (fooo0ooo??). That doesn’t make sense, but “fooo0oood??” does.

This still doesn’t let us pass the ‘a’ check above – but knowing the length of o[3], we could effectively brute force this via printf debugging to show that o[0] was “wh3r3”.

Put this together, and you got yourself the flag (and a few internet points): “sctf{wh3r3_iz_mai_fooo0oood??}”

Thanks to the sCTF team for putting together a fantastic CTF with lots of variety and non-traditional content. Special thanks to Dev for pushing this one along when I got stuck at the ‘a’ check 🙂