ctf-2011
old assets from capture-the-flag ictf 2011
git clone https://9o.is/git/ctf-2011.git
riskasses.py~
(27080B)
1 #!/usr/bin/env python
2 import socket
3 import threading
4 import SocketServer
5 from datetime import datetime
6 import re
7 import pickle
8 import time
9 import json
10 from termcolor import colored, cprint
11 from pprint import pprint as pp
12 import ipaddr
13 import threading
14
15 class ictf:
16 # Risk assesment and money laundering library based on
17 # http://ictf.cs.ucsb.edu/iCTF_2011_Description.txt
18 #
19 # Missing Information, that will be provided at beginning of iCTF2011:
20
21 # - Regular expression for flags
22 #flag_re = r'[A-Fa-f0-9]{40}'
23 flag_re = r'FOO|BAR|BAZ'
24
25 # - ID of this team
26 my_id = "A-team"
27
28 # - Overall risk function
29 def risk_function(self, R, M, N, Q):
30 ''' Calculates the risk, using the following input:
31 R: Risk associated with service (float, probability, <= 1)
32 M: Amount of money to launder
33 N: Amount of money that has been laundered through that team by us
34 Q: Amount of money that has been laundered through that svc by us
35
36 Must return a probability (float smaller/equal 1.0)'''
37
38 # TODO
39 return 0.5
40
41 # - Submit launder requests
42 def _launder_request(self, amount, flag):
43 '''Must return a tuple with the following format:
44 (result, points_gained, msg, exploited_team, exploited_svc) with
45 state: "SUCCESS", "BAD_FLAG", "OLD_FLAG", "BAD_LUCK" or
46 "OTHER"
47 points_gained: amount of points gained
48 msg: whatever flugsubmission returned
49 exploited_team, exploited_svc: if msg contains that information,
50 include team/svc id, otherwise None'''
51
52 # TODO
53 return ("OTHER", 0.0, 'not yet implemented', None, None)
54
55 # - Submit betrayal requests
56 def _betrayal_request(self, flag):
57 '''Must return a tuple with the following format:
58 (result, msg, from_team, from_service) with state being:
59 "SUCCESS", "OLD_FLAG", "BAD_FLAG" or "OTHER"
60 and msg whatever flugsubmission returned
61 from_team, from_service if msg contains that information,
62 include team/svc id, otherwise None'''
63
64 # TODO
65 return ("OTHER",'not yet implemented', None, None)
66
67 # THAT'S IT! NOTHING TO DONE BELOW
68
69 # - State Pusher receive port
70 recv_port = 55555
71
72 def __init__(self, my_id=my_id):
73 self.teams = {}
74 # Format:
75 # { "TEAM1_ID": {'money': 123,
76 # 'points': 123,
77 # 'svc_up': ["SVC1_ID",...],
78 # 'svc_comp': ["SVC99_ID",...],
79 # 'N': {"TEAM2_ID": 4, ...},
80 # 'Q': {"SVC1_ID": 6, ...}
81 # 'D': 0.33,
82 # 'subnet': '1.2.3.0/24',
83 # 'challenges_solved': [30, 5391, 305]}
84
85 self.services = {}
86 # Format:
87 # { "SVC1_ID": {'C': 0.33, # cut in Percentage/100
88 # 'P': 0.67, # profit in Percentage/100
89 # 'R': 0.1} # risk in Probability
90
91 self.transactions = []
92 # Format:
93 # [{ 'time_used': datetime.now(), # Time of usage
94 # 'flag': 'fooBAR', # String associated with flag
95 # 'time_stolen': ..., # As found in unused_flags
96 # 'from_team': 'TEAM1_ID', # As found in unused_flags
97 # 'from_service': 'SVC1_ID', # As found in new_flag
98 # 'action': 'laundering', # "betrayal" or "laundering"
99 # 'money_given': 12.3, # if "laundering", otherwise 0.0
100 # 'points_gained': 12.3, # if "laundering" and successfull,
101 # # otherwise 0.0
102 # 'result': "SUCCESS", # "SUCCESS", "BAD_FLAG", "BAD_LUCK",
103 # # "OLD_FLAG" or "OTHER"
104 # 'msg': 'Flag sub. suc.', # Whatever flagsubmission returned
105 # },...]
106
107 self.unused_flags = []
108 # Format:
109 # [{ 'flag': 'fooBAR', # String associated with flag
110 # 'time_received': ..., # Time of reception
111 # 'from_team': 'TEAM1_ID', # Where did it come from?
112 # # if unknown, None
113 # 'from_service': 'SVC1_ID', # Where did it come from?
114 # # if unknown, None
115 # },...]
116
117 self.all_flags = []
118 # List of all flags ever put in unused_flags or transactions
119
120 self.my_id = my_id
121 self.recv_state_socket = None
122 self.recv_state_thread = None
123 self.recv_flag_server = None
124
125 self.flag_dissipater_instance = None
126
127 # Do you want every tick to produce a message?
128 self.show_ticks = True
129 self.tick_callback = self.print_scorboard
130
131 def get_risks(self, amount, service=None, team=None):
132 '''Compiles a list of tuples describing the risk associated in
133 laundering *amount* money through each team and service, except
134 our self.
135
136 Returned list of tuples has the following format:
137 [(team_id, service_id, risk),...]
138
139 If service and team are given, only one risk will be returned.'''
140
141 current_risks = []
142
143 if team and service:
144 N = 0
145 if team in self.my_state['N']:
146 # The next line may needs to be exchanged by the following
147 # t['N'][self.my_id]
148 N = self.my_state['N'][team]
149
150 Q = 0
151 if service in self.my_state['Q']:
152 Q = self.my_state['Q'][service]
153
154 return self.risk_function(self.services[service]['R'], amount, N, Q)
155
156 for t_id,t in self.teams.items():
157 # Ignore myself
158 if t_id == self.my_id:
159 continue
160
161 N = 0
162 if t_id in self.my_state['N']:
163 # The next line may needs to be exchanged by the following
164 # t['N'][self.my_id]
165 N = self.my_state['N'][t_id]
166
167 for s_id,s in self.services.items():
168 Q = 0
169 if s_id in self.my_state['Q']:
170 Q = self.my_state['Q'][s_id]
171
172 current_risks.append(
173 (t_id, s_id, self.risk_function(s['R'], amount, N, Q)))
174
175 return current_risks
176
177 def get_payoffs(self, amount, service=None):
178 '''Gives a quantitative prediction on how good a service would payoff
179 using it to launder *amount* money.
180
181 If *defense* is not set, the quotient will be assumed 1.0 (meaning no
182 defense penalty)
183
184 Returned list of tuples has the following format:
185 [(service_id, payoff_prediction),...]
186
187 If service is given, only that payoff will be returned'''
188
189 if service:
190 s = self.services[service]
191 after_cut = amount*(1.0-s['C']) # cut is given as percentage
192 return after_cut*s['P']*self.my_state['D']
193
194 payoffs = []
195
196 for s_id,s in self.services.items():
197 # Payoff fuction: (amount laundered - cut) * defense% * payoff%
198 after_cut = amount*(1.0-s['C']) # cut is given as percentage
199 payoffs.append((s_id, after_cut*s['P']*self.my_state['D']))
200
201 return payoffs
202
203 def parse_pushed_state(self, json_input):
204 ''' Updates self.teams and self.services
205 For format of members, see _init__()
206 and for json see: http://ictf.cs.ucsb.edu/your_pusher.txt
207
208 Be careful to create new teams and services as they appear!'''
209 for json_line in json_input.split('\n'):
210 try:
211 state = json.loads(json_line)
212 assert type(state) == dict, 'Has to be a dict'
213 except Exception, err:
214 if json_input.strip():
215 print 'Could not parse json input line:',json_line
216 print 'Error:',err
217 return
218
219 self.tick = state['tick']
220
221 for s_id,s in state['services'].items():
222 # Parse
223 s['C'] = s['C']/100.0
224 s['P'] = s['P']/100.0
225 s['R'] = s['R']/100.0
226
227 # Create service if necessary
228 if s_id not in self.services:
229 self.services[s_id] = s
230 else:
231 # Or just update
232 self.services[s_id].update(s)
233
234 for t_id,t in state['teams'].items():
235 # Parse
236 t['D'] = t['D']/100.0
237
238 # Subnet to ipaddr.IPv4Network object
239 t['subnet'] = ipaddr.IPv4Network(t['subnet'])
240
241 # N, Q, money, points and challenges_solved are fine like
242 # they are, nothing to do
243
244 # Rename services_compromised_last_tick -> svc_up
245 t['svc_up'] = t['services_compromised_last_tick']
246 del t['services_compromised_last_tick']
247 # and services_up_last_tick -> svc_comp
248 t['svc_comp'] = t['services_up_last_tick']
249 del t['services_up_last_tick']
250
251 # Create team if necessary
252 if t_id not in self.teams:
253 self.teams[t_id] = t
254 else:
255 # Or just update
256 self.teams[t_id].update(t)
257
258 if self.show_ticks:
259 print 'New tick:',self.tick
260 if self.tick_callback:
261 self.tick_callback()
262
263 def get_flag(self, flag, remove=False):
264 r = filter(lambda x: x['flag']==flag, self.unused_flags)
265
266 if r:
267 if remove:
268 self.unused_flags.remove(r[0])
269 return r[0]
270 else:
271 return None
272
273 def launder(self, amount, flag_data):
274 '''Tries to launder *amount* money with flag contained in *flag_data*
275 *flag_data* must have same format as in unused_flags, see __init__()
276 and should have been poped (removed) from unused_flags.
277
278 Returns (result, points_gained, msg)'''
279
280 assert amount<=self.teams[self.my_id]['money'], 'Insufficient funds.'
281
282 result, points_gained, msg, from_team, from_service = \
283 self._launder_request(amount, flag_data['flag'])
284
285 # If from_team or from_service were returned on request, use them,
286 # they must be correct
287 if from_team:
288 flag_data['from_team'] = from_team
289 if from_service:
290 flag_data['from_service'] = from_service
291
292 flag_data['time_used'] = datetime.now()
293 flag_data['action'] = 'laundering'
294 flag_data['money_given'] = amount
295 flag_data['points_gained'] = points_gained
296 flag_data['result'] = result
297 flag_data['msg'] = msg
298
299 # Reduce our left-over money
300 if result in ["SUCCESS", "BAD_LUCK"]:
301 self.teams[self.my_id]['money'] -= amount
302 # Own points are not used for anything, thus need no update
303
304 self.transactions.append(flag_data)
305
306 return (result, points_gained, msg)
307
308 def betray(self, flag_data):
309 '''Tries to betray others with flag contained in *flag_data*
310 *flag_data* must have same format as in unused_flags, see __init__()
311 and should have been poped (removed) from unused_flags.
312
313 Returns (result, msg)'''
314
315 result, msg, from_team, from_service = \
316 self._betrayal_request(flag_data['flag'])
317
318 # If from_team or from_service were returned on request, use them,
319 # they must be correct
320 if from_team:
321 flag_data['from_team'] = from_team
322 if from_service:
323 flag_data['from_service'] = from_service
324
325 flag_data['time_used'] = datetime.now()
326 flag_data['action'] = 'betrayal'
327 flag_data['money_given'] = 0.0
328 flag_data['points_gained'] = 0.0
329 flag_data['result'] = result
330 flag_data['msg'] = msg
331
332 self.transactions.append(flag_data)
333
334 return (result, msg)
335
336 def add_new_flag(self, flag, team, service):
337 if flag in self.all_flags:
338 return False
339 self.all_flags.append(flag)
340
341 flag_data = {
342 'flag': flag,
343 'time_received': datetime.now(),
344 'from_team': team,
345 'from_service': service,
346 }
347
348 self.unused_flags.append(flag_data)
349 return True
350
351 @property
352 def my_state(self):
353 return self.teams[self.my_id]
354
355 def start_state_receiver(self, host="localhost", port=recv_port):
356 assert not self.recv_state_socket, 'Server has already been started!'
357
358 class ThreadedTCPRequestHandler(SocketServer.BaseRequestHandler):
359 def handle(self_server):
360 try:
361 f = self_server.request.makefile("rb")
362 while True:
363 self.parse_pushed_state(f.readline())
364 except:
365 return
366
367 class ThreadedTCPServer(SocketServer.ThreadingMixIn, \
368 SocketServer.TCPServer):
369 pass
370
371 self.recv_state_socket = ThreadedTCPServer((host, port), \
372 ThreadedTCPRequestHandler)
373 ip, port = self.recv_state_socket.server_address
374
375 # Start a thread with the server -- that thread will then start one
376 # more thread for each request
377 self.recv_state_thread = threading.Thread( \
378 target=self.recv_state_socket.serve_forever)
379 # Exit the server thread when the main thread terminates
380 self.recv_state_thread.daemon = True
381 self.recv_state_thread.start()
382
383 return ip, port
384
385 def stop_state_receiver(self):
386 if self.recv_state_socket:
387 self.recv_state_socket.shutdown()
388
389 def find_team(self, needle):
390 if needle in self.teams.keys():
391 return needle
392
393 try:
394 ip = ipaddr.IPv4Address(needle)
395 teams = filter(lambda x: ip in x[1]['subnet'], self.teams.items())
396 if teams:
397 return teams[0][0]
398 except:
399 pass
400
401 team_ids = map(lambda x: (x[0], x[1]['subnet'].ip.exploded.split('.')[2]), \
402 self.teams.items())
403 teams = filter(lambda x: needle in x[1], team_ids)
404 if teams:
405 return teams[0][0]
406
407 return None
408
409 def start_flag_receiver(self, host="localhost", port=0):
410 assert not self.recv_flag_server, 'Server has already been started!'
411 self.recv_flag_server = {}
412
413 class ThreadedTCPRequestHandler(SocketServer.BaseRequestHandler):
414 def handle(self_server):
415 try:
416 f = self_server.request.makefile("rb")
417
418 f.write("Which team? Number, IP or name\n")
419 team = self.find_team(f.readline().strip())
420 if team == None:
421 f.write('Unknown team!\n')
422 return
423
424 f.write("Which service? Choices: "+ \
425 str(self.services.keys())+"\n")
426 service = f.readline().strip()
427 if service not in self.services.keys():
428 f.write('Unknown service!\n')
429 return
430
431 while True:
432 recv = f.readline()
433 if not recv: break
434
435 counter_new, counter_old = 0, 0
436 for m in re.finditer(self.flag_re, recv):
437 if self.add_new_flag(m.group(0), team, service):
438 counter_new += 1
439 else:
440 counter_old += 1
441
442 if counter_new or counter_old:
443 self_server.request.send(str(counter_new)+ \
444 ' new and '+str(counter_old)+' old flags.\n')
445 else:
446 self_server.request.send('No flags :(\n')
447 except Exception, e:
448 # Client may have disconnected
449 return
450
451 class ThreadedTCPServer(SocketServer.ThreadingMixIn, \
452 SocketServer.TCPServer):
453 pass
454
455 self.recv_flag_server['socket'] = ThreadedTCPServer((host, port), \
456 ThreadedTCPRequestHandler)
457 ip, port = self.recv_flag_server['socket'].server_address
458 self.recv_flag_server['ip'] = ip
459 self.recv_flag_server['port'] = port
460
461 # Start a thread with the server -- that thread will then start one
462 # more thread for each request
463 self.recv_flag_server['thread'] = threading.Thread( \
464 target=self.recv_flag_server['socket'].serve_forever)
465 # Exit the server thread when the main thread terminates
466 self.recv_flag_server['thread'].daemon = True
467 self.recv_flag_server['thread'].start()
468
469 return ip, port
470
471 def stop_flag_receiver(self):
472 if self.recv_flag_server:
473 self.recv_flag_server['socket'].shutdown()
474 del self.recv_flag_server
475
476 def print_scorboard(self, sort_by='points', reverse=True):
477 print
478 print "Scorboard as of tick",str(self.tick)+':'
479
480 teams = sorted(self.teams.items(), key=lambda x: x[1][sort_by], \
481 reverse=reverse)
482 cprint('% 20s % 8s % 8s % 7s' % \
483 ('Team', 'Points', 'Money', 'Chal.'), attrs=['bold'])
484 for t_id,t in teams:
485 if t_id == self.my_id:
486 cprint('% 20s % 8i % 8i % 7i' % \
487 (t_id, t['points'], t['money'], len(t['challenges_solved'])), \
488 'blue', 'on_red')
489 else:
490 print '% 20s % 8i % 8i % 7i' % \
491 (t_id, t['points'], t['money'], len(t['challenges_solved']))
492
493 def print_risks(self, amount, reverse=False):
494 print
495 print "Risks for converion of",amount,"as of tick",str(self.tick)+':'
496
497 risks = sorted(self.get_risks(amount), key=lambda x: x[2], \
498 reverse=reverse)
499 cprint('% 20s % 10s % 6s' % \
500 ('Team', 'Service', 'Risk'), attrs=['bold'])
501 for t_id,s_id,risk in risks:
502 have_flags = False
503 for f in self.unused_flags:
504 if f['from_team'] == t_id and f['from_service'] == s_id:
505 have_flags = True
506 break
507
508 if have_flags:
509 cprint('% 20s % 10s % 6.2f' % \
510 (t_id, s_id, risk), 'blue', 'on_red')
511 else:
512 print '% 20s % 10s % 6.2f' % (t_id, s_id, risk)
513
514 def print_payoffs(self, amount, reverse=True):
515 print
516 print "Payoffs for conversion of",amount,"as of tick",str(self.tick)+':'
517
518 payoffs = sorted(self.get_payoffs(amount), key=lambda x: x[1], \
519 reverse=reverse)
520 cprint('% 10s % 8s' % \
521 ('Service', 'Payoff'), attrs=['bold'])
522 for s_id,payoff in payoffs:
523 have_flags = False
524 for f in self.unused_flags:
525 if f['from_service'] == s_id:
526 have_flags = True
527 break
528
529 if have_flags:
530 cprint('% 10s % 8.0f' % \
531 (s_id, payoff), 'blue', 'on_red')
532 else:
533 print '% 10s % 8.0f' % (s_id, payoff)
534
535 def print_expectancy(self, amount, cutoff=None):
536 flags = []
537
538 for f in self.unused_flags:
539 r = self.get_risks(amount, service=f['from_service'], \
540 team=f['from_team'])
541 p = self.get_payoffs(amount, service=f['from_service'])
542
543 if not cutoff or amount/r*p > cutoff:
544 flags.append((f['flag'], r*p))
545
546 flags = sorted(flags, key=lambda x: x[1], reverse=True)
547
548 print
549 print 'Payoff expectancy for',amount,'as of tick',str(self.tick)+':'
550 cprint('% 12s % 40s' % \
551 ('Exp.Points', 'Flag'), attrs=['bold'])
552 for flag,payoff in flags:
553 print '% 12.0f % 40s' % (payoff,flag)
554
555 def print_transactions(self, num=20):
556 # 'time_used': datetime.now(), # Time of usage
557 # 'flag': 'fooBAR', # String associated with flag
558 # 'time_stolen': ..., # As found in unused_flags
559 # 'from_team': 'TEAM1_ID', # As found in unused_flags
560 # 'from_service': 'SVC1_ID', # As found in new_flag
561 # 'action': 'laundering', # "betrayal" or "laundering"
562 # 'money_given': 12.3, # if "laundering", otherwise 0.0
563 # 'points_gained': 12.3, # if "laundering" and successfull,
564 # # otherwise 0.0
565 # 'result': "SUCCESS", # "SUCCESS", "BAD_FLAG", "BAD_LUCK",
566 # # "OLD_FLAG" or "OTHER"
567 # 'msg': 'Flag sub. suc.'
568 print
569 print 'Latest transactions:'
570 print '% 7s % 20s % 10s % 10s % 8s % 20s' % \
571 ('Time', 'Team', 'Service', 'Result', 'Points', 'Return Message')
572 for t in self.transactions[num*-1:]:
573 time = t['time_used'].time()
574 print '% 7s % 20s % 10s % 10s % 8i % 20s' % \
575 (str(time.hour)+':'+str(time.minute), \
576 t['from_team'], t['from_service'], t['result'], \
577 t['points_gained'], t['msg'])
578
579
580 def __getstate__(self):
581 '''Needed for pickleability... (sockets can not be pickled)'''
582 new_dict = self.__dict__
583 new_dict['recv_flag_server'] = None
584 new_dict['recv_state_thread'] = None
585 new_dict['recv_state_socket'] = None
586 new_dict['tick_callback'] = None
587 new_dict['flag_dissipater_instance'] = None
588
589 return new_dict
590
591 def backup(self, filename='ictf2011.backup'):
592 pickle.dump(self, open(filename, 'w'))
593
594 @staticmethod
595 def load(filename='ictf2011.backup'):
596 return pickle.load(open(filename))
597
598 def stop_flag_dissipater(self):
599 if self.flag_dissipater_instance:
600 self.flag_dissipater_instance.stop = True
601
602 def start_flag_dissipater(self):
603 if not self.flag_dissipater_instance:
604 return
605
606 class flag_dissipater(threading.Thread):
607 stop = False
608 cutoff = 0.75
609
610 def run(self):
611 '''Runs until stop is set to True
612 Will "sell" all flags with expectancy over cutoff and use
613 all others to compromise other people services
614 TODO'''
615 while not stop:
616 time.sleep(30)
617 # TODO
618 print 'foo'
619
620 self.flag_dissipater_instance = flag_dissipater()
621 self.flag_dissipater_instance.start()
622
623 def client(ip, port, messages):
624 '''Just for debugging and testing purposes'''
625 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
626 sock.connect((ip, port))
627 try:
628 for message in messages:
629 sock.send(message)
630 finally:
631 sock.close()
632
633 if __name__ == '__main__':
634 ctf = ictf()
635 ip, port = ctf.start_state_receiver()
636
637 # Run test data through here!
638 client(ip, port, '{"services": {"S1": {"P": 58, "C": 3, "R": 10}, "foobar": {"P": 98, "C": 42, "R": 90}, "hogwarts": {"P": 60, "C": 10, "R": 5}}, "tick": 666, "teams": {"A-team": {"subnet": "127.0.0.0/24", "D": 70, "services_compromised_last_tick": ["foobar", "S1"], "money": 395, "N": { "Justice League" : 600 }, "Q": {"S1": 10, "foobar": 10000}, "points": 394, "services_up_last_tick": ["S1", "foobar", "hogwarts"], "challenges_solved": [10, 971, 424242]}, "Justice League": {"subnet": "10.230.230.0/24", "D": 30, "services_compromised_last_tick": ["hogwarts"], "money": 3859, "N": { "foobar" : 6093 }, "Q": {"hogwarts": 1093, "foobar": 10000}, "points": 10293, "services_up_last_tick": ["hogwarts"], "challenges_solved": [30, 5391, 305]}}}'+ \
639 "\nIf you can read this on your console, that is good :) (test passed)")
640
641 time.sleep(1)
642 t = ctf.teams['Justice League']
643 assert t['svc_comp'] == ['hogwarts'], 'Justice League.svc_comp not received'
644 assert t['svc_up'] == ['hogwarts'], 'Justice League.svc_up not received'
645 assert t['points'] == 10293, 'Justice League.points not received'
646 assert t['money'] == 3859, 'Justice League.money not received'
647 assert t['N']['foobar'] == 6093, 'Justice League.N not received'
648
649 s = ctf.services['foobar']
650 assert s['C'] == 42/100.0, 'foobar.C not received'
651 assert s['P'] == 98/100.0, 'foobar.P not received'
652 assert s['R'] == 90/100.0, 'foobar.R not received'
653
654 ctf.start_flag_receiver()
655 time.sleep(1)
656 ip, port = ctf.recv_flag_server['ip'], \
657 ctf.recv_flag_server['port']
658 client(ip, port, ["Justice League\n","foobar\n","ABCDEFGHIJKLMNOPQRSTUVWXYZ!\n"])
659 client(ip, port, ["230\n","S1\n","ABCDEFGHIJKLFOOPQRSTUVWXYZ!\n"])
660 client(ip, port, ["10.230.230.3\n","hogwarts\n","FOOBARGHIJKLMNOPQRSTUVWXYZ!\n", \
661 "!!BARFOOBAZ!!\n","BLUBBER!\n","BAR\n"])
662 time.sleep(1)
663 assert 'FOO' in ctf.all_flags, 'Flag FOO not recognized'
664 assert 'BAR' in ctf.all_flags, 'Flag BAR not recognized'
665 assert 'BAZ' in ctf.all_flags, 'Flag BAZ not recognized'
666 assert len(ctf.unused_flags) == 3, 'Flags did not make it in unused_flags'
667
668 ctf.stop_flag_receiver()
669 ctf.stop_state_receiver()
670
671 ctf.backup()
672 new_ctf = ictf.load()
673
674 assert new_ctf.get_risks(100) == ctf.get_risks(100), 'Dump and reload faulty'
675 assert new_ctf.get_payoffs(100) == ctf.get_payoffs(100), 'Dump and reload faulty'
676 del new_ctf
677
678 ctf.print_scorboard()
679 ctf.print_risks(1000)
680 ctf.print_payoffs(1000)
681 ctf.print_expectancy(1000)
682
683 print ctf.launder(100, ctf.get_flag('FOO',remove=True))
684 assert None == ctf.get_flag('FOO'), 'get_flag(f, remove=True) failed'
685 assert 'FOO' == ctf.transactions[-1]['flag'], 'Transaction failed'
686 print ctf.betray(ctf.get_flag('BAR',remove=True))
687 assert 'BAR' == ctf.transactions[-1]['flag'], 'Transaction failed'
688
689 ctf.print_transactions()
690
691 print 'ok'