Remove more cruft, add polish

This commit is contained in:
Neale Pickett 2010-09-13 17:32:51 -06:00
parent 14d7e3509d
commit 7e4f523347
24 changed files with 265 additions and 2025 deletions

View File

@ -1,77 +1,12 @@
BASE = /opt/ctf SUBDIRS = src
VAR = $(BASE)/var
WWW = $(BASE)/www
LIB = $(BASE)/lib
BIN = $(BASE)/bin
SBIN = $(BASE)/sbin
BASE_URL = /
USERNAME = www-data
TEMPLATE = $(CURDIR)/template.html all: build
MDWNTOHTML = $(CURDIR)/mdwntohtml.py --template=$(TEMPLATE) --base=$(BASE_URL)
default: install
SUBDIRS = mdwn
INSTALL_TARGETS = $(addsuffix -install, $(SUBDIRS))
include $(addsuffix /*.mk, $(SUBDIRS)) include $(addsuffix /*.mk, $(SUBDIRS))
install: base-install $(INSTALL_TARGETS) test: build
install --directory --owner=$(USERNAME) $(VAR)/tanks ./test.sh
install --directory --owner=$(USERNAME) $(VAR)/tanks/results
install --directory --owner=$(USERNAME) $(VAR)/tanks/errors
install --directory --owner=$(USERNAME) $(VAR)/tanks/ai
install --directory --owner=$(USERNAME) $(VAR)/tanks/ai/players
install --directory --owner=$(USERNAME) $(VAR)/tanks/ai/house
echo 'VAR = "$(VAR)"' > ctf/paths.py
echo 'WWW = "$(WWW)"' >> ctf/paths.py
echo 'LIB = "$(LIB)"' >> ctf/paths.py
echo 'BIN = "$(BIN)"' >> ctf/paths.py
echo 'SBIN = "$(SBIN)"' >> ctf/paths.py
echo 'BASE_URL = "$(BASE_URL)"' >> ctf/paths.py
install bin/pointscli $(BIN)
install bin/in.pointsd bin/in.flagd \
bin/scoreboard bin/run-tanks \
bin/run-ctf $(SBIN)
cp -r lib/* $(LIB)
cp -r www/* $(WWW)
rm -f $(WWW)/tanks/results
ln -s $(VAR)/tanks/results $(WWW)/tanks/results
cp template.html $(LIB)
./mkpuzzles.py --base=$(BASE_URL) --puzzles=puzzles \
--htmldir=$(WWW)/puzzler --keyfile=$(LIB)/puzzler.keys
install --directory $(VAR)/disabled
touch $(VAR)/disabled/bletchley
touch $(VAR)/disabled/compaq
touch $(VAR)/disabled/crypto
touch $(VAR)/disabled/forensics
touch $(VAR)/disabled/hackme
touch $(VAR)/disabled/hispaniola
touch $(VAR)/disabled/net-re
touch $(VAR)/disabled/skynet
touch $(VAR)/disabled/survey
python setup.py install
base-install:
install --directory $(LIB) $(BIN) $(SBIN)
install --directory --owner=$(USERNAME) $(VAR)
install --directory --owner=$(USERNAME) $(WWW)
install --directory --owner=$(USERNAME) $(WWW)/puzzler
install --directory --owner=$(USERNAME) $(VAR)/points
install --directory --owner=$(USERNAME) $(VAR)/points/tmp
install --directory --owner=$(USERNAME) $(VAR)/points/cur
install --directory --owner=$(USERNAME) $(VAR)/flags
uninstall:
rm -rf $(VAR) $(WWW) $(LIB) $(BIN) $(SBIN)
rmdir $(BASE) || true
build: $(addsuffix -build, $(SUBDIRS))
clean: $(addsuffix -clean, $(SUBDIRS)) clean: $(addsuffix -clean, $(SUBDIRS))

View File

@ -1,265 +0,0 @@
#! /usr/bin/python
import badmath
import time
import os
import traceback
import pickle
import irc
from ctf import teams
from ctf.flagger import Flagger
class Gyopi(irc.Bot):
STATE_FN = 'badmath.state'
SALT = 'this is questionable.'
MAX_ATTEMPT_RATE = 3
NOBODY = '\002[nobody]\002'
def __init__(self, host, channels, dataPath, flagger):
irc.Bot.__init__(self, host, ['gyopi', 'gyopi_', '_gyopi', '[gyopi]'], 'Gyopi', channels)
self._dataPath = dataPath
self._flag = flagger
try:
self._loadState()
except:
self._lvl = 0
self._flag.set_flag(teams.house)
self._tokens = []
self._lastAttempt = {}
self._affiliations = {}
self._newPuzzle()
def err(self, exception):
"""Save the traceback for later inspection"""
irc.Bot.err(self, exception)
t,v,tb = exception
info = []
while 1:
info.append('%s:%d(%s)' %
(os.path.basename(tb.tb_frame.f_code.co_filename),
tb.tb_lineno,
tb.tb_frame.f_code.co_name))
tb = tb.tb_next
if not tb:
break
del tb # just to be safe
infostr = '[' + '] ['.join(info) + ']'
self.last_tb = '%s %s %s' % (t, v, infostr)
print(self.last_tb)
def cmd_JOIN(self, sender, forum, addl):
"""On join, announce who has the flag."""
if sender.name() in self.nicks:
self._tellFlag(forum)
self._tellPuzzle(forum)
self.write(['TOPIC', '#badmath'], 'type !help')
def _newPuzzle(self):
"""Create a new puzzle."""
self._key, self._puzzle, self._banned = badmath.mkPuzzle(self._lvl)
def _loadState(self):
"""Load the last state from the stateFile."""
statePath = os.path.join(self._dataPath, self.STATE_FN)
stateFile = open( statePath, 'rb' )
state = pickle.load(stateFile)
self._lvl = state['lvl']
self._flag.set_flag( state['flag'] )
self._lastAttempt = state['lastAttempt']
self._affiliations = state['affiliations']
self._puzzle = state['puzzle']
self._key = state['key']
self._banned = state['banned']
self._tokens = state.get('tokens', [])
def _saveState(self):
"""Write the current state to file."""
state = {'lvl': self._lvl,
'flag': self._flag.flag,
'lastAttempt': self._lastAttempt,
'affiliations': self._affiliations,
'puzzle': self._puzzle,
'key': self._key,
'banned': self._banned,
'tokens': self._tokens}
# Do the write as an atomic move operation
statePath = os.path.join(self._dataPath, self.STATE_FN)
stateFile = open(statePath + '.tmp', 'wb')
pickle.dump(state, stateFile)
stateFile.close()
os.rename( statePath + '.tmp', statePath)
def _tellFlag(self, forum):
"""Announce who owns the flag."""
forum.msg('%s has the flag.' % (self._flag.flag))
def _tellPuzzle(self, forum):
"""Announce the current puzzle."""
forum.msg('Difficulty level is %d' % self._lvl)
forum.msg('The problem is: %s' % ' '.join( map(str, self._puzzle)))
def _getStations(self):
stations = {}
file = open(os.path.join(STORAGE, 'stations.txt'))
lines = file.readlines()
for line in lines:
try:
name, file = line.split(':')
except:
continue
stations[name] = file
return stations
def _giveToken(self, user, forum):
"""Hand a Jukebox token to the user."""
token = self._jukebox.mkToken(user)
forum.msg('You get a jukebox token: %s' % token)
forum.msg('Use this with the !set command to change the music.')
forum.msg('This token is specific to your user name, and is only '
'useable once.')
def _useToken(self, user, forum, token, station):
"""Use the given token, and change the current station to station."""
try:
station = int(station)
stations = self._getStations()
assert station in stations
except:
forum.msg('%s: Invalid Station (%s)' % station)
return
if token in self._tokens[user]:
self._tokens[user].remove(token)
def cmd_PRIVMSG(self, sender, forum, addl):
text = addl[0]
who = sender.name()
if text.startswith('!'):
parts = text[1:].split(' ', 1)
cmd = parts[0]
if len(parts) > 1:
args = parts[1]
else:
args = None
if cmd.startswith('r'):
# Register
if args:
self._affiliations[who] = args
team = self._affiliations.get(who, self.NOBODY)
forum.msg('%s is playing for %s' % (who, team))
elif cmd.startswith('w'):
forum.msg('Teams:')
for player in self._affiliations:
forum.msg('%s: %s' % (player, self._affiliations[player]))
elif cmd.startswith('embrace'):
# Embrace
forum.ctcp('ACTION', 'is devoid of emotion.')
elif cmd.startswith('f'):
# Flag
self._tellFlag(forum)
elif cmd.startswith('h'):
# Help
forum.msg('''Goal: Help me with my math homework, FROM ANOTHER DIMENSION! Order of operations is always left to right in that dimension, but the operators are alien.''')
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'):
self._tellPuzzle(forum)
elif cmd.startswith('solve') and args:
# Solve
team = self._affiliations.get(who)
lastAttempt = time.time() - self._lastAttempt.get(team, 0)
#UN-COMMENT AFTER NMT CTF
# self._lastAttempt[team] = time.time()
answer = badmath.solve(self._key, self._puzzle)
try:
attempt = int(''.join(args).strip())
except:
forum.msg("%s: Answers are always integers.")
if not team:
forum.msg('%s: register first (!register TEAM).' % who)
elif self._flag.flag == team:
forum.msg('%s: Greedy, greedy.' % who)
elif lastAttempt < self.MAX_ATTEMPT_RATE:
forum.msg('%s: Wait at least %d seconds between attempts' %
(team, self.MAX_ATTEMPT_RATE))
elif answer == attempt:
self._flag.set_flag( team )
self._lvl = self._lvl + 1
self._tellFlag(forum)
self._newPuzzle()
self._tellPuzzle(forum)
# self._giveToken(who, sender)
self._saveState()
else:
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), msg:
forum.msg('%s: %s' % (who, msg))
return
if len(tokens) > 3:
forum.msg('%s: You can only test one op at a time.' % who)
return
for num in self._banned:
if num in tokens:
forum.msg('%s: You can\'t test numbers in the '
'puzzle.' % who)
return
try:
result = badmath.solve(self._key, tokens)
forum.msg('%s: %s -> %d' % (who, ''.join(args), result))
except Exception, msg:
forum.msg("%s: That doesn't work at all: %s" % (who, msg))
elif cmd == 'birdzerk':
self._saveState()
elif cmd == 'traceback':
forum.msg(self.last_tb or 'No traceback')
if __name__ == '__main__':
import optparse
p = optparse.OptionParser()
p.add_option('-i', '--irc', dest='ircHost', default='localhost',
help='IRC Host to connect to.')
p.add_option('-f', '--flagd', dest='flagd', default='localhost',
help='Flag Server to connect to')
p.add_option('-p', '--password', dest='password',
default='badmath:::a41c6753210c0bdafd84b3b62d7d1666',
help='Flag server password')
p.add_option('-d', '--path', dest='path', default='/var/lib/ctf/badmath',
help='Path to where we can store state info.')
p.add_option('-c', '--channel', dest='channel', default='#badmath',
help='Which channel to join')
opts, args = p.parse_args()
channels = [opts.channel]
flagger = Flagger(opts.flagd, opts.password.encode('utf-8'))
gyopi = Gyopi((opts.ircHost, 6667), channels, opts.path, flagger)
irc.run_forever()

View File

@ -1,96 +0,0 @@
#! /usr/bin/python
import sys
import optparse
import hmac
import time
import select
from ctf import teams, pointscli
import os
from urllib import quote
basedir = None
flagsdir = None
key = 'My First Shared Secret (tm)'
def hexdigest(s):
return hmac.new(key, s.encode('utf-8')).hexdigest()
def auth():
# Pretend to be in.tcpmuxd
while True:
line = sys.stdin.readline()
if not line:
return
line = line.strip().lower()
if line == 'tcpmux':
sys.stdout.write('+Okay, fine.\r\n')
sys.stdout.flush()
continue
elif line == 'help':
sys.stdout.write('tcpmux\r\n')
elif ':::' in line:
# Authentication
cat, passwd = line.split(':::')
if passwd == hexdigest(cat):
return cat
else:
sys.stdout.write('-Blow me.\r\n')
else:
sys.stdout.write('-Blow me.\r\n')
return
def award(cat, team):
qcat = quote(cat, '')
fn = os.path.join(flagsdir, qcat)
f = open(fn, 'w')
f.write(team)
f.close()
pointscli.award(cat, team, 1)
print('+%s' % team)
sys.stdout.flush()
def run():
cat = auth()
if not cat:
return
now = time.time()
next_award = now - (now % 60)
flag = teams.house
while True:
now = time.time()
while now >= next_award:
next_award += 60
award(cat, flag)
timeout = next_award - now
r, w, x = select.select([sys.stdin], [], [], timeout)
if r:
line = sys.stdin.readline()
if not line:
break
new_flag = line.strip() or teams.house
if new_flag != flag:
# Award a point if the flag is captured
flag = new_flag
award(cat, flag)
def main():
p = optparse.OptionParser(usage='%prog [options] FLAGSDIR')
p.add_option('-a', '--auth', dest='cat', default=None,
help='Generate authentication for the given category')
opts, args = p.parse_args()
if opts.cat:
print('%s:::%s' % (opts.cat, hexdigest(opts.cat.encode('utf-8'))))
elif len(args) != 1:
p.error('Wrong number of arguments')
else:
global flagsdir
flagsdir = args[0]
run()
if __name__ == '__main__':
main()

View File

@ -1,5 +0,0 @@
#! /bin/sh
ip=$(echo $UDPREMOTEADDR | cut -d: -f1)
touch $1/$ip
echo 'Hello.'

View File

@ -1,19 +0,0 @@
#! /bin/sh
##
## This is meant to be run from inotifyd like so:
##
## inotifyd in.pointsd $base/cur:y
##
## inotifyd runs in.pointsd serially, so all we have to do is just echo
## each file to the log, and then remove it. This allows a log message
## to be entered by writing a file into tmp, moving it to cur, and then
## moving along. Even if pointsd dies, everybody is still able to score
## points asynchronously without losing anything: it'll just get picked
## up when pointsd restarts.
##
# Args: flag dir file
fn=$2/$3
cat $fn >> $2/../log && rm $fn

136
bin/kevin
View File

@ -1,136 +0,0 @@
#! /usr/bin/python
import os
import optparse
import asynchat
import socket
import asyncore
from urllib import quote_plus as quote
import irc
from ctf.flagger import Flagger
nobody = '\002[nobody]\002'
class Kevin(irc.Bot):
def __init__(self, host, flagger, tokens, victims):
irc.Bot.__init__(self, host,
['kevin', 'kev', 'kevin_', 'kev_', 'kevinm', 'kevinm_'],
'Kevin',
['+kevin'])
self.flagger = flagger
self.tokens = tokens
self.victims = victims
self.affiliation = {}
def cmd_001(self, sender, forum, addl):
self.write(['OPER', 'bot', 'BottyMcBotpants'])
irc.Bot.cmd_001(self, sender, forum, addl)
def cmd_JOIN(self, sender, forum, addl):
if sender.name == self.nick:
self.write(['TOPIC', '#badmath'], 'type !help')
self.tell_flag(forum)
def cmd_381(self, sender, forum, addl):
# You are now an IRC Operator
if self.nick != 'kevin':
self.write(['KILL', 'kevin'], 'You are not kevin. I am kevin.')
self.write(['NICK', 'kevin'])
def err(self, exception):
"""Save the traceback for later inspection"""
irc.Bot.err(self, exception)
t,v,tb = exception
info = []
while 1:
info.append('%s:%d(%s)' % (os.path.basename(tb.tb_frame.f_code.co_filename),
tb.tb_lineno,
tb.tb_frame.f_code.co_name))
tb = tb.tb_next
if not tb:
break
del tb # just to be safe
infostr = '[' + '] ['.join(info) + ']'
self.last_tb = '%s %s %s' % (t, v, infostr)
print(self.last_tb)
def tell_flag(self, forum):
forum.msg('%s has the flag.' % (self.flagger.flag or nobody))
def cmd_PRIVMSG(self, sender, forum, addl):
text = addl[0]
if text.startswith('!'):
parts = text[1:].split(' ', 1)
cmd = parts[0].lower()
if len(parts) > 1:
args = parts[1]
else:
args = None
if cmd.startswith('r'):
# Register
who = sender.name()
if args:
self.affiliation[who] = args
team = self.affiliation.get(who, nobody)
forum.msg('%s is playing for %s' % (who, team))
elif cmd.startswith('e'):
# Embrace
forum.ctcp('ACTION', 'hugs %s' % sender.name())
elif cmd.startswith('f'):
# Flag
self.tell_flag(forum)
elif cmd.startswith('h'):
# Help
forum.msg('Goal: Obtain a token with social engineering.')
forum.msg('Commands: !help, !flag, !register [TEAM], !claim TOKEN, !victims, !embrace')
elif cmd.startswith('c') and args:
# Claim
sn = sender.name()
team = self.affiliation.get(sn)
token = quote(args, safe='')
fn = os.path.join(self.tokens, token)
if not team:
forum.msg('%s: register first (!register TEAM).' % sn)
elif self.flagger.flag == team:
forum.msg('%s: Greedy, greedy.' % sn)
elif not os.path.exists(fn):
forum.msg('%s: Token does not exist (possibly already claimed).' % sn)
else:
os.unlink(fn)
self.flagger.set_flag(team)
self.tell_flag(forum)
elif cmd.startswith('v'):
# Victims
# Open the file each time, so it can change
try:
for line in open(self.victims):
forum.msg(line.strip())
except IOError:
forum.msg('There are no victims!')
elif cmd == 'traceback':
forum.msg(self.last_tb or 'No traceback')
def main():
p = optparse.OptionParser()
p.add_option('-t', '--tokens', dest='tokens', default='./tokens',
help='Directory containing tokens')
p.add_option('-v', '--victims', dest='victims', default='victims.txt',
help='File containing victims information')
p.add_option('-i', '--ircd', dest='ircd', default='localhost',
help='IRC server to connect to')
p.add_option('-f', '--flagd', dest='flagd', default='localhost',
help='Flag server to connect to')
p.add_option('-p', '--password', dest='password',
default='kevin:::7db3e44d53d4a466f8facd7b7e9aa2b7',
help='Flag server password')
p.add_option('-c', '--channel', dest='channel',
help='Channel to join')
opts, args = p.parse_args()
f = Flagger(opts.flagd, opts.password.encode('utf-8'))
k = Kevin((opts.ircd, 6667), f, opts.tokens, opts.victims)
irc.run_forever()
if __name__ == '__main__':
main()

View File

@ -1,5 +0,0 @@
#! /usr/bin/python
from ctf import pointscli
pointscli.main()

View File

@ -1,285 +0,0 @@
#!/usr/bin/python
import os
import re
import sys
import time
import socket
import traceback
import subprocess
import random
import httplib
import optparse
import cStringIO as io
from ctf import pointscli, html
ifconfig = '/sbin/ifconfig'
udhcpc = '/sbin/udhcpc'
class BoundHTTPConnection(httplib.HTTPConnection):
''' http.client.HTTPConnection doesn't support binding to a particular
address, which is something we need. '''
def __init__(self, bindip, host, port=None, strict=None, timeout=None):
httplib.HTTPConnection.__init__(self, host, port, strict)
self.bindip = bindip
self.timeout = timeout
def connect(self):
''' Connect to the host and port specified in __init__, but
also bind first. '''
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.bind((self.bindip, 0))
self.sock.settimeout(self.timeout)
self.sock.connect((self.host, self.port))
def random_mac():
''' Set a random mac on the poll interface. '''
retcode = subprocess.call((ifconfig, opts.iface, 'down'))
mac = ':'.join([opts.mac_vendor] + ['%02x' % random.randint(0,255) for i in range(3)])
retcode = subprocess.call((ifconfig, opts.iface, 'hw', 'ether', mac, 'up'))
def dhcp_request():
''' Request a new IP on the poll interface. '''
retcode = subprocess.call((udhcpc, '-i', opts.iface, '-q'))
def get_ip():
''' Return the IP of the poll interface. '''
ip_match = re.compile(r'inet addr:([0-9.]+)')
p = subprocess.Popen((ifconfig, opts.iface), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(out, err) = p.communicate()
for line in out.splitlines():
m = ip_match.search(line)
if m is not None:
return m.group(1).decode('utf-8')
return '10.1.1.1'
def socket_poll(srcip, ip, port, msg, prot, max_recv=1):
''' Connect via socket to the specified <ip>:<port> using the
specified <prot>, send the specified <msg> and return the
response or None if something went wrong. <max_recvs> specifies
how many times to read from the socket (defaults to once). '''
# create a socket
try:
sock = socket.socket(socket.AF_INET, prot)
except Exception, e:
print('pollster: create socket failed (%s)' % e)
traceback.print_exc()
return None
sock.bind((srcip, 0))
sock.settimeout(opts.timeout)
# connect
try:
sock.connect((ip, port))
except socket.timeout, e:
print('pollster: attempt to connect to %s:%d timed out (%s)' % (ip, port, e))
traceback.print_exc()
return None
except Exception, 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:
# read from the socket until <max_recv> responses or read,
# a timeout occurs, the socket closes, or some other exception
# is raised
for i in range(max_recv):
data = sock.recv(1024)
if len(data) == 0:
break
resp.append(data)
except socket.timeout, e:
print('pollster: timed out waiting for a response from %s:%d (%s)' % (ip, port, e))
traceback.print_exc()
except Exception, e:
print('pollster: receive from %s:%d failed (%s)' % (ip, port, e))
traceback.print_exc()
sock.close()
if len(resp) == 0:
return None
return ''.join(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(srcip, ip):
''' Poll the fingerd service. Returns None or a team name. '''
resp = socket_poll(srcip, ip, 79, 'flag\n', socket.SOCK_STREAM)
if resp is None:
return None
return resp.split('\n')[0]
def poll_noted(srcip, ip):
''' Poll the noted service. Returns None or a team name. '''
resp = socket_poll(srcip, ip, 4000, 'rflag\n', socket.SOCK_STREAM)
if resp is None:
return None
return resp.split('\n')[0]
def poll_catcgi(srcip, ip):
''' Poll the cat.cgi web service. Returns None or a team name. '''
try:
conn = BoundHTTPConnection(srcip, ip, timeout=opts.timeout)
conn.request('GET', '/var/www/flag')
except Exception, e:
traceback.print_exc()
return None
resp = conn.getresponse()
if resp.status != 200:
conn.close()
return None
data = resp.read()
conn.close()
return data.split('\n')[0]
def poll_tftpd(srcip, ip):
''' Poll the tftp service. Returns None or a team name. '''
resp = socket_poll(srcip, ip, 69, '\x00\x01' + 'flag' + '\x00' + 'octet' + '\x00', socket.SOCK_DGRAM)
if resp is None:
return None
if len(resp) <= 5:
return None
resp = resp.split('\n')[0]
# ack
_ = socket_poll(srcip, ip, 69, '\x00\x04\x00\x01' + '\x00' * 14, socket.SOCK_DGRAM, 0)
return resp[4:].split('\n')[0]
# PUT POLL FUNCTIONS IN HERE OR THEY WONT BE POLLED
POLLS = {
'fingerd' : poll_fingerd,
'noted' : poll_noted,
'catcgi' : poll_catcgi,
'tftpd' : poll_tftpd,
}
p = optparse.OptionParser()
p.add_option('-d', '--debug', action='store_true', dest='debug',
default=False,
help='Turn on debugging output')
p.add_option('-s', '--interval', type='float', dest='interval',
default=60,
help='Time between polls, in seconds (default: %default)')
p.add_option('-t', '--timeout', type='float', dest='timeout',
default=0.5,
help='Poll timeout, in seconds (default: %default)')
p.add_option('-b', '--heartbeat-dir', dest='heartbeat_dir',
default='/var/lib/ctf/heartbeat',
help='Where in.heartbeatd writes its files')
p.add_option('-r', '--results-page', dest='results',
default='/tmp/services.html',
help='Where to write results file')
p.add_option('-i', '--interface', dest='iface',
default='eth1',
help='Interface to bind to')
p.add_option('-m', '--mac-vendor', dest='mac_vendor',
default='00:01:c0',
help='MAC vendor to look for (default: %default)')
opts, args = p.parse_args()
socket.setdefaulttimeout(opts.timeout)
ip_re = re.compile('(\d{1,3}\.){3}\d{1,3}')
# loop forever
while True:
random_mac()
dhcp_request()
srcip = get_ip()
t_start = time.time()
# gather the list of IPs to poll
ips = os.listdir(opts.heartbeat_dir)
out = io.StringIO()
for ip in ips:
# check file name format is ip
if ip_re.match(ip) is None:
continue
# remove the file
fn = os.path.join(opts.heartbeat_dir, ip)
try:
os.remove(fn)
except Exception, e:
print('pollster: could not remove %s' % fn)
traceback.print_exc()
results = {}
if opts.debug:
print('ip: %s' % ip)
if out is not None:
out.write('<h2>%s</h2>\n' % ip)
out.write('<table class="pollster">\n<thead><tr><td>Service Name</td></td>')
out.write('<td>Flag Holder</td></tr></thead>\n')
# perform polls
for service,func in POLLS.items():
try:
team = func(srcip, ip).decode('utf-8')
if len(team) == 0:
team = 'dirtbags'
except:
team = 'dirtbags'
if opts.debug:
print('\t%s - %s' % (service, team))
if out is not None:
out.write('<tr><td>%s</td><td>%s</td>\n' % (service, team))
pointscli.award('svc.' + service, team, 1)
if out is not None:
out.write('</table>\n')
if opts.debug:
print('+-----------------------------------------+')
time_str = time.strftime('%a, %d %b %Y %H:%M:%S %Z')
out.write('''
<p>This page was generated on %s. That was <span id="diff">?</span> seconds ago.</p>
<script type="text/javascript">
var gen_time = new Date(%f);
var cur_time = new Date();
var diff = (cur_time.getTime() - gen_time.getTime())/1000;
document.getElementById("diff").innerHTML = diff;
</script>
''' % (time_str, time.time()*1000))
t_end = time.time()
exec_time = int(t_end - t_start)
sleep_time = opts.interval - exec_time
html.write(opts.results,'Team Service Availability', out.getvalue())
# sleep until its time to poll again
time.sleep(sleep_time)

View File

@ -1,36 +1,35 @@
#! /bin/sh #! /bin/sh
# First argument is seconds between running everything # First argument is seconds between running everything
cycle=${1:-60} period=${1:-60}
POINTS=var/points/log CTF_BASE=${CTF_BASE:-/srv/ctf} export CTF_BASE
cd $(dirname $0)/..
POINTS=$CTF_BASE/points.log
SCOREBOARD=$CTF_BASE/www/scoreboard.html
PUZZLES=$CTF_BASE/www/puzzles.html
while true; do while true; do
# Timestamp
start=$(date +%s) start=$(date +%s)
next=$(expr $start + $cycle) next=$(expr $start + $period)
# If enabled, run tanks
if ! [ -f var/disabled/tanks ]; then
sbin/run-tanks --no-barren-points --once var/tanks
fi
# Collect any new points # Collect any new points
for fn in var/points/cur/*; do for fn in $CTF_BASE/points.new/*; do
[ -f $fn ] || continue [ -f $fn ] || continue
cat $fn >> $POINTS || break cat $fn >> $POINTS || break
rm $fn rm $fn
done done
# Update the scoreboard if [ $POINTS -nt $SCOREBOARD ]; then
if [ -f $POINTS ]; then $CTF_BASE/sbin/scoreboard < $POINTS > $SCOREBOARD.new
sbin/scoreboard -t www/scoreboard.html -j www/myplot.js < $POINTS mv $SCOREBOARD.new $SCOREBOARD
fi
if [ $CTF_BASE/puzzler.db -nt $PUZZLES ]; then
$CTF_BASE/sbin/puzzles.cgi > $PUZZLES.new
mv $PUZZLES.new $PUZZLES
fi fi
# Wait until the next minute
now=$(date +%s) now=$(date +%s)
if [ $now -lt $next ]; then if [ $now -lt $next ]; then
sleep $(expr $next - $now) sleep $(expr $next - $now)

View File

@ -1,61 +0,0 @@
#! /usr/bin/python
import optparse
import os
import shutil
import socket
import time
from ctf import pointscli, teams, paths
from tanks import Pflanzarr
running = True
def run_tanks(basedir, turns, barren_points=True):
try:
p = Pflanzarr.Pflanzarr(basedir)
p.run(turns)
winner = p.winner or teams.house
except Pflanzarr.NotEnoughPlayers:
if not barren_points:
return
winner = teams.house
pointscli.award('tanks', winner, 1)
winnerFile = open(os.path.join(basedir, 'winner'),'w')
winnerFile.write(winner)
winnerFile.close()
# Fake being a flag, so the most recent winner shows up on the
# scoreboard.
try:
open(os.path.join(paths.VAR, 'flags', 'tanks'), 'w').write(winner)
except IOError:
pass
def main():
parser = optparse.OptionParser('%prog [options] DATA_DIR')
parser.add_option('-1', '--once',
action='store_true', dest='once',
help='Run only once')
parser.add_option('-b', '--no-barren-points',
action='store_false', dest='barren', default=True,
help="Don't award points if there aren't enough players")
parser.add_option('-t', '--max-turns',
type='int', dest='turns', default=500,
help='Maximum number of turns per round')
parser.add_option('-s', '--sleep-time',
type='int', dest='sleep', default=60,
help='Wait SLEEP seconds between turns (default %default)')
opts, args = parser.parse_args()
if (len(args) != 1):
parser.error('Wrong number of arguments')
while running:
run_tanks(args[0], opts.turns, opts.barren)
if opts.once:
break
time.sleep(opts.sleep)
if __name__ == '__main__':
main()

View File

@ -1,174 +1,240 @@
#! /usr/bin/python #! /usr/bin/awk -f
import sys ##
import codecs ##
import time ## I'm not happy with how this code looks. I've
import optparse ##
import string ##
import os
from urllib import unquote
from ctf import teams, html, paths
from codecs import open
from sets import Set as set
from cgi import escape
flags_dir = os.path.join(paths.VAR, 'flags') function qsort(A, left, right, i, last) {
sys.stdin = codecs.getreader('utf-8')(sys.stdin) if (left >= right)
return
swap(A, left, left+int((right-left+1)*rand()))
last = left
for (i = left+1; i <= right; i++)
if (A[i] < A[left])
swap(A, ++last, i)
swap(A, left, last)
qsort(A, left, last-1)
qsort(A, last+1, right)
}
function swap(A, i, j, t) {
t = A[i]; A[i] = A[j]; A[j] = t
}
def incdict(dict, key, amt=1): function escape(s) {
dict[key] = dict.get(key, 0) + amt gsub("&", "&amp;", s)
gsub("<", "&lt;", s)
gsub(">", "&gt;", s)
return s
}
class Chart: function print_bar(cat, team, n, d) {
def __init__(self): printf("<div class=\"t%s score\"" \
self.points_by_cat = {} " style=\"height: %.2f%%;\"" \
self.points_by_cat_team = {} " onmouseover=\"highlight('%s')\"" \
self.high_score = 0.001 " onmouseout=\"restore('%s')\">\n" \
self.teams = set() "<!-- %s --> %s: %s\n" \
self.cats = set() "</div>",
self.log = [] team,
100 * n / d,
team,
team,
cat, escape(names_by_team[team]), n)
}
def add_points(self, when, cat, team, points): function output( t, c) {
self.log.append((when, cat, team, points)) for (t in teams) {
self.teams.add(team) score = 0;
self.cats.add(cat) for (c in points_by_cat) {
incdict(self.points_by_cat, cat, points) if (points_by_cat[c] > 0) {
incdict(self.points_by_cat_team, (cat, team), points) score += points_by_cat_team[c, t] / points_by_cat[c];
}
}
if (score > maxscore) {
maxscore = score
}
if (score > maxscores_by_team[t]) {
maxscores_by_team[t] = score
}
scores_by_team_time[t, lasttime] = score
}
timestamps[tslen++] = lasttime
}
def team_points(self, team): BEGIN {
points = 0 base = ENVIRON["CTF_BASE"]
for cat, tot in self.points_by_cat.items(): if (! base) {
if not tot: base = "/srv/ctf"
continue }
team_points = self.team_points_in_cat(cat, team)
points += team_points / float(tot)
return points
def team_points_in_cat(self, cat, team): # Only display two decimal places
return self.points_by_cat_team.get((cat, team), 0) CONVFMT = "%.2f"
def write_js(self, f): # New point at least every 2.5 minutes
start = self.log[0][0] interval = 150
end = self.log[-1][0] tslen = 0
# Calculate high score while (1 == getline) {
high_score = reduce(max, [self.team_points(t) for t in self.teams]) time = $1
team = $2
cat = $3
points = int($4)
width = end - start if (! start) {
height = high_score * 1.1 start = time
}
f.write('function draw(id) {\n') if (time > (outtime + interval)) {
f.write(' p = new Plot(id, %d, %.3f);\n' % (width, height)) outtime = time
for team in self.teams: output()
f.write(' p.line("#%s",[' % teams.color(team)) }
score = 0 lasttime = time
for when, cat, t, points in self.log:
if t == team:
cat_points = self.points_by_cat[cat]
if not cat_points:
continue
pct = float(points) / cat_points
score += pct
f.write('[%d,%.2f],' % (when - start, score))
f.write(']); // %s\n' % team)
f.write('}')
def make_table(self): teams[team] = team
body = [] points_by_cat[cat] += points
body.append('<table class="scoreboard">') points_by_cat_team[cat, team] += points
body.append('<tr>') }
body.append('<th>Overall</th>')
for cat in self.cats:
points = self.points_by_cat[cat]
if not points:
continue
body.append('<th>')
body.append(' %s (%d)' % (cat, points))
try:
fn = os.path.join(flags_dir, cat)
team = open(fn).read().strip() or teams.house
body.append(' <br/>')
body.append(' <span style="color: #%s" title="flag holder">' % teams.color(team))
body.append(' <!-- flag: %s --> %s\n' % (cat, escape(team[:15])))
body.append(' </span>')
except IOError:
pass
body.append('</th>')
body.append('</tr>')
body.append('<tr>') output()
body.append('<td><ol>')
totals = []
for team in self.teams:
total = self.team_points(team)
totals.append((total, team))
totals.sort()
totals.reverse()
for total, team in totals:
if total < 0.1:
break
body.append('<li><span style="color: #%s;">%s (%0.3f)</span></li>'
% (teams.color(team), escape(team[:15]), total))
body.append('</ol></td>')
for cat in self.cats:
total = self.points_by_cat[cat]
if not total:
continue
body.append('<td>')
scores = sorted([(self.team_points_in_cat(cat, team), team) for team in self.teams])
for score, team in scores:
if not score:
continue
color = teams.color(team)
body.append('<div style="height: %f%%; overflow: hidden; background: #%s; color: black;">' % (float(score * 100)/total, color))
body.append('<!-- category: %s --> %s: %d' % (cat, escape(team[:15]), score))
body.append('</div>')
body.append('</td>')
body.append('</tr>')
body.append('</table>')
return '\n'.join(body) # Get team colors and names
for (team in teams) {
fn = base "/teams/colors/" team
getline colors_by_team[team] < fn
close(fn)
def main(): fn = base "/teams/names/" team
p = optparse.OptionParser(usage='%prog [options] < logfile') getline names_by_team[team] < fn
p.add_option('-t', '--html', dest='html', default=None, close(fn)
help='Write a web page to HTML') }
p.add_option('-j', '--javascript', dest='js', default=None,
help='Write javascript params to JS')
opts, args = p.parse_args() # Sort categories
if args: ncats = 0
return p.print_help() for (cat in points_by_cat) {
cats[ncats++] = cat
}
qsort(cats, 0, ncats-1)
chart = Chart() # Create a sorted list of scores
for line in sys.stdin: nteams = 0
line = line.strip() for (team in teams) {
try: scores[nteams++] = scores_by_team_time[team, lasttime]
date, qcat, qteam, points = line.split('\t') }
except ValueError: qsort(scores, 0, nteams-1)
print 'Possible line corruption: %s' % (repr(line)[:40])
cat = unquote(qcat)
team = unquote(qteam)
when = time.strptime(date, '%Y-%m-%dT%H:%M:%S')
chart.add_points(time.mktime(when),
cat,
team,
int(points))
if opts.html:
hdr = ('<script type="application/javascript" src="plot.js"></script>'
'<script type="application/javascript" src="myplot.js"></script>'
'<meta http-equiv="refresh" content="60" />')
body = chart.make_table()
body += '\n<canvas id="history"></canvas>'
html.write(opts.html,
'Scoreboard',
body,
hdr=hdr,
body_class='wide',
onload="draw('history')")
if opts.js:
f = open(opts.js, 'w', encoding='utf-8')
chart.write_js(f)
if __name__ == '__main__': # Now we can start writing the document
main() print "<!DOCTYPE html>"
print "<html>"
print " <head>"
print " <title>Scoreboard</title>"
print " <link rel=\"stylesheet\" href=\"ctf.css\" type=\"text/css\">"
print " <script type=\"application/javascript\" src=\"scoreboard.js\"></script>"
# Provide raw data for the chart
print " <script type=\"application/javascript\">"
print "function init() {"
printf(" plot(\"chart\", %d, %.2f, {\n", tslen, maxscore)
c = 0
for (team in teams) {
if (maxscores_by_team[team] / maxscore < 0.01) continue
printf(" \"%s\": [\"#%s\",[", team, colors_by_team[team])
for (i = 1; i < tslen; i += 1) {
time = timestamps[i]
printf("[%d,%.2f],",
i, scores_by_team_time[team, time])
}
printf("]],\n");
}
print " });"
print " if (location.hash) {"
print " cycle();"
print " setInterval(cycle, 10000);"
print " }"
print "}"
print "window.onload = init;"
print " </script>"
# Reload every minute
print " <meta http-equiv=\"refresh\" content=\"60\">"
# Set up team colors and a few page-specific styles
print " <style type=\"text/css\">"
print " body { width: 100%; }"
print " .score { overflow: hidden; color: black; }"
for (team in teams) {
printf(" .t%s { background-color: #%s; }\n",
team, colors_by_team[team])
}
print " </style>"
print " </head>"
print " <body>"
print " <h1>Scoreboard</h1>"
print "<p id=\"debug\"></p>"
print " <table id=\"scoreboard\">"
print " <tr>"
print " <th>Overall</th>"
# Print out category names
for (i = 0; i < ncats; i += 1) {
cat = cats[i]
points = points_by_cat[cat]
if (0 == points) continue
printf("<th>%s (%d)</th>\n", cat, points)
}
print " </tr>"
print " <tr>"
# Print out teams, ranked by score
print " <td>"
for (i = 0; i < nteams; i += 1) {
if (scores[i] == scores[i-1]) continue;
for (team in teams) {
if (scores[i] == scores_by_team_time[team, lasttime]) {
name = names_by_team[team]
print_bar("total", team, scores[i], ncats)
}
}
}
print " </td>"
# Print out scores within each category
for (i = 0; i < ncats; i += 1) {
cat = cats[i]
points = points_by_cat[cat]
if (0 == points) break;
print "<td>"
# Create sorted list of scores in this category
n = 0
for (team in teams) {
l[n++] = points_by_cat_team[cat, team];
}
qsort(l, 0, n-1)
# Print out teams, ranked by points
for (j = 0; j < n; j += 1) {
if (l[j] == l[j-1]) continue;
if (0 == l[j]) break;
for (team in teams) {
points = points_by_cat_team[cat, team]
if (l[j] == points) {
name = names_by_team[team]
print_bar(cat, team, points, points_by_cat[cat])
}
}
}
print "</td>"
}
print " </tr>"
print " </table>"
print " <canvas id=\"chart\" width=\"800\" height=\"400\"></canvas>"
print " </body>"
print "</html>"
}

View File

@ -1,16 +0,0 @@
MDWN_DIR = mdwn
MDWN_SRC += $(wildcard $(MDWN_DIR)/src/*.mdwn)
MDWN_SRC += $(wildcard $(MDWN_DIR)/src/*/*.mdwn)
MDWN_SRC += $(wildcard $(MDWN_DIR)/src/*/*/*.mdwn)
MDWN_OUT = $(subst $(MDWN_DIR)/src/, $(WWW)/, $(MDWN_SRC:.mdwn=.html))
mdwn-install: $(MDWN_OUT)
$(WWW)/%.html: $(MDWN_DIR)/src/%.mdwn
install -d $(@D)
$(MDWNTOHTML) $< $@
mdwn-clean:
rm -f $(MDWN_OUT)

