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

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


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

Solving a crackme implemented in JavaScript that attempts to obfuscate the algorithm through some anti-debugging.

Solution Script / HTML: https://gist.github.com/LiveOverflow/
John Hammond:    / rootofthenull  

=[ ❤️ Support ]=

→ per Video: https://www.patreon.com/join/liveover
→ per Month:    / @liveoverflow  

=[ 🐕 Social ]=

→ Twitter: https://twitter.com/LiveOverflow/
→ Website: https://liveoverflow.com/
→ Subreddit: https://www.reddit.com/r/LiveOverflow/
→ Facebook: https://www.facebook.com/LiveOverflow/

=[ 📄 P.S. ]=

All links with ”*” are affiliate links.
LiveOverflow / Security Flag GmbH is part of the Amazon Affiliate Partner Programm.

#CTF


Content

0.13 -> Recently I get too little time to play CTFs but John Hammond, who also has a CTF and hacking
5.82 -> YouTube channel approached me and asked if I was playing the Google CTF 2018, and so
11.29 -> I was persuaded into playing a few hours with him.
14.78 -> Because we are both noobs, we chose a challenge that already had a high number of solves.
19.65 -> This way we know it shouldn’t be too hard and thus perfect for us.
26.829 -> JS SAFE 2.0 was a web challenge for the Google CTF 2018.
35.23 -> “You stumbled upon someone's "JS Safe" on the web.
38.9 -> It's a simple HTML file that can store secrets in the browser's localStorage.
43.99 -> This means that you won't be able to extract any secret from it (the secrets are on the
48.16 -> computer of the owner), but it looks like it was hand-crafted to work only with the
53.44 -> password of the owner…”
55.4 -> Ok, so let’s download the attachment, which is a zip file and unpack it.
60.68 -> There is a single js_safe_2.html file in it.
64.759 -> So let’s open it in Chrome and have a first look at it.
68.45 -> We have a key input field and a cool spinning cube animation.
72.39 -> If we enter something we get an “Access Denied” and we have to reload the page to
76.689 -> try again.
78.51 -> Next let’s have a look at the source code.
80.18 -> There are some texts here, so let’s read them because maybe they provide some hints
84.45 -> for us:
85.45 -> JS safe v2.0 - the leading localStorage based safe solution with military grade JS anti-debug
93.069 -> technology
94.069 -> Anti-debug.
95.77 -> Okay that already sounds annoying.
97.35 -> Let’s see what that is about.
99.609 -> Advertisement: Looking for a hand-crafted, browser based
102.719 -> virtual safe to store your most interesting secrets?
105.92 -> Look no further, you have found it.
107.869 -> You can order your own by sending a mail to [email protected].
112.38 -> When ordering, please specify the password you'd like to use to open and close
116.13 -> the safe.
117.13 -> We'll hand craft a unique safe just for you, that only works
120.369 -> with your password of choice.
122.85 -> WOW!
123.869 -> I’m SOLD!
125.899 -> Then we have some CSS, and oh.
128.24 -> Keyframe animations.
129.24 -> The cube is actually animated in HTML and CSS.
132.94 -> No webgl or anything.
134.21 -> That’s a cool solution.
136.44 -> And then we have two scripts here.
138.52 -> One is minified and the other one not.
142.1 -> Down here we see the keyhole input element which on a change, so when we entered a key,
147.56 -> will call open_safe().
148.56 -> And that function will execute a regular expression in our input, so it has to be this flag format.
156.9 -> So out input password has to start with CTF and in curly braces some regular characters.
162.9 -> This means the correct password for this safe will also be the solution, the flag, that
168.03 -> we can submit for points.
169.71 -> And then it will call x with the extracted password.
173.07 -> So it will call x with the part inside of the curly braces.
176.76 -> And if x returns a 0, or false, it will fail and return with denied.
181.57 -> But if that function x returned a 1 or true, it will do the stuff here and show that access
187.44 -> is granted.
188.62 -> Now just simply removing the check here and jump to granted doesn’t help us, because
192.89 -> the challenge is about finding the correct password.
195.65 -> That is the flag.
197.28 -> So we have to check out the function x.
199.92 -> Now x() is up here in the minified version.
202.65 -> I will copy this file now to keep the original, but then we can use jsbeautifier to prettify
208.04 -> the script and work with this now.
211.14 -> Immediately that really weird string is poking out.
214.26 -> But let’s see.
215.96 -> X first defines three helper functions.
218.33 -> Ord to get the numer, the char code for a given character, chr is converting from a
223.94 -> number to the character string and str is simply making sure the a value is a string.
230.74 -> This is pretty much the javascript equivalent for the python functions ord, chr and str.
237.08 -> Then x defines two function h and c. h is a bit weird, it takes a string, sums up values
243.51 -> onto a and b and then returns some stuff.
246.85 -> And c is a for loop over the a input, and it appears to XOR the a string with the key
253.87 -> b.
254.87 -> So c is, I guess, just a XOR implementation.
258.329 -> Then there is a for loop calling debugger many times.
261.32 -> I guess that’s the anti-debugging trick.
264.52 -> And then we call h on the string x.
267.139 -> And x is our password we gave in as a parameter?
271.87 -> And after that we define this source, overwrite the toString of it.
276.98 -> So if you would attempt to get the string representation of source, it would call XOR
282.54 -> decryption with itself again and x.
286.52 -> No clue what the f that does.
288.78 -> And then we have a try catch that attempts to eval, the eval of a XOR on source and x.
295.93 -> Mhmh… very odd.
298.51 -> Let’s move on to a more dynamic approach and see if we can debug this.
304.35 -> We can open the developer tools and go to the sources tab.
307.82 -> And then let’s just set a breakpoint just before we pass our password to the x function,
312.06 -> by clicking the line number here.
314.47 -> And then let’s enter a easy test input with CTF{AAAABBBB}.
319.45 -> Boom.
321.9 -> breakpoint hit.
322.99 -> And now we can ivnestigate.
324.71 -> So at this point the regex was already executed and password looks like this.
329.62 -> And the second element of password is passed to x as a parameter.
334.62 -> Then let’s single step forward into x.
337.11 -> But at some point we reach the debugger loop.
338.95 -> So let’s remove that first.
341.05 -> Now here you have to be very careful.
343.46 -> A very simple mistake you could make here is to remove the whole loop.
348.13 -> But the function h uses a and b.
351.52 -> If a is undefined it initializes it with 1, but a is used in this loop here.
356.54 -> And I think because the loop variable is here not defined with the var keyword, it makes
361.88 -> the variable extend out of that scope of the loop.
365.3 -> So it affects the global state of a.
366.639 -> So actually a is 1000 when h is called right afterwards.
371.389 -> So you just have to make the loop empty.
373.5 -> So let’s rerun this change with our input.
376.84 -> Cool, now we reached the h.
379.38 -> So h gets passed in the x, right?
382.69 -> And x was the parameter of the function, so it’s our password, right?
387.83 -> But when you print x now with the developer tools it prints the function x instead.
392.97 -> The function x uses the parameter x.
395.61 -> And str on x will simply get the source code of x as a string.Is that a bug of the debugger
402.16 -> tools or is h really using the source code of x?
405.46 -> Let’s single step forward into h and let’s see.
409.21 -> NO!
410.21 -> The parameter s is in fact the source code of our function.
413.87 -> And it now loops over this string character by character and is now creating a sum of
419.12 -> these characters with a and b.
421.92 -> If we let that loop run we can inspect the final state of a and b.
425.169 -> And now that is assembled into a string and returned.
428.86 -> Here is another easy mistake you could make.
431.45 -> We modified the code, right?
433.22 -> We replaced the debugger statement and we beautified the code.
436.66 -> It was minified before.
438.04 -> So now we pass in a wrong string to h.
441.29 -> So let’s go back to our original html file and try to extract the correct source string.
448.41 -> To do that I open the developer tools and set a breakpoint again just before we call
453.04 -> x.
454.29 -> Then we can let it run until it hits the debugger loop.
457.33 -> And to bypass that now I simply set a to a very high value by hand, so we don’t have
461.76 -> to execute that 1000 times.
463.62 -> And then we can single step forward into h.
466.949 -> And now here we get s.
468.419 -> BTW if you prettify on-the-fly with chrome developer tools like that, that doesn’t
474.43 -> affect the string sources of the function.
476.07 -> It’s not modifying the real sources.
478.4 -> So no worries here.
480.37 -> We can also quickly verify here that indeed the a used inside of h is already at 1000
486.1 -> because of the loop before.
487.759 -> Cool.
488.759 -> So let’s copy that string into our modified version and harcode that as a parameter for
493.96 -> the call to h.
495.57 -> But just to make sure we got everything right, let’s go back to the original source, set
499.9 -> a breakpoint after the loop and extract the final state of a and b.
503.77 -> This way we can verify that with the hardcoded parameter we get the same result!
509.1 -> So 2714 33310.
513.33 -> We go to our modified html again, set a breakpoint at the end of h, and let it run.
518.11 -> And we check a and b.
519.58 -> YES!
520.6 -> Same values.
521.6 -> Perfect.
522.6 -> Now let’s step forward again and now we reach this weird source string.
527.45 -> Ok we set it.
528.6 -> We overwrite the to String function and now we call console.log on the source.
533.86 -> I assume that will trigger the toString function and then does this call to c with recursively
540.94 -> itself?
542.58 -> Not really sure what javascript will do in this case.
545.19 -> I’m not that familiar with quirky code liket that.
548.93 -> But in any way.
549.95 -> Our single step attempt over console.log caused the debugger to become unresponsive and after
554.95 -> 30 seconds or so the whole tab is killed by chrome.
559.13 -> Okay. that really looks like anti debugging here.
563.77 -> I wonder if it’s just that line here that is bad, or more.
567.52 -> I remove it for now and try it again.
570.08 -> With a breakpoint here.
571.58 -> Run again.
573.24 -> But damn… it will again hang.
576.23 -> That was probably the most annoying part and where I spent the most time on.
580.64 -> Because I needed to get some way to debug and get visibility into the code but it always
586.16 -> hangs.
587.55 -> My assumption was, it has to do with this overwritten toString.
590.56 -> And I kinda assumed that the developer tools, when trying to display variables in the current
595.29 -> scope will try to get the string representation.
598.93 -> And that then causes a DoS (denial of service).
601.27 -> So I played around with that a lot and tried different things, debug statements in different
605.7 -> places.
606.76 -> Changing the toString, adding console log outputs and so forth.
611.43 -> Probably did easily for an hour or more.
613.8 -> But then I had a big breakthrough.
615.61 -> one of my tests was console.log in the XOR decryption function to print the xor result.
621.24 -> The c.
622.399 -> Check it out.
623.399 -> It printed two outputs and while they both look like garbage data, the first one definetly
628.51 -> isn’t.
629.51 -> This is javascript code.
631.72 -> X == and then a call to c, the XOR decryption with some crazy string and as an XOR key it
638.68 -> will call h with x.
640.64 -> So at this point x has to be equal to this part decrypted with a key that is derived
647.59 -> from x as well.
649.58 -> Huh?!
650.76 -> So two questions.
652.01 -> What is x at this point and how does that fit into the larger picture.
656.68 -> Let’s look at the latter one first.
659.49 -> When you look at the eval you see that it is an eval inside an eval.
664.98 -> And the first call to XOR decrypt, so the inner most eval, resulted in this x==.
671.19 -> And then this eval will now execute that string and, and that string has another call to c,
676.01 -> which is this ouput.
677.38 -> So the eval comapres this output to the x.
681.589 -> That is then either true or false and then that result is evaled too and returned from
686.79 -> this function.
687.79 -> And if this returned a true, we get into the access granted.
692.89 -> Ok how does that x work.
695.34 -> Is that x again the source code of the function x?
698.83 -> A simple way to find out what x is, to add a console.log inside of h.
704.22 -> Because x is passed into that.
706.48 -> And when we do that and run it with our test input, we of course see the first call to
711.8 -> h with the source code, but then the second time it uses our input string.
717.09 -> So this time x is not the source code but it’s the parameter.
722.2 -> WHAT THE FFFFFF I don’t understand Javascript Namespacing or Variable scopes.
727.66 -> Goddamit.
728.66 -> I dind’;t fully investigate that, but I guess it has to do with the with() statement.
732.779 -> Here is a short quote from the mozilla developer docs:
736.37 -> “The with statement makes it hard for a human reader or JavaScript compiler to decide
741.49 -> whether an unqualified name will be found along the scope chain, and if so, in which
745.87 -> object.
746.87 -> Yeah ok.
748.82 -> Basically nobody in the world knows what it does.
751.01 -> It just does what we observe here.
753.5 -> Anyway.
754.5 -> Now we have basically everything we need to solve it.
757.22 -> Our input has to generate the correct xor key when passed to h, to decrypt this string
763.69 -> with xor to the original input again.
766.51 -> I want you to take a second and think about what the weakness here is.
770.15 -> How can this be attacked?
771.39 -> How can we possibly find the correct input, that decrypts to the correct input.
775.49 -> Isn’t that a chicken and egg problem?
778.5 -> Well, the fail is the h function.
781.74 -> H is essentially a key or password derivation function.
784.92 -> It takes a secret or a seed and generates a key, in this case used for XOR, based on
790.07 -> some kind of algorithm.
791.11 -> It doesn’t really matter now what h exactly does, important is just that the result of
796.16 -> h is always 4 bytes.
798.52 -> The XOR decryption only uses a 4 byte key, that is obviously always repeated.
803.99 -> So no matter what our input is, this secret can be decrypted with the correct key, and
809.75 -> the decrypted result has to be a valid character in our flag.
813.92 -> And that is super easy to bruteforce now.
816.26 -> Because of the repeating nature of the key we can bruteforce each byte of the key individually.
822.529 -> Basically we take every 4th byte of the secret, and decrypt it with the first key byte.
827.98 -> We bruteforce all the 256 possible byte values and if one results in all of the chars to
834.23 -> be valid flag characters, it’s a good chance that the key is real.
837.84 -> And we do the same for each 4th byte starting with the second and so forth.
842.88 -> Makes sense, right?
843.88 -> And if we do that, and combine all 4 bytes together, we are able to find a pssoible decrypted
850.88 -> secret.
851.88 -> Which of course is now tested against our input, which means this is the flag input.
857.06 -> We can test it, CTF, curly braces, next version has anti, anti, anti debug.
863.05 -> Access granted.
864.38 -> And we can submit the string now to the CTF and get points.
868.029 -> Awesome.
869.029 -> By the way you should checkout John Hammond’s YouTube channel, he has a lot of CTF video
874.872 -> writeups as well and could use a few new subscribers.
878.22 -> He also has a few more content about the google ctf.6

Source: https://www.youtube.com/watch?v=8yWUaqEcXr4