EasyCTF2018 Zippity 80 points
Intro
This is my second year participating in EasyCTF,and I liked one challenge in particular.
I heard you liked zip codes! Connect via nc c1.easyctf.com 12483 to prove your zip code knowledge.
Clearly looking up the codes manually is out of the question. One colleague of mine tried to enumerate enough responses to pass, but gave up after 80k unique entries ( 33.3k Zip codes * and 4 possible questions ~= 133.2k possibilities). At first trying to form a database using US Census website was proving futile, and some datasets were behind paywalls. After some more searching I found https://www.census.gov/geo/maps-data/data/gazetteer.html which contained the correct data, after testing a few questions and answers against the server.
Script
After messing about with Ruby CSV library, converting to JSON and Enumerables, I decided to just use grep . Ruby returns “MatchData” type to regex matches, so that was also a bother to manipulate. My C upbringing and noob scripting skills really show.
require 'socket'
def answerer ( zc, question)
line=`grep '^#{zc}' db.csv|head -1` #match first zipcode in the file. ^ means line begins with
arrayified=line.split(",") #put the output into array
ans=(arrayified[question]) # return the answer to question ||1=LAND|| 2=WATER || 3=LAT|| 4=LONG
puts "Answer is #{ans}"
return ans
end
#regex for catching questions
landre=/land/
waterre=/water/
longitudere=/longitude/
latitudere=/latitude/
#TCP client
hostname = 'c1.easyctf.com'
port = 12483
string=""
s = Socket.new Socket::AF_INET, Socket::SOCK_STREAM
s.connect Socket.pack_sockaddr_in(port, hostname)
while char = s.recv(1) # Read chars from the socket
string+=char
print char
if char=="?" # detect question
zipcode = string.scan(/[0-9]{5}/)
#zipcode=zipre.match(string)
zipcode=zipcode[0].to_i.to_s #perhaps an ugly hack, but strips leading 0-es fast
if landre.match(string)
s.puts(answerer(zipcode,1))
end
if waterre.match(string)
s.puts(answerer(zipcode,2))
end
if latitudere.match(string)
s.puts(answerer(zipcode,3))
end
if longitudere.match(string)
s.puts(answerer(zipcode,4))
end
string="" # empty for next question
end
end
puts(string)
s.close()
Look at it go!
You can find the code and database on my GitHub
Problems encountered
- tcp client would not print the question, after the countdown. The problem was that default buffer size for the socket was too big, meaning the line was only outputted when it was full. Fixed by outputting characters received in a stream with ‘s.recv(1)’
- leading zeroes were not properly handled, since the DB file did not contain leading 0’s, but questions do. While in practice you can still succeed if you get lucky (indeed, 48/50 got me really close). Fixed with some ugly StackOverflow hack, converting the value to integer, then back to string.
- consequence of previous fix, now I was getting multiple matches, which only happens with leading zeroes. In those case only the first first occurence matters (but only because list is sorted) so simple
|head -1
is enough.