View File

@ -1,15 +0,0 @@
Title: Welcome
Welcome to Caf&eacute; Scientifique Capture The Flag.
1. [Register](register.cgi) your team
2. [View the score board](scoreboard.html)
3. Check out the [base conversion table](table.html)
4. Start playing
The following categories exist for this version of CTF:
* [Sequence](puzzler.cgi?c=sequence)
* [Code Breaking](puzzler.cgi?c=codebreaking)
* [Webapp](puzzler.cgi?c=webapp)
* [Tanks](tanks/results.html)

View File

@ -1,103 +0,0 @@
Title: Introduction
Welcome to Capture The Flag.
What This Is
============
* A hacking contest
* A chance to experience the nature of cyber incident response
* An environment to safely experiment with offensive techniques
What This Is Not
================
* An arena for purely malicious attacks
* A rave
Rules
=====
Important Rules
---------------
* The contest network is 10.<i>x</i>.<i>x</i>.<i>x</i>. **Do
not attack machines outside the contest network**. All
federal, state, and school laws still apply to the outside
network.
* If the "outside network" requires you to plug into a different
switch, do not connect any machine that has been on the contest
network.
* Consider this network hostile: your machine may be
compromised.
* We expect you to be disruptive within the framework of the
game (malicious code, network scanning, social engineering,
etc.). Disruptive behavior outside the game will result in a
public and humiliating ejection from the contest area.
* No ARP attacks. While cute, they are not particularly clever
given our network topology, and would require expensive and
bulky equipment to prevent. Find something else to do.
Less-Important Rules
--------------------
* If IRC is up, you should use it to communicate with the
contest staff. Staff will have operator status in #ctf.
* If you think something is wrong with the game, you are
expected to demonstrate the problem and explain what you think
is the correct behavior.
Scoring
=======
The contest is made up of multiple categories. Each category is worth
one point toward the total score; the percentage of the total points
held by your team is the percentage of one point your team has for that
category. The team that has 30% of the points in each of five
categories has 1.5 points, whereas the team that has 80% of the points
in only one category has 0.8 points. It is typically better to have a
few points in many categories, than many points in a few categories.
There are two kinds of categories: *flags* and *puzzles*.
Flags
-----
Flag categories are challenges with a notion of a *winner* or *service
availability*. In these categories, the flag-holder (the winner, or
each team with a running service) makes 1 point per minute for as long
as they hold the flag. If there is a single flag-holder, and the flag
changes hands, a point is awarded to the new winner at the moment the
flag moves.
Puzzles
-------
Most of the categories come in the form of multiple *puzzles*: for each
puzzle presented, a key (answer) must be found to recieve the amount of
points that puzzle is worth. Any team may answer any puzzle question at
any time. A new puzzle is revealed when a team correctly answers the
highest-valued puzzle in that category.
Hints
=====
If you are really stuck, you can ask for a hint. It will cost you
points, though. For puzzles, you will lose ¼ of the points for that
puzzle <em>even if you never solve the puzzle</em>. For other events,
the staff member will decide how many points it will cost. You can try
to bribe or otherwise fanagle information out of us or other
contestants. *It's a hacking contest.*
About Us
========
We are the <a href="http://dirtbags.net/">dirtbags</a>. People pay us
money to do the sorts of things you'll be doing in this contest.

