Google CTF Quals 2018 - JS Safe

JavaScript Anti-Debug

General problem definition

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...

The assignment was a Javascript file, which needs the Flag as input.

Getting to the flag-check

The function x is a oneliner, which does a few nasty things. There are some anti-debugging measures, for example a very long loop, that calls the debugger every iteration. Another overwrites the toString method of the object source, which would loop endlessly if printed.

Removing these codeparts causes the whole program to break, because the function hashes itself at position x = h(str(x));. This use of x does not use the parameter of the function, as one is the ASCII x and the other une is a Unicode cyrillic x.

In the function there are 2 subfunctions defined. The first one c xors a string with a second one(repeating if it is too small) and the second function h does a simple hash function over a string. source = /Ӈ#7ùª9¨M¤ŸÀ.áÔ¥6¦¨¹.ÿÓÂ.։£JºÓ¹WþʖmãÖÚG¤…¢dÈ9&òªћ#³­1᧨/; is an interesting line, as it creates a regex object. In the end return eval('eval(c(source,x))') is executed and should return true if the flag was entered correctly. Notice, that the input х was never used only the function x.

Now we looked at the nested eval statement. To find out what it results in, we reimplemented the whole function in python and evaluated c(source,x) there:

import string
a = 1000
b = 0
byteord = lambda b: bytes([b])
def hash(unicodestr):
    global a
    global b

    for char in unicodestr:
        a = (a + ord(char)) % 65521
        b = (b + a) % 65521

    print("end a:", a)
    print("end b:", b)
    return b.to_bytes(2, 'big') + a.to_bytes(2, 'big')

def crypt(enc, key):
    """
    enc: encrypted (unicode)
    key: key (bytes)
    """
    c = ""

    for i in range(len(enc)):
        c += chr(ord(enc[i]) ^ key[i % len(key)])

    return c
code = "function x(х){...}"
enc_code = "Ӈ#7ùª9¨M¤ŸÀ.áÔ¥6¦¨¹.ÿÓÂ.։£JºÓ¹WþʖmãÖÚG¤…¢dÈ9&òªћ#³­1᧨"

hashed_code = hash(code)
crypt_code = crypt(enc_code, hashed_code)

print(list(hashed_code))
print(crypt_code)

As a result we get the string: х==c('¢×&Ê´cʯ¬$¶³´}ÍÈ´T—©Ð8ͳÍ|Ԝ÷aÈÐÝ&þJ',h(х))//᧢. Finally the parameter x(our flag) is used.

Getting the Hash

The string х==c('¢×&Ê´cʯ¬$¶³´}ÍÈ´T—©Ð8ͳÍ|Ԝ÷aÈÐÝ&þJ',h(х))//᧢ is again evaluated. Now we just need to find an input were this condition is true. Looking down where the input to the function is checked we can see, that the regex /^CTF{([0-9a-zA-Z_@!?-]+)}$/ is used to check the flag, so we know that only digits, lowercase- and uppercase characters and _@!?- can be part of the flag. Now we can check for every possible byte in the 4 byte hash if every byte xored with every 4th byte in the '¢×&Ê´cʯ¬$¶³´}ÍÈ´T—©Ð8ͳÍ|Ԝ÷aÈÐÝ&þJ'-string is in our possible charset. This reduces the possible hashes that are possible.

# crypt_code is "х==c('¢×&Ê´cʯ¬$¶³´}ÍÈ´T—©Ð8ͳÍ|Ԝ÷aÈÐÝ&þJ',h(х))//᧢"
the_string = crypt_code[6:-10]
possibleset = string.ascii_letters + string.digits + "_@!?-"

for hashind in range(4):
    print("-----------------")
    for p in possibleset:
        testsol = ord(the_string[hashind]) ^ ord(p)
        for j in range(hashind + 4, len(the_string), 4):
            if chr(testsol ^ ord(the_string[j])) not in possibleset:
                break
        else:
            print(testsol)

As a result we get

-----------------
253
-----------------
149
153
-----------------
21
-----------------
249

as the output. That means there are only 2 possible hashes : [253, 149, 21, 249] and [253, 153, 21, 249]. Now we can just use the crypt function with the hashes and the '¢×&Ê´cʯ¬$¶³´}ÍÈ´T—©Ð8ͳÍ|Ԝ÷aÈÐÝ&þJ' string and we get our possible flags:

print(">" + crypt(crypt_code[6:-10], [253, 149, 21, 249]))
#>_B3x7!v3R91ON!h45!AnTE-4NXi-abt1-H3bUk_
print(">" + crypt(crypt_code[6:-10], [253, 153, 21, 249]))
#>_N3x7-v3R51ON-h45-AnTI-4NTi-ant1-D3bUg_

Only the second of the two actually looks readable, so the flag is CTF{_N3x7-v3R51ON-h45-AnTI-4NTi-ant1-D3bUg_}


Google CTF Quals 2018 - Shall We Play A Game?

Android APK Reversing

General problem description Win the game 1,000,000 times to get the flag. For this challenge we got an .apk file, which we should apperently run and win 1,000,000 times. We let the online Java-decompiler at http://www.javadecompilers.com/apk. Running the apk on an Android-Phone or emulator shows us the game: Tic Tac Toe. We also get a counter 0/1000000 on the bottom of the screen. Each win increases the counter by one. Naive approach by recompiling the app We used...

Read More
Google CTF Quals 2018 - Back To Basics

C64 BASIC Reversing

General problem description You won't find any assembly in this challenge, only C64 BASIC. Once you get the password, the flag is CTF{password}. P.S. The challenge has been tested on the VICE emulator. We got an old .prg file, which is a C64 program file. Parsing the file First we tried using parsers that exist in the wild, that would parse the file for us, but that proved to be not effective, as there were not many and...

Read More
Navigation