diff --git a/.gitignore b/.gitignore index dd4952d..d18b70c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,9 @@ *.pyc *.dat *.swp +*.tce passwd +fake target/ puzzler/ ctf.tce diff --git a/Makefile b/Makefile index 96254d0..d644fd8 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,9 @@ all: ctf.tce target: $(PYC) $(INSTALL) -d --mode=0755 --owner=100 $(DESTDIR)/var/lib/ctf + + $(INSTALL) -d --mode=0755 --owner=100 $(DESTDIR)/var/lib/ctf/survey + $(INSTALL) -d $(DESTDIR)/var/lib/ctf/disabled touch $(DESTDIR)/var/lib/ctf/disabled/survey @@ -25,6 +28,7 @@ target: $(PYC) $(INSTALL) -d $(DESTDIR)/usr/sbin $(INSTALL) ctfd.py $(DESTDIR)/usr/sbin + $(INSTALL) new-contest $(DESTDIR)/usr/sbin $(INSTALL) -d $(WWWDIR) $(INSTALL) index.html intro.html ctf.css grunge.png $(WWWDIR) diff --git a/badmath/Gyopi.py b/badmath/Gyopi.py index 47cef85..c36bdce 100644 --- a/badmath/Gyopi.py +++ b/badmath/Gyopi.py @@ -40,6 +40,7 @@ class Gyopi(irc.Bot): self._lvl = 0 self._flag.set_flag( self.FLAG_DEFAULT ) + self._tokens = [] self._lastAttempt = {} self._affiliations = {} self._newPuzzle() @@ -62,7 +63,7 @@ class Gyopi(irc.Bot): self.last_tb = '%s %s %s' % (t, v, infostr) print(self.last_tb) - def cmd_join(self, sender, forum, addl): + def cmd_JOIN(self, sender, forum, addl): """On join, announce who has the flag.""" if sender.name() in self.nicks: self._tellFlag(forum) @@ -150,11 +151,11 @@ class Gyopi(irc.Bot): self._tokens[user].remove(token) - def cmd_privmsg(self, sender, forum, addl): + def cmd_PRIVMSG(self, sender, forum, addl): text = addl[0] who = sender.name() if text.startswith('!'): - parts = text[1:].lower().split(' ', 1) + parts = text[1:].split(' ', 1) cmd = parts[0] if len(parts) > 1: args = parts[1] @@ -179,6 +180,7 @@ class Gyopi(irc.Bot): elif cmd.startswith('h'): # Help forum.msg('Goal: Help me with my math homework, FROM ANOTHER DIMENSION!') + forum.msg('Order of operations is always left to right.') #forum.msg('Goal: The current winner gets to control the contest music.') forum.msg('Commands: !help, !flag, !register [TEAM], !solve SOLUTION,!? EQUATION, !ops, !problem, !who') elif cmd.startswith('prob'): @@ -208,11 +210,15 @@ class Gyopi(irc.Bot): # self._giveToken(who, sender) self._saveState() else: - forum.msg('%s: %s != %s' % (who, attempt, answer)) forum.msg('%s: That is not correct.' % who) # Test a simple one op command. elif cmd.startswith('?'): + if not args: + forum.msg('%s: Give me an easier problem, and I\'ll ' + 'give you the answer.' % who) + return + try: tokens = badmath.parse(''.join(args)) except (ValueError) as msg: @@ -253,7 +259,7 @@ if __name__ == '__main__': help='Flag server password') p.add_option('-d', '--path', dest='path', default='/var/lib/badmath', help='Path to where we can store state info.') - p.add_option('-c', '--channel', dest='channel', default='+badmath', + p.add_option('-c', '--channel', dest='channel', default='#badmath', help='Which channel to join') opts, args = p.parse_args() diff --git a/badmath/badmath.tce b/badmath/badmath.tce index ec0516a..fab5817 100644 Binary files a/badmath/badmath.tce and b/badmath/badmath.tce differ diff --git a/badmath/fake b/badmath/fake index 5e3c978..85d3f2c 100644 --- a/badmath/fake +++ b/badmath/fake @@ -10,5 +10,6 @@ dev=16,ino=87557238,mode=40755,uid=0,gid=0,nlink=2,rdev=0 dev=16,ino=87557239,mode=40755,uid=0,gid=0,nlink=2,rdev=0 dev=16,ino=87557240,mode=40755,uid=0,gid=0,nlink=2,rdev=0 dev=16,ino=87573208,mode=100755,uid=0,gid=0,nlink=1,rdev=0 +dev=16,ino=87573213,mode=100755,uid=0,gid=0,nlink=1,rdev=0 +dev=16,ino=87573285,mode=100755,uid=0,gid=0,nlink=1,rdev=0 dev=16,ino=87573433,mode=100755,uid=0,gid=0,nlink=1,rdev=0 -dev=16,ino=87573456,mode=100755,uid=0,gid=0,nlink=1,rdev=0 diff --git a/badmath/run b/badmath/run index 4477536..d5c3b2b 100755 --- a/badmath/run +++ b/badmath/run @@ -5,4 +5,4 @@ DATA_PATH=/var/lib/badmath mkdir -p $DATA_PATH -exec envuidgid ctf python3.0 usr/lib/ctf/badmath/Gyopi.py --data=$DATA_PATH +exec envuidgid ctf python3 /usr/lib/ctf/badmath/Gyopi.py --path=$DATA_PATH diff --git a/ctf.css b/ctf.css index b9aa22b..d3c033a 100644 --- a/ctf.css +++ b/ctf.css @@ -77,3 +77,19 @@ p { margin-bottom: 20px; color: #f4f4f4; } + +.solved { + text-decoration: line-through; +} + +table.pollster { + margin-left: 5em; +} + +table.pollster td { + padding: 2px 1em 2px 5px; +} + +table.pollster thead { + font-weight: bold; +} diff --git a/ctf/config.py b/ctf/config.py index 034eb71..65f9ae2 100755 --- a/ctf/config.py +++ b/ctf/config.py @@ -37,6 +37,10 @@ else: 'house_team': 'dirtbags', 'passwd': '/var/lib/ctf/passwd', 'team_colors': team_colors, + 'poll_interval': 60, + 'poll_timeout': 0.5, + 'heartbeat_dir': '/var/lib/pollster', + 'poll_dir': '/var/lib/www', }, 'puzzler': {'dir': '/usr/lib/www/puzzler', @@ -66,3 +70,23 @@ def datafile(filename): def url(path): return base_url + path + +def start_html(title): + if os.environ.get('GATEWAY_INTERFACE'): + print('Content-type: text/html') + print() + print(''' + + + + %s + + + +