View File

@ -1,161 +0,0 @@
Title: Counting in different bases
<table style="background-color: #444;">
<tr><th>Base</th></tr>
<tr>
<th>2</th>
<td>0</td>
<td>1</td>
<td>10</td>
<td>11</td>
<td>100</td>
<td>101</td>
<td>110</td>
<td>111</td>
<td>1000</td>
<td>1001</td>
<td>1010</td>
<td>1011</td>
<td>1100</td>
<td>1101</td>
<td>1110</td>
<td>1111</td>
<td>10000</td>
<td>10001</td>
<td>10010</td>
<td>10011</td>
<td>10100</td>
<td>10101</td>
<td>10110</td>
<td>10111</td>
<td>11000</td>
<td>11001</td>
<td>11010</td>
<td>11011</td>
<td>11100</td>
<td>11101</td>
<td>11110</td>
<td>11111</td>
<td>100000</td>
<td>100001</td>
<td>100010</td>
<td>100011</td>
</tr>
<tr>
<th>8</th>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>10</td>
<td>11</td>
<td>12</td>
<td>13</td>
<td>14</td>
<td>15</td>
<td>16</td>
<td>17</td>
<td>20</td>
<td>21</td>
<td>22</td>
<td>23</td>
<td>24</td>
<td>25</td>
<td>26</td>
<td>27</td>
<td>30</td>
<td>31</td>
<td>32</td>
<td>33</td>
<td>34</td>
<td>35</td>
<td>36</td>
<td>37</td>
<td>40</td>
<td>41</td>
<td>42</td>
<td>43</td>
</tr>
<tr>
<th>10</th>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td>10</td>
<td>11</td>
<td>12</td>
<td>13</td>
<td>14</td>
<td>15</td>
<td>16</td>
<td>17</td>
<td>18</td>
<td>19</td>
<td>20</td>
<td>21</td>
<td>22</td>
<td>23</td>
<td>24</td>
<td>25</td>
<td>26</td>
<td>27</td>
<td>28</td>
<td>29</td>
<td>30</td>
<td>31</td>
<td>32</td>
<td>33</td>
<td>34</td>
<td>35</td>
</tr>
<tr>
<th>16</th>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>5</td>
<td>6</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td>a</td>
<td>b</td>
<td>c</td>
<td>d</td>
<td>e</td>
<td>f</td>
<td>10</td>
<td>11</td>
<td>12</td>
<td>13</td>
<td>14</td>
<td>15</td>
<td>16</td>
<td>17</td>
<td>18</td>
<td>19</td>
<td>1a</td>
<td>1b</td>
<td>1c</td>
<td>1d</td>
<td>1e</td>
<td>1f</td>
<td>20</td>
<td>21</td>
<td>22</td>
<td>23</td>
</tr>
</table>

