Solving a JavaScript crackme: JS SAFE 2.0 (web) – Google CTF 2018

Solving a JavaScript crackme: JS SAFE 2.0 (web) – Google CTF 2018


Recently I get too little time to play CTFs
but John Hammond, who also has a CTF and hacking YouTube channel approached me and asked if
I was playing the Google CTF 2018, and so I was persuaded into playing a few hours with
him. Because we are both noobs, we chose a challenge
that already had a high number of solves. This way we know it shouldn’t be too hard
and thus perfect for us. JS SAFE 2.0 was a web challenge for the Google
CTF 2018. “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…” Ok, so let’s download the attachment, which
is a zip file and unpack it. There is a single js_safe_2.html file in it. So let’s open it in Chrome and have a first
look at it. We have a key input field and a cool spinning
cube animation. If we enter something we get an “Access
Denied” and we have to reload the page to try again. Next let’s have a look at the source code. There are some texts here, so let’s read
them because maybe they provide some hints for us: JS safe v2.0 – the leading localStorage based
safe solution with military grade JS anti-debug technology Anti-debug. Okay that already sounds annoying. Let’s see what that is about. Advertisement:
Looking for a hand-crafted, browser based virtual safe to store your most
interesting secrets? Look no further, you have found it. You can order your own
by sending a mail to [email protected] When ordering, please specify the
password you’d like to use to open and close the safe. We’ll hand craft a
unique safe just for you, that only works with your password of choice. WOW! I’m SOLD! Then we have some CSS, and oh. Keyframe animations. The cube is actually animated in HTML and
CSS. No webgl or anything. That’s a cool solution. And then we have two scripts here. One is minified and the other one not. Down here we see the keyhole input element
which on a change, so when we entered a key, will call open_safe(). And that function will execute a regular expression
in our input, so it has to be this flag format. So out input password has to start with CTF
and in curly braces some regular characters. This means the correct password for this safe
will also be the solution, the flag, that we can submit for points. And then it will call x with the extracted
password. So it will call x with the part inside of
the curly braces. And if x returns a 0, or false, it will fail
and return with denied. But if that function x returned a 1 or true,
it will do the stuff here and show that access is granted. Now just simply removing the check here and
jump to granted doesn’t help us, because the challenge is about finding the correct
password. That is the flag. So we have to check out the function x. Now x() is up here in the minified version. I will copy this file now to keep the original,
but then we can use jsbeautifier to prettify the script and work with this now. Immediately that really weird string is poking
out. But let’s see. X first defines three helper functions. Ord to get the numer, the char code for a
given character, chr is converting from a number to the character string and str is
simply making sure the a value is a string. This is pretty much the javascript equivalent
for the python functions ord, chr and str. Then x defines two function h and c. h is
a bit weird, it takes a string, sums up values onto a and b and then returns some stuff. And c is a for loop over the a input, and
it appears to XOR the a string with the key b. So c is, I guess, just a XOR implementation. Then there is a for loop calling debugger
many times. I guess that’s the anti-debugging trick. And then we call h on the string x. And x is our password we gave in as a parameter? And after that we define this source, overwrite
the toString of it. So if you would attempt to get the string
representation of source, it would call XOR decryption with itself again and x. No clue what the f that does. And then we have a try catch that attempts
to eval, the eval of a XOR on source and x. Mhmh… very odd. Let’s move on to a more dynamic approach
and see if we can debug this. We can open the developer tools and go to
the sources tab. And then let’s just set a breakpoint just
before we pass our password to the x function, by clicking the line number here. And then let’s enter a easy test input with
CTF{AAAABBBB}. Boom. breakpoint hit. And now we can ivnestigate. So at this point the regex was already executed
and password looks like this. And the second element of password is passed
to x as a parameter. Then let’s single step forward into x. But at some point we reach the debugger loop. So let’s remove that first. Now here you have to be very careful. A very simple mistake you could make here
is to remove the whole loop. But the function h uses a and b. If a is undefined it initializes it with 1,
but a is used in this loop here. And I think because the loop variable is here
not defined with the var keyword, it makes the variable extend out of that scope of the
loop. So it affects the global state of a. So actually a is 1000 when h is called right
afterwards. So you just have to make the loop empty. So let’s rerun this change with our input. Cool, now we reached the h. So h gets passed in the x, right? And x was the parameter of the function, so
it’s our password, right? But when you print x now with the developer
tools it prints the function x instead. The function x uses the parameter x. And str on x will simply get the source code
of x as a string.Is that a bug of the debugger tools or is h really using the source code
of x? Let’s single step forward into h and let’s
see. NO! The parameter s is in fact the source code
of our function. And it now loops over this string character
by character and is now creating a sum of these characters with a and b. If we let that loop run we can inspect the
final state of a and b. And now that is assembled into a string and
returned. Here is another easy mistake you could make. We modified the code, right? We replaced the debugger statement and we
beautified the code. It was minified before. So now we pass in a wrong string to h. So let’s go back to our original html file
and try to extract the correct source string. To do that I open the developer tools and
set a breakpoint again just before we call x. Then we can let it run until it hits the debugger
loop. And to bypass that now I simply set a to a
very high value by hand, so we don’t have to execute that 1000 times. And then we can single step forward into h. And now here we get s. BTW if you prettify on-the-fly with chrome
developer tools like that, that doesn’t affect the string sources of the function. It’s not modifying the real sources. So no worries here. We can also quickly verify here that indeed
the a used inside of h is already at 1000 because of the loop before. Cool. So let’s copy that string into our modified
version and harcode that as a parameter for the call to h. But just to make sure we got everything right,
let’s go back to the original source, set a breakpoint after the loop and extract the
final state of a and b. This way we can verify that with the hardcoded
parameter we get the same result! So 2714 33310. We go to our modified html again, set a breakpoint
at the end of h, and let it run. And we check a and b. YES! Same values. Perfect. Now let’s step forward again and now we
reach this weird source string. Ok we set it. We overwrite the to String function and now
we call console.log on the source. I assume that will trigger the toString function
and then does this call to c with recursively itself? Not really sure what javascript will do in
this case. I’m not that familiar with quirky code liket
that. But in any way. Our single step attempt over console.log caused
the debugger to become unresponsive and after 30 seconds or so the whole tab is killed by
chrome. Okay. that really looks like anti debugging
here. I wonder if it’s just that line here that
is bad, or more. I remove it for now and try it again. With a breakpoint here. Run again. But damn… it will again hang. That was probably the most annoying part and
where I spent the most time on. Because I needed to get some way to debug
and get visibility into the code but it always hangs. My assumption was, it has to do with this
overwritten toString. And I kinda assumed that the developer tools,
when trying to display variables in the current scope will try to get the string representation. And that then causes a DoS (denial of service). So I played around with that a lot and tried
different things, debug statements in different places. Changing the toString, adding console log
outputs and so forth. Probably did easily for an hour or more. But then I had a big breakthrough. one of my tests was console.log in the XOR
decryption function to print the xor result. The c. Check it out. It printed two outputs and while they both
look like garbage data, the first one definetly isn’t. This is javascript code. X==and then a call to c, the XOR decryption
with some crazy string and as an XOR key it will call h with x. So at this point x has to be equal to this
part decrypted with a key that is derived from x as well. Huh?! So two questions. What is x at this point and how does that
fit into the larger picture. Let’s look at the latter one first. When you look at the eval you see that it
is an eval inside an eval. And the first call to XOR decrypt, so the
inner most eval, resulted in this x==. And then this eval will now execute that string
and, and that string has another call to c, which is this ouput. So the eval comapres this output to the x. That is then either true or false and then
that result is evaled too and returned from this function. And if this returned a true, we get into the
access granted. Ok how does that x work. Is that x again the source code of the function
x? A simple way to find out what x is, to add
a console.log inside of h. Because x is passed into that. And when we do that and run it with our test
input, we of course see the first call to h with the source code, but then the second
time it uses our input string. So this time x is not the source code but
it’s the parameter. WHAT THE FFFFFF I don’t understand Javascript
Namespacing or Variable scopes. Goddamit. I dind’;t fully investigate that, but I
guess it has to do with the with() statement. Here is a short quote from the mozilla developer
docs: “The with statement makes it hard for a
human reader or JavaScript compiler to decide whether an unqualified name will be found
along the scope chain, and if so, in which object. Yeah ok. Basically nobody in the world knows what it
does. It just does what we observe here. Anyway. Now we have basically everything we need to
solve it. Our input has to generate the correct xor
key when passed to h, to decrypt this string with xor to the original input again. I want you to take a second and think about
what the weakness here is. How can this be attacked? How can we possibly find the correct input,
that decrypts to the correct input. Isn’t that a chicken and egg problem? Well, the fail is the h function. H is essentially a key or password derivation
function. It takes a secret or a seed and generates
a key, in this case used for XOR, based on some kind of algorithm. It doesn’t really matter now what h exactly
does, important is just that the result of h is always 4 bytes. The XOR decryption only uses a 4 byte key,
that is obviously always repeated. So no matter what our input is, this secret
can be decrypted with the correct key, and the decrypted result has to be a valid character
in our flag. And that is super easy to bruteforce now. Because of the repeating nature of the key
we can bruteforce each byte of the key individually. Basically we take every 4th byte of the secret,
and decrypt it with the first key byte. We bruteforce all the 256 possible byte values
and if one results in all of the chars to be valid flag characters, it’s a good chance
that the key is real. And we do the same for each 4th byte starting
with the second and so forth. Makes sense, right? And if we do that, and combine all 4 bytes
together, we are able to find a pssoible decrypted secret. Which of course is now tested against our
input, which means this is the flag input. We can test it, CTF, curly braces, next version
has anti, anti, anti debug. Access granted. And we can submit the string now to the CTF
and get points. Awesome. By the way you should checkout John Hammond’s
YouTube channel, he has a lot of CTF video writeups as well and could use a few new subscribers. He also has a few more content about the google
ctf.6