%s

+''' % (title, css, title)) + +def end_html(): + print('') diff --git a/ctf/flagd.py b/ctf/flagd.py index 6ae444b..f827972 100755 --- a/ctf/flagd.py +++ b/ctf/flagd.py @@ -142,6 +142,8 @@ class FlagServer(asynchat.async_chat): self.inbuf.append(data) def set_flag(self, team): + if not self.cat: + return self.flag = team self.submitter.set_flag(self.cat, team) f = open(os.path.join(flags_dir, self.cat), 'w') diff --git a/ctf/puzzler.py b/ctf/puzzler.py index 48e487e..e6d44a6 100755 --- a/ctf/puzzler.py +++ b/ctf/puzzler.py @@ -27,7 +27,7 @@ cat_re = re.compile(r'^[a-z]+$') points_re = re.compile(r'^[0-9]+$') def dbg(*vals): - print('<--: \nContent-type: text/html\n\n-->
')
+    print('
')
     print(*vals)
     print('
') @@ -59,32 +59,16 @@ passwd = f.getfirst('w', passwd) key = f.getfirst('k') def start_html(title): - if os.environ.get('GATEWAY_INTERFACE'): - print('Content-type: text/html') - if team or passwd: - c = http.cookies.SimpleCookie() - if team: - c['team'] = team - if passwd: - c['passwd'] = passwd - print(c) - print() - print(''' - - - - %s - - - -

%s

-''' % (title, config.css, title)) - -def end_html(): - print('') + if team or passwd: + c = http.cookies.SimpleCookie() + if team: + c['team'] = team + if passwd: + c['passwd'] = passwd + print(c) + config.start_html(title) +end_html = config.end_html def safe_join(*args): safe = list(args[:1]) @@ -125,7 +109,17 @@ def show_puzzles(cat, cat_dir): if puzzles: print('') diff --git a/ctf/register.py b/ctf/register.py index 422b798..95e8e92 100755 --- a/ctf/register.py +++ b/ctf/register.py @@ -7,6 +7,22 @@ import string from . import teams from . import config +def head(title): + return ''' + + + + Team Registration + + + +

%s

+''' % (config.css, title) + +def foot(): + return '''''' + def main(): print('Content-type: text/html') print() @@ -17,17 +33,8 @@ def main(): pw = f.getfirst('pw') confirm_pw = f.getfirst('confirm_pw') - html = string.Template(''' - - - - Team Registration - - - -

Team Registration

