From 5f39aff5de71e9b05cca3d735919e1180f1a4900 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Wed, 2 Sep 2009 10:15:54 -0600 Subject: [PATCH] Somewhat working game This commit adds: * Registration * Puzzler cgi script * Several puzzles * Style sheet for HTML --- ctf.css | 21 +++ flagd.py | 3 +- game.py | 33 +++-- games/crypto/scytale.py | 30 +++++ index.html | 22 ++++ points.py | 4 +- pointscli.py | 24 ++-- pointsd.py | 4 +- puzzler.cgi | 176 ++++++++++++++++++++++++++ puzzles/bletchey/100/key | 1 + puzzles/bletchey/100/key.png | Bin 0 -> 87 bytes puzzles/bletchey/200/index.html | 1 + puzzles/bletchey/200/key | 1 + puzzles/bletchey/250/index.html | 1 + puzzles/bletchey/250/key | 1 + puzzles/bletchey/300/index.html | 13 ++ puzzles/bletchey/300/key | 1 + puzzles/bletchey/500/200601262232.ogg | Bin 0 -> 10237 bytes puzzles/bletchey/500/cipher.txt | 1 + puzzles/bletchey/500/index.html | 1 + puzzles/bletchey/500/key | 1 + register.cgi | 62 +++++++++ scoreboard.cgi | 5 +- teams.py | 40 ++++++ 24 files changed, 417 insertions(+), 29 deletions(-) create mode 100644 ctf.css create mode 100755 games/crypto/scytale.py create mode 100644 index.html create mode 100755 puzzler.cgi create mode 100644 puzzles/bletchey/100/key create mode 100644 puzzles/bletchey/100/key.png create mode 100644 puzzles/bletchey/200/index.html create mode 100644 puzzles/bletchey/200/key create mode 100644 puzzles/bletchey/250/index.html create mode 100644 puzzles/bletchey/250/key create mode 100644 puzzles/bletchey/300/index.html create mode 100644 puzzles/bletchey/300/key create mode 100644 puzzles/bletchey/500/200601262232.ogg create mode 100644 puzzles/bletchey/500/cipher.txt create mode 100644 puzzles/bletchey/500/index.html create mode 100644 puzzles/bletchey/500/key create mode 100755 register.cgi create mode 100755 teams.py diff --git a/ctf.css b/ctf.css new file mode 100644 index 0000000..1eb1fc1 --- /dev/null +++ b/ctf.css @@ -0,0 +1,21 @@ +body { + background: #000; + color: #0f0; +} +.readme { + background: #444; +} +a:link { + color: #ff0; +} +a:visited { + color: #880; +} +a:hover { + color: #000; + background: #ff0; +} +.error { + color: #000; + background: #f00; +} diff --git a/flagd.py b/flagd.py index 2e48be4..862afc7 100755 --- a/flagd.py +++ b/flagd.py @@ -9,6 +9,7 @@ import hmac import optparse import points import pointscli +import teams import traceback key = b'My First Shared Secret (tm)' @@ -67,7 +68,7 @@ class Submitter(asyncore.dispatcher): def set_flag(self, cat, team): now = int(time.time()) - team = team or points.house + team = team or teams.house if self.flags.get(cat) != team: self.flags[cat] = team diff --git a/game.py b/game.py index 82fe91c..45ae28b 100755 --- a/game.py +++ b/game.py @@ -6,6 +6,7 @@ import asynchat import socket import traceback import time +import teams from errno import EPIPE @@ -25,7 +26,6 @@ class Listener(asyncore.dispatcher): self.listen(4) self.player_factory = player_factory self.manager = manager - self.last_beat = 0 def handle_accept(self): conn, addr = self.accept() @@ -34,9 +34,7 @@ class Listener(asyncore.dispatcher): # has a reference to it for as long as it's open. def readable(self): - now = time.time() - if now > self.last_beat + pulse: - self.manager.heartbeat(now) + self.manager.heartbeat(time.time()) return True @@ -89,11 +87,25 @@ class Manager: self.lobby = set() self.contestants = [] self.last_beat = 0 + self.timers = set() def heartbeat(self, now): - # Called by listener to beat heart - for game in list(self.games): - game.heartbeat(now) + """Called by listener to beat heart.""" + + now = time.time() + if now > self.last_beat + pulse: + for game in list(self.games): + game.heartbeat(now) + for event in self.timers: + when, cb = event + if now >= when: + self.timers.remove(event) + cb() + + def add_timer(self, when, cb): + """Add a timed callback.""" + + self.timers.add((when, cb)) def enter_lobby(self, player): self.lobby.add(player) @@ -248,13 +260,14 @@ class Player(asynchat.async_chat): cmd, args = val[0].lower(), val[1:] if cmd == 'login': - if not self.name: - # XXX Check password + if self.name: + self.err('Already logged in.') + elif teams.chkpasswd(args[0], args[1]): self.name = args[0] self.write('Welcome to the fray, %s.' % self.name) self.manager.enter_lobby(self) else: - self.err('Already logged in.') + self.err('Invalid password.') elif cmd == '^': # Send to manager ret = self.manager.player_cmd(args) diff --git a/games/crypto/scytale.py b/games/crypto/scytale.py new file mode 100755 index 0000000..ece0efd --- /dev/null +++ b/games/crypto/scytale.py @@ -0,0 +1,30 @@ +#! /usr/bin/env python3 + +import sys +import random + +primes = [2, 3, 5, 7, 11, 13, 17, 19] +letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + +data = sys.stdin.read().strip() +jumble = ''.join(data.split()) + +lj = len(jumble) +below = (0, 0) +above = (lj, 2) +for i in primes: + for j in primes: + m = i * j + if (m < lj) and (m > below[0] * below[1]): + below = (i, j) + elif (m >= lj) and (m < (above[0] * above[1])): + above = (i, j) + +for i in range(lj, (above[0] * above[1])): + jumble += random.choice(letters) + +out = [] +for i in range(above[0]): + for j in range(above[1]): + out.append(jumble[j*above[0] + i]) +print(''.join(out)) diff --git a/index.html b/index.html new file mode 100644 index 0000000..89fe545 --- /dev/null +++ b/index.html @@ -0,0 +1,22 @@ + + + + + Capture The Flag + + + +

Capture The Flag

+ +
    +
  1. Register your team
  2. +
  3. Scoreboard
  4. +
+ +

+ Some challenges are puzzles. Some are + sitting on the network; you must find these yourself! +

+ + diff --git a/points.py b/points.py index 952ee00..10cadff 100755 --- a/points.py +++ b/points.py @@ -4,9 +4,7 @@ import socket import hmac import struct import io - -## Name of the house team -house = 'dirtbags' +import teams ## ## Authentication diff --git a/pointscli.py b/pointscli.py index c4bd659..c43a534 100755 --- a/pointscli.py +++ b/pointscli.py @@ -6,10 +6,17 @@ import points import socket import time -def submit(sock, cat, team, score): +def makesock(host): + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect((host, 6667)) + return s + +def submit(cat, team, score, sock=None): + if not sock: + sock = makesock('cfl-sunray1') begin = time.time() mark = int(begin) - req = points.encode_request(mark, cat, team, score) + req = points.encode_request(1, mark, cat, team, score) while True: sock.send(req) r, w, x = select.select([sock], [], [], begin + 2 - time.time()) @@ -17,12 +24,12 @@ def submit(sock, cat, team, score): break b = sock.recv(500) try: - when, cat_, txt = points.decode_response(b) + id, txt = points.decode_response(b) except ValueError: # Ignore invalid packets continue - if (when != mark) or (cat_ != cat): - # Ignore wrong timestamp + if id != 1: + # Ignore wrong ID continue if txt == 'OK': return @@ -30,11 +37,6 @@ def submit(sock, cat, team, score): raise ValueError(txt) -def makesock(host): - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - s.connect((host, 6667)) - return s - def main(): p = optparse.OptionParser(usage='%prog CATEGORY TEAM SCORE') p.add_option('-s', '--host', dest='host', default='localhost', @@ -50,7 +52,7 @@ def main(): s = makesock(opts.host) try: - submit(s, cat, team, score) + submit(cat, team, score, sock=s) except ValueError as err: print(err) raise diff --git a/pointsd.py b/pointsd.py index c415dbc..3ec0e63 100755 --- a/pointsd.py +++ b/pointsd.py @@ -34,11 +34,11 @@ class MyHandler(asyncore.dispatcher): team = team or house # Replays can happen legitimately. - if not (id in self.acked): + if not ((peer, id) in self.acked): if not (now - 2 < when <= now): return self.respond(peer, id, 'Your clock is off') self.store.add((when, cat, team, score)) - self.acked.add(id) + self.acked.add((peer, id)) self.respond(peer, id, 'OK') diff --git a/puzzler.cgi b/puzzler.cgi new file mode 100755 index 0000000..84fe253 --- /dev/null +++ b/puzzler.cgi @@ -0,0 +1,176 @@ +#! /usr/bin/env python3 + +import cgitb; cgitb.enable() +import cgi +import os +import fcntl +import re +import sys +import pointscli +import teams + +cat_re = re.compile(r'^[a-z]+$') +points_re = re.compile(r'^[0-9]+$') + +def dbg(*vals): + print('Content-type: text/plain\n\n') + print(*vals) + + +points_by_cat = {} +points_by_team = {} +try: + for line in open('puzzler.dat'): + line = line.strip() + cat, team, pts = line.split('\t') + pts = int(pts) + points_by_cat[cat] = max(points_by_cat.get(cat, 0), pts) + points_by_team.setdefault((team, cat), set()).add(pts) +except IOError: + pass + + +f = cgi.FieldStorage() + +cat = f.getfirst('c') +points = f.getfirst('p') +team = f.getfirst('t') +passwd = f.getfirst('w') +key = f.getfirst('k') + +verboten = ['key', 'index.html'] + +def start_html(title): + print('''Content-type: text/html + + + + + + %s + + + +

%s

+''' % (title, title)) + +def end_html(): + print('') + + +def safe_join(*args): + safe = [] + for a in args: + if not a: + return None + else: + a = a.replace('..', '') + a = a.replace('/', '') + safe.append(a) + ret = '/'.join(safe) + if os.path.exists(ret): + return ret + +def dump_file(fn): + f = open(fn, 'rb') + while True: + d = f.read(4096) + if not d: + break + sys.stdout.buffer.write(d) + +def show_cats(): + start_html('Categories') + print('') + end_html() + + +def show_puzzles(cat, cat_dir): + start_html('Open in %s' % cat) + opened = points_by_cat.get(cat, 0) + puzzles = sorted([int(v) for v in os.listdir(cat_dir)]) + if puzzles: + print('') + else: + print('

None (someone is slacking)

') + end_html() + +def show_puzzle(cat, points, points_dir): + # Show puzzle in cat for points + start_html('%s for %s' % (cat, points)) + fn = os.path.join(points_dir, 'index.html') + if os.path.exists(fn): + print('
') + dump_file(fn) + print('
') + print('') + print('
') + print('' % cat) + print('' % points) + print('Team:
') + print('Password:
') + print('Key:
') + print('') + print('
') + end_html() + +def win(cat, team, points): + start_html('Winner!') + points = int(points) + pointscli.submit(cat, team, points) + end_html() + f = open('puzzler.dat', 'a') + fctnl.lockf(f, LOCK_EX) + f.write('%s\t%s\t%d\n' % (cat, team, points)) + +def main(): + cat_dir = safe_join('puzzles', cat) + points_dir = safe_join('puzzles', cat, points) + + if not cat_dir: + # Show categories + show_cats() + elif not points_dir: + # Show available puzzles in category + show_puzzles(cat, cat_dir) + elif not (team and passwd and key): + fn = f.getfirst('f') + if fn in verboten: + fn = None + fn = safe_join('puzzles', cat, points, fn) + if fn: + # Provide a file from this directory + print('Content-type: application/octet-stream') + print() + dump_file(fn) + else: + show_puzzle(cat, points, points_dir) + else: + thekey = open('%s/key' % points_dir).read().strip() + if not teams.chkpasswd(team, passwd): + start_html('Wrong password') + end_html() + elif key != thekey: + show_puzzle(cat, points, points_dir) + elif points_by_team.get((team, cat)): + start_html('Greedy greedy') + end_html() + else: + win(cat, team, points) + +main() diff --git a/puzzles/bletchey/100/key b/puzzles/bletchey/100/key new file mode 100644 index 0000000..5403edd --- /dev/null +++ b/puzzles/bletchey/100/key @@ -0,0 +1 @@ +antediluvian diff --git a/puzzles/bletchey/100/key.png b/puzzles/bletchey/100/key.png new file mode 100644 index 0000000000000000000000000000000000000000..b658ad9a0c30e3f6ab63f02fe75c080305c69c60 GIT binary patch literal 87 zcmeAS@N?(olHy`uVBq!ia0vp^96-#&2qYNFpB^*-QgWUyjv*W~lV_YT=ueb#R(BRZ koT6YmJ5kKpoH>ApA((;fW6O-WcR)HkUHx3vIVCg!01BEFA^-pY literal 0 HcmV?d00001 diff --git a/puzzles/bletchey/200/index.html b/puzzles/bletchey/200/index.html new file mode 100644 index 0000000..398be1c --- /dev/null +++ b/puzzles/bletchey/200/index.html @@ -0,0 +1 @@ +tkftsuiuqvaheohrnsnuoleyriod"eic" diff --git a/puzzles/bletchey/200/key b/puzzles/bletchey/200/key new file mode 100644 index 0000000..d398ae2 --- /dev/null +++ b/puzzles/bletchey/200/key @@ -0,0 +1 @@ +unequivocal diff --git a/puzzles/bletchey/250/index.html b/puzzles/bletchey/250/index.html new file mode 100644 index 0000000..80ad312 --- /dev/null +++ b/puzzles/bletchey/250/index.html @@ -0,0 +1 @@ +27586126814341379597440261571645814840581961154587430529221052323 diff --git a/puzzles/bletchey/250/key b/puzzles/bletchey/250/key new file mode 100644 index 0000000..3933ee2 --- /dev/null +++ b/puzzles/bletchey/250/key @@ -0,0 +1 @@ +DB1663<3 diff --git a/puzzles/bletchey/300/index.html b/puzzles/bletchey/300/index.html new file mode 100644 index 0000000..c773f26 --- /dev/null +++ b/puzzles/bletchey/300/index.html @@ -0,0 +1,13 @@ +

Kolejne modele Panzerfausta, odpowiednio: 60, 100, 150, różnił kaliber głowicy i wielkość ładunku miotającego. Konstrukcja i mechanizm nie ulegał istotnym zmianom, z racji wzrastania zasięgu broni modyfikacjom ulegały nastawy celowników. Jedynie we wzorze 150 wprowadzono (a był to już początek 1945 roku) wielokrotne użycie wyrzutni rurowej. Osiągnięto to przez umieszczenie ładunku miotającego w głowicy oraz przez wzmocnienie rury. W wyniku problemu z transportem model ów nie wszedł do walki. Model 250 (o teoretycznym zasięgu 250 m) z racji zakończenia wojny nie opuścił desek kreślarskich nigdy nie wchodząc nawet w fazę prototypową.

+
(61, 4)
+(47, 8)
+(19, 4)
+(37, 1)
+(51, 3)
+(67, 5)
+(9, 2)
+(26, 1)
+(2, 2)
+(26, 3)
+(50, 2)
+ diff --git a/puzzles/bletchey/300/key b/puzzles/bletchey/300/key new file mode 100644 index 0000000..ea86794 --- /dev/null +++ b/puzzles/bletchey/300/key @@ -0,0 +1 @@ +jako561962 diff --git a/puzzles/bletchey/500/200601262232.ogg b/puzzles/bletchey/500/200601262232.ogg new file mode 100644 index 0000000000000000000000000000000000000000..d00f8256ae3090b81586ae944d92e1198bbd1b96 GIT binary patch literal 10237 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 + + + + Team Registration + + + +

Team Registration

+ +
+
+ + + $team_error
+ + +
+ + + + $pw_match_error
+ + +
+
+ + +''') + +if not (team and pw and confirm_pw): #If we're starting from the beginning? + html = html.substitute(team_error='', + pw_match_error='') +elif teams.exists(team): + html = html.substitute(team_error='Team team already taken', + pw_match_error='') +elif pw != confirm_pw: + html = html.substitute(team_error='', + pw_match_error='Passwords do not match') +else: + teams.add(team, pw) + html = 'Team registered.' + +print(html) diff --git a/scoreboard.cgi b/scoreboard.cgi index 2f42bc0..7bb66a3 100755 --- a/scoreboard.cgi +++ b/scoreboard.cgi @@ -17,9 +17,10 @@ print(''' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> - yo mom + CTF Scoreboard + - +

Scoreboard

''') print('') diff --git a/teams.py b/teams.py new file mode 100755 index 0000000..a9bb384 --- /dev/null +++ b/teams.py @@ -0,0 +1,40 @@ +#! /usr/bin/env python3 + +import fcntl + +house = 'dirtbags' + +teams = None + +def build_teams(): + global teams + + teams = {} + try: + f = open('passwd') + for line in f: + team, passwd = line.strip().split('\t') + teams[team] = passwd + except IOError: + pass + +def chkpasswd(team, passwd): + if teams is None: + build_teams() + if teams.get(team) == passwd: + return True + else: + return False + +def exists(team): + if teams is None: + build_teams() + if team == house: + return True + return team in teams + +def add(team, passwd): + f = open('passwd', 'a') + fcntl.lockf(f, fcntl.LOCK_EX) + f.seek(0, 2) + f.write('%s\t%s\n' % (team, passwd))