View File

@ -1,332 +0,0 @@
Title: Tanks Documentation
Introduction
============
You are the proud new operator of a M-375 Pflanzarr Tank. Your tank is
equipped with a powerful laser cannon, independently rotating turret
section, up to 10 enemy detection sensors, and a standard issue NATO
hull. Unfortunately, it lacks seats, and thus must rely own its own
wits and your skills at designing those wits to survive.
An Example Tank
===============
You can to paste this tank code into the [submit page](submit.html) page
to get started. Then, start changing things to see how it affects your
tank's behavior.
>addsensor(50, 0, 5, 1); # Sensor 0: Fire Sensor
>addsensor(30, 0, 50); # Sensor 1: Anti-collision sensor
# Commands
: move(90, 100) . turretset(0); # Always do this
sense(0) : fire(); # If sensor 0 is active, fire
sense(1) : move(-100, 100) # If sensor 1 is active, turn
Programming Your Tank
=====================
Your tanks are programmed using the Super Useful Command and Kontrol
language, the very best in laser tank AI languages. It includes amazing
features such as comments (Started by a #, ended at EOL), logic,
versatility, and semi-colons (all lines must end in one). As with all
new military systems it utilizes only integers; we must never rest in
our diligence against the communist floating point conspiracy.
Whitespace is provided by trusted contractors, and should never
interfere with operations.
Your program should be separated into Setup and AI commands. The
definitions section lets you designated the behaviors of its
sensors and memory. Each setup command must begin with a
'>'. Placing setup commands after the first AI command is a
violation of protocol. Here are some examples of correct setup
commands:
>addsensor(80, 90, 33);
>addsensor(50, 0, 10, 1);
>addtimer(3);
The AI section will act as the brain of your tank. Each AI line
is separated into a group of conditions functions and a group of
action functions. If all the conditions are satisfactory (true),
all of the actions are given as orders. Conditions are separated
by ampersands, actions separated by periods. Here are some
examples of AI commands:
sense(1) & sense(2) & fireready() : fire();
sense(0,0)&sin(5): move(40, 30) . turretcw(50);
sense(4) & random(4,5) : led(1).settoggle(0,1);
Your tank will execute its program each turn(frame), and attempt
to the best of its abilities to carry out its orders (or die
trying). Like any military mind, your tank may receive a plethora
of often conflicting orders and information. This a SMART TANK,
however. It knows that the proper thing to do with each subsystem
is to have that subsystem follow only the last order given each
turn.
#Setup commands define your tank when your program
#compiles
>addsensor(50, 0, 5, 1); # 0-Fire Sensor
>addsensor(30, 0, 180); # 1-Anti-collision sensor
# These commands execute each frame.
# Blank condition sections are true.
: move(90, 100).
turretset(0);
sense(0) : fire();
sense(1) : move(-100, 100)
Setup Actions
-------------
These functions can be used to setup your tank. Abuse of these
functions has, in the past, resulted in mine sweeping duty. With
a broom.
<dl>
<dt>addsensor(range, angle, width, [turretAttached])</dt>
<dd>
<p>Add a new sensor to the tank.</p>
<p>
Sensors are an arc (pie slice) centered on the tank that
detect other tanks within their sweep.<br/>
A sensor is 'on' if another tank is within this arc.
</p>
<p>
Sensors are numbered, starting at 0, in the order they are
added.
</p>
<p>
range - The range of the sensor, as a percent of the tanks max
range.<br/>
angle - The angle of the center of the sensor, in degrees.<br />
width - The width of the sensor, in degrees.<br />
turretAttached - Normally, the angle is relative to the front of
the
tank.<br /> When this is set, the angle is relative to the current
turret
direction.<br />
</p>
</dd>
<dt>addtimer(timeout)</dt>
<dd>
<p>
Add a new timer (they're numbered in the order added, starting from 0),
with the given timeout.
</p>
<p>
The timeout is in number of turns.<br />
The timer
is created in inactive mode.<br /> You'll need to do a starttimer()
action
to reset and start the timer.<br /> When the timer expires, the
timer()
condition will begin to return True.
</p>
</dd>
<dt>addtoggle([state])</dt>
<dd>
<p>Add a toggle to the tank.</p>
<p>
The state of the toggle defaults to 0 (False).<br />
ese essentially act as a single bit of memory.<br />
e the toggle() condition to check its state and the settoggle,
eartoggle,
d toggle actions to change the state.<br /> Toggles are named
merically,
arting at 0.
</p>
</dd>
</dl>
Conditions
----------
These functions are used to check the state of reality. If reality
stops being real, refer to chapter 5 in your girl scout handbook.
<dl>
<dt>cos(T)</dt>
<dd>
<p>
A cos wave with period T (in turns).
</p>
<p>
Returns True when the wave is
positive.<br /> A wave of period 1 is always True.<br /> Period
2 is True every
other turn, etc.
</p>
</dd>
<dt>firenotready()</dt>
<dd>
<p>
True when the tank can not fire.
</p>
</dd>
<dt>fireready()</dt>
<dd>
<p>
True when the tank can fire.
</p>
</dd>
<dt>random(n,m)</dt>
<dd>
<p>Generate a random number.</p>
<p>
Takes two
arguments, n and m.<br /> Generates a random number between 1
and m (inclusive) each time it's checked.<br /> If the random
number is less
than or equal
to n, then the condition returns True.<br /> Returns False
otherwise.
</p>
</dd>
<dt>sense(#, [invert])</dt>
<dd>
<p>True when a sensor is activated.</p>
<p>
Takes a Sensor number as an argument.<br />
Returns True if the given sensor is currently activated, False
otherwise.<br />
If the option argument invert is set to true then logic is
inverted,
and then sensor returns True when it is NOT activated, and
False when
it is.<br /> Invert is false by default.
</p>
</dd>
<dt>sin(T)</dt>
<dd>
<p>A sin wave of period T (in turns).</p>
<p>
Returns True when the wave is positive.<br />
A wave with period 1 or 2 is always False (it's 0 each turn),
only
at periods of 3 or more does this become useful.
</p>
</dd>
<dt>timer(#, [invert])</dt>
<dd>
<p>Checks the state of timer # 'key'.</p>
<p>
Returns True if time has run
out.<br />
If invert is given (and true), then True is returned if the
timer has
yet to expire.
</p>
</dd>
<dt>toggle(#)</dt>
<dd>
<p>Returns True if the given toggle is set, False
otherwise.</p>
</dd>
</dl>
Actions
-------
These actions are not for cowards. Remember, if actions contradict,
your tank will simply do the last thing it was told in a turn. If
ordered to hop on a plane to hell it will gladly do so. If order to
make tea shortly afterwards, it will serve it politely and with cookies
instead.
<dl>
<dt>cleartimer(#)</dt>
<dd>
<p>Clear the given timer such that it is no longer active (inactive timers
are always False).</p>
</dd>
<dt>fire()</dt>
<dd>
<p>Attempt to fire the tanks laser cannon.</p>
<p>
Its range is 50% of your sensor range.
</p>
</dd>
<dt>led(state)</dt>
<dd>
<p>Set the tank's LED to state (true is on, false is off).</p>
<p>
The led is a light that appears behind the tanks turret.<br/>
It remains on for a single turn.
</p>
</dd>
<dt>move(left tread speed, right tread speed)</dt>
<dd>
<p>Set the speeds for the tanks right and left treads.</p>
<p>
The speeds should
be a number (percent power) between -100 and
100.
</p>
</dd>
<dt>settoggle(key, state)</dt>
<dd>
<p>Set toggle 'key' to 'state'.</p>
</dd>
<dt>starttimer(#)</dt>
<dd>
<p>Start (and reset) the given timer, but only if it is
inactive.</p>
</dd>
<dt>toggle(key)</dt>
<dd>
<p>Toggle the value of toggle 'key'.</p>
</dd>
<dt>turretccw([percent speed])</dt>
<dd>
<p>Rotate the turret counter clockwise as a
percentage of the max speed.</p>
</dd>
<dt>turretcw([percent speed])</dt>
<dd>
<p>Rotate the turret clockwise at a rate
preportional to speed.</p>
</dd>
<dt>turretset([angle])</dt>
<dd>
<p>Set the turret to the given angle, in degrees, relative to the
front of the tank.</p>
<p>
Angles increase counterclockwise.<br/> The angle can be left
out; in that case, this locks the turret to its current
position.
</p>
</dd>
</dl>

View File

@ -1,10 +0,0 @@
<h3>Tanks</h3>
<ul>
<li><a href="docs.html">Docs</a></li>
<li><a href="results.cgi">Results</a></li>
<li><a href="submit.html">Submit</a></li>
<li><a href="errors.cgi">My Errors</a></li>
</ul>

View File

@ -1,14 +0,0 @@
Title: Tanks Submission
<form action="submit.cgi" method="post">
<fieldset>
<legend>Your program:</legend>
Team:
<input type="text" name="team"/><br/>
Password:
<input type="password" name="passwd"/><br/>
<textarea cols="80" rows="30" name="code"></textarea><br/>
<button type="submit">Submit</button>
</fieldset>
</form>

View File

@ -1,6 +1,8 @@
TARGETS = in.tokend pointscli claim.cgi puzzler.cgi puzzles.cgi TARGETS = in.tokend pointscli claim.cgi puzzler.cgi puzzles.cgi
all: $(TARGETS) all: build
build: $(TARGETS)
in.tokend: in.tokend.o xxtea.o common.o in.tokend: in.tokend.o xxtea.o common.o
pointscli: pointscli.o common.o pointscli: pointscli.o common.o

View File

@ -1,240 +0,0 @@
#! /usr/bin/awk -f
##
##
## I'm not happy with how this code looks. I've
##
##
function qsort(A, left, right, i, last) {
if (left >= right)
return
swap(A, left, left+int((right-left+1)*rand()))
last = left
for (i = left+1; i <= right; i++)
if (A[i] < A[left])
swap(A, ++last, i)
swap(A, left, last)
qsort(A, left, last-1)
qsort(A, last+1, right)
}
function swap(A, i, j, t) {
t = A[i]; A[i] = A[j]; A[j] = t
}
function escape(s) {
gsub("&", "&amp;", s)
gsub("<", "&lt;", s)
gsub(">", "&gt;", s)
return s
}
function print_bar(cat, team, n, d) {
printf("<div class=\"t%s score\"" \
" style=\"height: %.2f%%;\"" \
" onmouseover=\"highlight('%s')\"" \
" onmouseout=\"restore('%s')\">\n" \
"<!-- %s --> %s: %s\n" \
"</div>",
team,
100 * n / d,
team,
team,
cat, escape(names_by_team[team]), n)
}
function output( t, c) {
for (t in teams) {
score = 0;
for (c in points_by_cat) {
if (points_by_cat[c] > 0) {
score += points_by_cat_team[c, t] / points_by_cat[c];
}
}
if (score > maxscore) {
maxscore = score
}
if (score > maxscores_by_team[t]) {
maxscores_by_team[t] = score
}
scores_by_team_time[t, lasttime] = score
}
timestamps[tslen++] = lasttime
}
BEGIN {
base = ENVIRON["CTF_BASE"]
if (! base) {
base = "/srv/ctf"
}
# Only display two decimal places
CONVFMT = "%.2f"
# New point at least every 2.5 minutes
interval = 150
tslen = 0
while (1 == getline) {
time = $1
team = $2
cat = $3
points = int($4)
if (! start) {
start = time
}
if (time > (outtime + interval)) {
outtime = time
output()
}
lasttime = time
teams[team] = team
points_by_cat[cat] += points
points_by_cat_team[cat, team] += points
}
output()
# Get team colors and names
for (team in teams) {
fn = base "/teams/colors/" team
getline colors_by_team[team] < fn
close(fn)
fn = base "/teams/names/" team
getline names_by_team[team] < fn
close(fn)
}
# Sort categories
ncats = 0
for (cat in points_by_cat) {
cats[ncats++] = cat
}
qsort(cats, 0, ncats-1)
# Create a sorted list of scores
nteams = 0
for (team in teams) {
scores[nteams++] = scores_by_team_time[team, lasttime]
}
qsort(scores, 0, nteams-1)
# Now we can start writing the document
print "<!DOCTYPE html>"
print "<html>"
print " <head>"
print " <title>Scoreboard</title>"
print " <link rel=\"stylesheet\" href=\"ctf.css\" type=\"text/css\">"
print " <script type=\"application/javascript\" src=\"scoreboard.js\"></script>"
# Provide raw data for the chart
print " <script type=\"application/javascript\">"
print "function init() {"
printf(" plot(\"chart\", %d, %.2f, {\n", tslen, maxscore)
c = 0
for (team in teams) {
if (maxscores_by_team[team] / maxscore < 0.01) continue
printf(" \"%s\": [\"#%s\",[", team, colors_by_team[team])
for (i = 1; i < tslen; i += 1) {
time = timestamps[i]
printf("[%d,%.2f],",
i, scores_by_team_time[team, time])
}
printf("]],\n");
}
print " });"
print " if (location.hash) {"
print " cycle();"
print " setInterval(cycle, 10000);"
print " }"
print "}"
print "window.onload = init;"
print " </script>"
# Reload every minute
print " <meta http-equiv=\"refresh\" content=\"60\">"
# Set up team colors and a few page-specific styles
print " <style type=\"text/css\">"
print " body { width: 100%; }"
print " .score { overflow: hidden; color: black; }"
for (team in teams) {
printf(" .t%s { background-color: #%s; }\n",
team, colors_by_team[team])
}
print " </style>"
print " </head>"
print " <body>"
print " <h1>Scoreboard</h1>"
print "<p id=\"debug\"></p>"
print " <table id=\"scoreboard\">"
print " <tr>"
print " <th>Overall</th>"
# Print out category names
for (i = 0; i < ncats; i += 1) {
cat = cats[i]
points = points_by_cat[cat]
if (0 == points) continue
printf("<th>%s (%d)</th>\n", cat, points)
}
print " </tr>"
print " <tr>"
# Print out teams, ranked by score
print " <td>"
for (i = 0; i < nteams; i += 1) {
if (scores[i] == scores[i-1]) continue;
for (team in teams) {
if (scores[i] == scores_by_team_time[team, lasttime]) {
name = names_by_team[team]
print_bar("total", team, scores[i], ncats)
}
}
}
print " </td>"
# Print out scores within each category
for (i = 0; i < ncats; i += 1) {
cat = cats[i]
points = points_by_cat[cat]
if (0 == points) break;
print "<td>"
# Create sorted list of scores in this category
n = 0
for (team in teams) {
l[n++] = points_by_cat_team[cat, team];
}
qsort(l, 0, n-1)
# Print out teams, ranked by points
for (j = 0; j < n; j += 1) {
if (l[j] == l[j-1]) continue;
if (0 == l[j]) break;
for (team in teams) {
points = points_by_cat_team[cat, team]
if (l[j] == points) {
name = names_by_team[team]
print_bar(cat, team, points, points_by_cat[cat])
}
}
}
print "</td>"
}
print " </tr>"
print " </table>"
print " <canvas id=\"chart\" width=\"800\" height=\"400\"></canvas>"
print " </body>"
print "</html>"
}

2
src/src.mk Normal file
View File

@ -0,0 +1,2 @@
src-%:
$(MAKE) -C src $*

View File

@ -1,7 +1,7 @@
#! /bin/sh -e #! /bin/sh -e
die () { die () {
echo $* echo "$*"
exit 1 exit 1
} }
@ -29,7 +29,7 @@ done
mkdir -p $CTF_BASE/teams/names mkdir -p $CTF_BASE/teams/names
mkdir -p $CTF_BASE/teams/colors mkdir -p $CTF_BASE/teams/colors
for team in team1 team2 team3; do for team in team1 team2 team3; do
hash=$(./register $team | awk '{print $NF;}') hash=$(bin/register $team | awk '{print $NF;}')
done done
@ -37,35 +37,35 @@ done
## Puzzler tests ## Puzzler tests
## ##
if ./puzzles.cgi | grep 20; then if src/puzzles.cgi | grep 20; then
die "20 points puzzles shouldn't show up here" die "20 points puzzles shouldn't show up here"
fi fi
if ./puzzler.cgi t=$hash c=cat1 p=10 a=cat1answer20 | grep -q 'awarded'; then if src/puzzler.cgi t=$hash c=cat1 p=10 a=cat1answer20 | grep -q 'awarded'; then
die "Awarded points with wrong answer" die "Awarded points with wrong answer"
fi fi
if ./puzzler.cgi t=$hash c=cat2 p=10 a=cat1answer10 | grep -q 'awarded'; then if src/puzzler.cgi t=$hash c=cat2 p=10 a=cat1answer10 | grep -q 'awarded'; then
die "Awarded points with wrong category" die "Awarded points with wrong category"
fi fi
if ./puzzler.cgi t=$hash c=cat1 p=20 a=cat1answer10 | grep -q 'awarded'; then if src/puzzler.cgi t=$hash c=cat1 p=20 a=cat1answer10 | grep -q 'awarded'; then
die "Awarded points with wrong point value" die "Awarded points with wrong point value"
fi fi
if ./puzzler.cgi t=merfmerfmerfmerf c=cat2 p=10 a=cat1answer10 | grep -q 'awarded'; then if src/puzzler.cgi t=merfmerfmerfmerf c=cat2 p=10 a=cat1answer10 | grep -q 'awarded'; then
die "Awarded points with bad team" die "Awarded points with bad team"
fi fi
if ! ./puzzler.cgi t=$hash c=cat1 p=10 a=cat1answer10 | grep -q 'awarded 10'; then if ! src/puzzler.cgi t=$hash c=cat1 p=10 a=cat1answer10 | grep -q 'awarded 10'; then
die "Didn't award points for correct answer" die "Didn't award points for correct answer"
fi fi
if ! ./puzzles.cgi | grep -q 20; then if ! src/puzzles.cgi | grep -q 20; then
die "20 point answer didn't show up" die "20 point answer didn't show up"
fi fi
if ./puzzler.cgi t=$hash c=cat1 p=10 a=cat1answer10 | grep -q 'awarded 10'; then if src/puzzler.cgi t=$hash c=cat1 p=10 a=cat1answer10 | grep -q 'awarded 10'; then
die "Awarded same points twice" die "Awarded same points twice"
fi fi
@ -73,11 +73,11 @@ fi
## Scoreboard tests ## Scoreboard tests
## ##
if ! cat $CTF_BASE/points.new/* | ./scoreboard | grep -q 'total.*team3: 1'; then if ! cat $CTF_BASE/points.new/* | bin/scoreboard | grep -q 'total.*team3: 1'; then
die "Scoreboard total incorrect" die "Scoreboard total incorrect"
fi fi
if ! cat $CTF_BASE/points.new/* | ./scoreboard | grep -q 'cat1.*team3: 10'; then if ! cat $CTF_BASE/points.new/* | bin/scoreboard | grep -q 'cat1.*team3: 10'; then
die "Scoreboard cat1 points incorrect" die "Scoreboard cat1 points incorrect"
fi fi
@ -89,25 +89,25 @@ mkdir -p $CTF_BASE/token.keys
echo -n '0123456789abcdef' > $CTF_BASE/token.keys/tokencat echo -n '0123456789abcdef' > $CTF_BASE/token.keys/tokencat
# in.tokend uses a random number generator # in.tokend uses a random number generator
echo -n 'tokencat' | ./in.tokend > /dev/null echo -n 'tokencat' | src/in.tokend > /dev/null
if ! grep -q 'tokencat:x....-....x' $CTF_BASE/tokens.db; then if ! grep -q 'tokencat:x....-....x' $CTF_BASE/tokens.db; then
die "in.tokend didn't write to database" die "in.tokend didn't write to database"
fi fi
if ./claim.cgi t=lalalala k=$(cat $CTF_BASE/tokens.db) | grep -q success; then if src/claim.cgi t=lalalala k=$(cat $CTF_BASE/tokens.db) | grep -q success; then
die "claim.cgi gave points to a bogus team" die "claim.cgi gave points to a bogus team"
fi fi
if ./claim.cgi t=$hash k=tokencat:xanax-xanax | grep -q success; then if src/claim.cgi t=$hash k=tokencat:xanax-xanax | grep -q success; then
die "claim.cgi gave points for a bogus token" die "claim.cgi gave points for a bogus token"
fi fi
if ! ./claim.cgi t=$hash k=$(cat $CTF_BASE/tokens.db) | grep -q success; then if ! src/claim.cgi t=$hash k=$(cat $CTF_BASE/tokens.db) | grep -q success; then
die "claim.cgi didn't give me any points" die "claim.cgi didn't give me any points"
fi fi
if ./claim.cgi t=$hash k=$(cat $CTF_BASE/tokens.db) | grep -q success; then if src/claim.cgi t=$hash k=$(cat $CTF_BASE/tokens.db) | grep -q success; then
die "claim.cgi gave me points twice for the same token" die "claim.cgi gave me points twice for the same token"
fi fi
@ -115,5 +115,4 @@ if ! [ -f $CTF_BASE/points.new/*.$hash.tokencat.1 ]; then
die "claim.cgi didn't actually record any points" die "claim.cgi didn't actually record any points"
fi fi
echo "$0: All tests passed!" echo "All tests passed! You're the best programmer ever!"
echo "$0: Aren't you just the best programmer ever?"