- + html = string.Template(config.start_html('Team Registration') + + ('''

Pick a short team name: you'll be typing it a lot.

@@ -50,10 +57,8 @@ def main(): - - - - ''' % (config.css, config.url('register.cgi'))) + ''' % config.url('register.cgi')) + + config.end_html()) if not (team and pw and confirm_pw): # If we're starting from the beginning? html = html.substitute(team_error='', @@ -66,7 +71,9 @@ def main(): pw_match_error='Passwords do not match') else: teams.add(team, pw) - html = 'Team registered.' + html = (config.start_html('Team registered') + + ('

Congratulations, %s is now registered. Go back to the front page and start playing!

' % (team, config.url(''))) + + config.end_html()) print(html) diff --git a/ctf/teams.py b/ctf/teams.py index 29ed25b..90b6216 100755 --- a/ctf/teams.py +++ b/ctf/teams.py @@ -28,9 +28,7 @@ def build_teams(): f = open(passwdfn) for line in f: line = line.strip() - team, passwd = [unquote(v) for v in line.strip().split('\t')] - color = team_colors.pop(0) - team_colors.append(color) + team, passwd, color = map(unquote, line.strip().split('\t')) teams[team] = (passwd, color) except IOError: pass @@ -53,10 +51,15 @@ def exists(team): return team in teams def add(team, passwd): + build_teams() + color = team_colors[len(teams)%len(team_colors)] + + assert team not in teams, "Team already exists." + f = open(passwdfn, 'a') fcntl.lockf(f, fcntl.LOCK_EX) f.seek(0, 2) - f.write('%s\t%s\n' % (quote(team), quote(passwd))) + f.write('%s\t%s\t%s\n' % (quote(team), quote(passwd), quote(color))) def color(team): t = teams.get(team) @@ -66,6 +69,3 @@ def color(team): if not t: return '888888' return t[1] - - - diff --git a/ctfd.py b/ctfd.py index 99071b5..bd34ea5 100755 --- a/ctfd.py +++ b/ctfd.py @@ -4,6 +4,7 @@ import asyncore import os import sys import optparse +import signal from ctf import pointsd from ctf import flagd from ctf import histogram @@ -38,6 +39,7 @@ def main(): pointsrv = pointsd.start() flagsrv = flagd.start() + signal.signal(signal.SIGCHLD, sigchld) s = pointsrv.store slen = 0 while True: diff --git a/index.html b/index.html index 50fce64..285121e 100644 --- a/index.html +++ b/index.html @@ -10,7 +10,7 @@

Welcome

    -
  1. Read the introduction to this event
  2. +
  3. Read the introduction and rules
  4. Register your team
  5. View the score board
diff --git a/new-contest b/new-contest new file mode 100755 index 0000000..2325e44 --- /dev/null +++ b/new-contest @@ -0,0 +1,18 @@ +#! /bin/sh -e + +ctime () { + stat -c %z $1 | awk '{ print $1; }' +} + +rotate () { + mv $1 $1.$(ctime $1) +} + +rotate /var/lib/ctf/puzzler.dat +rotate /var/lib/ctf/scores.dat +rotate /var/lib/ctf/passwd +rm -f /var/lib/ctf/flags/* || true + +echo "Things you may want to tweak:" +find /var/lib/ctf/disabled +find /var/lib/kevin/tokens diff --git a/pollster/in.heartbeatd b/pollster/in.heartbeatd new file mode 100755 index 0000000..e991a6a --- /dev/null +++ b/pollster/in.heartbeatd @@ -0,0 +1,8 @@ +#! /bin/sh + +case "$REMOTEADDR" in + 10.0.0.[2-254]) + touch /var/lib/pollster/$REMOTEADDR + ;; +esac + diff --git a/pollster/pollster.py b/pollster/pollster.py new file mode 100755 index 0000000..bde9fea --- /dev/null +++ b/pollster/pollster.py @@ -0,0 +1,249 @@ +#!/usr/bin/env python3 + +import os +import re +import sys +import time +import socket +import traceback +import threading +import queue + +from ctf import config +from ctf import pointscli + +DEBUG = False +POLL_INTERVAL = config.get('poll_interval') +IP_DIR = config.get('heartbeat_dir') +REPORT_PATH = config.get('poll_dir') +SOCK_TIMEOUT = config.get('poll_timeout') + +class PointSubmitter(threading.Thread): + ''' Pulls point allocations from the queue and submits them. ''' + def __init__(self, point_queue): + threading.Thread.__init__(self) + self.point_queue = point_queue + self.sock = pointscli.makesock('localhost') + + def run(self): + # loop forever + while(True): + cat, team, score = self.point_queue.get() + if None in [cat, team, score]: + continue + + try: + pointscli.submit(cat, team, score, sock=self.sock) + except ValueError: + print('pollster: error submitting score (%s, %s, %d)' % (cat, team, score)) + traceback.print_exc() + +def socket_poll(ip, port, msg, prot, max_recv=1): + ''' Connect via socket to the specified : using the + specified , send the specified and return the + response or None if something went wrong. specifies + how many times to read from the socket (default to once). ''' + + # create a socket + try: + sock = socket.socket(socket.AF_INET, prot) + except Exception as e: + print('pollster: create socket failed (%s)' % e) + traceback.print_exc() + return None + + sock.settimeout(SOCK_TIMEOUT) + + # connect + try: + sock.connect((ip, port)) + except socket.timeout as e: + print('pollster: attempt to connect to %s:%d timed out (%s)' % (ip, port, e)) + traceback.print_exc() + return None + except Exception as e: + print('pollster: attempt to connect to %s:%d failed (%s)' % (ip, port, e)) + traceback.print_exc() + return None + + # send something + sock.send(msg) + + # get a response + resp = '' + try: + # first read + data = sock.recv(1024) + resp += data.decode('utf-8') + max_recv -= 1 + + # remaining reads as necessary until timeout or socket closes + while(len(data) > 0 and max_recv > 0): + data = sock.recv(1024) + resp += data.decode('utf-8') + max_recv -= 1 + sock.close() + + except socket.timeout as e: + print('pollster: timed out waiting for a response from %s:%d (%s)' % (ip, port, e)) + traceback.print_exc() + except Exception as e: + print('pollster: receive from %s:%d failed (%s)' % (ip, port, e)) + traceback.print_exc() + + if len(resp) == 0: + return None + + return resp + +# PUT POLLS FUNCTIONS HERE +# Each function should take an IP address and return a team name or None +# if (a) the service is not up, (b) it doesn't return a valid team name. + +def poll_fingerd(ip): + ''' Poll the fingerd service. Returns None or a team name. ''' + resp = socket_poll(ip, 79, b'flag\n', socket.SOCK_STREAM) + if resp is None: + return None + return resp.strip('\r\n') + +def poll_noted(ip): + ''' Poll the noted service. Returns None or a team name. ''' + resp = socket_poll(ip, 4000, b'rflag\n', socket.SOCK_STREAM) + if resp is None: + return None + return resp.strip('\r\n') + +def poll_catcgi(ip): + ''' Poll the cat.cgi web service. Returns None or a team name. ''' + request = bytes('GET /cat.cgi/flag HTTP/1.1\r\nHost: %s\r\n\r\n' % ip, 'ascii') + resp = socket_poll(ip, 80, request, socket.SOCK_STREAM, 3) + if resp is None: + return None + + content = resp.split('\r\n\r\n') + if len(content) < 3: + return None + + content = content[1].split('\r\n') + + try: + content_len = int(content[0]) + except Exception as e: + return None + + if content_len <= 0: + return None + return content[1].strip('\r\n') + +def poll_tftpd(ip): + ''' Poll the tftp service. Returns None or a team name. ''' + resp = socket_poll(ip, 69, b'\x00\x01' + b'flag' + b'\x00' + b'octet' + b'\x00', socket.SOCK_DGRAM) + if resp is None: + return None + + if len(resp) <= 5: + return None + + resp = resp.split('\n')[0] + return resp[4:].strip('\r\n') + +# PUT POLL FUNCTIONS IN HERE OR THEY WONT BE POLLED +POLLS = { + 'fingerd' : poll_fingerd, + 'noted' : poll_noted, + 'catcgi' : poll_catcgi, + 'tftpd' : poll_tftpd, +} + +ip_re = re.compile('(\d{1,3}\.){3}\d{1,3}') + +# start point submitter thread +point_queue = queue.Queue() +t = PointSubmitter(point_queue) +t.start() + +# loop forever +while True: + + t_start = time.time() + + # gather the list of IPs to poll + try: + ips = os.listdir(IP_DIR) + except Exception as e: + print('pollster: could not list dir %s (%s)' % (IP_DIR, e)) + traceback.print_exc() + + try: + os.remove(REPORT_PATH) + except Exception as e: + pass + + try: + out = open(REPORT_PATH, 'w') + except Exception as e: + out = None + pass + + if out is not None: + out.write('\n\n') + out.write('Pollster Results\n') + out.write('\n') + out.write('\n

Polling Results

\n') + + for ip in ips: + + # check file name format is ip + if ip_re.match(ip) is None: + continue + + # remove the file + try: + os.remove(os.path.join(IP_DIR, ip)) + except Exception as e: + print('pollster: could not remove %s' % os.path.join(IP_DIR, ip)) + traceback.print_exc() + + results = {} + + if DEBUG is True: + print('ip: %s' % ip) + + if out is not None: + out.write('

%s

\n' % ip) + out.write('\n') + out.write('\n') + + # perform polls + for service,func in POLLS.items(): + team = func(ip) + if team is None: + team = 'dirtbags' + + if DEBUG is True: + print('\t%s - %s' % (service, team)) + + if out is not None: + out.write('\n' % (service, team)) + + point_queue.put((service, team, 1)) + + if out is not None: + out.write('
Service NameFlag Holder
%s%s
\n') + + if DEBUG is True: + print('+-----------------------------------------+') + + t_end = time.time() + exec_time = int(t_end - t_start) + sleep_time = POLL_INTERVAL - exec_time + + if out is not None: + out.write('

Next poll in: %ds

\n' % sleep_time) + out.write('\n\n') + out.close() + + # sleep until its time to poll again + time.sleep(sleep_time) + diff --git a/pollster/run.heartbeatd b/pollster/run.heartbeatd new file mode 100755 index 0000000..8241141 --- /dev/null +++ b/pollster/run.heartbeatd @@ -0,0 +1,3 @@ +#! /bin/sh + +exec udpsvd 0 9 /usr/sbin/in.heartbeatd diff --git a/puzzles/bletchley/1000/index.html b/puzzles/bletchley/1000/index.html new file mode 100644 index 0000000..7dd37e9 --- /dev/null +++ b/puzzles/bletchley/1000/index.html @@ -0,0 +1,108 @@ +Safe to execute. + +Santa's helpers binary diff --git a/puzzles/bletchley/1000/key b/puzzles/bletchley/1000/key new file mode 100644 index 0000000..3be61b9 --- /dev/null +++ b/puzzles/bletchley/1000/key @@ -0,0 +1 @@ +It is a lovely day outside diff --git a/puzzles/compaq/100/index.html b/puzzles/compaq/100/index.html new file mode 100644 index 0000000..672516c --- /dev/null +++ b/puzzles/compaq/100/index.html @@ -0,0 +1,2 @@ +Recovery, while not strictly necessary, may be tremendously helpful. + diff --git a/puzzles/compaq/150/b2f3f6b43ecadc7ae0b5f0edde694c78 b/puzzles/compaq/150/b2f3f6b43ecadc7ae0b5f0edde694c78 new file mode 100755 index 0000000..c20b65b Binary files /dev/null and b/puzzles/compaq/150/b2f3f6b43ecadc7ae0b5f0edde694c78 differ diff --git a/puzzles/compaq/150/key b/puzzles/compaq/150/key new file mode 100644 index 0000000..164f87f --- /dev/null +++ b/puzzles/compaq/150/key @@ -0,0 +1 @@ +This is our world now... the world of the electron and the switch, the beauty of the baud. diff --git a/puzzles/compaq/350/e76cb42be0c0f12f97b2071aba8b74f2 b/puzzles/compaq/350/e76cb42be0c0f12f97b2071aba8b74f2 new file mode 100755 index 0000000..10b82c9 Binary files /dev/null and b/puzzles/compaq/350/e76cb42be0c0f12f97b2071aba8b74f2 differ diff --git a/puzzles/compaq/350/key b/puzzles/compaq/350/key new file mode 100644 index 0000000..c4fa843 --- /dev/null +++ b/puzzles/compaq/350/key @@ -0,0 +1 @@ +Actually, Werner, we're all tickled to here you say that. Frankly, watchin' Donny beat Nazis to death is is the closest we ever get to goin' to the movies. diff --git a/puzzles/compaq/50/adddbafb502355634d9ef10e1848cf52 b/puzzles/compaq/50/adddbafb502355634d9ef10e1848cf52 new file mode 100644 index 0000000..97e2900 Binary files /dev/null and b/puzzles/compaq/50/adddbafb502355634d9ef10e1848cf52 differ diff --git a/puzzles/compaq/50/key b/puzzles/compaq/50/key new file mode 100644 index 0000000..0db4aae --- /dev/null +++ b/puzzles/compaq/50/key @@ -0,0 +1 @@ +extra special text diff --git a/puzzles/compaq/600/daa36d50d4c807634dfd13a8239046de b/puzzles/compaq/600/daa36d50d4c807634dfd13a8239046de new file mode 100755 index 0000000..41ba532 Binary files /dev/null and b/puzzles/compaq/600/daa36d50d4c807634dfd13a8239046de differ diff --git a/puzzles/compaq/600/key b/puzzles/compaq/600/key new file mode 100644 index 0000000..99d2435 --- /dev/null +++ b/puzzles/compaq/600/key @@ -0,0 +1 @@ +Now think real hard. You been bird-doggin' this township awhile now. They wouldn't mind a corpse of you. Now, you can luxuriate in a nice jail cell, but if your hand touches metal, I swear by my pretty floral bonnet, I will end you. diff --git a/puzzles/survey/1000000/submit.cgi b/puzzles/survey/1000000/,submit.cgi similarity index 57% rename from puzzles/survey/1000000/submit.cgi rename to puzzles/survey/1000000/,submit.cgi index bf1bbc4..39a9a08 100755 --- a/puzzles/survey/1000000/submit.cgi +++ b/puzzles/survey/1000000/,submit.cgi @@ -1,6 +1,8 @@ #! /usr/bin/env python3 import cgi +import time +import os f = cgi.FieldStorage() if f.getfirst('submit'): @@ -8,6 +10,13 @@ if f.getfirst('submit'): print() print('Thanks for filling in the survey.') print() + try: + fn = '/var/lib/ctf/survey/%s.%d.%d.txt' % (time.strftime('%Y-%m-%d'), time.time(), os.getpid()) + o = open(fn, 'w') + for k in f.keys(): + o.write('%s: %r\n' % (k, f.getlist(k))) + except IOError: + pass print('The key is:') print(' quux blorb frotz') else: diff --git a/puzzles/survey/1000000/,survey.html b/puzzles/survey/1000000/,survey.html new file mode 100644 index 0000000..0ed037b --- /dev/null +++ b/puzzles/survey/1000000/,survey.html @@ -0,0 +1,44 @@ + + + + + Survey + + + +
+
    +
  • + Did you have any trouble figuring out how to play? + +
  • + +
  • + How difficult were the puzzles? + +
  • +
+ +

+ Please use the provided space for any additional comments. +

+ +

+ Thanks for your feedback! We hope you had fun and learned + something! +

+ +
+ + + diff --git a/puzzles/survey/1000000/index.html b/puzzles/survey/1000000/index.html index 2b39091..aa2727d 100644 --- a/puzzles/survey/1000000/index.html +++ b/puzzles/survey/1000000/index.html @@ -4,38 +4,7 @@ recieve a key redeemable for ONE MILLION POINTS.

-
- Survey - -
-
    -
  • - Did you have any trouble figuring out how to play? - -
  • - -
  • - How difficult were the puzzles? - -
  • -
- -

- Please use the provided space for any additional comments. -

- -

- Thanks for your feedback! We hope you had fun and learned - something! -

- -
-
+ + Survey + diff --git a/pwnables/Makefile b/pwnables/Makefile index 154b3a8..5327560 100644 --- a/pwnables/Makefile +++ b/pwnables/Makefile @@ -5,9 +5,9 @@ TARGET = $(CURDIR)/target FAKE = fakeroot -s $(CURDIR)/fake -i $(CURDIR)/fake INSTALL = $(FAKE) install -all: 99-pwnables.tce +all: pwnables.tce -99-pwnables.tce: target +pwnables.tce: target $(FAKE) sh -c 'cd target && tar -czf - --exclude=placeholder --exclude=*~ .' > $@ target: @@ -17,13 +17,5 @@ target: $(MAKE) -C daemons TARGET=$(TARGET) install - $(INSTALL) -d $(TARGET)/usr/lib/www - $(INSTALL) $(CGI) $(TARGET)/usr/lib/www - - $(INSTALL) -D flag $(TARGET)/var/lib/tftp/flag - $(INSTALL) -D flag $(TARGET)/var/lib/notes/flag - $(INSTALL) -D flag $(TARGET)/home/flag/.plan - - clean: - rm -rf target + rm -rf target pwnables.tce diff --git a/pwnables/fingerd/Makefile b/pwnables/fingerd/Makefile deleted file mode 100644 index 74a0f1b..0000000 --- a/pwnables/fingerd/Makefile +++ /dev/null @@ -1 +0,0 @@ -all: in.fingerd diff --git a/pwnables/fingerd/in.fingerd.c b/pwnables/fingerd/in.fingerd.c deleted file mode 100644 index cb5d9e7..0000000 --- a/pwnables/fingerd/in.fingerd.c +++ /dev/null @@ -1,38 +0,0 @@ -#include - -int -main(int argc, char *argv) -{ - char user[256]; - char path[512]; - char *data; - FILE *f; - size_t count; - int i; - - if (NULL == gets(user)) { - return 0; - } - for (data = user; *data; data += 1) { - if ('\r' == *data) { - *data = 0; - } - } - if (0 == user[0]) { - printf("Nobody's home.\n"); - return 0; - } - - sprintf(path, "/home/%s/.plan", user); - f = fopen(path, "r"); - if (NULL == f) { - printf("No such user.\n"); - return 0; - } - - data = path; - while (count = fread(data, sizeof(*data), 1, f)) { - fwrite(data, count, 1, stdout); - } - return 0; -} diff --git a/pwnables/fingerd/run b/pwnables/fingerd/run deleted file mode 100755 index 4088271..0000000 --- a/pwnables/fingerd/run +++ /dev/null @@ -1,3 +0,0 @@ -#! /bin/sh - -exec tcpsvd 0 79 /usr/sbin/in.fingerd diff --git a/pwnables/skel/home/flag/.plan b/pwnables/skel/home/flag/.plan new file mode 100644 index 0000000..a69e835 --- /dev/null +++ b/pwnables/skel/home/flag/.plan @@ -0,0 +1 @@ +dirtbags diff --git a/pwnables/cat.cgi b/pwnables/skel/usr/lib/www/cat.cgi similarity index 100% rename from pwnables/cat.cgi rename to pwnables/skel/usr/lib/www/cat.cgi diff --git a/pwnables/skel/usr/lib/www/flag b/pwnables/skel/usr/lib/www/flag new file mode 120000 index 0000000..f4bdb9f --- /dev/null +++ b/pwnables/skel/usr/lib/www/flag @@ -0,0 +1 @@ +/var/lib/cat/flag \ No newline at end of file diff --git a/pwnables/skel/var/lib/cat/flag b/pwnables/skel/var/lib/cat/flag new file mode 100644 index 0000000..a69e835 --- /dev/null +++ b/pwnables/skel/var/lib/cat/flag @@ -0,0 +1 @@ +dirtbags diff --git a/pwnables/skel/var/lib/notes/flag b/pwnables/skel/var/lib/notes/flag new file mode 100644 index 0000000..a69e835 --- /dev/null +++ b/pwnables/skel/var/lib/notes/flag @@ -0,0 +1 @@ +dirtbags diff --git a/pwnables/skel/var/lib/tftp/flag b/pwnables/skel/var/lib/tftp/flag new file mode 100644 index 0000000..a69e835 --- /dev/null +++ b/pwnables/skel/var/lib/tftp/flag @@ -0,0 +1 @@ +dirtbags diff --git a/pwnables/skel/var/service/heartbeat/run b/pwnables/skel/var/service/heartbeat/run new file mode 100755 index 0000000..f7704d8 --- /dev/null +++ b/pwnables/skel/var/service/heartbeat/run @@ -0,0 +1,9 @@ +#! /bin/sh + +# Busybox netcat doesn't support UDP unless you compile in desktop mode. +# No problem, traceroute can send a UDP packet too. +while true; do + # Apparently traceroute adds 1 to the base port (-p) + traceroute -m 2 -q 1 -p 8 10.0.0.1 2>/dev/null >/dev/null + sleep 10 +done diff --git a/pwnables/tftpd/run b/pwnables/tftpd/run deleted file mode 100755 index bcba031..0000000 --- a/pwnables/tftpd/run +++ /dev/null @@ -1,3 +0,0 @@ -#! /bin/sh - -exec udpsvd 0 69 tftpd /var/lib/tftp diff --git a/run.log.ctfd b/run.log.ctfd new file mode 100755 index 0000000..820d402 --- /dev/null +++ b/run.log.ctfd @@ -0,0 +1,3 @@ +#! /bin/sh + +exec logger -t ctfd diff --git a/tanks/Makefile b/tanks/Makefile index cb27c38..4c83034 100644 --- a/tanks/Makefile +++ b/tanks/Makefile @@ -3,12 +3,16 @@ INSTALL = $(FAKE) install -o 100 all: tanks.tce +push: tanks.tce + netcat -l -q 0 -p 3333 < tanks.tce + tanks.tce: target $(FAKE) sh -c 'cd target && tar -czf - .' > $@ target: $(INSTALL) -d target/var/lib/tanks/ $(INSTALL) -d target/var/lib/tanks/results/ + $(INSTALL) -d target/var/lib/tanks/errors/ $(INSTALL) -d target/var/lib/tanks/ai/easy $(INSTALL) -d target/var/lib/tanks/ai/medium $(INSTALL) -d target/var/lib/tanks/ai/hard @@ -17,16 +21,17 @@ target: $(INSTALL) AI/medium/* target/var/lib/tanks/ai/medium/ $(INSTALL) AI/hard/* target/var/lib/tanks/ai/hard/ - $(INSTALL) -d target/var/lib/www/tanks/ - $(INSTALL) www/* target/var/lib/www/tanks/ - $(FAKE) ln -s target/var/lib/tanks/ target/var/lib/www/data + $(INSTALL) -d target/usr/lib/www/tanks/ + $(INSTALL) www/* target/usr/lib/www/tanks/ + + ln -s /var/lib/tanks/results target/usr/lib/www/tanks/results $(INSTALL) -d target/usr/lib/python2.6/site-packages/tanks/ $(INSTALL) lib/* target/usr/lib/python2.6/site-packages/tanks/ $(INSTALL) -d target/var/service/tanks - $(INSTALL) run target/var/service/tanks/run - + $(INSTALL) run run_tanks.py target/var/service/tanks/ + $(INSTALL) -d target/var/service/tanks/log/ $(INSTALL) log.run target/var/service/tanks/log/run diff --git a/tanks/lib/GameMath.py b/tanks/lib/GameMath.py index ff47880..481bf81 100644 --- a/tanks/lib/GameMath.py +++ b/tanks/lib/GameMath.py @@ -48,7 +48,7 @@ def displacePoly(points, disp, limits, coordSequence=False): maxX, maxY = limits basePoints = [] for point in points: - x,y = point[0] + disp[0], point[1] + disp[1] + x,y = int(point[0] + disp[0]), int(point[1] + disp[1]) # Check if duplication is needed on each axis if x > maxX: diff --git a/tanks/lib/Pflanzarr.py b/tanks/lib/Pflanzarr.py index 4f31ca1..ac600a2 100644 --- a/tanks/lib/Pflanzarr.py +++ b/tanks/lib/Pflanzarr.py @@ -9,19 +9,12 @@ from urllib import unquote, quote from PIL import Image, ImageColor, ImageDraw -try: - from ctf import teams -except: - import sys - path = '/home/pflarr/repos/gctf/' - sys.path.append(path) - from ctf import teams -teams.build_teams() - import Tank class Pflanzarr: + TEAMS_FILE = '/var/lib/ctf/passwd' + FRAME_DELAY = 15 SPACING = 150 @@ -42,12 +35,14 @@ class Pflanzarr: if not os.path.exists(self._gameDir): os.mkdir(self._gameDir) + colors = self._getColors() + tmpPlayers = os.listdir(self._playerDir) players = [] for p in tmpPlayers: p = unquote(p) if not (p.startswith('.') or p.endswith('#') or p.endswith('~'))\ - and p in teams.teams: + and p in colors: players.append(p) AIs = {} @@ -73,7 +68,7 @@ class Pflanzarr: self._board = (cols*self.SPACING, rows*self.SPACING) while len(players) < cols*rows: - players.append('#default') + players.append(None) self._tanks = [] for i in range(cols): @@ -82,13 +77,13 @@ class Pflanzarr: startY = j*self.SPACING + self.SPACING/2 player = random.choice(players) players.remove(player) - if player == '#default': + if player == None: color = '#a0a0a0' else: - color = '#%s' % teams.teams[player][1] + color = colors[player] tank = Tank.Tank( player, (startX, startY), color, self._board, testMode=True) - if player == '#default': + if player == None: tank.program(random.choice(defaultAIs)) else: tank.program(AIs[player]) @@ -156,10 +151,12 @@ class Pflanzarr: if tank in kills[tank]: kills[tank].remove(tank) - self._saveResults(kills) for tank in self._tanks: self._outputErrors(tank) self._makeMovie() + # This needs to go after _makeMovie; the web scripts look for these + # files to see if the game has completed. + self._saveResults(kills) def _killTanks(self, tanks, reason): for tank in tanks: @@ -204,22 +201,32 @@ class Pflanzarr: break winner = random.choice(winners) - html = ['', + html = ['', + 'Game %d results', + '', + '', + '', '
TeamKillsCause of Death'] for tank in tanks: if tank is winner: - rowStyle = 'style="color:red;"' + rowStyle = 'style="font-weight:bold; '\ + 'background-color:%s"' % tank.color else: - rowStyle = '' + rowStyle = 'style="background-color:%s"' % tank.color + if tank.name: + name = xml.sax.saxutils.escape(tank.name) + else: + name = '#default' html.append('
%s%d%s' % (rowStyle, - xml.sax.saxutils.escape(tank.name), + name, len(kills[tank]), xml.sax.saxutils.escape(tank.deathReason))) html.append('
') - if winner.name != '#default': + # Write a blank file if the winner is a default tank.. + if winner.name != None: winnerFile.write(tanks[0].name) winnerFile.close() @@ -243,14 +250,14 @@ class Pflanzarr: clearFrames = ['rm', '-rf', '%s' % self._imageDir] print 'Making Movie' - subprocess.call(movieCmd) -# subprocess.call(movieCmd, stderr=open('/dev/null', 'w'), -# stdout=open('/dev/null', 'w')) +# subprocess.call(movieCmd) + subprocess.call(movieCmd, stderr=open('/dev/null', 'w'), + stdout=open('/dev/null', 'w')) subprocess.call(clearFrames) def _outputErrors(self, tank): """Output errors for each team.""" - if tank.name == '#default': + if tank.name == None: return if tank._program.errors: @@ -378,7 +385,27 @@ class Pflanzarr: defaultAIs.append( file.read() ) return defaultAIs - + + def _getColors(self): + """Get the team colors from the passwd file. The passwd file location + is set by self.TEAMS_FILE. Returns a dictionary of players->color""" + errorColor = '#ffffff' + + try: + file = open(self.TEAMS_FILE) + except: + return {}.fromkeys(players, errorColor) + + colors = {} + for line in file: + try: + team, passwd, color = map(unquote, line.split('\t')) + colors[team] = '#%s' % color + except: + colors[team] = errorColor + + return colors + def _getGameNum(self): """Figure out what game number this is from the past games played.""" diff --git a/tanks/lib/Tank.py b/tanks/lib/Tank.py index dffa85e..7042a59 100644 --- a/tanks/lib/Tank.py +++ b/tanks/lib/Tank.py @@ -75,7 +75,7 @@ class Tank(object): else: self._tAngle = tAngle - self._color = color + self.color = color # You can't fire until fireReady is 0. self._fireReady = self.FIRE_RATE @@ -466,7 +466,7 @@ class Tank(object): # The base body rectangle. for poly in gm.displacePoly(hood, self.pos, self._limits): - d.polygon( poly, fill=self._color ) + d.polygon( poly, fill=self.color ) # The treads for poly in gm.displacePoly(tread1, self.pos, self._limits) + \ @@ -475,7 +475,7 @@ class Tank(object): # The turret circle for poly in gm.displacePoly(self.body, self.pos, self._limits): - d.ellipse( poly, fill=self._color, outline='black') + d.ellipse( poly, fill=self.color, outline='black') self._drawLaser(d) @@ -491,7 +491,7 @@ class Tank(object): if self._fired: laser = gm.rotatePoly( self.laser, self._angle + self._tAngle ) for poly in gm.displacePoly(laser, self.pos, self._limits): - drawing.polygon(poly, fill=self._color) + drawing.polygon(poly, fill=self.color) self._fired = False @@ -522,7 +522,7 @@ class Tank(object): if self._sensorState[i]: color = '#000000' else: - color = self._color + color = self.color r, angle, width, tAttached = self._sensors[i] r = int(r) diff --git a/tanks/run b/tanks/run index a8157be..5a5b932 100755 --- a/tanks/run +++ b/tanks/run @@ -2,5 +2,5 @@ [ -f /var/lib/ctf/disabled/tanks ] && exit 0 -envuidgid ctf python2.6 run_tanks.py /var/lib/tanks/ easy 100 & -envuidgid ctf report_score.py +exec envuidgid ctf python2.6 run_tanks.py /var/lib/tanks/ easy 100 2>&1 +#envuidgid ctf report_score.py 2>&1 diff --git a/tanks/run_tanks.py b/tanks/run_tanks.py old mode 100644 new mode 100755 index 4263e15..ae83990 --- a/tanks/run_tanks.py +++ b/tanks/run_tanks.py @@ -1,20 +1,93 @@ +#! /usr/bin/python + +import asynchat +import asyncore +import optparse +import os +import shutil +import socket import time from tanks import Pflanzarr -import sys T = 60*5 +MAX_HIST = 30 +HIST_STEP = 100 +key = 'tanks:::2bac5e912ff2e1ad559b177eb5aeecca' -try: - while 1: - start = time.time() - p = Pflanzarr(sys.argv[1], sys.argv[2]) - p.run(int(sys.argv[3])) - - diff = time.time() - start - if diff - T > 0: - time.sleep( diff - T ) +class Flagger(asynchat.async_chat): + """Use to connect to flagd and submit the current flag holder.""" -except: - print 'Usage: python2.6 run_tanks.py data_dir easy|medium|hard max_turns' + def __init__(self, addr, auth): + asynchat.async_chat.__init__(self) + self.create_socket(socket.AF_INET, socket.SOCK_STREAM) + self.connect((addr, 6668)) + self.push(auth + '\n') + self.flag = None - + def handle_read(self): + msg = self.recv(4096) + raise ValueError("Flagger died: %r" % msg) + + def handle_error(self): + # If we lose the connection to flagd, nobody can score any + # points. Terminate everything. + asyncore.close_all() + asynchat.async_chat.handle_error(self) + + def set_flag(self, team): + if team: + eteam = team + else: + eteam = '' + self.push(eteam + '\n') + self.flag = team + + +def run_tanks(args, turns, flagger): + p = Pflanzarr.Pflanzarr(args[0], args[1]) + p.run(turns) + + path = os.path.join(args[0], 'results') + files = os.listdir(path) + gameNums = [] + for file in files: + try: + gameNums.append( int(file) ) + except: + continue + + gameNums.sort(reverse=True) + highest = gameNums[0] + for num in gameNums: + if highest - MAX_HIST > num and not (num % HIST_STEP == 0): + shutil.rmtree(os.path.join(path, str(num))) + + try: + winner = open('/var/lib/tanks/winner').read().strip() + except: + winner = None + flagger.set_flag(winner) + + +def main(): + parser = optparse.OptionParser('DATA_DIR easy|medium|hard MAX_TURNS') + opts, args = parser.parse_args() + if (len(args) != 3) or (args[1] not in ('easy', 'medium', 'hard')): + parser.error('Wrong number of arguments') + try: + turns = int(args[2]) + except: + parser.error('Invalid number of turns') + + + flagger = Flagger('localhost', key) + lastrun = 0 + while True: + asyncore.loop(60, count=1) + now = time.time() + if now - lastrun >= 60: + run_tanks(args, turns, flagger) + lastrun = now + +if __name__ == '__main__': + main() diff --git a/tanks/t.py b/tanks/t.py new file mode 100644 index 0000000..7a2285d --- /dev/null +++ b/tanks/t.py @@ -0,0 +1,3 @@ +import sys + +print >> sys.stderr, 'hello' diff --git a/tanks/www/docs.cgi b/tanks/www/docs.cgi index 8ceb4d0..26306e3 100755 --- a/tanks/www/docs.cgi +++ b/tanks/www/docs.cgi @@ -7,7 +7,7 @@ import os import sys try: - from Tanks import Program, setup, conditions, actions, docs + from tanks import Program, setup, conditions, actions, docs except: path = os.getcwd().split('/') path.pop() diff --git a/tanks/www/errors.cgi b/tanks/www/errors.cgi index 1359cc9..f61d2bc 100755 --- a/tanks/www/errors.cgi +++ b/tanks/www/errors.cgi @@ -1,9 +1,10 @@ -#!/usr/bin/python +#!/usr/bin/python3 -print """Content-Type: text/html\n\n""" -print """\n\n""" +print("""Content-Type: text/html\n\n""") +print("""\n\n""") import cgi import cgitb; cgitb.enable() +import sys import os import Config @@ -16,18 +17,17 @@ except: try: from ctf import teams except: - import sys path = '/home/pflarr/repos/gctf/' sys.path.append(path) from ctf import teams teams.build_teams() head = open('head.html').read() % "Error Report" -print head -print open('links.html').read() +print(head) +print(open('links.html').read()) def done(): - print '' + print('') sys.exit(0) fields = cgi.FieldStorage() @@ -38,25 +38,25 @@ if team and passwd and \ path = os.path.join(Config.DATA_PATH, 'errors', quote(team)) if os.path.isfile(path): errors = open(path).readlines() - print '

Your latest errors:' - print '

' + print('

Your latest errors:') + print('

') if errors: - print '
\n'.join(errors) + print('
\n'.join(errors)) else: - print 'There were no errors.' - print '
' + print('There were no errors.') + print('
') else: - print '

No error file found.' + print('

No error file found.') done() if team and team not in teams.teams: - print '

Invalid team.' + print('

Invalid team.') if team and team in teams.teams and passwd != teams.teams[team][0]: - print '

Invalid password.' + print('

Invalid password.') -print ''' +print('''

Error report request: @@ -64,6 +64,6 @@ print ''' Password:
-
''' +''') done() diff --git a/tanks/www/results.cgi b/tanks/www/results.cgi index a17ee77..ceb4ae2 100755 --- a/tanks/www/results.cgi +++ b/tanks/www/results.cgi @@ -28,22 +28,28 @@ except: if not games: print "

No games have occurred yet." + gameNums = [] for game in games: try: - num = int(game) - path = os.path.join( 'results', game, 'results.html') - if os.path.exists( path ): - gameNums.append( int(num) ) - else: - continue - + gameNums.append( int(game) ) except: continue gameNums.sort(reverse=True) +# Don't include games that haven't completed +i = 0 +num = str(gameNums[i]) +for i in range(len(gameNums)): + path = os.path.join( 'results', str(gameNums[i]), 'results.html') ) + if os.path.exists( path ): + break +gameNums = gameNums[i:] + for num in gameNums: print '

%d - ' % num, print 'v' % num, print 'r' % num + +print '' diff --git a/tanks/www/submit.cgi b/tanks/www/submit.cgi index 9d57907..5dad944 100755 --- a/tanks/www/submit.cgi +++ b/tanks/www/submit.cgi @@ -1,8 +1,11 @@ -#!/usr/bin/python +#!/usr/bin/python3 +print("Content-Type: text/html\n\n") +print("""\n\n""") import cgi import cgitb; cgitb.enable() import os +import sys import Config @@ -14,21 +17,18 @@ except: try: from ctf import teams except: - import sys path = '/home/pflarr/repos/gctf/' sys.path.append(path) from ctf import teams teams.build_teams() -print """Content-Type: text/html\n\n""" -print """\n\n""" head = open('head.html').read() % "Submission Results" -print head -print "

Results

" -print open('links.html').read() +print(head) +print("

Results

") +print(open('links.html').read()) def done(): - print '' + print('') sys.exit(0) fields = cgi.FieldStorage() @@ -36,21 +36,23 @@ team = fields.getfirst('team', '').strip() passwd = fields.getfirst('passwd', '').strip() code = fields.getfirst('code', '') if not team: - print '

No team specified'; done() + print('

No team specified'); done() elif not passwd: - print '

No password given'; done() + print('

No password given'); done() elif not code: - print '

No program given.'; done() + print('

No program given.'); done() if team not in teams.teams: - print '

Team is not registered.'; done() + print('

Team is not registered.'); done() if passwd != teams.teams[team][0]: - print '

Invalid password.'; done() + print('

Invalid password.'); done() -path = os.path.join(Config.DATA_PATH, 'ai/players', encode(team) ) +path = os.path.join(Config.DATA_PATH, 'ai/players', quote(team) ) file = open(path, 'w') file.write(code) file.close() +print("

Submission Successful") + done() diff --git a/tanksFlagger/Makefile b/tanksFlagger/Makefile new file mode 100644 index 0000000..7bd61de --- /dev/null +++ b/tanksFlagger/Makefile @@ -0,0 +1,20 @@ +FAKE = fakeroot -s fake -i fake +INSTALL = $(FAKE) install -o 100 + +all: tanksFlagger.tce + +push: tanksFlagger.tce + netcat -l -q 0 -p 3333 < tanksFlagger.tce + +tanksFlagger.tce: target + $(FAKE) sh -c 'cd target && tar -czf - .' > $@ + +target: + $(INSTALL) -d target/var/service/tanksFlagger + $(INSTALL) run report_score.py target/var/service/tanksFlagger/ + + $(INSTALL) -d target/var/service/tanksFlagger/log/ + $(INSTALL) log.run target/var/service/tanksFlagger/log/run + +clean: + rm -rf target tanksFlagger.tce fake diff --git a/tanksFlagger/log.run b/tanksFlagger/log.run new file mode 100755 index 0000000..f7a77bf --- /dev/null +++ b/tanksFlagger/log.run @@ -0,0 +1,3 @@ +#! /bin/sh + +exec logger -t tanksFlagger diff --git a/tanks/report_score.py b/tanksFlagger/report_score.py similarity index 100% rename from tanks/report_score.py rename to tanksFlagger/report_score.py diff --git a/tanksFlagger/run b/tanksFlagger/run new file mode 100755 index 0000000..e2eb214 --- /dev/null +++ b/tanksFlagger/run @@ -0,0 +1,5 @@ +#! /bin/sh + +[ -f /var/lib/ctf/disabled/tanks ] && exit 0 + +exec envuidgid ctf python3 report_score.py 2>&1