Rating:
# JS SAFE 2.0 [121]
> You stumbled upon someone's "JS Safe" on the web. It's a simple HTML file that
> can store secrets in the browser's localStorage. This means that you won't be
> able to extract any secret from it (the secrets are on the computer of the
> owner), but it looks like it was hand-crafted to work only with the password
> of the owner...
This challenge gives us a single HTML file which shows an input box representing
a key and an ominous spinning cube (presumably the safe it opens).
![Safe Screenshot](https://i.imgur.com/Rcu8zqK.png)
To get the flag, we need to reverse engineer the code to figure out what
passphrase will unlock the safe. When we try to enter some arbitrary text, the
page will give us a big red `Access Denied` error.
![Safe Screenshot Locked](https://i.imgur.com/PoS15tK.png)
## Initial Analyses
Before we can properly work on solving the challenge, we need to do a bit of
intel gathering. How is the locking code implemented? What protections has the
developer put in place to stop us from simply reading the password?
First off it runs in a browser so it's probably JavaScript, but CSS 3 is Turing
Complete so you never know. Lets dive into the code...
At the top of the page we get a nice comment telling us pretty much everything
we need to know. The safe is implemented in JavaScript and the developer has
done their best to prevent it from being debugged. Anti debugging techniques are
quite common among proprietary JavaScript libraries, because in this situation
you are essentially selling your source code, and want to prevent one of your
consumers from simply looking at your code, learning how you solved a certain
problem, and then implementing it themselves and selling their version for a
lower price.
```html
<title>JS safe v2.0 - the leading localStorage based safe solution with military grade JS anti-debug technology</title>
```
Then we get all the CSS styling and animations for the rotating cube, and at the
very bottom we get the JavaScript for unlocking the safe. Some of the code is
minified, so let's use the Atom beautifier to make it more readable.
```html
<script>
function x(х) {
ord = Function.prototype.call.bind(''.charCodeAt);
chr = String.fromCharCode;
str = String;
function h(s) {
for (i = 0; i != s.length; i++) {
/* --snip-- */
}
return chr(b >> 8) + chr(b & 0xFF) + chr(a >> 8) + chr(a & 0xFF)
}
function c(a, b, c) {
for (i = 0; i != a.length; i++) c = (c || '') + chr(ord(str(a[i])) ^ ord(str(b[i % b.length])));
return c
}
for (a = 0; a != 1000; a++) debugger;
x = h(str(x));
source = /Ӈ#7ùª9¨M¤À.áÔ¥6¦¨¹.ÿÓÂ.Ö£JºÓ¹WþÊmãÖÚG¤
¢dÈ9&òªћ#³1᧨/;
source.toString = function() {
return c(source, x)
};
try {
console.log('debug', source);
with(source) return eval('eval(c(source,x))')
} catch (e) {}
}
</script>
<script>
function open_safe() {
keyhole.disabled = true;
password = /^CTF{([0-9a-zA-Z_@!?-]+)}$/.exec(keyhole.value);
if (!password || !x(password[1])) return document.body.className = 'denied';
document.body.className = 'granted';
/* --snip-- */
}
function save() {
/* --snip-- */
}
</script>
```
The code doesn't look too bad, it's pretty short, there are only a few functions,
and the developer was nice enough to use `ord`, `chr`, and `str` to make it
easier for us to understand what's going on.
When we enter some input into the safe it gets checked against a regex to make
sure it's in the form `CTF{some ascii here}`, and then the part between the
curly braces gets passed to the `x()` function. If the return value of `x()`
evaluates to `true`, then we know we have the right password.
Inside of `x()`, there are a few helper functions. First we have `h()` which
transforms our input into a 4 character string. The exactly implementation
doesn't really matter, we just treat it as a hash function which generates 4
character hashes. We also have `c()` which appears to be doing a repeating key
xor.
There is also an interesting variable called `source` which is assigned to a
regex literal containing a bunch of unicode characters. This is passed to the
xor function in a few places, so it looks like there are some goodies hidden
inside which the developer is trying to hide from us.
**Summary**
Functions:
* open_safe - Opens the safe
* save - Saves data to localStorage if the safe is open
* x - Takes a password and tells us if it's correct
* h - Generates a 4 character hash
* c - Computes a repeating key xor
Other objects of interest:
* source - Some bytes probably containing the key, but "encrypted" by `c()`
## *Anti* Anti Debug
One of the ways we could try to solve this challenge is to add a bunch of
`console.log()` printouts throughout the code to gain some insight into
intermediate values. For example, if we can print out the the xor key used
to decrypt the bytes of `source` or better yet, print the result of that xor,
we would be a lot closer to getting the flag.
However, if we try this, the first thing we'll notice is that when the developer
tools are open, JavaScript execution will pause whenever it reaches a `debugger`
statement. And unfortunately the code will do this 1000 times during `x()`.
```JavaScript
for (a = 0; a != 1000; a++) debugger;
```
Well that's easy enough to deal with. We can just delete (or comment out) the
loop and refresh the page. Now we can try to print out the value of the xor key
after it is calculated.
```JavaScript
x = h(str(x));
console.log("xor key is:", x)
```
We see some unicode characters printed out, but after a few seconds our
computer fans start to spin up and we get an alert that the tab is not
responding. We also notice that the debug output which was present in the
original, never happens.
![Script Hanging](https://i.imgur.com/9DRBmqt.png)
If we look closer at the code we will notice that this is happening because of
the modified `toString` function which is applied to `source`. It appears that
when `source` is printed, it should decrypt itself first, however, the xor
function (`c()`) is expecting the input to have a `length` attribute, which is
not defined for regex literals. As a result the for loop's condition
(`i != a.length`) is always `true` and it gets stuck in an infinite loop.
When we investigate what attributes actually exists for regex literals,
we see that they have an attribute called `source` which is a string
representation of the regex.
![Regex Literal](https://i.imgur.com/4ermQ75.png)
So we can fix this little issue by using `source.source` instead. And now when
we run the page again we see the decrypted value printed out as we would expect.
```JavaScript
source.toString = function() {
return c(source.source, x)
};
```
![Printed Source](https://i.imgur.com/gGWxbMf.png)
Hmmm... That still looks like garbage. Somehow we must not be using the correct
xor key. Wait a second, the xor key that is being printed out now is different
than it was before! We used the same test input, so what changed? Is the input
being modified somewhere else that we haven't noticed? Lets do a search for all
occurrences of `x` in the file.
![Searching For X](https://i.imgur.com/OiwrZo5.png)
And now we notice something very strange. The function appears to take an
argument which is also named `x`, however it isn't highlighted when we searched
for 'x' in the file.
When we copy and paste the parameter `х` into DuckDuckGo it brings up the
Wikipedia page for the Cyrillic letter Kha
[https://en.wikipedia.org/wiki/Kha_(Cyrillic)](https://en.wikipedia.org/wiki/Kha_(Cyrillic)).
Wow! The parameter is actually just a unicode character which looks exactly the
same as the ascii character 'x' but is a completely different symbol in
JavaScript. The xor key is not being generated from our input, but instead is
the result of hashing the string representation of the whole function!
That's why it changed when we modified the `toString` function. We also realize
now, that beautifying the code in the first place would break the functionality
of the safe. The good news is that all we have to do is calculate the hash of
the original string representation of the minified function.
We can grab a fresh copy of the file, open up the browser console on it, and
call the hash function manually.
Now, there are a few catches here. The first is that we need to paste in `h()`
because it's only defined within the scope of `x()`. The second is much more
profound and also a giant headache to figure out. Do you remember the
debugger loop from earlier?
```JavaScript
for (a = 0; a != 1000; a++) debugger;
```
Notice that the variable it's using isn't `i` like the rest of the loops.
It's actually `a`, which appears again inside the hash function. So in order to
get the correct output, we need to set `a` to 1000 at the start of `h()`.
![Calling H](https://i.imgur.com/V8dCnpA.png)
Now that we have the key, we can finally figure out what secrets are hidden in
the mess of the unicode regex. We can paste in the xor function and `source`
regex and manually invoke the decryption.
![Decrypting Source](https://i.imgur.com/xjCAiEq.png)
Great! We are almost there. It looks like `source` actually contains more valid
JavaScript code which checks that the unicode `х` parameter from before is
equal to the flag. Unfortunately the flag isn't in plaintext. Once again we have
some encrypted unicode string which we need to decrypt first. Luckily it's just
a multibyte xor, and we even know the key length.
```JavaScript
х==c('¢×&\u0081Ê´cʯ¬$¶³´}ÍÈ´T\u0097©Ð8ͳÍ|Ô\u009c÷aÈÐÝ&\u009b¨þJ',h(х))//᧢
```
Because we know that the key length is 4, we know that every 4th character has
been xored with the same character from the key. For example if the key were
`abcd` and the text were `01234567`, then `0` and `4` would get xored with `a`,
`1` and `5` would get xored with `b` and so on. Therefore if we split the
ciphertext into 4 parts, where we know that each part has been xored with the
same character, we can solve each part separately by trying to xor it with every
possible character, and evaluating whether the result looks like valid
plaintext. The caveat is that we need to know something about the original
plaintext, in order to evaluate if our candidates look valid. In our case we
have the regex from `open_safe()` which we can test our candidates against.
First we create a JavaScript file which we will run using NodeJS. We copy in the
ciphertext and create our 4 single byte xor strings.
```JavaScript
cipher = '¢×&\u0081Ê´cʯ¬$¶³´}ÍÈ´T\u0097©Ð8ͳÍ|Ô\u009c÷aÈÐÝ&\u009b¨þJ'
transposed = ['', '', '', '']
for (let i = 0; i < cipher.length; i++) {
transposed[i % transposed.length] += cipher[i]
}
console.log(transposed)
```
Output:
`[ '¢Ê¯³È©³Ð¨', '×´¬´´ÐÍ÷Ýþ', '&c$}T8|a&J', 'ʶÍÍÔÈ ]`
Then we solve each of the strings as a single byte xor against the regex from
`open_safe()` and print any possible candidates.
```JavaScript
for (let i = 0; i < transposed.length; i++) {
for (let j = 0; j < 1000; j++) {
xored = c(transposed[i], chr(j));
if (/^[0-9a-zA-Z_@!?-]+$/.test(xored)) {
console.log(i, xored)
}
}
}
```
Output:
```
0 '_7RN5TNa-U'
1 'B!9!!EXbHk'
1 'N-5--ITnDg'
2 '3v1hA-it3_'
3 'x3O4n4-1b'
```
This looks very promising! Only the second string had more than one possible
solution, so now we just need to pick one (or try both), and put the pieces back
together. Since we know that the flag will probably be English text written in
leetspeak, we guess that the second candidate is the correct one.
```JavaScript
chunks = ['_7RN5TNa-U', 'N-5--ITnDg', '3v1hA-it3_', 'x3O4n4-1b']
flag = ''
for (let i = 0; i < cipher.length; i++) {
flag += chunks[i % chunks.length][Math.floor(i / chunks.length)]
}
console.log("CTF{" + flag + "}")
```
Output:
`CTF{_N3x7-v3R51ON-h45-AnTI-4NTi-ant1-D3bUg_}`