100 thoughts on “Solving a JavaScript crackme: JS SAFE 2.0 (web) – Google CTF 2018

  1. The confusion with the x's isn't cuz of the with statement. The parameter х (U+0445 or 1093) isn't the x from the English alphabet. It is a Cyrillic alphabet which only looks like it. And this input variable was never used in the code except for the last eval() which dynamically generated 'х==c(weird_string,h(х))' referring to our input х. THAT was the beauty of the challenge 😛

  2. hello Developers if any one expert in android studio i have this problem when i use TextTospeech class the error is "W/TextToSpeech: speak failed: not bound to TTS engine"
    if any one can help me or i can explait for him this problem and he can help by chat live and thank you for reading my problem

  3. I think the guys over at JS Safe should really have used a sha256 hash. Then there’s no need for all the anti debugging and it’s also unbreakable (given the current state of technology). Very fun challenge though!

  4. i want to appreciate ur time and dedication towards the efforts by you not related to solving the ctf but the thing not many noticed, which was the ur skills in copying and redrawing the "google capture the flag" logo as your thumbnail XD lol. Seriously how the hell did u do that, which software?

  5. Hey, can you suggest me an IRC with teams from the current ongoing Google CTF, I would like to chat with likeminded! 🙂

  6. I tried to do this challenge myself, the part with the regexp object was the part I got stuck.

    About the with statement:

    `var test = {a: () => console.log("Hello local")}; var a = () => console.log("Hello world"); with (test) a();`

    outputs "Hello local" as it uses properties in the with statement as first lookup table instead of global scope.

  7. Same here, joined for a few hours and only solved the noob question :p The argument was not an x but a homograph of x as others also mentioned and that took me a bit to figure out. Also I didn't figure out exactly why the browser hanged but it was so annoying, yet I didn't want to spend time on that. I took the same approach for decryption but heard that there was another way based on low redundancy of the internal variables (a and b). I hope I find time and motivation to look into it soon.

  8. Hey,

    I'm still a n00b but I'd like to try playing CTF, does anyone know if there's anyway to find n00bs like me online and start playing ?

  9. If password = the flag why doesn't you just do like element.innerHtml(password); to make it display what the password string is

  10. Thanks. It was informative. Will check John Hammond's channel as well. Really like that how much great online content is produced nowadays 🙂

  11. Great video!
    Are there any similiar websites where you can practice JS by solving similiar tasks (I still feel I’m not ready to join CTF) ?
    Any recommendations are welcome.

  12. This is why I don't trust JavaScript programmers. Shit like this is what nightmares are made of.

    In Typescript we trust. All day er' day

  13. Uuuuuh. I guess some people used the solution in this video to validate the challenge … To people doing this : you are stupid to cheat on a challenge.

  14. Love you videos, i'm gradually learning more and more thanks to you ! Your explainations helps me understand the mindset behind these CTF, hopefully with more knowledge and time i'll be able to clear some CTF one day 🙂 Cheers !

  15. I'm an outsider looking in. I tried to figure it out, but as far as I can tell "x" provides the password in hidden and obscured fashion. That of course being the main difficulty. Me having little knowledge of JS I know I would be incapable of solving for "x" assuming my assessment here is correct.

  16. This was kind of hard to follow but also easy to understand. Where have you been all my life? Shit, I guess I'm hunting for flags now.

  17. after the whole "with" statement thing, you just kind of fumbled over what was going on into calling it a solution. I wish you better explained how the solution/script helped crack that strange key for the value of source at the start, and the value of what CTF{AAAABBBB} made it

  18. im new to this so forgive me if this is ignorant but if you know that the password is the flag why can't you just simply console.log the password variable?

  19. How do you know when to give up and just learn a new feature and how do you know when to just carry on trying things

  20. I love watching these videos. I’m at a stage in life where I kind of understand what is going on, enough to comprehend the logic expressed, but not enough to be able to try this myself.

  21. This challenge perfectly summarizes why I can't stand JavaScript. All of these weird rules like how scopes of variables extend to weird places to how you can use non-ASCII symbols that look like ASCII symbols for variable names. You know that your language has issues when even an authoritative documentation like MDN states that one of your builtin syntax statements is too unpredictable to reliably use. You can write bad code in any language, but JavaScript seems like it was specifically designed for spaghetti code. More than half the stuff in this crackme would be literally impossible in almost any other language, because they have rules in place to keep people from writing garbage like this.

  22. How people think Google Employees are: "Oh my God they must be really good at there job since Google hired them and must write code insanely well that a baby could read it"
    Google Employees when writing code: "I speak AblaEnglJaIes"
    Highly Skilled Programmers: "Ah I see… Keep your secrets to yourself"
    Google: "I see your a man of culture"

Leave a Reply

Your email address will not be published. Required fields are marked *