├── CGC_Extended_Application.pdf ├── Makefile ├── cb-acceptance ├── cb-acceptance.md ├── cb-replay ├── cb-replay-pov ├── cb-replay-pov.md ├── cb-replay.md ├── cb-test ├── cb-test.md ├── cgc-cb.mk ├── debian ├── changelog ├── compat ├── control ├── copyright ├── rules └── source │ └── format ├── poll-validate ├── poll-validate.md ├── syscall-stub.c └── tests ├── test_cbtest.py └── test_prng.py /CGC_Extended_Application.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CyberGrandChallenge/cb-testing/6b82a9a2589bd0eae3ac7589ce19c3ce047dbbbf/CGC_Extended_Application.pdf -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | MAN_DIR = $(DESTDIR)/usr/share/man/man1 2 | BIN_DIR = $(DESTDIR)/usr/bin 3 | CB_SHARE_DIR = $(DESTDIR)/usr/share/cb-testing 4 | BINS = cb-test poll-validate cb-replay cb-acceptance cb-replay-pov 5 | 6 | MAN = $(addsuffix .1.gz,$(BINS)) 7 | 8 | # MAN = cb-test.1.gz poll-validate.1.gz cb-replay.1.gz cb-acceptance.1.gz 9 | 10 | all: man 11 | 12 | man: $(MAN) 13 | 14 | %.1.gz: %.md 15 | pandoc -s -t man $< -o $<.tmp 16 | gzip -9 < $<.tmp > $@ 17 | 18 | install: 19 | install -d $(BIN_DIR) 20 | install -d $(MAN_DIR) 21 | install -d $(CB_SHARE_DIR) 22 | install $(BINS) $(BIN_DIR) 23 | install -m 444 CGC_Extended_Application.pdf $(CB_SHARE_DIR) 24 | install -m 444 cgc-cb.mk $(CB_SHARE_DIR) 25 | install -m 444 syscall-stub.c $(CB_SHARE_DIR) 26 | install $(MAN) $(MAN_DIR) 27 | 28 | clean: 29 | rm -f *.1.gz *.tmp 30 | -------------------------------------------------------------------------------- /cb-acceptance: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import argparse 4 | import random 5 | import shutil 6 | import socket 7 | import tempfile 8 | import hashlib 9 | import os 10 | import subprocess 11 | import re 12 | 13 | CHECK_TYPES = ['build', 'pov', 'polls', 'static', 'remote'] 14 | 15 | class check_type(object): 16 | def __init__(self, *args): 17 | for check in args: 18 | assert check is None or check in CHECK_TYPES, "invalid check type: %s" % str(check) 19 | self.checks = args 20 | 21 | def __call__(self, func): 22 | def wrapped(wrap_self, *args, **kwargs): 23 | if wrap_self.build_type is None or wrap_self.build_type in self.checks: 24 | wrap_self._check_output(['make', 'clean']) 25 | 26 | if 'pov' in self.checks: 27 | wrap_self._check_output(['make', 'pov']) 28 | 29 | if 'build' in self.checks: 30 | wrap_self._check_output(['make', 'build']) 31 | 32 | if 'polls' in self.checks: 33 | wrap_self._check_output(['make', 'patched-so']) 34 | wrap_self._check_output(['make', 'generate-polls']) 35 | 36 | func(wrap_self, *args, **kwargs) 37 | 38 | if wrap_self.no_cleanup is False: 39 | wrap_self._check_output(['make', 'clean']) 40 | return True 41 | else: 42 | return None 43 | return wrapped 44 | 45 | 46 | class Acceptance(object): 47 | def __init__(self, name, path, debug=False, build_type=None, cb_type=None, no_cleanup=False, strict=False, gen_poll_seed=None): 48 | self.name = name 49 | self.path = path 50 | self.debug = debug 51 | self.build_type = build_type 52 | self.cb_type = cb_type 53 | self.no_cleanup = no_cleanup 54 | self.strict = strict 55 | self.gen_poll_seed = gen_poll_seed 56 | 57 | self.current = True 58 | self.current_method = '' 59 | 60 | def get_checks(self): 61 | methods = [x for x in dir(self) if callable(getattr(self, x)) and (x.startswith('check_') or x.startswith('warn_'))] 62 | methods.sort() 63 | return methods 64 | 65 | def __call__(self, check_name): 66 | methods = self.get_checks() 67 | 68 | ret = True 69 | if check_name is not None: 70 | assert check_name in methods, 'Invalid check: %s' % (repr(check_name)) 71 | 72 | for method_name in methods: 73 | if check_name is not None and check_name != method_name: 74 | continue 75 | 76 | self.current_method = method_name 77 | print "# %s - %s" % (self.name, method_name) 78 | 79 | try: 80 | result = getattr(self, method_name)() 81 | except subprocess.CalledProcessError as err: 82 | self.fail("command failed: %s" % (str(err))) 83 | except AssertionError as err: 84 | self.fail(str(err)) 85 | 86 | if self.current is True: 87 | if result is None: 88 | print "ok - skipped %s" % method_name 89 | else: 90 | print "ok - %s" % method_name 91 | else: 92 | ret = False 93 | self.current = True 94 | return ret 95 | 96 | def _check_output(self, cmd, should_fail=False): 97 | if cmd[0] == "make" and ("generate-polls" in cmd or len(cmd) == 1): 98 | if self.gen_poll_seed is not None: 99 | cmd.append("POLLS_RELEASE_SEED={0}".format(self.gen_poll_seed)) 100 | cmd.append("POLLS_TESTING_SEED={0}".format(self.gen_poll_seed)) 101 | 102 | if self.debug: 103 | print '# %s' % (' '.join(cmd)) 104 | 105 | process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 106 | stdout, stderr = process.communicate() 107 | retcode = process.poll() 108 | if self.debug > 1: 109 | for line in stdout.splitlines(): 110 | print "# stdout: %s" % line 111 | for line in stderr.splitlines(): 112 | print "# stderr: %s" % line 113 | print "# retcode: %s" % repr(retcode) 114 | 115 | if should_fail: 116 | assert retcode != 0, "command expected to fail, but did not: %s" % ' '.join(cmd) 117 | if not should_fail and retcode: 118 | raise subprocess.CalledProcessError(retcode, cmd, output=stdout) 119 | 120 | return stdout 121 | 122 | def fail(self, reason): 123 | self.current = False 124 | if self.current_method.startswith('warn_') and not self.strict: 125 | print "# WARNING - %s - %s : %s" % (self.name, self.current_method, reason) 126 | else: 127 | print "not ok - %s - %s : %s" % (self.name, self.current_method, reason) 128 | return False 129 | 130 | def is_true(self, value, reason): 131 | if value is not True: 132 | return self.fail(reason) 133 | return True 134 | 135 | def is_in(self, value, allowed, reason): 136 | if value not in allowed: 137 | return self.fail(reason) 138 | return True 139 | 140 | def is_not_in(self, value, not_allowed, reason): 141 | if value in not_allowed: 142 | return self.fail(reason) 143 | return True 144 | 145 | def is_equal(self, value, expected, reason): 146 | if not value == expected: 147 | return self.fail(reason) 148 | return True 149 | 150 | def is_not_equal(self, value, expected, reason): 151 | if value == expected: 152 | return self.fail(reason) 153 | return True 154 | 155 | def is_startswith(self, value, expected, reason): 156 | if not value.startswith(expected): 157 | return self.fail(reason) 158 | return True 159 | 160 | @staticmethod 161 | def checksum(files): 162 | out = [hashlib.md5(open(i, 'rb').read()).hexdigest() for i in files] 163 | out.sort() 164 | return out 165 | 166 | @check_type(None) 167 | def check_basic(self): 168 | # 'make' should pass on its own 169 | self._check_output(['make', 'clean']) 170 | self._check_output(['make']) 171 | 172 | @check_type('remote') 173 | def check_remote(self): 174 | if not self.is_startswith(socket.gethostname(), 'crs', 'unable to verify remote CBs'): 175 | return 176 | 177 | self._check_output(['make', 'build', 'pov', 'generate-polls']) 178 | self._check_output(['make', 'check-remote']) 179 | if os.path.isdir(os.path.join(self.path, 'ids')): 180 | self._check_output(['make', 'check-ids']) 181 | 182 | @check_type('build') 183 | def check_determistic_builds(self): 184 | # this is where I rebuild the binaries over and over, verifying we get 185 | # the same checksum each time (make clean; make build;) 186 | bins = [os.path.join('bin', x) for x in os.listdir('bin')] 187 | before = self.checksum(bins) 188 | self._check_output(['make', 'clean']) 189 | self._check_output(['make', 'build']) 190 | after = self.checksum(bins) 191 | 192 | self.is_equal(before, after, 'checksums differ') 193 | 194 | @check_type('build') 195 | def check_patches_change_bin(self): 196 | bins, bins_patched, bins_partial = self.get_bins() 197 | 198 | bins1 = [os.path.join(self.path, 'bin', x) for x in bins] 199 | bins2 = [os.path.join(self.path, 'bin', x) for x in bins_patched] 200 | 201 | cs1 = self.checksum(bins1) 202 | cs2 = self.checksum(bins2) 203 | self.is_not_equal(cs1, cs2, 'patched and unpatched bins are the same') 204 | 205 | @check_type('polls') 206 | def check_polls(self): 207 | xml = [] 208 | 209 | path = os.path.join(self.path, 'poller', 'for-release') 210 | if os.path.isdir(path): 211 | xml += [os.path.join(path, x) for x in os.listdir(path) if x.endswith('.xml')] 212 | 213 | if self.cb_type is None or self.cb_type == 'cqe': 214 | path = os.path.join(self.path, 'poller', 'for-testing') 215 | if os.path.isdir(path): 216 | xml += [os.path.join(path, x) for x in os.listdir(path) if x.endswith('.xml')] 217 | 218 | xml.sort() 219 | 220 | self.is_true(len(xml) >= 1000, 'not enough polls: %d' % len(xml)) 221 | 222 | # Validate all of the XML from the polls 223 | self._check_output(['poll-validate'] + xml) 224 | 225 | checksums = self.checksum(xml) 226 | self.is_equal(len(checksums), len(set(checksums)), 'duplicate polls') 227 | 228 | for filename in xml: 229 | with open(filename, 'r') as fh: 230 | poll = fh.read() 231 | self.is_true('= 1, 'at least one pov: %d' % len(povs)) 246 | 247 | for filename in povs: 248 | if filename.endswith('.xml'): 249 | self._check_output(['poll-validate', filename]) 250 | else: 251 | self._check_output(['cgcef_verify', filename]) 252 | 253 | def get_bins(self): 254 | bins_all = os.listdir('bin') 255 | bins = [x for x in bins_all if not x.endswith('_patched') and 'partial' not in x] 256 | bins_partial = [x for x in bins_all if 'partial' in x] 257 | bins_patched = [x for x in bins_all if x.endswith('_patched') and 'partial' not in x] 258 | return bins, bins_patched, bins_partial 259 | 260 | @check_type('pov', 'build') 261 | def check_pov_prng_skip(self): 262 | bins, bins_patched = self.get_bins()[:2] 263 | 264 | skip = "%d" % random.randint(10, 1000) 265 | 266 | pov_path = os.path.join(self.path, 'pov') 267 | 268 | for _ in range(256): 269 | skip = "%d" % random.randint(10, 1000) 270 | cmd = ['cb-test', '--negotiate_seed', '--directory', 'bin', '--cb'] + bins + ['--xml_dir', pov_path, '--should_core', '--cb_seed_skip', skip] 271 | self._check_output(cmd) 272 | 273 | cmd = ['cb-test', '--negotiate_seed', '--directory', 'bin', '--cb'] + bins_patched + ['--xml_dir', pov_path, '--failure_ok', '--cb_seed_skip', skip] 274 | self._check_output(cmd) 275 | 276 | @check_type('polls', 'build') 277 | def warn_sequential_read_write(self): 278 | bins, bins_patched = self.get_bins()[:2] 279 | strace_bin_path = os.path.join(self.path, 'build', 'strace-wrap.sh') 280 | strace_out_path = os.path.join(self.path, 'build', 'strace.out') 281 | with open(strace_bin_path, 'w') as fh: 282 | fh.write('#!/bin/bash\nset -u\nstrace -o ${STRACE_PATH} $*\n') 283 | os.chmod(strace_bin_path, 0o755) 284 | 285 | too_many = 25 286 | 287 | for poll_dir in ['for-release', 'for-testing']: 288 | base_dir = os.path.join(self.path, 'poller', poll_dir) 289 | 290 | if not os.path.isdir(base_dir): 291 | continue 292 | 293 | bins = [os.path.abspath(os.path.join('bin', x)) for x in bins] 294 | bins_patched = [os.path.abspath(os.path.join('bin', x)) for x in bins_patched] 295 | 296 | polls = [x for x in sorted(os.listdir(base_dir)) if x.endswith('.xml')] 297 | 298 | for filename in polls: 299 | file_path = os.path.join(base_dir, filename) 300 | for cb_set in [bins, bins_patched]: 301 | if len(cb_set) > 1: 302 | continue 303 | cmd = ['cb-test', '--negotiate_seed', '--directory', 'bin', '--xml', file_path, '--wrapper', strace_bin_path, '--debug', '--cb_env', 'STRACE_PATH=%s' % strace_out_path, '--cb'] + cb_set 304 | self._check_output(cmd) 305 | last = '' 306 | count = 0 307 | with open(strace_out_path, 'r') as fh: 308 | stdout = fh.read() 309 | 310 | for line in stdout.split('\n'): 311 | # reset counter at exit 312 | if line.startswith('_terminate'): 313 | if not self.is_true(count < too_many, 'too many sequential %s (count: %d)' % (repr(last), count)): 314 | return 315 | count = 0 316 | continue 317 | 318 | if line.startswith('transmit') or line.startswith('receive'): 319 | # cheap hack for parsers, not worrying about quoting, just 320 | # grabbing the first and second to last arg 321 | args = line.split(', ') 322 | args = [args[0].replace('(', ' ')] + args[1:] 323 | size = int(args[-2]) 324 | 325 | if size > 1: 326 | count = 0 327 | 328 | if args[0] == last: 329 | count += 1 330 | else: 331 | last = args[0] 332 | count = 0 333 | 334 | if not self.is_true(count < too_many, 'too many sequential %s (count: %d)' % (repr(last), count)): 335 | return 336 | 337 | @check_type('polls', 'build') 338 | def check_prng_skip(self): 339 | bins, bins_patched = self.get_bins()[:2] 340 | 341 | skip = "%d" % random.randint(10, 1000) 342 | 343 | for poll_dir in ['for-release', 'for-testing']: 344 | base_dir = os.path.join(self.path, 'poller', poll_dir) 345 | 346 | if not os.path.isdir(base_dir): 347 | continue 348 | 349 | cmd = ['cb-test', '--negotiate_seed', '--directory', 'bin', '--cb'] + bins + ['--xml_dir', base_dir, '--cb_seed_skip', skip] 350 | self._check_output(cmd) 351 | 352 | cmd = ['cb-test', '--negotiate_seed', '--directory', 'bin', '--cb'] + bins_patched + ['--xml_dir', base_dir, '--cb_seed_skip', skip] 353 | self._check_output(cmd) 354 | 355 | @check_type('build') 356 | def check_max_filesize(self): 357 | bins, bins_patched, bins_partial = self.get_bins() 358 | for filename in bins + bins_patched + bins_partial: 359 | path = os.path.join('bin', filename) 360 | self.is_true(os.path.getsize(path) <= (4**10)*25, "max file size of %s" % filename) 361 | 362 | @check_type('polls', 'build') 363 | def warn_munge_seed(self): 364 | bins, bins_patched = self.get_bins()[:2] 365 | 366 | for poll_dir in ['for-release', 'for-testing']: 367 | base_dir = os.path.join(self.path, 'poller', poll_dir) 368 | 369 | if not os.path.isdir(base_dir): 370 | continue 371 | 372 | for cb_set in [bins, bins_patched]: 373 | cmd = ['cb-test', '--negotiate_seed', '--directory', 'bin', '--cb'] + cb_set + ['--xml_dir', base_dir] 374 | self._check_output(cmd) 375 | cmd += ['--cb_seed_munge'] 376 | self._check_output(cmd, should_fail=True) 377 | 378 | 379 | @check_type('polls', 'build') 380 | def check_segmented_writes_polls(self): 381 | bins, bins_patched = self.get_bins()[:2] 382 | 383 | for poll_dir in ['for-release', 'for-testing']: 384 | base_dir = os.path.join(self.path, 'poller', poll_dir) 385 | 386 | if not os.path.isdir(base_dir): 387 | continue 388 | 389 | cmd = ['cb-test', '--negotiate_seed', '--directory', 'bin', '--cb'] + bins + ['--xml_dir', base_dir, '--max_send', '1', '--timeout', '1500'] 390 | self._check_output(cmd) 391 | 392 | cmd = ['cb-test', '--negotiate_seed', '--directory', 'bin', '--cb'] + bins_patched + ['--xml_dir', base_dir, '--max_send', '1', '--timeout', '1500'] 393 | self._check_output(cmd) 394 | 395 | @check_type('pov', 'build') 396 | def check_segmented_writes_pov(self): 397 | bins, bins_patched = self.get_bins()[:2] 398 | 399 | pov_path = os.path.join(self.path, 'pov') 400 | 401 | for _ in range(256): 402 | cmd = ['cb-test', '--negotiate_seed', '--directory', 'bin', '--cb'] + bins + ['--xml_dir', pov_path, '--max_send', '1', '--should_core', '--timeout', '1500'] 403 | self._check_output(cmd) 404 | 405 | cmd = ['cb-test', '--negotiate_seed', '--directory', 'bin', '--cb'] + bins_patched + ['--xml_dir', pov_path, '--failure_ok', '--max_send', '1', '--timeout', '1500'] 406 | self._check_output(cmd) 407 | 408 | @check_type('polls', 'build') 409 | def check_deterministic_polls(self): 410 | # polls can use the shared object made during build time 411 | 412 | depths = {} 413 | with open('Makefile') as fh: 414 | for line in fh: 415 | m = re.match(r'^POLLS_(RELEASE|TESTING)_MAX_DEPTH\s*=\s*([0-9]+)$', line) 416 | if m: 417 | name = 'for-' + m.group(1).lower() 418 | value = int(m.group(2)) 419 | depths[name] = value 420 | 421 | for poll_dir in ['for-release', 'for-testing']: 422 | base_path = os.path.join(self.path, 'poller', poll_dir) 423 | 424 | machine = os.path.join(base_path, 'machine.py') 425 | graph = os.path.join(base_path, 'state-graph.yaml') 426 | 427 | if not os.path.isfile(machine): 428 | continue 429 | 430 | if not os.path.isfile(graph): 431 | continue 432 | 433 | out = [] 434 | for _ in [0, 1]: 435 | tmp_dir = tempfile.mkdtemp() 436 | 437 | cmd = ['generate-polls'] 438 | 439 | if poll_dir in depths: 440 | cmd += ['--depth', '%d' % depths[poll_dir]] 441 | 442 | cmd += ['--count', '1000', '--seed', '0', machine, graph, tmp_dir] 443 | self._check_output(cmd) 444 | 445 | polls = [os.path.join(tmp_dir, x) for x in os.listdir(tmp_dir) if x.endswith('.xml')] 446 | out.append(self.checksum(polls)) 447 | shutil.rmtree(tmp_dir) 448 | 449 | self.is_equal(out[0], out[1], '%s generator is not repeatable' % (poll_dir)) 450 | 451 | @check_type('static') 452 | def check_directories(self): 453 | 454 | required = { 455 | 'files': { 456 | '': ['Makefile', 'README.md'] 457 | }, 458 | 'directories' : { 459 | '': ['poller'], 460 | } 461 | } 462 | 463 | optional = { 464 | 'files': { 465 | '': lambda x: x in required['files'][''], 466 | '/include': lambda x: x.endswith('.h'), 467 | '/src': lambda x: x.endswith('.c') or x.endswith('.cc') or x.endswith('.h'), 468 | '/lib': lambda x: x.endswith('.c') or x.endswith('.cc') or x.endswith('.h'), 469 | '/ids': lambda x: x.endswith('.rules'), 470 | '/pov': lambda x: re.match(r'^POV_\d{5}.(xml|povxml)$', x) is not None, 471 | '/poller/for-testing': lambda x: x in ['machine.py', 'state-graph.yaml'] or re.match(r'^POLL_\d{5}.xml$', x) is not None, 472 | '/poller/for-release': lambda x: x in ['machine.py', 'state-graph.yaml'] or re.match(r'^POLL_\d{5}.xml$', x) is not None, 473 | '/poller': lambda x: False, # this should fail, no files should be here 474 | }, 475 | } 476 | 477 | if self.cb_type == 'cfe': 478 | optional['files']['/pov'] = lambda x: re.match(r'^POV_\d{5}.povxml$', x) is not None 479 | optional['files']['/poller/for-release'] = lambda x: x in ['machine.py', 'state-graph.yaml'] 480 | 481 | del optional['files']['/poller/for-testing'] 482 | elif self.cb_type == 'cqe': 483 | del optional['files']['/ids'] 484 | optional['files']['/pov'] = lambda x: re.match(r'^POV_\d{5}.xml$', x) is not None 485 | 486 | check_paths = [self.path] 487 | 488 | while len(check_paths): 489 | path = check_paths.pop() 490 | 491 | seen = {} 492 | sub_cb = [] 493 | is_sub_cb = False 494 | 495 | if path != self.path: 496 | is_sub_cb = True 497 | 498 | for directory, subdirs, files in os.walk(path): 499 | if '/.svn/' in directory or directory.endswith('/.svn'): 500 | continue 501 | 502 | if '.svn' in subdirs: 503 | subdirs.remove('.svn') 504 | 505 | relative = directory[len(path):] 506 | 507 | seen[relative] = True 508 | 509 | if len(subdirs) == 0 and len(files) == 0: 510 | self.fail('empty directory: %s' % relative) 511 | 512 | if not is_sub_cb: 513 | if re.match(r'^/cb_\d+$', relative): 514 | check_paths.append(directory) 515 | sub_cb.append(relative) 516 | continue 517 | elif re.match(r'^/cb_\d+/.*$', relative): 518 | # sub cb subdirs handled elsewhere 519 | continue 520 | 521 | # allow whatever they want in 'support'. but thats it 522 | if relative == '/support' or relative.startswith('/support/'): 523 | continue 524 | 525 | if relative in required['files']: 526 | for filename in required['files'][relative]: 527 | self.is_in(filename, files, 'required file: %s' % filename) 528 | 529 | if (self.cb_type is None or self.cb_type == 'cfe') and re.match(r'^/pov_\d+$', relative): 530 | self.is_true(len(subdirs) == 0, 'POV-CB subdirs: %s' % (repr(subdirs))) 531 | for filename in files: 532 | self.is_true(filename.endswith('.c') or filename.endswith('.h'), 'invalid file in %s: %s' % (repr(relative), repr(filename))) 533 | continue 534 | 535 | if relative not in optional['files'] and relative not in required['files']: 536 | self.fail('Invalid directory: %s (%s)' % (repr(relative), ', '.join(files))) 537 | continue 538 | 539 | if 'libcgc.h' in files: 540 | self.fail('libcgc.h should not be included') 541 | 542 | if 'libpov.h' in files: 543 | self.fail('libpov.h should not be included') 544 | 545 | for filename in files: 546 | self.is_true(optional['files'][relative](filename), 'invalid file in %s: %s' % (repr(relative), repr(filename))) 547 | 548 | if sub_cb: 549 | self.is_not_in('/src', seen, 'src must not be provided in case of IPC CBs') 550 | self.is_not_in('/lib', seen, 'lib must not be provided in case of IPC CBs') 551 | self.is_not_in('/include', seen, 'include must not be provided in case of IPC CBs') 552 | else: 553 | self.is_in('/src', seen, 'src is required except for IPC CBs: %s' % (repr(path))) 554 | 555 | @check_type('static') 556 | def check_makefile(self): 557 | required = { 558 | 'SERVICE_ID': lambda x: len(x) == 5 and x.isdigit(), 559 | 'AUTHOR_ID': lambda x: len(x) == 5 and x.isalnum(), 560 | } 561 | 562 | optional = { 563 | 'NO_STRIP': lambda x: x == '1', 564 | 'POLLS_RELEASE_COUNT': lambda x: x.isdigit(), 565 | 'POLLS_RELEASE_MAX_DEPTH': lambda x: x.isdigit(), 566 | 'POLLS_RELEASE_SEED': lambda x: x.isdigit() or x[0] == '-' and x[1:].isdigit(), 567 | 'POLLS_TESTING_COUNT': lambda x: x.isdigit(), 568 | 'POLLS_TESTING_MAX_DEPTH': lambda x: x.isdigit(), 569 | 'POLLS_TESTING_SEED': lambda x: x.isdigit() or x[0] == '-' and x[1:].isdigit(), 570 | 'CFLAGS': lambda x: x == x, # XXX - add more later 571 | 'CXXFLAGS': lambda x: x == x, # XXX - add more later 572 | 'override LDFLAGS': lambda x: x == x, # XXX - add more later. Not thrilled with this 573 | } 574 | 575 | if self.cb_type == 'cfe': 576 | required['VULN_COUNT'] = lambda x: x.isdigit() 577 | 578 | for key in optional.keys(): 579 | if key.startswith('POLLS') and key != 'POLLS_RELEASE_MAX_DEPTH': 580 | del optional[key] 581 | elif self.cb_type is None: 582 | optional['VULN_COUNT'] = lambda x: x.isdigit() 583 | 584 | seen = {} 585 | 586 | makefile = [] 587 | with open('Makefile') as fh: 588 | for line in fh: 589 | line = line.replace('\n', '') 590 | if not len(line): 591 | continue 592 | 593 | self.is_not_in('#', line, 'No comments (%s)' % repr(line)) 594 | if line[0] == '#': 595 | continue 596 | 597 | for char in ';|><`()!$\\': 598 | self.is_not_in(char, line, 'shell meta characters ' 599 | '%s : %s' % (char, repr(line))) 600 | 601 | makefile.append(line) 602 | 603 | last_line = makefile.pop() 604 | self.is_equal(last_line, 'include /usr/share/cb-testing/cgc-cb.mk', 605 | 'end with cgc-cb.mk include: %s' % last_line) 606 | 607 | for line in makefile: 608 | self.is_in('=', line, 'not a variable: %s' % line) 609 | if '=' not in line: 610 | continue 611 | 612 | var, arg = line.split('=', 1) 613 | var = var.rstrip() 614 | arg = arg.strip() 615 | 616 | if var in required: 617 | self.is_true(required[var](arg), 'invalid %s: %s' % (var, repr(arg))) 618 | elif var in optional: 619 | self.is_true(optional[var](arg), 'invalid %s: %s' % (var, repr(arg))) 620 | else: 621 | self.fail('disallowed entry: %s' % line) 622 | 623 | self.is_not_in(var, seen, '%s defined multiple times' % (var)) 624 | seen[var] = True 625 | 626 | for var in required: 627 | self.is_in(var, seen, 'missing required variable: %s' % (var)) 628 | 629 | def get_checks(): 630 | a = Acceptance('', '/') 631 | return a.get_checks() 632 | 633 | def main(): 634 | current_dir = os.getcwd() 635 | 636 | parser = argparse.ArgumentParser(description='Test CB for acceptance ' 637 | 'within CGC') 638 | parser.add_argument('--debug', required=False, action='count', 639 | help='Enable debugging output (use multiple times to increase verbosity)') 640 | parser.add_argument('--check', choices=get_checks(), type=str, 641 | help='Run just a specific check') 642 | parser.add_argument('--check_type', choices=CHECK_TYPES, default=None, 643 | help='Only run the checks that match the check type') 644 | parser.add_argument('challenges', metavar='CB', type=str, nargs='+', 645 | help='paths to CB') 646 | parser.add_argument('--cb_type', choices=['cqe', 'cfe'], default=None) 647 | parser.add_argument('--no_cleanup', action='store_true', help='disable cleanup') 648 | parser.add_argument('--strict', action='store_true', help='error on warnings') 649 | parser.add_argument('--gen_poll_seed', required=False, type=str, default=None, 650 | help='Seed to use when generating polls') 651 | 652 | args = parser.parse_args() 653 | 654 | failed = False 655 | 656 | for cb_path in args.challenges: 657 | while cb_path[-1] == '/': 658 | cb_path = cb_path[:-1] 659 | 660 | if cb_path == '.': 661 | cb_path = current_dir 662 | elif not cb_path.startswith('/'): 663 | cb_path = os.path.join(current_dir, cb_path) 664 | 665 | cb_name = os.path.split(cb_path)[-1] 666 | 667 | cb_tmp_dir = None 668 | 669 | try: 670 | assert re.match('^[A-Z0-9]{5}_[0-9]{5}$', cb_name), "invalid CB: %s" % cb_name 671 | cb_tmp_dir = tempfile.mkdtemp() 672 | dest_path = os.path.join(cb_tmp_dir, cb_name) 673 | subprocess.check_call(['cp', '-r', cb_path, dest_path]) 674 | os.chdir(dest_path) 675 | 676 | a = Acceptance(cb_name, dest_path, args.debug, args.check_type, args.cb_type, args.no_cleanup, args.strict, args.gen_poll_seed) 677 | ret = a(args.check) 678 | 679 | if not ret: 680 | failed = True 681 | except AssertionError as err: 682 | failed = True 683 | print "not ok - %s : %s" % (cb_name, err) 684 | except subprocess.CalledProcessError as err: 685 | failed = True 686 | print "not ok - %s : %s" % (cb_name, err) 687 | 688 | if cb_tmp_dir is not None: 689 | if args.no_cleanup: 690 | print "# not cleaning up %s" % repr(cb_tmp_dir) 691 | else: 692 | shutil.rmtree(cb_tmp_dir) 693 | 694 | os.chdir(current_dir) 695 | 696 | return failed == True 697 | 698 | if __name__ == '__main__': 699 | exit(main()) 700 | -------------------------------------------------------------------------------- /cb-acceptance.md: -------------------------------------------------------------------------------- 1 | % CB-ACCEPTANCE(1) Cyber Grand Challenge Manuals 2 | % Brian Caswell 3 | % May 5, 2015 4 | 5 | # NAME 6 | 7 | cb-acceptance - Challenge Binary acceptance testing utility 8 | 9 | # SYNOPSIS 10 | 11 | cb-acceptance [options] *CB* [*CB* ...] 12 | 13 | # DESCRIPTION 14 | 15 | cb-acceptance is a utility to verify CGC challenge binary (CB) acceptability. 16 | 17 | cb-acceptance output is in the TAP format. 18 | 19 | # ARGUMENTS 20 | *CB* [*CB* ...] 21 | 22 | # OPTIONS 23 | 24 | \-\-help 25 | : Show the available options 26 | 27 | \-\-debug 28 | : Enable debugging output 29 | 30 | \-\-list_checks 31 | : List available checks and exit 32 | 33 | \-\-check *CHECK* 34 | : Only run the specified check 35 | 36 | \-\-check_type *CHECK_TYPE* 37 | : Only run checks that match the provided type 38 | 39 | \-\-cb_type *CB_TYPE* 40 | : Validate a binary to be a specific type of CB (CFE or CQE) 41 | 42 | # EXAMPLE USES 43 | 44 | * cb-acceptance /usr/share/cgc-sample-challenges/examples/CADET_00001 45 | 46 | This will test the CADET_00001 binary. 47 | 48 | * cb-acceptance --cb_type cqe /usr/share/cgc-sample-challenges/examples/CADET_00001 49 | 50 | This will test the CADET_00001 binary, verifying it is a CB for CQE. 51 | 52 | * cb-acceptance --check_type static /usr/share/cgc-sample-challenges/examples/CADET_00001 53 | 54 | This will test the CADET_00001 binary, only verifying the static components that do not require any content generation. 55 | 56 | # COPYRIGHT 57 | 58 | Copyright (C) 2015, Brian Caswell 59 | 60 | # SEE ALSO 61 | 62 | `cb-test` (1), `poll-validate` (1) 63 | 64 | For information regarding the TAP Format, see 65 | 66 | For more information relating to DARPA's Cyber Grand Challenge, please visit 67 | -------------------------------------------------------------------------------- /cb-replay: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | CB POV / Poll communication verification tool 5 | 6 | Copyright (C) 2014 - Brian Caswell 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | 26 | This tool allows for deterministic communication to a CGC Challenge Binary 27 | using a communication spec [0] defined in XML. Results are logged in the TAP 28 | format [1]. 29 | 30 | 0 - file:///usr/share/cgc-docs/replay.dtd 31 | 1 - http://testanything.org/ 32 | """ 33 | 34 | import os 35 | import argparse 36 | import multiprocessing 37 | import signal 38 | import re 39 | import socket 40 | import struct 41 | import time 42 | import zipfile 43 | import defusedxml.ElementTree as ET 44 | 45 | 46 | class RegexMatch(object): 47 | """ Simple wrapper for handling regexes in Throw. 48 | 49 | Attributes: 50 | group: which re group to use when extracting data 51 | regex: The compiled re to be evaluated 52 | 53 | """ 54 | def __init__(self, regex, group=None): 55 | if group is None: 56 | group = 0 57 | 58 | self.regex = regex 59 | self.group = group 60 | 61 | def match(self, data): 62 | """ 63 | Match the compiled regular expression 64 | 65 | Arguments: 66 | data: Data to match 67 | 68 | Returns: 69 | Result of the re.match call 70 | 71 | Raises 72 | None 73 | """ 74 | 75 | return self.regex.match(data) 76 | 77 | 78 | class _ValueStr(str): 79 | """ Wrapper class, used to specify the string is meant to be a 'key' in the 80 | Throw.values key/value store.""" 81 | pass 82 | 83 | 84 | class TimeoutException(Exception): 85 | """ Exception to be used by Timeout(), to allow catching of timeout 86 | exceptions """ 87 | pass 88 | 89 | 90 | class TestFailure(Exception): 91 | """ Exception to be used by Throw(), to allow catching of test failures """ 92 | pass 93 | 94 | 95 | class Timeout(object): 96 | """ Timeout - A class to use within 'with' for timing out a block via 97 | exceptions and alarm.""" 98 | 99 | def __init__(self, seconds): 100 | self.seconds = seconds 101 | 102 | @staticmethod 103 | def cb_handle_timeout(signum, frame): 104 | """ SIGALRM signal handler callback """ 105 | raise TimeoutException("timed out") 106 | 107 | def __enter__(self): 108 | if self.seconds: 109 | signal.signal(signal.SIGALRM, self.cb_handle_timeout) 110 | signal.alarm(self.seconds) 111 | 112 | def __exit__(self, exit_type, exit_value, traceback): 113 | if self.seconds: 114 | signal.alarm(0) 115 | 116 | 117 | class Throw(object): 118 | """Throw - Perform the interactions with a CB 119 | 120 | This class implements the basic methods to interact with a CB, verifying 121 | the interaction works as expected. 122 | 123 | Usage: 124 | a = Throw((source_ip, source_port), (target_ip, target_port), POV, 125 | timeout, should_debug, negotiate) 126 | a.run() 127 | 128 | Attributes: 129 | source: touple of host and port for the outbound connection 130 | target: touple of host and port for the CB 131 | 132 | count: Number of actions performed 133 | 134 | debug: Is debugging enabled 135 | 136 | failed: Number of actions that did not work as expected 137 | 138 | passed: Number of actions that did worked as expected 139 | 140 | pov: POV, as defined by POV() 141 | 142 | sock: TCP Socket to the CB 143 | 144 | timeout: connection timeout 145 | 146 | values: Variable dictionary 147 | 148 | logs: all of the output from the interactions 149 | 150 | max_send: maxmimum amount of data to send per request 151 | 152 | negotiate: Should the CB negotiation process happen 153 | """ 154 | def __init__(self, source, target, pov, timeout, debug, max_send, negotiate): 155 | self.source = source 156 | self.target = target 157 | self.count = 0 158 | self.failed = 0 159 | self.passed = 0 160 | self.pov = pov 161 | self.debug = debug 162 | self.sock = None 163 | self.timeout = timeout 164 | self.values = {} 165 | self.logs = [] 166 | self.max_send = max_send 167 | self._read_buffer = '' 168 | self.negotiate = negotiate 169 | 170 | def is_ok(self, expected, result, message): 171 | """ Verifies 'expected' is equal to 'result', logging results in TAP 172 | format 173 | 174 | Args: 175 | expected: Expected value 176 | result: Action value 177 | message: String describing the action being evaluated 178 | 179 | Returns: 180 | legnth: If the 'expected' result is a string, returns the length of 181 | the string, otherwise 0 182 | 183 | Raises: 184 | None 185 | """ 186 | 187 | if isinstance(expected, _ValueStr): 188 | message += ' (expanded from %s)' % repr(expected) 189 | if expected not in self.values: 190 | message += ' value not provided' 191 | self.log_fail(message) 192 | return 0 193 | expected = self.values[expected] 194 | 195 | if isinstance(expected, str): 196 | if result.startswith(expected): 197 | self.log_ok(message) 198 | return len(expected) 199 | else: 200 | if result == expected: 201 | self.log_ok(message) 202 | return 0 203 | 204 | if self.debug: 205 | self.log('expected: %s' % repr(expected)) 206 | self.log('result: %s' % repr(result)) 207 | 208 | self.log_fail(message) 209 | return 0 210 | 211 | def is_not(self, expected, result, message): 212 | """ Verifies 'expected' is not equal to 'result', logging results in 213 | TAP format 214 | 215 | Args: 216 | expected: Expected value 217 | result: Action value 218 | message: String describing the action being evaluated 219 | 220 | Returns: 221 | legnth: If the 'expected' result is a string, returns the length of 222 | the string, otherwise 0 223 | 224 | Raises: 225 | None 226 | """ 227 | if isinstance(expected, _ValueStr): 228 | message += ' (expanded from %s)' % repr(expected) 229 | if expected not in self.values: 230 | message += ' value not provided' 231 | self.log_fail(message) 232 | return 0 233 | expected = self.values[expected] 234 | 235 | if isinstance(expected, str): 236 | if not result.startswith(expected): 237 | self.log_ok(message) 238 | return len(expected) 239 | else: 240 | if result != expected: 241 | self.log_ok(message) 242 | return 0 243 | 244 | if self.debug: 245 | self.log('these are expected to be different:') 246 | self.log('expected: %s' % repr(expected)) 247 | self.log('result: %s' % repr(result)) 248 | self.log_fail(message) 249 | return 0 250 | 251 | def log_ok(self, message): 252 | """ Log a test that passed in the TAP format 253 | 254 | Args: 255 | message: String describing the action that 'passed' 256 | 257 | Returns: 258 | None 259 | 260 | Raises: 261 | None 262 | """ 263 | self.passed += 1 264 | self.count += 1 265 | self.logs.append("ok %d - %s" % (self.count, message)) 266 | 267 | def log_fail(self, message): 268 | """ Log a test that failed in the TAP format 269 | 270 | Args: 271 | message: String describing the action that 'passed' 272 | 273 | Returns: 274 | None 275 | 276 | Raises: 277 | None 278 | """ 279 | self.failed += 1 280 | self.count += 1 281 | self.logs.append("not ok %d - %s" % (self.count, message)) 282 | raise TestFailure('failed: %s' % message) 283 | 284 | def log(self, message): 285 | """ Log diagnostic information in the TAP format 286 | 287 | Args: 288 | message: String being logged 289 | 290 | Returns: 291 | None 292 | 293 | Raises: 294 | None 295 | """ 296 | self.logs.append("# %s" % message) 297 | 298 | def sleep(self, value): 299 | """ Sleep a specified amount 300 | 301 | Args: 302 | value: Amount of time to sleep, specified in miliseconds 303 | 304 | Returns: 305 | None 306 | 307 | Raises: 308 | None 309 | """ 310 | time.sleep(value) 311 | self.log_ok("slept %f" % value) 312 | 313 | def declare(self, values): 314 | """ Declare variables for use within the current CB communication 315 | iteration 316 | 317 | Args: 318 | values: Dictionary of key/value pair values to be set 319 | 320 | Returns: 321 | None 322 | 323 | Raises: 324 | None 325 | """ 326 | self.values.update(values) 327 | 328 | set_values = [repr(x) for x in values.keys()] 329 | self.log_ok("set values: %s" % ', '.join(set_values)) 330 | 331 | def _perform_match(self, match, data, invert=False): 332 | """ Validate the data read from the CB is as expected 333 | 334 | Args: 335 | match: Pre-parsed expression to validate the data from the CB 336 | data: Data read from the CB 337 | 338 | Returns: 339 | None 340 | 341 | Raises: 342 | None 343 | """ 344 | offset = 0 345 | for item in match: 346 | if isinstance(item, str): 347 | if invert: 348 | offset += self.is_not(item, data[offset:], 349 | 'match: not string') 350 | else: 351 | offset += self.is_ok(item, data[offset:], 'match: string') 352 | elif hasattr(item, 'match'): 353 | match = item.match(data[offset:]) 354 | if match: 355 | if invert: 356 | if self.debug: 357 | self.log('pattern: %s' % repr(item.pattern)) 358 | self.log('data: %s' % repr(data[offset:])) 359 | self.log_fail('match: not pcre') 360 | else: 361 | self.log_ok('match: pcre') 362 | offset += match.end() 363 | else: 364 | if invert: 365 | self.log_ok('match: not pcre') 366 | else: 367 | if self.debug: 368 | self.log('pattern: %s' % repr(item.pattern)) 369 | self.log('data: %s' % repr(data[offset:])) 370 | self.log_fail('match: pcre') 371 | else: 372 | raise Exception('unknown match type: %s' % repr(item)) 373 | 374 | def _perform_expr(self, expr, key, data): 375 | """ Extract a value from the value read from the CB using 'slice' or 376 | 'pcre' 377 | 378 | Args: 379 | expr: Pre-parsed expression to extract the value 380 | key: Key to store the value in the instance iteration 381 | data: Data read from the CB 382 | 383 | Returns: 384 | None 385 | 386 | Raises: 387 | None 388 | """ 389 | value = None 390 | 391 | # self.log('PERFORMING EXPR (%s): %s' % (key, repr(expr))) 392 | # self.log('DATA: %s' % repr(data)) 393 | if isinstance(expr, slice): 394 | value = data[expr] 395 | elif isinstance(expr, RegexMatch): 396 | match = expr.match(data) 397 | if match: 398 | try: 399 | value = match.group(expr.group) 400 | except IndexError: 401 | self.log_fail('match group unavailable') 402 | else: 403 | self.log_fail('match failed') 404 | 405 | else: 406 | self.log_fail('unknown expr type: %s' % repr(expr)) 407 | 408 | if value is not None: 409 | self.values[key] = value 410 | if self.debug: 411 | self.log('set %s to %s' % (key, value.encode('hex'))) 412 | self.log_ok('set %s' % (key)) 413 | 414 | def _read_len(self, read_len): 415 | """ 416 | Read a specified size, but only ever get 4096 bytes from the socket 417 | """ 418 | if len(self._read_buffer) >= read_len: 419 | data = self._read_buffer[:read_len] 420 | self._read_buffer = self._read_buffer[read_len:] 421 | return data 422 | 423 | data = [self._read_buffer] 424 | data_len = len(self._read_buffer) 425 | while data_len < read_len: 426 | left = read_len - data_len 427 | data_read = self.sock.recv(max(4096, left)) 428 | if len(data_read) == 0: 429 | self.log_fail('recv failed. (%s so far)' % repr(data)) 430 | self._read_buffer = ''.join(data) 431 | return '' 432 | 433 | data.append(data_read) 434 | data_len += len(data_read) 435 | 436 | data = ''.join(data) 437 | self._read_buffer = data[read_len:] 438 | return data[:read_len] 439 | 440 | def _read_delim(self, delim): 441 | """ 442 | Read until a delimiter is found, but only ever get 4096 bytes from the 443 | socket 444 | """ 445 | while delim not in self._read_buffer: 446 | data_read = self.sock.recv(4096) 447 | if len(data_read) == 0: 448 | self.log_fail('recv failed. No data returned.') 449 | return '' 450 | self._read_buffer += data_read 451 | 452 | depth = self._read_buffer.index(delim) + len(delim) 453 | data = self._read_buffer[:depth] 454 | self._read_buffer = self._read_buffer[depth:] 455 | return data 456 | 457 | def read(self, read_args): 458 | """ Read data from the CB, validating the results 459 | 460 | Args: 461 | read_args: Dictionary of arguments 462 | 463 | Returns: 464 | None 465 | 466 | Raises: 467 | Exception: if 'expr' argument is provided and 'assign' is not 468 | """ 469 | data = '' 470 | try: 471 | if 'length' in read_args: 472 | data = self._read_len(read_args['length']) 473 | self.is_ok(read_args['length'], len(data), 'read length') 474 | elif 'delim' in read_args: 475 | data = self._read_delim(read_args['delim']) 476 | except socket.error as err: 477 | self.log_fail('recv failed: %s' % str(err)) 478 | 479 | if 'echo' in read_args and self.debug: 480 | assert read_args['echo'] in ['yes', 'no', 'ascii'] 481 | 482 | if 'yes' == read_args['echo']: 483 | self.log('received %s' % data.encode('hex')) 484 | elif 'ascii' == read_args['echo']: 485 | self.log('received %s' % repr(data)) 486 | 487 | if 'match' in read_args: 488 | self._perform_match(read_args['match']['values'], data, 489 | read_args['match']['invert']) 490 | 491 | if 'expr' in read_args: 492 | assert 'assign' in read_args 493 | self._perform_expr(read_args['expr'], read_args['assign'], data) 494 | 495 | def _send_all(self, data, max_send=None): 496 | total_sent = 0 497 | while total_sent < len(data): 498 | if max_send is not None: 499 | sent = self.sock.send(data[total_sent:total_sent+max_send]) 500 | # allow the kernel a chance to forward the data 501 | time.sleep(0.00001) 502 | else: 503 | sent = self.sock.send(data[total_sent:]) 504 | if sent == 0: 505 | return total_sent 506 | total_sent += sent 507 | 508 | return total_sent 509 | 510 | def write(self, args): 511 | """ Write data to the CB 512 | 513 | Args: 514 | args: Dictionary of arguments 515 | 516 | Returns: 517 | None 518 | 519 | Raises: 520 | None 521 | """ 522 | data = [] 523 | for value in args['value']: 524 | if isinstance(value, _ValueStr): 525 | if value not in self.values: 526 | self.log_fail('write failed: %s not available' % value) 527 | return 528 | data.append(self.values[value]) 529 | else: 530 | data.append(value) 531 | to_send = ''.join(data) 532 | 533 | if self.debug: 534 | if args['echo'] == 'yes': 535 | self.log('writing: %s' % to_send.encode('hex')) 536 | elif args['echo'] == 'ascii': 537 | self.log('writing: %s' % repr(to_send)) 538 | 539 | try: 540 | sent = self._send_all(to_send, self.max_send) 541 | if sent != len(to_send): 542 | self.log_fail('write failed. wrote %d of %d bytes' % 543 | (sent, len(to_send))) 544 | return 545 | else: 546 | self.log_ok('write: sent %d bytes' % sent) 547 | except socket.error: 548 | self.log_fail('write failed') 549 | 550 | def _encode(self, records): 551 | """ 552 | record is a list of records in the format (type, data) 553 | 554 | Current wire format: 555 | RECORD_COUNT (DWORD) 556 | record_0_type (DWORD) 557 | record_0_len (DWORD) 558 | record_0_data (record_0_len bytes) 559 | record_N_type (DWORD) 560 | record_N_len (DWORD) 561 | record_N_data (record_N_len bytes) 562 | """ 563 | 564 | packed = [] 565 | for record_type, data in records: 566 | packed.append(struct.pack(':). 761 | 762 | Args: 763 | data: XML element defining a slice 764 | 765 | Returns: 766 | None 767 | 768 | Raises: 769 | AssertionError: If the tag text is not empty 770 | AssertionError: If the tag name is not 'slice' 771 | """ 772 | assert data.tag == 'slice' 773 | assert data.text is None 774 | begin = int(POV.get_attribute(data, 'begin', '0')) 775 | end = POV.get_attribute(data, 'end', None) 776 | if end is not None: 777 | end = int(end) 778 | return slice(begin, end) 779 | 780 | @staticmethod 781 | def compile_string_match(data): 782 | """ Parse a string into an 'asciic' format, for easy use. Allows for 783 | \\r, \\n, \\t, \\\\, and hex values specified via C Style \\x notation. 784 | 785 | Args: 786 | data: String to be parsed into a 'asciic' supported value. 787 | 788 | Returns: 789 | None 790 | 791 | Raises: 792 | AssertionError: if either of two characters following '\\x' are not 793 | hexidecimal values 794 | Exception: if the escaped value is not one of the supported escaped 795 | strings (See above) 796 | """ 797 | # \\, \r, \n, \t \x(HEX)(HEX) 798 | data = str(data) # no unicode support 799 | state = 0 800 | out = [] 801 | chars = {'n': '\n', 'r': '\r', 't': '\t', '\\': '\\'} 802 | hex_chars = '0123456789abcdef' 803 | hex_tmp = '' 804 | for val in data: 805 | if state == 0: 806 | if val != '\\': 807 | out.append(val) 808 | continue 809 | state = 1 810 | elif state == 1: 811 | if val in chars: 812 | out.append(chars[val]) 813 | state = 0 814 | continue 815 | elif val == 'x': 816 | state = 2 817 | else: 818 | raise Exception('invalid asciic string (%s)' % repr(data)) 819 | elif state == 2: 820 | assert val.lower() in hex_chars 821 | hex_tmp = val 822 | state = 3 823 | else: 824 | assert val.lower() in hex_chars 825 | hex_tmp += val 826 | out.append(hex_tmp.decode('hex')) 827 | hex_tmp = '' 828 | state = 0 829 | return ''.join(out) 830 | 831 | @staticmethod 832 | def compile_string(data_type, data): 833 | """ Converts a string from a specified format into the converted into 834 | an optimized form for later use 835 | 836 | Args: 837 | data_type: Which 'compiler' to use 838 | data: String to be 'compiled' 839 | 840 | Returns: 841 | None 842 | 843 | Raises: 844 | None 845 | """ 846 | funcs = { 847 | 'pcre': POV.compile_pcre, 848 | 'asciic': POV.compile_string_match, 849 | 'hex': POV.compile_hex_match, 850 | } 851 | return funcs[data_type](data) 852 | 853 | @staticmethod 854 | def get_child(data, name): 855 | """ Retrieve the specified 'BeautifulSoup' child from the current 856 | element 857 | 858 | Args: 859 | data: Current element that should be searched 860 | name: Name of child element to be returned 861 | 862 | Returns: 863 | child: BeautifulSoup element 864 | 865 | Raises: 866 | AssertionError: if a child with the specified name is not contained 867 | in the specified element 868 | """ 869 | child = data.findChild(name) 870 | assert child is not None 871 | return child 872 | 873 | @staticmethod 874 | def get_attribute(data, name, default=None, allowed=None): 875 | """ Return the named attribute from the current element. 876 | 877 | Args: 878 | data: Element to read the named attribute 879 | name: Name of attribute 880 | default: Optional default value to be returne if the attribute is 881 | not provided 882 | allowed: Optional list of allowed values 883 | 884 | Returns: 885 | None 886 | 887 | Raises: 888 | AssertionError: if the value is not in the specified allowed values 889 | """ 890 | value = default 891 | if name in data.attrib: 892 | value = data.attrib[name] 893 | if allowed is not None: 894 | assert value in allowed 895 | return value 896 | 897 | def add_variable(self, name): 898 | """ Add a variable the POV interaction 899 | 900 | This allows for insurance of runtime access of initialized variables 901 | during parse time. 902 | 903 | Args: 904 | name: Name of variable 905 | 906 | Returns: 907 | None 908 | 909 | Raises: 910 | None 911 | """ 912 | if name not in self._variables: 913 | self._variables.append(name) 914 | 915 | def has_variable(self, name): 916 | """ Verify a variable has been defined 917 | 918 | Args: 919 | name: Name of variable 920 | 921 | Returns: 922 | None 923 | 924 | Raises: 925 | None 926 | """ 927 | return name in self._variables 928 | 929 | def add_step(self, step_type, data): 930 | """ Add a step to the POV iteraction sequence 931 | 932 | Args: 933 | step_type: Type of interaction 934 | data: Data for the interaction 935 | 936 | Returns: 937 | None 938 | 939 | Raises: 940 | AssertionError: if the step_type is not one of the pre-defined 941 | types 942 | """ 943 | assert step_type in ['declare', 'sleep', 'read', 'write'] 944 | self._steps.append((step_type, data)) 945 | 946 | def parse_delay(self, data): 947 | """ Parse a 'delay' interaction XML element 948 | 949 | Args: 950 | data: XML Element defining the 'delay' iteraction 951 | 952 | Returns: 953 | None 954 | 955 | Raises: 956 | AssertionError: if there is not only one child in the 'delay' 957 | element 958 | """ 959 | self.add_step('sleep', float(data.text) / 1000) 960 | 961 | def parse_decl(self, data): 962 | """ Parse a 'decl' interaction XML element 963 | 964 | Args: 965 | data: XML Element defining the 'decl' iteraction 966 | 967 | Returns: 968 | None 969 | 970 | Raises: 971 | AssertionError: If there is not two children in the 'decl' element 972 | AssertionError: If the 'var' child element is not defined 973 | AssertionError: If the 'var' child element does not have only one 974 | child 975 | AssertionError: If the 'value' child element is not defined 976 | AssertionError: If the 'value' child element does not have only one 977 | child 978 | """ 979 | assert len(data) == 2 980 | assert data[0].tag == 'var' 981 | key = data[0].text 982 | 983 | values = [] 984 | assert data[1].tag == 'value' 985 | assert len(data[1]) > 0 986 | for item in data[1]: 987 | values.append(self.parse_data(item)) 988 | 989 | value = ''.join(values) 990 | 991 | self.add_variable(key) 992 | self.add_step('declare', {key: value}) 993 | 994 | def parse_assign(self, data): 995 | """ Parse an 'assign' XML element 996 | 997 | Args: 998 | data: XML Element defining the 'assign' iteraction 999 | 1000 | Returns: 1001 | None 1002 | 1003 | Raises: 1004 | AssertionError: If the 'var' element is not defined 1005 | AssertionError: If the 'var' element does not have only one child 1006 | AssertionError: If the 'pcre' or 'slice' element of the 'assign' 1007 | element is not defined 1008 | """ 1009 | 1010 | assert data.tag == 'assign' 1011 | assert data[0].tag == 'var' 1012 | assign = data[0].text 1013 | self.add_variable(assign) 1014 | 1015 | if data[1].tag == 'pcre': 1016 | expression = POV.compile_string('pcre', data[1].text) 1017 | group = POV.get_attribute(data[1], 'group', '0') 1018 | expression.group = int(group) 1019 | 1020 | elif data[1].tag == 'slice': 1021 | expression = POV.compile_slice(data[1]) 1022 | else: 1023 | raise Exception("unknown expr tag: %s" % data[1].tag) 1024 | 1025 | return assign, expression 1026 | 1027 | def parse_read(self, data): 1028 | """ Parse a 'read' interaction XML element 1029 | 1030 | Args: 1031 | data: XML Element defining the 'read' iteraction 1032 | 1033 | Returns: 1034 | None 1035 | 1036 | Raises: 1037 | AssertionError: If the 'delim' element is defined, it does not have 1038 | only one child 1039 | AssertionError: If the 'length' element is defined, it does not 1040 | have only one child 1041 | AssertionError: If both 'delim' and 'length' are specified 1042 | AssertionError: If neither 'delim' and 'length' are specified 1043 | AssertionError: If the 'match' element is defined, it does not have 1044 | only one child 1045 | AssertionError: If the 'timeout' element is defined, it does not 1046 | have only one child 1047 | """ 1048 | # 1049 | # 1050 | 1051 | # defaults 1052 | read_args = {'timeout': 0} 1053 | 1054 | # yay, pass by reference. this allows us to just return when we're out 1055 | # of sub-elements. 1056 | self.add_step('read', read_args) 1057 | 1058 | read_args['echo'] = POV.get_attribute(data, 'echo', 'no', ['yes', 'no', 1059 | 'ascii']) 1060 | 1061 | assert len(data) > 0 1062 | 1063 | children = data.getchildren() 1064 | 1065 | read_until = children.pop(0) 1066 | 1067 | if read_until.tag == 'length': 1068 | read_args['length'] = int(read_until.text) 1069 | elif read_until.tag == 'delim': 1070 | read_args['delim'] = self.parse_data(read_until, 'asciic', 1071 | ['asciic', 'hex']) 1072 | else: 1073 | raise Exception('invalid first argument') 1074 | 1075 | if len(children) == 0: 1076 | return 1077 | current = children.pop(0) 1078 | 1079 | if current.tag == 'match': 1080 | invert = False 1081 | if POV.get_attribute(current, 'invert', 'false', 1082 | ['false', 'true']) == 'true': 1083 | invert = True 1084 | 1085 | assert len(current) > 0 1086 | 1087 | values = [] 1088 | for item in current: 1089 | if item.tag == 'data': 1090 | values.append(self.parse_data(item, 'asciic', 1091 | ['asciic', 'hex'])) 1092 | elif item.tag == 'pcre': 1093 | values.append(POV.compile_string('pcre', item.text)) 1094 | elif item.tag == 'var': 1095 | values.append(_ValueStr(item.text)) 1096 | else: 1097 | raise Exception('invalid data.match element name: %s' % 1098 | item.name) 1099 | 1100 | read_args['match'] = {'invert': invert, 'values': values} 1101 | 1102 | if len(children) == 0: 1103 | return 1104 | current = children.pop(0) 1105 | 1106 | if current.tag == 'assign': 1107 | assign, expr = self.parse_assign(current) 1108 | read_args['assign'] = assign 1109 | read_args['expr'] = expr 1110 | if len(children) == 0: 1111 | return 1112 | current = children.pop(0) 1113 | 1114 | assert current.tag == 'timeout', "%s tag, not 'timeout'" % current.tag 1115 | read_args['timeout'] = int(current.text) 1116 | 1117 | @staticmethod 1118 | def parse_data(data, default=None, formats=None): 1119 | """ Parse a 'data' element' 1120 | 1121 | Args: 1122 | data: XML Element defining the 'data' item 1123 | formats: Allowed formats 1124 | 1125 | Returns: 1126 | A 'normalized' string 1127 | 1128 | Raises: 1129 | AssertionError: If element is not named 'data' 1130 | AssertionError: If the element has more than one child 1131 | """ 1132 | 1133 | if formats is None: 1134 | formats = ['asciic', 'hex'] 1135 | 1136 | if default is None: 1137 | default = 'asciic' 1138 | 1139 | assert data.tag in ['data', 'delim', 'value'] 1140 | assert len(data.text) > 0 1141 | data_format = POV.get_attribute(data, 'format', default, formats) 1142 | return POV.compile_string(data_format, data.text) 1143 | 1144 | def parse_write(self, data): 1145 | """ Parse a 'write' interaction XML element 1146 | 1147 | Args: 1148 | data: XML Element defining the 'write' iteraction 1149 | 1150 | Returns: 1151 | None 1152 | 1153 | Raises: 1154 | AssertionError: If any of the child elements do not have the name 1155 | 'data' 1156 | AssertionError: If any of the 'data' elements have more than one 1157 | child 1158 | """ 1159 | # 1160 | # 1161 | # 1162 | 1163 | # self._add_variables(name) 1164 | 1165 | values = [] 1166 | assert len(data) > 0 1167 | for val in data: 1168 | if val.tag == 'data': 1169 | values.append(self.parse_data(val)) 1170 | else: 1171 | assert val.tag == 'var' 1172 | assert self.has_variable(val.text) 1173 | values.append(_ValueStr(val.text)) 1174 | 1175 | echo = POV.get_attribute(data, 'echo', 'no', ['yes', 'no', 'ascii']) 1176 | self.add_step('write', {'value': values, 'echo': echo}) 1177 | 1178 | def parse(self, raw_data, filename=None): 1179 | """ Parse the specified replay XML 1180 | 1181 | Args: 1182 | raw_data: Raw XML to be parsed 1183 | 1184 | Returns: 1185 | None 1186 | 1187 | Raises: 1188 | AssertionError: If the XML file has more than top-level children 1189 | (Expected: pov and doctype) 1190 | AssertionError: If the first child is not a Doctype instance 1191 | AssertionError: If the doctype does not specify the replay.dtd 1192 | AssertionError: If the second child is not named 'pov' 1193 | AssertionError: If the 'pov' element has more than two elements 1194 | AssertionError: If the 'pov' element does not contain a 'cbid' 1195 | element 1196 | AssertionError: If the 'cbid' element value is blank 1197 | """ 1198 | 1199 | self.filename = filename 1200 | 1201 | tree = ET.fromstring(raw_data) 1202 | assert tree.tag == 'pov' 1203 | assert len(tree) in [2, 3] 1204 | 1205 | assert tree[0].tag == 'cbid' 1206 | assert len(tree[0].tag) > 0 1207 | self.name = tree[0].text 1208 | 1209 | assert tree[1].tag in ['seed', 'replay'] 1210 | 1211 | seed_tree = None 1212 | replay_tree = None 1213 | if tree[1].tag == 'seed': 1214 | seed_tree = tree[1] 1215 | replay_tree = tree[2] 1216 | else: 1217 | seed_tree = None 1218 | replay_tree = tree[1] 1219 | 1220 | if seed_tree is not None: 1221 | assert len(seed_tree.tag) > 0 1222 | seed = seed_tree.text 1223 | assert len(seed) == 96 1224 | if self.seed is not None: 1225 | print "# Seed is set by XML and command line, using XML seed" 1226 | self.seed = seed.decode('hex') 1227 | 1228 | parse_fields = { 1229 | 'decl': self.parse_decl, 1230 | 'read': self.parse_read, 1231 | 'write': self.parse_write, 1232 | 'delay': self.parse_delay, 1233 | } 1234 | 1235 | for replay_element in replay_tree: 1236 | assert replay_element.tag in parse_fields 1237 | parse_fields[replay_element.tag](replay_element) 1238 | 1239 | def dump(self): 1240 | """ Print the steps in the POV, via repr 1241 | 1242 | Args: 1243 | None 1244 | 1245 | Returns: 1246 | None 1247 | 1248 | Raises: 1249 | None 1250 | """ 1251 | for step in self._steps: 1252 | print repr(step) 1253 | 1254 | 1255 | class Results(object): 1256 | """ Class to handle gathering result stats from Throw() instances """ 1257 | def __init__(self): 1258 | self.passed = 0 1259 | self.failed = 0 1260 | self.errors = 0 1261 | self.full_passed = 0 1262 | 1263 | def cb_pov_result(self, results): 1264 | """ 1265 | Throw() result callback 1266 | 1267 | Arguments: 1268 | results: tuple containing the number of results passed, failed, and 1269 | a list of logs 1270 | 1271 | Returns: 1272 | None 1273 | 1274 | Raises: 1275 | None 1276 | """ 1277 | got_passed, got_failed, got_logs = results 1278 | print '\n'.join(got_logs) 1279 | self.passed += got_passed 1280 | self.failed += got_failed 1281 | if got_failed > 0: 1282 | self.errors += 1 1283 | else: 1284 | self.full_passed += 1 1285 | 1286 | def init_worker(): 1287 | signal.signal(signal.SIGINT, signal.SIG_IGN) 1288 | 1289 | def run_pov(src, dst, pov_info, timeout, debug, max_send, negotiate, cb_seed, munge_seed): 1290 | """ 1291 | Parse and Throw a POV/Poll 1292 | 1293 | Arguments: 1294 | src: IP/Port tuple for the source of the connection 1295 | dst: IP/Port tuple for the destination of the connection 1296 | pov_info: content/filename tuple of the POV 1297 | timeout: How long the POV communication is allowed to take 1298 | debug: Flag to enable debug logs 1299 | max_send: Maximum amount of data for each send request 1300 | negotiate: Should the poller negotiate with cb-server 1301 | cb_seed: specify a seed to use in the pools 1302 | munge_seed: should the seed be xored before use 1303 | 1304 | Returns: 1305 | The number of passed tests 1306 | The number of failed tests 1307 | A list containing the logs 1308 | 1309 | Raises: 1310 | Exception if parsing the POV times out 1311 | """ 1312 | 1313 | xml, filename = pov_info 1314 | pov = POV(seed=cb_seed) 1315 | error = None 1316 | try: 1317 | with Timeout(30): 1318 | pov.parse(xml, filename=filename) 1319 | except TimeoutException: 1320 | error = "parsing %s timed out" % filename 1321 | except ET.ParseError as err: 1322 | error = "parsing %s errored: %s" % (filename, str(err)) 1323 | 1324 | if munge_seed: 1325 | pov.mutate_seed() 1326 | 1327 | thrower = Throw(src, dst, pov, timeout, debug, max_send, negotiate) 1328 | if error is not None: 1329 | try: 1330 | thrower.log_fail(error) 1331 | except TestFailure: 1332 | pass # log_fail throws an exception on purpose 1333 | else: 1334 | try: 1335 | with Timeout(timeout): 1336 | thrower.run() 1337 | except TimeoutException: 1338 | try: 1339 | thrower.log_fail('pov timed out') 1340 | except TestFailure: 1341 | # this exception should always happen. don't stop because 1342 | # one timed out. 1343 | pass 1344 | thrower.dump() 1345 | 1346 | return thrower.passed, thrower.failed, thrower.logs 1347 | 1348 | 1349 | def main(): 1350 | """ Parse and Throw the POVs """ 1351 | parser = argparse.ArgumentParser(description='Send CGC Polls and POVs') 1352 | required = parser.add_argument_group(title='required arguments') 1353 | required.add_argument('--host', required=True, type=str, 1354 | help='IP address of CB server') 1355 | required.add_argument('--port', required=True, type=int, 1356 | help='PORT of the listening CB') 1357 | required.add_argument('files', metavar='xml_file', type=str, nargs='+', 1358 | help='POV/Poll XML file') 1359 | parser.add_argument('--source_host', required=False, type=str, default='', 1360 | help='Source IP address to use in connections') 1361 | parser.add_argument('--source_port', required=False, type=int, 1362 | default=0, help='Source port to use in connections') 1363 | parser.add_argument('--concurrent', required=False, type=int, default=1, 1364 | help='Number of Polls/POVs to throw concurrently') 1365 | parser.add_argument('--timeout', required=False, type=int, default=None, 1366 | help='Connect timeout') 1367 | parser.add_argument('--munge_seed', required=False, action='store_true', 1368 | default=False, help='Bit invert the seed for testing') 1369 | parser.add_argument('--failure_ok', required=False, action='store_true', 1370 | default=False, 1371 | help='Failures for this test are accepted') 1372 | parser.add_argument('--debug', required=False, action='store_true', 1373 | default=False, help='Enable debugging output') 1374 | parser.add_argument('--max_send', required=False, type=int, 1375 | help='Maximum amount of data in each send call') 1376 | parser.add_argument('--negotiate', required=False, action='store_true', 1377 | default=False, help='The CB seed should be negotiated') 1378 | parser.add_argument('--cb_seed', required=False, type=str, 1379 | help='Specify the CB Seed') 1380 | 1381 | args = parser.parse_args() 1382 | 1383 | assert args.concurrent > 0, "Conccurent count must be less than 1" 1384 | 1385 | if args.cb_seed is not None and not args.negotiate: 1386 | raise Exception('CB Seeds can only be set with seed negotiation') 1387 | 1388 | povs = [] 1389 | for pov_filename in args.files: 1390 | pov_xml = [] 1391 | if pov_filename.endswith('.xml'): 1392 | with open(pov_filename, 'rb') as pov_fh: 1393 | pov_xml.append(pov_fh.read()) 1394 | elif pov_filename.endswith('.zip'): 1395 | with zipfile.ZipFile(pov_filename, 'r') as pov_fh: 1396 | for filename in pov_fh.namelist(): 1397 | pov_xml.append(pov_fh.read(filename)) 1398 | else: 1399 | raise Exception('unknown POV format') 1400 | 1401 | for xml in pov_xml: 1402 | povs.append((xml, pov_filename)) 1403 | 1404 | result_handler = Results() 1405 | pool = multiprocessing.Pool(args.concurrent, init_worker) 1406 | pool_responses = [] 1407 | try: 1408 | for pov in povs: 1409 | pov_args = ((args.source_host, args.source_port), 1410 | (args.host, args.port), pov, args.timeout, args.debug, 1411 | args.max_send, args.negotiate, args.cb_seed, 1412 | args.munge_seed) 1413 | if args.concurrent > 1: 1414 | pool_response = pool.apply_async(run_pov, args=pov_args, 1415 | callback=result_handler.cb_pov_result) 1416 | pool_responses.append(pool_response) 1417 | else: 1418 | result_handler.cb_pov_result(run_pov(*pov_args)) 1419 | 1420 | for response in pool_responses: 1421 | response.get() 1422 | 1423 | except KeyboardInterrupt: 1424 | print "# Interrupted. Logging as error" 1425 | result_handler.errors += 1 1426 | if args.concurrent > 1: 1427 | pool.terminate() 1428 | finally: 1429 | if args.concurrent > 1: 1430 | pool.close() 1431 | pool.join() 1432 | 1433 | print "# total tests passed: %d" % result_handler.passed 1434 | print "# total tests failed: %d" % result_handler.failed 1435 | print "# polls passed: %d" % result_handler.full_passed 1436 | print "# polls failed: %d" % result_handler.errors 1437 | 1438 | if args.failure_ok: 1439 | return 0 1440 | else: 1441 | return result_handler.errors != 0 1442 | 1443 | if __name__ == "__main__": 1444 | exit(main()) 1445 | -------------------------------------------------------------------------------- /cb-replay-pov: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | CB POV / Poll communication verification tool 5 | 6 | Copyright (C) 2014 - Brian Caswell 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | 26 | This tool allows for deterministic communication to a CGC Challenge Binary 27 | using a Challenge Binary as input. 28 | 29 | 1 - http://testanything.org/ 30 | """ 31 | 32 | import subprocess 33 | import multiprocessing 34 | import random 35 | import sys 36 | import argparse 37 | import os 38 | import signal 39 | import re 40 | import socket 41 | import struct 42 | import time 43 | 44 | class TimeoutException(Exception): 45 | """ Exception to be used by Timeout(), to allow catching of timeout 46 | exceptions """ 47 | pass 48 | 49 | 50 | class TestFailure(Exception): 51 | """ Exception to be used by Throw(), to allow catching of test failures """ 52 | pass 53 | 54 | 55 | class Timeout(object): 56 | """ Timeout - A class to use within 'with' for timing out a block via 57 | exceptions and alarm.""" 58 | 59 | def __init__(self, seconds): 60 | self.seconds = seconds 61 | 62 | @staticmethod 63 | def cb_handle_timeout(signum, frame): 64 | """ SIGALRM signal handler callback """ 65 | raise TimeoutException("timed out") 66 | 67 | def __enter__(self): 68 | if self.seconds > 0: 69 | signal.signal(signal.SIGALRM, self.cb_handle_timeout) 70 | signal.alarm(self.seconds) 71 | 72 | def __exit__(self, exit_type, exit_value, traceback): 73 | if self.seconds: 74 | signal.alarm(0) 75 | 76 | 77 | def ptrace_traceme(): 78 | from ctypes import cdll 79 | from ctypes.util import find_library 80 | from ctypes import c_long, c_ulong 81 | 82 | LIBC_FILENAME = find_library('c') 83 | libc = cdll.LoadLibrary(LIBC_FILENAME) 84 | 85 | _ptrace = libc.ptrace 86 | _ptrace.argtypes = (c_ulong, c_ulong, c_ulong, c_ulong) 87 | _ptrace.restype = c_ulong 88 | 89 | PTRACE_TRACEME = 0 90 | 91 | result = _ptrace(PTRACE_TRACEME, 0, 0, 0) 92 | result_signed = c_long(result).value 93 | return result_signed 94 | 95 | def launch_gdb_proxy(pid, attach_port): 96 | gdb_pid = os.fork() 97 | if gdb_pid == 0: 98 | subprocess.call(['/usr/bin/gdbserver', ':%d' % attach_port, '--attach', '%d' % pid]) 99 | exit(0) 100 | else: 101 | # ugh. 102 | time.sleep(2) 103 | result = os.waitpid(gdb_pid, os.WNOHANG) 104 | if result != (0, 0): 105 | print "Unable to attach to the process" 106 | return 0 107 | 108 | return gdb_pid 109 | 110 | class Throw(object): 111 | """Throw - Perform the interactions with a CB 112 | 113 | This class implements the basic methods to interact with a CB, verifying 114 | the interaction works as expected. 115 | 116 | Usage: 117 | a = Throw((source_ip, source_port), (target_ip, target_port), POV, 118 | timeout, should_debug, negotiate, cb_seed, attach_port) 119 | a.run() 120 | 121 | Attributes: 122 | source: touple of host and port for the outbound connection 123 | target: touple of host and port for the CB 124 | 125 | count: Number of actions performed 126 | 127 | debug: Is debugging enabled 128 | 129 | failed: Number of actions that did not work as expected 130 | 131 | passed: Number of actions that did worked as expected 132 | 133 | pov: POV, as defined by POV() 134 | 135 | sock: TCP Socket to the CB 136 | 137 | timeout: connection timeout 138 | 139 | values: Variable dictionary 140 | 141 | logs: all of the output from the interactions 142 | 143 | negotiate: Should the PRNG be negotiated with the CB 144 | 145 | """ 146 | def __init__(self, source, target, pov, timeout, debug, negotiate, cb_seed, attach_port, max_send, pov_seed): 147 | self.times = 10 148 | self.source = source 149 | self.target = target 150 | self.count = 0 151 | self.failed = 0 152 | self.passed = 0 153 | self.pov = pov 154 | self.debug = debug 155 | self.sock = None 156 | self.timeout = timeout 157 | self.negotiate_fd_fd = negotiate 158 | self.negotiate = negotiate 159 | self.cb_seed = cb_seed 160 | self.logs = [] 161 | self.attach_port = attach_port 162 | self.max_send = max_send 163 | self.pov_seed = pov_seed 164 | 165 | if self.cb_seed is None: 166 | self.cb_seed = os.urandom(48) 167 | else: 168 | self.cb_seed = self.cb_seed.decode('hex') 169 | 170 | def setup_negotiation(self): 171 | if not self.is_pov(): 172 | return 173 | self.negotiate_fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 174 | self.negotiate_fd.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) 175 | self.negotiate_fd.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack('ii', 1, 5)) 176 | self.negotiate_fd.bind(('',0)) 177 | self.negotiate_fd.listen(self.times) 178 | negotiate_address = self.negotiate_fd.getsockname() 179 | 180 | if self.debug: 181 | self.log("negotiation listen at: %s" % repr(negotiate_address)) 182 | 183 | self.negotiate_fd_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 184 | self.negotiate_fd_client.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) 185 | self.negotiate_fd_client.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack('ii', 1, 5)) 186 | self.negotiate_fd_client.connect(negotiate_address) 187 | 188 | def log(self, data): 189 | print "# %s" % data 190 | 191 | def count_bits_set(self, value): 192 | count = 0 193 | while value: 194 | value &= value - 1 195 | count += 1 196 | return count 197 | 198 | def recv_all(self, sock, data_len): 199 | data = '' 200 | while len(data) < data_len: 201 | read = sock.recv(data_len - len(data)) 202 | assert len(read) 203 | data += read 204 | return data 205 | 206 | def pov_negotiate(self): 207 | MIN_BITS = 20 208 | if not self.is_pov(): 209 | return 210 | 211 | if self.debug: 212 | self.log("negotiating") 213 | connection, address = self.negotiate_fd.accept() 214 | 215 | # as soon as we've accepted one, drop this server. 216 | self.negotiate_fd.close() 217 | 218 | data = self.recv_all(connection, 4) 219 | pov_type = struct.unpack(' 7: 238 | self.log('invalid GPR') 239 | accept = False 240 | 241 | if accept: 242 | ip = random.randint(0, 0xFFFFFFFF) & ipmask 243 | reg = random.randint(0, 0xFFFFFFFF) & regmask 244 | 245 | self.log('type 1 masks: %08x %08x' % (ipmask, regmask)) 246 | self.log('type 1 pov: %08x %08x %d' % (ip, reg, regnum)) 247 | 248 | pov_values = struct.pack(' 0 and not self.attach_port: 391 | signal.alarm(self.timeout) 392 | 393 | os.dup2(self.sock.fileno(), sys.stdin.fileno()) 394 | os.dup2(self.sock.fileno(), sys.stdout.fileno()) 395 | 396 | if not self.debug: 397 | null = os.open('/dev/null', 0) 398 | os.dup2(null, 2) 399 | os.close(null) 400 | 401 | if self.is_pov(): 402 | os.dup2(self.negotiate_fd_client.fileno(), 3) 403 | 404 | if self.attach_port: 405 | ptrace_traceme() 406 | 407 | args = [self.pov] 408 | 409 | if self.max_send > 0: 410 | args.append('max_transmit=%d' % self.max_send) 411 | args.append('max_receive=%d' % self.max_send) 412 | 413 | if self.pov_seed: 414 | args.append('seed=%s' % self.pov_seed) 415 | 416 | queue.get(1) 417 | os.execv(self.pov, args) 418 | exit(-1) 419 | else: 420 | if self.attach_port: 421 | gdb_pid = launch_gdb_proxy(pid, self.attach_port) 422 | 423 | queue.put(1) 424 | 425 | if self.timeout > 0 and not self.attach_port: 426 | with Timeout(self.timeout + 5): 427 | self.pov_negotiate() 428 | else: 429 | self.pov_negotiate() 430 | 431 | if self.debug: 432 | self.log('waiting') 433 | 434 | return os.waitpid(pid, 0) 435 | 436 | def init_worker(): 437 | signal.signal(signal.SIGINT, signal.SIG_IGN) 438 | 439 | def run_pov(src, dst, pov, timeout, debug, negotiate, cb_seed, attach, max_send, pov_seed): 440 | """ 441 | Parse and Throw a POV/Poll 442 | 443 | Arguments: 444 | src: IP/Port tuple for the source of the connection 445 | dst: IP/Port tuple for the destination of the connection 446 | pov: filename of the POV 447 | timeout: How long the POV communication is allowed to take 448 | debug: Flag to enable debug logs 449 | negotate: Should PRNG be negotiated with the CB 450 | cb_seed: seed to use in the CB 451 | attach: should the POV be run under gdbserver 452 | max_send: maximum amount of transmit/receive 453 | pov_seed: the POV seed to use 454 | 455 | Returns: 456 | The number of passed tests 457 | The number of failed tests 458 | A list containing the logs 459 | 460 | Raises: 461 | Exception if parsing the POV times out 462 | """ 463 | 464 | thrower = Throw(src, dst, pov, timeout, debug, negotiate, cb_seed, attach, 465 | max_send, pov_seed) 466 | return thrower.run() 467 | 468 | def main(): 469 | """ Parse and Throw the POVs """ 470 | parser = argparse.ArgumentParser(description='Send CB based CGC Polls and POVs') 471 | required = parser.add_argument_group(title='required arguments') 472 | required.add_argument('--host', required=True, type=str, 473 | help='IP address of CB server') 474 | required.add_argument('--port', required=True, type=int, 475 | help='PORT of the listening CB') 476 | required.add_argument('files', metavar='pov', type=str, nargs='+', 477 | help='pov file') 478 | parser.add_argument('--source_host', required=False, type=str, default='', 479 | help='Source IP address to use in connections') 480 | parser.add_argument('--source_port', required=False, type=int, 481 | default=0, help='Source port to use in connections') 482 | parser.add_argument('--timeout', required=False, type=int, default=15, 483 | help='Connect timeout') 484 | parser.add_argument('--max_send', required=False, type=int, default=0, 485 | help='Maximum amount of data to send and receive at once') 486 | parser.add_argument('--debug', required=False, action='store_true', 487 | default=False, help='Enable debugging output') 488 | parser.add_argument('--negotiate', required=False, action='store_true', 489 | default=False, help='The CB seed should be negotiated') 490 | parser.add_argument('--cb_seed', required=False, type=str, 491 | help='Specify the CB Seed') 492 | parser.add_argument('--pov_seed', required=False, type=str, 493 | help='Specify the POV Seed') 494 | parser.add_argument('--attach_port', required=False, type=int, 495 | help='Attach with gdbserver prior to launching the ' 496 | 'POV on the specified port') 497 | 498 | args = parser.parse_args() 499 | 500 | if args.cb_seed is not None and not args.negotiate: 501 | raise Exception('CB Seeds can only be set with seed negotiation') 502 | 503 | assert len(args.files) 504 | for filename in args.files: 505 | assert os.path.isfile(filename), "pov must be a file: %s" % repr(filename) 506 | assert filename.endswith('.pov'), "%s does not end in .pov" % repr(filename) 507 | 508 | pool_responses = [] 509 | for pov in args.files: 510 | pid, status = run_pov((args.source_host, args.source_port), 511 | (args.host, args.port), pov, args.timeout, 512 | args.debug, args.negotiate, args.cb_seed, 513 | args.attach_port, args.max_send, args.pov_seed) 514 | 515 | return status != 0 516 | 517 | 518 | if __name__ == "__main__": 519 | exit(main()) 520 | -------------------------------------------------------------------------------- /cb-replay-pov.md: -------------------------------------------------------------------------------- 1 | % CB-REPLAY-POV(1) Cyber Grand Challenge Manuals 2 | % Brian Caswell 3 | % May 5, 2015 4 | 5 | # NAME 6 | 7 | cb-replay-pov - CB POV replay utility 8 | 9 | # SYNOPSIS 10 | 11 | cb-replay-pov [options] --host *HOST* --port *PORT* *POV* [*POV* ...] 12 | 13 | # DESCRIPTION 14 | 15 | cb-replay-pov is a utility to send deterministic testing traffic to a CGC challenge binary (CB) from a POV that is an executable. 16 | 17 | # ARGUMENTS 18 | \-\-port *PORT* 19 | : Specify the TCP port used for testing the CB. 20 | 21 | \-\-host *HOST* 22 | : Specify the IP address used for testing the CB. 23 | 24 | *POV* 25 | : Specify the POV binary to be used with the replay tool. Multiple POVs can be tested at once. 26 | 27 | # OPTIONS 28 | \-h 29 | : Display a usage message and exit 30 | 31 | \-\-timeout *TIMEOUT* 32 | : Specify the timeout for connecting to challenge binaries. 33 | 34 | \-\-debug 35 | : Specify if debug output should be enabled. 36 | 37 | \-\-cb_seed *SEED* 38 | : Specify the CB seed 39 | 40 | \-\-pov_seed *SEED* 41 | : Specify the POV seed 42 | 43 | \-\-source_host *HOST* 44 | : Specify the Source IP of the connection to the CB 45 | 46 | \-\-source_host *PORT* 47 | : Specify the Source PORT of the connection to the CB 48 | 49 | \-\-negotiate 50 | : Specify the PRNG for the CB should be negotiated 51 | 52 | \-\-attach_port *PORT* 53 | : Attach to the POV with gdbserver prior to execution on the specified port 54 | 55 | # EXAMPLE USES 56 | 57 | * cb-replay-pov --host 127.0.0.1 --port 10000 test.pov 58 | 59 | This will test the challenge binary listening on port '10000' on the IP address '127.0.0.1' with the POV 'test.pov' 60 | 61 | * cb-replay-pov --host 10.10.10.10 --port 31337 test-1.pov test-2.pov test-3.pov 62 | 63 | This will test the challenge binary listening on port '31337' on the IP address '10.10.10.10' with the Poll/POVs 'test-1.pov', 'test-2.pov', 'test-3.pov'. 64 | 65 | # COPYRIGHT 66 | 67 | Copyright (C) 2015, Brian Caswell 68 | 69 | # SEE ALSO 70 | For information regarding the TAP Format, see 71 | 72 | For more information relating to DARPA's Cyber Grand Challenge, please visit 73 | -------------------------------------------------------------------------------- /cb-replay.md: -------------------------------------------------------------------------------- 1 | % CB-REPLAY(1) Cyber Grand Challenge Manuals 2 | % Brian Caswell 3 | % April 18, 2014 4 | 5 | # NAME 6 | 7 | cb-replay - POV/Poll replay utility 8 | 9 | # SYNOPSIS 10 | 11 | cb-replay [options] --host *HOST* --port *PORT* *XML* [*XML* ...] 12 | 13 | # DESCRIPTION 14 | 15 | cb-replay is a utility to send deterministic testing traffic to a CGC challenge binary (CB), following an XML specification. Results are recorded in the TAP format. 16 | 17 | # ARGUMENTS 18 | \-\-port *PORT* 19 | : Specify the TCP port used for testing the CB. 20 | 21 | \-\-host *HOST* 22 | : Specify the IP address used for testing the CB. 23 | 24 | *XML* 25 | : Specify the XML files to be used with the replay tool. These can be POVs or polls. Multiple XML files can be tested at once. 26 | 27 | # OPTIONS 28 | \-h 29 | : Display a usage message and exit 30 | 31 | \-\-concurrent *NUMBER* 32 | : Specify the number of POVs/polls to run concurrently. 33 | 34 | \-\-timeout *TIMEOUT* 35 | : Specify the timeout for connecting to challenge binaries. 36 | 37 | \-\-failure_ok 38 | : Specify if failures are acceptable. Useful for testing a POV against a patched CB. 39 | 40 | \-\-debug 41 | : Specify if debug output should be enabled. 42 | 43 | # EXAMPLE USES 44 | 45 | * cb-replay --host 127.0.0.1 --port 10000 test-1.xml 46 | 47 | This will test the challenge binary listening on port '10000' on the IP address '127.0.0.1' with the Poll/POV 'test-1.xml'. 48 | 49 | * cb-replay --host 10.10.10.10 --port 31337 test-1.xml test-2.xml test-3.xml 50 | 51 | This will test the challenge binary listening on port '31337' on the IP address '10.10.10.10' with the Poll/POVs 'test-1.xml', 'test-2.xml', 'test-3.xml'. 52 | 53 | # COPYRIGHT 54 | 55 | Copyright (C) 2014, Brian Caswell 56 | 57 | # SEE ALSO 58 | For the Replay DTD, see '/usr/share/cgc-docs/replay.dtd'. 59 | 60 | For information regarding the TAP Format, see 61 | 62 | For more information relating to DARPA's Cyber Grand Challenge, please visit 63 | -------------------------------------------------------------------------------- /cb-test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | """ 4 | CB Testing tool 5 | 6 | Copyright (C) 2014,2015 - Brian Caswell 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | 26 | This tool allows verification of POV and POLLs with a CGC challenge binary 27 | using 'cb-replay', 'tcpdump', and 'cb-server'. 28 | """ 29 | 30 | import time 31 | import re 32 | import uuid 33 | import argparse 34 | import platform 35 | import glob 36 | import logging 37 | import os 38 | import random 39 | import resource 40 | import signal 41 | import socket 42 | import subprocess 43 | import sys 44 | import thread 45 | import threading 46 | import Queue 47 | import ansi_x931_aes128 48 | 49 | 50 | class TimeoutException(Exception): 51 | """ TimeoutException - A class used to catch just timeouts created by 52 | Timeout() """ 53 | pass 54 | 55 | 56 | class Timeout(object): 57 | """ Timeout - A class to use within 'with' for timing out a block via 58 | exceptions and alarm.""" 59 | 60 | def __init__(self, seconds): 61 | """ Instantiate a Timeout() instance, to be used in a 'with' context. 62 | 63 | Arguments: 64 | seconds: time until timeout 65 | 66 | Returns: 67 | None 68 | 69 | Raises: 70 | AssertionError: if a POV action is not in the pre-defined methods 71 | """ 72 | 73 | assert isinstance(seconds, int) 74 | assert seconds > 0 75 | self.seconds = seconds 76 | 77 | @staticmethod 78 | def cb_handle_timeout(signum, frame): 79 | """ signal handler callback method, called when SIGALRM is raised 80 | 81 | Arguments: 82 | signum: signal number that caused the signal handler to be called 83 | frame: frame that caused the signal 84 | 85 | Returns: 86 | None 87 | 88 | Raises: 89 | TimeoutException: Always raises this exception 90 | """ 91 | 92 | raise TimeoutException("timed out") 93 | 94 | def __enter__(self): 95 | """ Context guard for handling timeouts as a 'with' 96 | 97 | Arguments: 98 | None 99 | 100 | Returns: 101 | None 102 | 103 | Raises: 104 | None 105 | """ 106 | 107 | signal.signal(signal.SIGALRM, self.cb_handle_timeout) 108 | signal.alarm(self.seconds) 109 | 110 | def __exit__(self, exit_type, exit_value, traceback): 111 | """ Disable the alarm upon exiting the 'with' block 112 | 113 | Arguments: 114 | exit_type: the type of exit code being generated 115 | exit_value: the value of the exit method 116 | traceback: a stack trace 117 | 118 | Raises: 119 | None 120 | """ 121 | 122 | signal.alarm(0) 123 | 124 | 125 | class Background(object): 126 | """ Run an external command in a background thread 127 | 128 | Usage: 129 | a = Background(['echo', 'hello']) 130 | a.wait() 131 | 132 | Attributes: 133 | cmd: The command that should be run (as a list) 134 | process: The subprocess handle 135 | threads: A list of threads being maintained, one for stderr, one for 136 | stdout. 137 | """ 138 | def __init__(self, cmd, command_name=None): 139 | logging.warning('launching %s', ' '.join(cmd)) 140 | if command_name is None: 141 | self.cmd = cmd[0] 142 | else: 143 | self.cmd = command_name 144 | self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, 145 | stderr=subprocess.PIPE) 146 | self.threads = [] 147 | self.log_queue = Queue.Queue() 148 | self.log_handle(self.process.stdout, False) 149 | self.log_handle(self.process.stderr, True) 150 | 151 | def log_handle(self, filehandle, should_repr): 152 | """ Create a thread to log all of the output from the provided file 153 | handle. 154 | 155 | Arguments: 156 | filehandle: File handle to monitor 157 | should_repr: Should the output be passed through 'repr' before 158 | writing to the log. (Useful for untrusted input) 159 | 160 | Returns: 161 | None 162 | 163 | Raises: 164 | None 165 | """ 166 | def log_background(log, should_repr, queue): 167 | """ Thread callback to log the output from the background process 168 | 169 | Arguments: 170 | log: File handle to monitor 171 | should_repr: should the output be passed through repr before 172 | writing to the log. (Useful for untrusted input) 173 | """ 174 | try: 175 | for line in iter(log.readline, ''): 176 | queue.put(line) 177 | if should_repr: 178 | logging.warning('%s: %s', self.cmd, repr(line[:-1])) 179 | else: 180 | logging.warning('%s: %s', self.cmd, line[:-1]) 181 | log.close() 182 | queue.put(None) 183 | except KeyboardInterrupt: 184 | thread.interrupt_main() 185 | 186 | my_thread = threading.Thread(target=log_background, 187 | args=(filehandle, should_repr, self.log_queue)) 188 | my_thread.daemon = True 189 | my_thread.start() 190 | self.threads.append(my_thread) 191 | 192 | def terminate(self): 193 | """ Terminate the running process and associated threads. 194 | 195 | Arguments: 196 | None 197 | 198 | Returns: 199 | None 200 | 201 | Raises: 202 | None 203 | """ 204 | logging.debug('terminating %s', self.cmd) 205 | try: 206 | self.process.terminate() 207 | except OSError: 208 | pass 209 | for my_thread in self.threads: 210 | my_thread.join() 211 | 212 | def wait(self): 213 | """ Wait for the process to exit. 214 | 215 | Arguments: 216 | None 217 | 218 | Returns: 219 | Return code from the process 220 | 221 | Raises: 222 | None 223 | """ 224 | logging.debug('waiting for %s to terminate (pid: %s)', self.cmd, self.process.pid) 225 | wval = self.process.wait() 226 | result = [] 227 | 228 | waiting = 2 229 | while waiting > 0: 230 | item = self.log_queue.get() 231 | if item is None: 232 | waiting -= 1 233 | continue 234 | result.append(item) 235 | self.log_queue.task_done() 236 | 237 | logging.debug('process returned %s', repr(wval)) 238 | return wval, ''.join(result) 239 | 240 | 241 | class Runner(object): 242 | """ Run an external command in a background thread 243 | 244 | Usage: 245 | a = Runner(port, cb_list, xml_list, pcap, wrapper, directory, 246 | should_core, failure_ok, should_debug, timeout, log_fh, 247 | cb_seed, cb_seed_skip, max_send, concurrent, 248 | negotiate_seed, pov_seed, cb_no_attach, cb_seed_munge, 249 | cb_env) 250 | a.run() 251 | 252 | Attributes: 253 | port: TCP port the CB should run on. 254 | cb_list: Set of CBs being tested 255 | xml_list: List of XML files to test the CB with 256 | pcap: Path the pcap file should be written to 257 | wrapper: The wrapper executable to run the CBs through 258 | directory: The directory containing the CBs 259 | should_core: Is the CB expected to core with the provided XML 260 | failure_ok: Should failures stop the testing process 261 | should_debug: Should debugging be enabled on child processes 262 | timeout: Timeout for child processes 263 | log_fh: The file handle for the log 264 | ids: Path to the IDS ruleset 265 | server_cb: Node the CB should run on (cb-server) 266 | server_pov: Node the XML should be run on (cb-replay) 267 | server_ids: Node the IDS should run on (cb-ids) 268 | ip_address: IP Address of the CB server 269 | cb_seed: PRNG for CBs 270 | cb_seed_skip: Amount to advance the PRNG for CBs 271 | max_send: Maximum data to send per request from the poll 272 | concurrent: Number of polls/povs to send concurrently 273 | negotiate_seed: Should the CB seed be negotiated from cb-replay 274 | pov_seed: the PRNG seed for POVs 275 | cb_no_attach: Should the CB not be attached within cb-server 276 | cb_seed_munge: Should the poll munge the seed 277 | cb_env: An enviornment variable to pass through to the CB in cb-server 278 | """ 279 | pov_signals = [signal.SIGSEGV, signal.SIGILL, signal.SIGBUS] 280 | 281 | def __init__(self, port, cb_list, xml_list, pcap, wrapper, directory, 282 | should_core, failure_ok, should_debug, timeout, log_fh, 283 | cb_seed, cb_seed_skip, max_send, concurrent, negotiate_seed, 284 | pov_seed, cb_no_attach, cb_seed_munge, cb_env): 285 | self.port = port 286 | self.cb_list = cb_list 287 | self.cb_no_attach = cb_no_attach 288 | self.cb_env = cb_env 289 | self.xml_list = xml_list 290 | self.pcap = pcap 291 | self.wrapper = wrapper 292 | self.concurrent = concurrent 293 | self.directory = directory 294 | self.should_core = should_core 295 | self.should_debug = should_debug 296 | self.failure_ok = failure_ok 297 | self.timeout = timeout 298 | self.processes = [] 299 | self.log_fh = log_fh 300 | self.cb_seed = cb_seed 301 | self.cb_seed_skip = cb_seed_skip 302 | self.cb_seed_munge = cb_seed_munge 303 | self.ids_rules = None 304 | self.max_send = max_send 305 | self.negotiate_seed = negotiate_seed 306 | self.pov_seed = pov_seed 307 | 308 | self._remote = False 309 | self._remote_uploaded = [] 310 | 311 | self.server_cb = None 312 | self.server_pov = None 313 | self.server_ids = None 314 | self._test_id = None 315 | self._tmp_dir = None 316 | 317 | self.ip_address = self.random_ip() 318 | if not isinstance(self.port, int) or self.port == 0: 319 | self.port = self.random_port() 320 | 321 | resource.setrlimit(resource.RLIMIT_CORE, (resource.RLIM_INFINITY, 322 | resource.RLIM_INFINITY)) 323 | 324 | def remote_cmd(self, node, cmd): 325 | """ Run the command on a remote host via ssh 326 | 327 | Arguments: 328 | node: host that the command should be run 329 | cmd: command that should be run 330 | 331 | Returns: 332 | Return the stdout from the command 333 | 334 | Raises: 335 | AssertionError: if the node is not in the set of known servers 336 | subprocess.error: on command excution failure 337 | """ 338 | assert node in [self.server_cb, self.server_pov, self.server_ids] 339 | 340 | return subprocess.check_output(['ssh', node] + cmd) 341 | 342 | def remote_upload(self, node, files): 343 | """ Upload a set of files to the tempdir on the remote node. 344 | 345 | NOTE: This method maintains a history of what we've uploaded, and only 346 | upload it once. 347 | 348 | Arguments: 349 | host: host that the command should be run 350 | files: Files to be uploaded 351 | 352 | Returns: 353 | None 354 | 355 | Raises: 356 | subprocess.error: on command excution failure 357 | """ 358 | assert node in [self.server_cb, self.server_pov, self.server_ids] 359 | assert isinstance(files, list) 360 | 361 | files = list(set(files) - set(self._remote_uploaded)) 362 | 363 | if len(files): 364 | subprocess.check_output(['scp'] + files + 365 | ['%s:%s' % (node, self._tmp_dir)]) 366 | 367 | self._remote_uploaded += files 368 | 369 | def set_ids_rules(self, ids_rules): 370 | """ Set IDS rules to use during teting 371 | 372 | Arguments: 373 | ids_rules: Rules to use 374 | 375 | Returns: 376 | None 377 | 378 | Raises: 379 | None 380 | """ 381 | 382 | self.ids_rules = ids_rules 383 | 384 | def enable_remote(self, server_cb, server_pov, server_ids): 385 | """ Setup remote cb testing 386 | 387 | Arguments: 388 | server_cb: The node to run the cb-server 389 | server_pov: The node to run the POVs 390 | server_ids: The node to run the IDS 391 | 392 | Returns: 393 | None 394 | 395 | Raises: 396 | subprocess.error: on command excution failure 397 | """ 398 | self.server_cb = server_cb 399 | self.server_pov = server_pov 400 | self.server_ids = server_ids 401 | self._remote = True 402 | self._test_id = str(uuid.uuid4()) 403 | self._tmp_dir = '/tmp/%s' % self._test_id 404 | # if self.ids_rules is None: 405 | # self.ip_address = self.server_cb 406 | # else: 407 | self.ip_address = self.server_ids 408 | 409 | for node in [server_cb, server_pov, server_ids]: 410 | self.remote_cmd(node, ['mkdir', '-p', self._tmp_dir]) 411 | 412 | def background(self, cmd, cmd_name=None): 413 | """ Run a command in the background, and verify it started 414 | 415 | Arguments: 416 | cmd: Command to be run 417 | cmd_name: Name of the command to show up in the logs 418 | 419 | Returns: 420 | None 421 | 422 | Raises: 423 | Exception: if the process didn't get created effectively 424 | """ 425 | process = Background(cmd, cmd_name) 426 | self.processes.append(process) 427 | if process.process.poll(): 428 | raise Exception('background process failed: %s' % (' '.join(cmd))) 429 | return process 430 | 431 | def launch(self, cmd): 432 | """ Run a command in the foreground, logging the stderr and stdout 433 | 434 | Arguments: 435 | cmd: Command to be run 436 | 437 | Returns: 438 | None 439 | 440 | Raises: 441 | None 442 | """ 443 | logging.warning('launching %s', ' '.join(cmd)) 444 | process = subprocess.Popen(cmd, stdout=subprocess.PIPE, 445 | stderr=subprocess.PIPE) 446 | stdout, stderr = process.communicate() 447 | if len(stderr): 448 | for line in stderr.split('\n'): 449 | logging.error('%s (stderr): %s', cmd[0], repr(line)) 450 | self.log_fh.flush() 451 | self.log_fh.write(stdout) 452 | self.log_fh.flush() 453 | return process.returncode, stdout 454 | 455 | def cleanup(self): 456 | """ Cleanup the current enviornment. 457 | 458 | Terminates all background processes, and removes files uploaded to 459 | remote nodes. 460 | 461 | Arguments: 462 | None 463 | 464 | Returns: 465 | None 466 | 467 | Raises: 468 | None 469 | """ 470 | for process in self.processes: 471 | process.terminate() 472 | 473 | if self._remote: 474 | for node in [self.server_cb, self.server_pov, self.server_ids]: 475 | try: 476 | self.remote_cmd(node, ['rm', '-rf', self._tmp_dir]) 477 | except subprocess.CalledProcessError: 478 | pass 479 | 480 | @staticmethod 481 | def random_ip(): 482 | """ Generate a random IP for local testing. 483 | 484 | Arguments: 485 | None 486 | 487 | Returns: 488 | A random IP in the 127/8 range 489 | 490 | Raises: 491 | None 492 | """ 493 | return "127.%d.%d.%d" % (random.randint(1, 254), 494 | random.randint(1, 254), 495 | random.randint(1, 254)) 496 | 497 | def verify_cb(self): 498 | """ Verify each CB with cgcef_verify 499 | 500 | Arguments: 501 | None 502 | 503 | Returns: 504 | 0 if all of the CBs are valid DECREE executables 505 | -1 if any of the CBs is not a valid DECREE executable 506 | 507 | Raises: 508 | None 509 | """ 510 | for cb_name in self.cb_list: 511 | cb_path = os.path.join(self.directory, cb_name) 512 | try: 513 | subprocess.check_output(['cgcef_verify', cb_path]) 514 | except subprocess.CalledProcessError as err: 515 | logging.error('not ok - CB did not verify: %s', str(err), extra={'raw':True}) 516 | return -1 517 | return 0 518 | 519 | @staticmethod 520 | def random_port(): 521 | """ Generate a random port for testing. 522 | 523 | Arguments: 524 | None 525 | 526 | Returns: 527 | A random port between 2000 and 2999 528 | 529 | Raises: 530 | None 531 | """ 532 | return random.randint(2000, 2999) 533 | 534 | @staticmethod 535 | def log_packages(): 536 | """ Logs all of the installed CGC packages 537 | 538 | Arguments: 539 | None 540 | 541 | Returns: 542 | None 543 | 544 | Raises: 545 | None 546 | """ 547 | if 'debian' in platform.dist()[0]: 548 | import apt 549 | cache = apt.Cache() 550 | cgc_packages = [] 551 | for package_name in cache.keys(): 552 | if 'cgc' not in package_name and 'cb-testing' not in package_name: 553 | continue 554 | 555 | package = cache[package_name] 556 | 557 | status = '%s: (installed: %s)' % (package.name, 558 | repr(package.is_installed)) 559 | for version in package.versions: 560 | status += ' version: %s' % version.version 561 | 562 | cgc_packages.append(status) 563 | 564 | cgc_packages.sort() 565 | for package in cgc_packages: 566 | logging.warning('package: %s', package) 567 | 568 | @staticmethod 569 | def signal_name(sig_id): 570 | """ Translate a signal number to its name 571 | 572 | Arguments: 573 | sig_id: signal number 574 | 575 | Returns: 576 | The signal name, or 'UNKNOWN' if it is an undefined signal number 577 | 578 | Raises: 579 | None 580 | """ 581 | for name, value in signal.__dict__.iteritems(): 582 | if sig_id == value: 583 | return name 584 | return 'UNKNOWN' 585 | 586 | def start_server(self, connection_count): 587 | """ Start cb-server in the background 588 | 589 | Arguments: 590 | connection_count: The maximum number of connections that cb-server 591 | should handle before exiting 592 | 593 | Returns: 594 | A handle to the Background() instance of cb-server 595 | 596 | Raises: 597 | None 598 | """ 599 | assert connection_count > 0 600 | 601 | server_cmd = ['cb-server', '--insecure', '-p', "%d" % self.port, 602 | '-m', "%d" % connection_count] 603 | 604 | if self._remote: 605 | cb_paths = [os.path.join(self.directory, x) for x in self.cb_list] 606 | self.remote_upload(self.server_cb, cb_paths) 607 | 608 | server_cmd = ['ssh', self.server_cb] + server_cmd 609 | server_cmd += ['-d', self._tmp_dir] 610 | else: 611 | server_cmd += ['-d', self.directory] 612 | 613 | if self.negotiate_seed: 614 | server_cmd += ['--negotiate'] 615 | 616 | if self.cb_no_attach: 617 | server_cmd += ['--no_attach'] 618 | 619 | if self.cb_seed is not None and self.negotiate_seed is None: 620 | server_cmd += ['-s', self.cb_seed] 621 | 622 | if self.cb_seed_skip is not None: 623 | server_cmd += ['-S', '%d' % self.cb_seed_skip] 624 | 625 | if self.cb_env is not None: 626 | server_cmd += ['-e', self.cb_env] 627 | 628 | if self.timeout > 0: 629 | server_cmd += ['-t', '%d' % self.timeout] 630 | 631 | if self.max_send is not None and self.max_send > 0: 632 | server_cmd += ['-M', '%d' % self.max_send] 633 | 634 | if self.should_debug: 635 | server_cmd += ['--debug'] 636 | else: 637 | server_cmd += ['-c', '0'] 638 | 639 | if self.wrapper: 640 | server_cmd += ['-w', self.wrapper] 641 | 642 | server_cmd += self.cb_list 643 | 644 | return self.background(server_cmd, 'cb-server') 645 | 646 | def start_ids(self, connection_count): 647 | """ Start cb-proxy in the background 648 | 649 | Arguments: 650 | None 651 | 652 | Returns: 653 | A handle to the Background() instance of cb-proxy 654 | 655 | Raises: 656 | None 657 | """ 658 | assert connection_count > 0 659 | 660 | # the IDS is only used for remote commands 661 | proxy_cmd = ['sleep', '100'] 662 | 663 | # if self._remote and self.ids_rules: 664 | if self._remote: 665 | if self.ids_rules: 666 | self.remote_upload(self.server_ids, [self.ids_rules]) 667 | base_filename = os.path.basename(self.ids_rules) 668 | remote_path = os.path.join(self._tmp_dir, base_filename) 669 | 670 | proxy_cmd = ['ssh', self.server_ids, 'cb-proxy', 671 | # '--listen_host', self.server_ids, 672 | '--max_connections', '%d' % connection_count, 673 | '--listen_port', '%d' % self.port, 674 | '--host', self.server_cb, 675 | '--port', '%d' % self.port] 676 | 677 | if self.ids_rules: 678 | proxy_cmd += ['--rules', remote_path] 679 | 680 | if self.pcap: 681 | proxy_cmd += ['--pcap_host', socket.gethostname()] 682 | 683 | if self.should_debug: 684 | proxy_cmd += ['--debug'] 685 | 686 | if self.negotiate_seed: 687 | proxy_cmd += ['--negotiate'] 688 | 689 | return self.background(proxy_cmd, 'cb-proxy') 690 | 691 | def start_pcap(self): 692 | """ Start pcap logging in the background 693 | 694 | Arguments: 695 | None 696 | 697 | Returns: 698 | If pcap logging is enabled, a handle to the Background() instance 699 | of the pcap logger 700 | Otherwise, None 701 | 702 | Raises: 703 | None 704 | """ 705 | # XXX add 'remote' support 706 | if self.pcap: 707 | if self._remote: 708 | pcap = self.background(['cb-packet-log', '--pcap_file', 709 | self.pcap]) 710 | else: 711 | pcap = self.background(['/usr/sbin/tcpdump', '-i', 'lo', '-w', 712 | self.pcap, '-s', '0', '-B', '65536', 713 | 'port', '%d' % self.port, 'and', 714 | 'host', self.ip_address], 'tcpdump') 715 | while not pcap.log_queue.qsize(): 716 | time.sleep(0.1) 717 | return None 718 | 719 | def start_replay(self, xml): 720 | """ Start cb-replay 721 | 722 | Arguments: 723 | None 724 | 725 | Returns: 726 | The handle to the running process 727 | 728 | Raises: 729 | None 730 | """ 731 | 732 | replay_bin = 'cb-replay' 733 | if xml[0].endswith('.pov'): 734 | replay_bin = 'cb-replay-pov' 735 | 736 | replay_cmd = [replay_bin, '--host', self.ip_address, '--port', '%d' % 737 | self.port] 738 | 739 | if self.timeout > 0: 740 | replay_cmd += ['--timeout', '%d' % self.timeout] 741 | 742 | if self.negotiate_seed: 743 | replay_cmd += ['--negotiate'] 744 | if self.cb_seed is not None: 745 | replay_cmd += ['--cb_seed', self.cb_seed] 746 | 747 | if self.pov_seed and replay_bin == 'cb-replay-pov': 748 | replay_cmd += ['--pov_seed', self.pov_seed] 749 | 750 | if self.cb_seed_munge and replay_bin != 'cb-replay-pov': 751 | replay_cmd += ['--munge_seed'] 752 | 753 | if self.should_debug: 754 | replay_cmd += ['--debug'] 755 | 756 | if replay_bin == 'cb-replay': 757 | if self.failure_ok: 758 | replay_cmd += ['--failure_ok'] 759 | 760 | if self.concurrent: 761 | replay_cmd += ['--concurrent', '%d' % self.concurrent] 762 | 763 | if self.max_send is not None and self.max_send > 0: 764 | replay_cmd += ['--max_send', '%d' % self.max_send] 765 | 766 | if self._remote: 767 | self.remote_upload(self.server_pov, xml) 768 | xml = [os.path.join(self._tmp_dir, os.path.basename(x)) for x in 769 | xml] 770 | replay_cmd = ['ssh', self.server_pov] + replay_cmd 771 | 772 | replay_cmd += xml 773 | 774 | return self.launch(replay_cmd) 775 | 776 | def use_server_connections(self, count): 777 | """ Connect to the cb-server a specifed number of times 778 | 779 | If the replay tool fails, the server might run forever waiting to 780 | process input. Give it the connections it's expecting and hope it exits 781 | normally. 782 | 783 | Arguments: 784 | count: number of times to connect to the cb-server 785 | 786 | Returns: 787 | None 788 | 789 | Raises: 790 | None 791 | """ 792 | for _ in range(0, count): 793 | try: 794 | sock = socket.create_connection((self.ip_address, 795 | self.port)) 796 | except socket.error: 797 | continue 798 | sock.close() 799 | 800 | def _check_result_cqe(self, sig, ret): 801 | if sig == signal.SIGALRM: 802 | if self.failure_ok: 803 | logging.warning('ok - process timed out', extra={'raw':True}) 804 | ret = 0 805 | else: 806 | logging.error('not ok - process timed out', extra={'raw':True}) 807 | ret = -1 808 | elif sig in Runner.pov_signals: 809 | if self.should_core: 810 | logging.warning('ok - process cored as expected: (signal %d: %s)', 811 | sig, self.signal_name(sig), extra={'raw':True}) 812 | ret = 0 813 | else: 814 | logging.error('not ok - process cored: (signal %d: %s)', 815 | sig, self.signal_name(sig), extra={'raw':True}) 816 | ret = -1 817 | elif self.should_core: 818 | logging.error('not ok - core expected, no cores identified', extra={'raw':True}) 819 | ret = -1 820 | 821 | return ret 822 | 823 | def _check_result_type1(self, sig, replay_stdout, server_stdout): 824 | if sig == 0: 825 | if self.should_core: 826 | logging.error('not ok - POV type 1 should have cored, and did not.', extra={'raw':True}) 827 | return -1 828 | else: 829 | logging.warning('ok - POV type 1 did not core, as expected', extra={'raw':True}) 830 | return 0 831 | elif not self.should_core: 832 | logging.error('not ok - POV type 1 expected to not core, but did. (signal %d: %s)', sig, self.signal_name(sig), extra={'raw':True}) 833 | return -1 834 | 835 | match = re.match(r"(?ms).*\n# type 1 masks: ([a-fA-F0-9]+) ([a-fA-F0-9]+)", replay_stdout) 836 | if not match: 837 | logging.error('not ok - POV type 1 negotiation failed', extra={'raw':True}) 838 | return -1 839 | ip_mask = int(match.group(1), 16) 840 | reg_mask = int(match.group(2), 16) 841 | 842 | match = re.match(r"(?ms).*\n# type 1 pov: ([a-fA-F0-9]+) ([a-fA-F0-9]+) (\d)", replay_stdout) 843 | if not match: 844 | logging.error('not ok - POV type 1 negotiation failed', extra={'raw':True}) 845 | return -1 846 | 847 | ip_value = int(match.group(1), 16) 848 | reg_value = int(match.group(2), 16) 849 | reg_num = int(match.group(3)) 850 | 851 | logging.warning('POV type 1 negotiated masks: %08x %08x %d', ip_mask, reg_mask, reg_num) 852 | # logging.warning('process cored as expected (signal %d: %s)', sig, self.signal_name(sig)) 853 | 854 | register_state_re = r"(?ms).*\nregister states" 855 | register_state_re2 = r"register states - eax: ([a-fA-F0-9]+) ecx: ([a-fA-F0-9]+) edx: ([a-fA-F0-9]+) ebx: ([a-fA-F0-9]+) esp: ([a-fA-F0-9]+) ebp: ([a-fA-F0-9]+) esi: ([a-fA-F0-9]+) edi: ([a-fA-F0-9]+) eip: ([a-fA-F0-9]+)" 856 | 857 | match = re.match(register_state_re, server_stdout) 858 | if not match: 859 | logging.error('not ok - cb-server did not log register states', extra={'raw':True}) 860 | return -1 861 | 862 | lines = server_stdout.split('\n') 863 | 864 | expected_eip = ip_value & ip_mask 865 | expected_reg = reg_value & reg_mask 866 | 867 | results = [] 868 | for line in lines: 869 | match = re.match(register_state_re2, line) 870 | if not match: 871 | continue 872 | 873 | registers = [] 874 | for i in range(8): 875 | registers.append(int(match.group(1+i), 16)) 876 | eip = int(match.group(9), 16) 877 | 878 | result_eip = eip & ip_mask 879 | result_reg = registers[reg_num] & reg_mask 880 | 881 | if (result_eip == expected_eip and result_reg == expected_reg): 882 | logging.warning('ok - TYPE 1 POV', extra={'raw':True}) 883 | return 0 884 | 885 | results.append([result_eip, result_reg]) 886 | 887 | for values in results: 888 | logging.error('not ok - TYPE 1 POV FAILED. expected eip: %08x actual eip: %08x expected reg: %08x actual reg: %08x', expected_eip, values[0], expected_reg, values[1], extra={'raw': True}) 889 | 890 | return -1 891 | 892 | def _check_result_type2(self, replay_stdout, server_stdout): 893 | seed = self.cb_seed 894 | if not seed: 895 | match = re.match(r"(?ms).*\nseed: ([a-fA-F0-9]+)", server_stdout) 896 | if not match: 897 | logging.error('not ok - POV type 2 server did not print seed', extra={'raw':True}) 898 | return -1 899 | 900 | seed = match.group(1) 901 | 902 | prng = ansi_x931_aes128.PRNG(seed.decode('hex')) 903 | flag_page = prng.get(0x1000) 904 | 905 | match = re.match(r"(?ms).*\n# secret value: ([a-fA-F0-9]+)", replay_stdout) 906 | if not match: 907 | if self.should_core: 908 | logging.error('not ok - POV type 2 did not return a secret value', extra={'raw':True}) 909 | return -1 910 | else: 911 | logging.warning('ok - POV type 2 did not return a secret value as expected', extra={'raw':True}) 912 | return 0 913 | 914 | value = match.group(1).decode('hex') 915 | if value not in flag_page: 916 | if not self.should_core: 917 | logging.warning('ok - POV type 2 secret value is not part of the secret page (as expected)', extra={'raw':True}) 918 | return 0 919 | else: 920 | logging.error('not ok - TYPE 2 POV FAILED. %s not in %s', value.encode('hex'), flag_page.encode('hex'), extra={'raw':True}) 921 | return -1 922 | 923 | if not self.should_core: 924 | logging.error('not ok - TYPE 2 unexpectedly passed. %s in %s', value.encode('hex'), flag_page.encode('hex'), extra={'raw':True}) 925 | return -1 926 | 927 | logging.warning('ok - TYPE 2 POV', extra={'raw': True}) 928 | return 0 929 | 930 | def _check_result_cfe(self, sig, replay_stdout, server_stdout): 931 | pov_type = None 932 | match = re.match(r"(?ms).*\n# negotiation type: (\d)\n", replay_stdout) 933 | if not match: 934 | logging.error("not ok - pov did not negotiate", extra={'raw':True}) 935 | return -1 936 | 937 | pov_type = int(match.group(1)) 938 | if pov_type not in [1, 2]: 939 | logging.error("not ok - pov negotiation is not 1 or 2: %d\n" % pov_type, extra={'raw':True}) 940 | return -1 941 | 942 | if pov_type == 1: 943 | return self._check_result_type1(sig, replay_stdout, server_stdout) 944 | 945 | if pov_type == 2: 946 | return self._check_result_type2(replay_stdout, server_stdout) 947 | 948 | def check_result(self, replay_result, server_result, xml_list): 949 | """ Check the results of cb-server and cb-replay 950 | 951 | Arguments: 952 | replay_result: the output of cb-replay 953 | sig: the return code of cb-server 954 | 955 | Returns: 956 | Returns 0 if the test 'passed' 957 | Returns -1 if the test 'failed' 958 | 959 | Raises: 960 | None 961 | """ 962 | ret, replay_stdout = replay_result 963 | sig, server_stdout = server_result 964 | 965 | magic_value = None 966 | 967 | if not xml_list[0].endswith('.pov'): 968 | return self._check_result_cqe(sig, ret) 969 | else: 970 | return self._check_result_cfe(sig, replay_stdout, server_stdout) 971 | 972 | def run(self): 973 | """ Runs the test 974 | 975 | Arguments: 976 | None 977 | 978 | Returns: 979 | Returns 0 if the tests all pass 980 | Returns non-0 if any of the tests failed 981 | 982 | Raises: 983 | None 984 | """ 985 | self.log_packages() 986 | 987 | if self.verify_cb() != 0: 988 | return -1 989 | 990 | xml_sets = [] 991 | if self.failure_ok: 992 | for xml in self.xml_list: 993 | xml_sets.append([xml]) 994 | else: 995 | cqe_xml = [] 996 | cfe_xml = [] 997 | for xml in self.xml_list: 998 | if xml.endswith('.xml'): 999 | cqe_xml.append(xml) 1000 | else: 1001 | cfe_xml.append(xml) 1002 | 1003 | for i in [cqe_xml, cfe_xml]: 1004 | if len(i): 1005 | xml_sets.append(i) 1006 | 1007 | pcap = self.start_pcap() 1008 | for xml_list in xml_sets: 1009 | server = self.start_server(len(xml_list)) 1010 | ids = self.start_ids(len(xml_list)) 1011 | replay_result = self.start_replay(xml_list) 1012 | 1013 | if server.process.poll() is None: 1014 | self.use_server_connections(len(xml_list)) 1015 | 1016 | server_result = None, '' 1017 | 1018 | # wait a maximum of 30 seconds after our replay and socket cleanup 1019 | # finishes before terminating cb-server if it hasn't returned yet. 1020 | try: 1021 | with Timeout(30): 1022 | server_result = server.wait() 1023 | except TimeoutException: 1024 | server.terminate() 1025 | 1026 | ids.terminate() 1027 | 1028 | result = self.check_result(replay_result, server_result, xml_list) 1029 | if result != 0: 1030 | return result 1031 | 1032 | if pcap is not None: 1033 | pcap.terminate() 1034 | 1035 | return 0 1036 | 1037 | class CB_Formatter(logging.Formatter): 1038 | 1039 | def format(self, record): 1040 | s = super(CB_Formatter, self).format(record) 1041 | result = [] 1042 | 1043 | for line in s.split('\n'): 1044 | if 'raw' in dir(record) and record.raw == True: 1045 | if line.startswith('# '): 1046 | line = line[2:] 1047 | else: 1048 | if line[:2] != '# ': 1049 | line = '# ' + line 1050 | result.append(line) 1051 | return '\n'.join(result) 1052 | 1053 | def main(): 1054 | """ Test a challenge binary using cb-server and cb-replay, with either 1055 | local or remote support. 1056 | 1057 | Arguments: 1058 | None 1059 | 1060 | Returns: 1061 | Returns 0 on test success 1062 | Returns non-0 if any of the tests failed 1063 | 1064 | Raises: 1065 | None 1066 | """ 1067 | 1068 | formatter = argparse.ArgumentDefaultsHelpFormatter 1069 | parser = argparse.ArgumentParser(description='Send CGC Polls and POVs', 1070 | formatter_class=formatter) 1071 | 1072 | required = parser.add_argument_group(title='required') 1073 | required.add_argument('--cb', required=True, type=str, nargs='+', 1074 | help='Challenge Binaries to run') 1075 | required.add_argument('--directory', required=True, type=str, 1076 | help='Directory containing challenge binaries') 1077 | 1078 | remote = parser.add_argument_group(title='Remote Testing') 1079 | remote.add_argument('--enable_remote', required=False, action='store_true', 1080 | default=False, help='Perform tests remotely') 1081 | remote.add_argument('--remote_nodes', required=False, nargs=3, type=str, 1082 | default=['cb', 'pov', 'ids'], 1083 | metavar=('', '', 1084 | ''), 1085 | help='Hosts to run each of the components') 1086 | 1087 | parser.add_argument('--debug', required=False, action='store_true', 1088 | default=False, help='Enable debugging') 1089 | parser.add_argument('--log', required=False, type=str, help='Log filename') 1090 | parser.add_argument('--pcap', required=False, type=str, 1091 | help='PCAP output file') 1092 | parser.add_argument('--concurrent', required=False, type=int, 1093 | help='Number of Polls/POVs to throw concurrently') 1094 | parser.add_argument('--cb_seed', required=False, type=str, 1095 | help='CB PRNG Seed') 1096 | parser.add_argument('--cb_seed_skip', required=False, type=int, 1097 | help='Advance the CB PRNG Seed before launch') 1098 | parser.add_argument('--cb_seed_munge', required=False, action='store_true', 1099 | default=False, help='Bit invert the seed for testing') 1100 | parser.add_argument('--port', required=False, type=int, 1101 | help='PORT to run the CB') 1102 | parser.add_argument('--pov_seed', required=False, type=str, 1103 | help='POV PRNG Seed') 1104 | parser.add_argument('--timeout', required=False, type=int, default=15, 1105 | help='Maximum duration for each Poll or POV') 1106 | parser.add_argument('--should_core', required=False, action='store_true', 1107 | default=False, help='This test should cause a core') 1108 | parser.add_argument('--wrapper', required=False, type=str, 1109 | help='Executable to wrap each CB for instrumentation') 1110 | parser.add_argument('--failure_ok', required=False, action='store_true', 1111 | default=False, help='Failures for this test are ' 1112 | 'accepted') 1113 | parser.add_argument('--max_send', required=False, type=int, 1114 | help='Maximum data to send in each request from the ' 1115 | 'poll') 1116 | parser.add_argument('--ids_rules', required=False, type=str, 1117 | help='IDS Rules') 1118 | parser.add_argument('--negotiate_seed', required=False, action='store_true', 1119 | default=False, help='Negotate the CB seed from cb-replay') 1120 | parser.add_argument('--cb_no_attach', required=False, action='store_true', 1121 | default=False, help='Do not attach to the CB') 1122 | parser.add_argument('--cb_env', required=False, type=str, 1123 | help='CB Enviornment Variable') 1124 | 1125 | exgroup = parser.add_argument_group(title='XML files') 1126 | group = exgroup.add_mutually_exclusive_group(required=True) 1127 | group.add_argument('--xml', type=str, nargs='+', help='POV/POLL XML File ' 1128 | 'to evaluate') 1129 | group.add_argument('--xml_dir', type=str, help='Directory containing ' 1130 | 'POV/POLL XML Files to evaluate') 1131 | 1132 | args = parser.parse_args() 1133 | 1134 | xml_files = args.xml 1135 | if args.xml is None: 1136 | assert os.path.isdir(args.xml_dir), "not a directory: " + args.xml_dir 1137 | xml_files = glob.glob("%s/*.xml" % args.xml_dir) 1138 | xml_files += glob.glob("%s/*.pov" % args.xml_dir) 1139 | xml_files.sort() 1140 | 1141 | assert len(xml_files), "No Polls or POVs" 1142 | 1143 | if args.cb_seed_skip is not None: 1144 | assert args.cb_seed_skip > 0, "Invalid cb_seed_skip: %d" % args.cb_seed_skip 1145 | 1146 | for filename in xml_files: 1147 | assert filename.endswith('.pov') or filename.endswith('.xml'), 'invalid POV/Poll' 1148 | 1149 | if args.cb_seed is not None: 1150 | assert len(args.cb_seed) == 96, "--cb_seed must be 96 bytes" 1151 | 1152 | if args.pov_seed is not None: 1153 | assert len(args.pov_seed) == 96, "--pov_seed must be 96 bytes" 1154 | 1155 | logger = logging.getLogger() 1156 | logger.setLevel(logging.WARNING) 1157 | if args.debug: 1158 | logger.setLevel(logging.DEBUG) 1159 | 1160 | if args.log is not None: 1161 | log_fh = open(args.log, 'w') 1162 | else: 1163 | log_fh = sys.stdout 1164 | 1165 | log_handler = logging.StreamHandler(log_fh) 1166 | log_handler.setFormatter(CB_Formatter('# %(message)s')) 1167 | 1168 | logger.addHandler(log_handler) 1169 | 1170 | if log_fh != sys.stdout: 1171 | error_handler = logging.StreamHandler(sys.stdout) 1172 | error_handler.setLevel(logging.ERROR) 1173 | error_handler.setFormatter(logging.Formatter('# %(message)s')) 1174 | logger.addHandler(error_handler) 1175 | 1176 | ret = -1 1177 | runner = Runner(args.port, args.cb, xml_files, args.pcap, args.wrapper, 1178 | args.directory, args.should_core, args.failure_ok, 1179 | args.debug, args.timeout, log_fh, args.cb_seed, 1180 | args.cb_seed_skip, args.max_send, args.concurrent, 1181 | args.negotiate_seed, args.pov_seed, args.cb_no_attach, 1182 | args.cb_seed_munge, args.cb_env) 1183 | 1184 | if args.ids_rules: 1185 | runner.set_ids_rules(args.ids_rules) 1186 | 1187 | if args.enable_remote: 1188 | runner.enable_remote(args.remote_nodes[0], args.remote_nodes[1], 1189 | args.remote_nodes[2]) 1190 | 1191 | try: 1192 | ret = runner.run() 1193 | except KeyboardInterrupt: 1194 | logger.warning('interrupted') 1195 | finally: 1196 | runner.cleanup() 1197 | return ret 1198 | 1199 | if __name__ == "__main__": 1200 | exit(main()) 1201 | -------------------------------------------------------------------------------- /cb-test.md: -------------------------------------------------------------------------------- 1 | % CB-TEST(1) Cyber Grand Challenge Manuals 2 | % Brian Caswell 3 | % April 18, 2014 4 | 5 | # NAME 6 | 7 | cb-test - Challenge Binary testing utility 8 | 9 | # SYNOPSIS 10 | 11 | cb-test [options] --cb *CB* [*CB* ...] --directory *DIRECTORY* (--xml *XML* [*XML* ...] | --xml_dir *DIR*) 12 | 13 | # DESCRIPTION 14 | 15 | cb-test is a utility to manage testing a CGC challenge binary (CB) with the replay tool, and recording the resulting network traffic. If any of the XML files does not validate, the replay tool indicates failures, or a core file is generated, the cb-test tool returns failure. 16 | 17 | cb-test output is in the TAP format. 18 | 19 | # ARGUMENTS 20 | \-\-cb *CB* [*CB* ...] 21 | : Specify the challenge binaries to be used in testing. 22 | 23 | \-\-directory *DIRECTORY* 24 | : Specify the directory containing challenge binaries. 25 | 26 | # MUTUALLY EXCLUSIVE ARGUMENTS 27 | 28 | \-\-xml *XML* [*XML* ...] 29 | : Specify the XML files to be used with the replay tool. These can be POVs or polls. 30 | 31 | \-\-xml_dir *DIR* 32 | : Specify the directory containing XML files to be used with the replay tool. These can be POVs or polls. 33 | 34 | # OPTIONS 35 | \-\-log *LOG_FILE* 36 | : Specify the filename to write output, rather than STDOUT 37 | 38 | \-\-port *PORT* 39 | : Specify the TCP port used for testing the CB. If *PORT* is not provided, a random port will be used. 40 | 41 | \-\-pcap *PCAP* 42 | : Specify the directory containing the challenge binaries. If *PCAP* is not provided, the traffic resulting from testing will not be recorded. 43 | 44 | \-\-wrapper *WRAPPER* 45 | : Specify the executable to wrap each challenge binary for instrumentation. 46 | 47 | \-\-timeout *TIMEOUT* 48 | : Specify the maximum duration for each Poll/POV. 49 | 50 | \-\-ids_rules *RULES_FILE* 51 | : Specify the filename to read IDS rules, for use with cb-proxy. *ONLY* supported with remote testing enabled. 52 | 53 | \-\-should_core 54 | : Specify if inspecting for a 'core' in the *DIRECTORY* takes precedent over all other testing. 55 | 56 | \-\-cb_env *ENV* 57 | : Specify an enviornment variable to use in the CB. 58 | 59 | \-\-cb_seed *SEED* 60 | : Specify the PRNG seed to use in the CB. 61 | 62 | \-\-cb_seed_skip *SKIP* 63 | : Specify the amount to advance the PRNG state before launching the CB. 64 | 65 | \-\-pov_seed *SEED* 66 | : Specify the PRNG seed to use in the POV. 67 | 68 | \-\-negotiate_seed 69 | : Specify if the PRNG should be negotiated by cb\-replay. 70 | 71 | \-\-enable_remote 72 | : Specify if remote testing should be enabled, via uploading the required artifacts via *SCP* and running the underlying tools via *SSH* to the nodes specified via --remote_nodes 73 | 74 | \-\-remote_nodes *CB_SERVER* *POV_SERVER* *IDS_SERVER* 75 | : Specify the hostnames or IP addresses for the nodes to be used in remote testing. 76 | 77 | \-\-failure_ok 78 | : Specify if failing the 'replay' interaction is acceptable. Useful for testing a POV against a patched CB. 79 | 80 | \-\-debug 81 | : Specify if debug output should be enabled. 82 | 83 | # EXAMPLE USES 84 | 85 | * cb-test --port 10000 --cb echo --xml echo-test.xml --directory /bin --pcap /tmp/echo-test.pcap 86 | 87 | This will test the challenge binary 'echo' in the directory '/bin', using the XML file 'echo-test.xml', on TCP port 10000, recording any resulting network traffic to '/tmp/echo-test.pcap'. 88 | 89 | * cb-test --cb my-service --xml test-1.xml test-2.xml --directory /usr/local/bin 90 | 91 | This will test the challenge binary 'my-service' in the directory '/usr/local/bin', using the XML files 'test-1.xml' and 'test-2.xml', on a random TCP port. No network traffic is recorded. 92 | 93 | * cb-test --cb my-service --xml_dir . --directory /usr/local/bin 94 | 95 | This will test the challenge binary 'my-service' in the directory '/usr/local/bin', using the XML files that match the file globbing '*.xml' in the current working directory, on a random TCP port. No network traffic is recorded. 96 | 97 | * cb-test --cb cb-1 cb-2 cb-3 --xml ipc.xml --directory /tmp --pcap /tmp/ipc.pcap 98 | 99 | This will test the IPC challenge binary made up of the challenge binaries 'cb-1', 'cb-2', and 'cb-3' in the directory '/tmp', using the XML file 'ipc.xml', on a random TCP port, record any resulting network traffic to '/tmp/ipc.pcap'. 100 | 101 | # COPYRIGHT 102 | 103 | Copyright (C) 2014, Brian Caswell 104 | 105 | # SEE ALSO 106 | 107 | `cb-server` (1), `poll-validate` (1), `cb-replay` (1), `tcpdump` (8) 108 | 109 | For information regarding the TAP Format, see 110 | 111 | For more information relating to DARPA's Cyber Grand Challenge, please visit 112 | -------------------------------------------------------------------------------- /cgc-cb.mk: -------------------------------------------------------------------------------- 1 | ifndef POLLS_RELEASE_COUNT 2 | POLLS_RELEASE_COUNT=1000 3 | endif 4 | 5 | ifndef POLLS_RELEASE_SEED 6 | POLLS_RELEASE_SEED=$(shell od -An -i -N 4 /dev/urandom) 7 | endif 8 | 9 | ifndef POLLS_RELEASE_MAX_DEPTH 10 | POLLS_RELEASE_MAX_DEPTH=1048575 11 | endif 12 | 13 | ifndef POLLS_TESTING_COUNT 14 | POLLS_TESTING_COUNT=1000 15 | endif 16 | 17 | ifndef POLLS_TESTING_SEED 18 | POLLS_TESTING_SEED=$(shell od -An -i -N 4 /dev/urandom) 19 | endif 20 | 21 | ifndef POLLS_TESTING_MAX_DEPTH 22 | POLLS_TESTING_MAX_DEPTH=1048575 23 | endif 24 | 25 | ifndef BIN_COUNT 26 | BIN_COUNT=0 27 | endif 28 | 29 | ifndef POLL_REUSE_COUNT 30 | POLL_REUSE_COUNT=0 31 | endif 32 | 33 | ifndef VULN_COUNT 34 | VULN_COUNT=0 35 | endif 36 | 37 | LIBS = -L/usr/lib -lcgc 38 | LDFLAGS = -nostdlib -static 39 | POV_LIBS = -lpov 40 | 41 | CC = /usr/i386-linux-cgc/bin/clang 42 | LD = /usr/i386-linux-cgc/bin/ld 43 | CXX = /usr/i386-linux-cgc/bin/clang++ 44 | OBJCOPY = /usr/i386-linux-cgc/bin/objcopy 45 | LD_ELF = /usr/bin/ld 46 | 47 | SHELL := $(SHELL) -e 48 | BIN_DIR = bin 49 | POV_DIR = pov 50 | BUILD_DIR = build 51 | ifeq ("/usr/i386-linux-cgc/bin/clang", "$(CC)") 52 | CGC_CFLAGS = -nostdlib -fno-builtin -nostdinc -Iinclude -Ilib -I/usr/include $(CFLAGS) -DCGC_BIN_COUNT=$(BIN_COUNT) 53 | else 54 | CGC_CFLAGS = -nostdlib -fno-builtin -nostdinc -nostartfiles -nodefaultlibs -Iinclude -Ilib -I/usr/include $(CFLAGS) -DCGC_BIN_COUNT=$(BIN_COUNT) 55 | endif 56 | 57 | POV_CFLAGS = -nostdlib -fno-builtin -nostdinc -Iinclude -Ilib -I/usr/include $(CFLAGS) 58 | 59 | EXE = $(AUTHOR_ID)_$(SERVICE_ID) 60 | CB_INSTALL_DIR = $(DESTDIR)/usr/share/cgc-challenges/$(EXE) 61 | PATH := /usr/i386-linux-cgc/bin:$(PATH) 62 | BINS = $(wildcard cb_*) 63 | POV_BINS = $(wildcard pov_*) 64 | POV_SRCS = $(wildcard pov_*/*.c) 65 | SRCS = $(wildcard src/*.c src/*.cc lib/*.c lib/*.cc) 66 | CXX_SRCS = $(filter %.cc, $(SRCS)) 67 | EXTENDED_APP = /usr/share/cb-testing/CGC_Extended_Application.pdf 68 | EXTENDED_APP_SZ = $(shell du -b $(EXTENDED_APP) | awk '{print $$1}') 69 | 70 | POLLS_RELEASE = $(wildcard poller/for-release/*.xml) 71 | POLLS_TESTING = $(wildcard poller/for-testing/*.xml) 72 | POVS = $(wildcard $(POV_DIR)/*.xml) 73 | POVXML = $(wildcard $(POV_DIR)/*.povxml) 74 | 75 | CB_ADDITIONS = 76 | ifndef NO_CB_DPKG 77 | CB_ADDITIONS +=dpkg.o 78 | endif 79 | ifndef NO_CB_EXTENDED_APP 80 | CB_ADDITIONS +=cgc-extended-application.o 81 | endif 82 | 83 | has_ids_rules = $(sort $(wildcard ids/*.rules)) 84 | has_cfe_pov = $(sort $(wildcard pov_*) $(wildcard pov/*.povxml)) 85 | get_poll_args = $(if $(call has_cfe_pov),--store_seed --repeat $(POLL_REUSE_COUNT),) 86 | get_pcap_args = $(if $(call has_cfe_pov),,--pcap $(PCAP_FILE_PATH)) 87 | get_test_args = $(if $(call has_cfe_pov),--negotiate,) 88 | get_ids_args = $(if $(call has_ids_rules),--ids_rules $(call has_ids_rules,)) 89 | get_cb_bins = $(sort $(filter-out %_partial, $(filter-out %_patched, $(notdir $(wildcard $(BIN_DIR)/$(EXE)*))))) 90 | get_cb_patched_bins = $(sort $(filter-out %_partial, $(filter %_patched, $(notdir $(wildcard $(BIN_DIR)/$(EXE)*))))) 91 | 92 | OBJS_ = $(SRCS:.c=.o) 93 | OBJS = $(OBJS_:.cc=.o) $(CB_ADDITIONS) 94 | ELF_OBJS = $(OBJS:.o=.elf) 95 | 96 | POV_OBJS = $(POV_SRCS:.c=.o) 97 | BUILD_POV_OBJS = $(addprefix $(BUILD_DIR)/, $(POV_OBJS)) 98 | BUILD_POV_DIRS = $(addprefix $(BUILD_DIR)/, $(POV_BINS)) 99 | 100 | POV_XML_BINS = $(POVXML:.povxml=.pov) 101 | 102 | POLL_RELEASE_LOGS = $(POLLS_RELEASE:.povxml=.log) 103 | POLL_TESTING_LOGS = $(POLLS_TESTING:.povxml=.log) 104 | POV_LOGS = $(POVS:.xml=.log) 105 | 106 | BUILD_POLL_TESTING_LOG = $(addprefix $(BUILD_DIR)/, $(POLL_RELEASE_LOGS)) 107 | BUILD_POLL_RELEASE_LOG = $(addprefix $(BUILD_DIR)/, $(POLL_TESTING_LOGS)) 108 | BUILD_POV_LOG = $(addprefix $(BUILD_DIR)/, $(POV_LOGS)) 109 | 110 | ifeq ($(VULN_COUNT), 0) 111 | PATCHED_CFLAGS = $(CGC_CFLAGS) -DPATCHED 112 | else 113 | PATCHED_CFLAGS = $(CGC_CFLAGS) $(shell seq -f '-DPATCHED_%g ' -s '' $(VULN_COUNT)) 114 | endif 115 | 116 | PATCHED_DIR = patched 117 | PATCHED_EXE = $(EXE)_patched 118 | PATCHED_PATH = $(BIN_DIR)/$(PATCHED_EXE) 119 | PATCHED_OBJS = $(addprefix $(BUILD_DIR)/$(PATCHED_DIR)/, $(OBJS)) 120 | PATCHED_DEBUG_PATH = $(BUILD_DIR)/$(PATCHED_DIR)/$(BIN_DIR)/$(EXE) 121 | 122 | PATCHED_ELF_OBJS = $(addprefix $(BUILD_DIR)/$(PATCHED_DIR)/, $(ELF_OBJS)) 123 | PATCHED_ELF_STUB = $(BUILD_DIR)/$(PATCHED_DIR)/syscall-stub.elf 124 | PATCHED_SO = $(BUILD_DIR)/$(PATCHED_DIR)/so/$(EXE).so 125 | 126 | RELEASE_CFLAGS = $(CGC_CFLAGS) 127 | RELEASE_DIR = release 128 | RELEASE_EXE = $(EXE) 129 | RELEASE_PATH = $(BIN_DIR)/$(RELEASE_EXE) 130 | RELEASE_OBJS = $(addprefix $(BUILD_DIR)/$(RELEASE_DIR)/, $(OBJS)) 131 | RELEASE_DEBUG_PATH = $(BUILD_DIR)/$(RELEASE_DIR)/$(BIN_DIR)/$(EXE) 132 | 133 | PARTIAL_CFLAGS = $(CGC_CFLAGS) -DPATCHED_$(PARTIAL_ID) 134 | PARTIAL_DIR = partial_$(PARTIAL_ID) 135 | PARTIAL_EXE = $(EXE)_patched_$(PARTIAL_ID)_partial 136 | PARTIAL_PATH = $(BIN_DIR)/$(PARTIAL_EXE) 137 | PARTIAL_OBJS = $(addprefix $(BUILD_DIR)/$(PARTIAL_DIR)/, $(OBJS)) 138 | PARTIAL_DEBUG_PATH = $(BUILD_DIR)/$(PARTIAL_DIR)/$(BIN_DIR)/$(EXE) 139 | 140 | PCAP_DIR = pcap 141 | PCAP_FILE_PATH = $(PCAP_DIR)/$(RELEASE_EXE)_poll.pcap 142 | PARTIAL_ID = 143 | 144 | all: build test 145 | 146 | testit: 147 | @echo $(call get_cb_bins) 148 | @echo $(call get_cb_patched_bins) 149 | @echo $(BINS) 150 | 151 | prep: 152 | @mkdir -p $(BUILD_DIR)/$(PATCHED_DIR)/lib $(BUILD_DIR)/$(PATCHED_DIR)/src $(BUILD_DIR)/$(PATCHED_DIR)/$(BIN_DIR) 153 | @mkdir -p $(BUILD_DIR)/$(RELEASE_DIR)/lib $(BUILD_DIR)/$(RELEASE_DIR)/src $(BUILD_DIR)/$(RELEASE_DIR)/$(BIN_DIR) 154 | @mkdir -p $(BUILD_DIR)/$(POV_DIR) 155 | @mkdir -p $(PCAP_DIR) $(BIN_DIR) $(POV_DIR) 156 | @mkdir -p $(BUILD_DIR)/poller/for-testing $(BUILD_DIR)/poller/for-release 157 | @mkdir -p $(BUILD_DIR)/$(PATCHED_DIR)/so $(BUILD_POV_DIRS) 158 | ifneq ($(strip $(PARTIAL_ID)), ) 159 | @mkdir -p $(BUILD_DIR)/$(PARTIAL_DIR)/lib $(BUILD_DIR)/$(PARTIAL_DIR)/src $(BUILD_DIR)/$(PARTIAL_DIR)/$(BIN_DIR) 160 | endif 161 | 162 | 163 | %.pov: %.povxml 164 | pov-xml2c -x $< -o $(BUILD_DIR)/$@.c 165 | $(CC) -c $(POV_CFLAGS) -o $(BUILD_DIR)/$@.o $(BUILD_DIR)/$@.c 166 | $(LD) $(LDFLAGS) -o $@ $(BUILD_DIR)/$@.o $(LIBS) $(POV_LIBS) 167 | 168 | $(BUILD_DIR)/%.o: %.c 169 | $(CC) -c $(POV_CFLAGS) -o $@ $^ 170 | 171 | build_pov_objs: prep $(BUILD_POV_OBJS) 172 | 173 | $(POV_BINS): build_pov_objs 174 | $(LD) $(LDFLAGS) -o $(POV_DIR)/$@.pov $(BUILD_DIR)/$@/*.o $(LIBS) $(POV_LIBS) 175 | 176 | pov: prep $(POV_XML_BINS) $(POV_BINS) 177 | 178 | $(BINS): cb_%: 179 | ( cd $@; make -f ../Makefile build build-partial SERVICE_ID=$(SERVICE_ID)_$* BIN_COUNT=$(words $(BINS)) VULN_COUNT=$(VULN_COUNT)) 180 | cp $@/$(BIN_DIR)/* $(BIN_DIR) 181 | 182 | build-binaries: $(BINS) 183 | 184 | clean-binaries: ; $(foreach dir, $(BINS), (cd $(dir) && make -f ../Makefile clean) &&) : 185 | 186 | $(BUILD_DIR)/$(RELEASE_DIR)/dpkg.o: /etc/decree_version 187 | echo "The DECREE packages used in the creation of this challenge binary were:" > $@.txt 188 | dpkg --list | grep -i cgc >> $@.txt 189 | $(OBJCOPY) --input binary --output cgc32-i386 --binary-architecture i386 $@.txt $@ 190 | 191 | $(BUILD_DIR)/$(PATCHED_DIR)/dpkg.o $(BUILD_DIR)/$(PARTIAL_DIR)/dpkg.o: $(BUILD_DIR)/$(RELEASE_DIR)/dpkg.o 192 | cp $< $@ 193 | 194 | $(BUILD_DIR)/$(RELEASE_DIR)/cgc-extended-application.o $(BUILD_DIR)/$(PATCHED_DIR)/cgc-extended-application.o $(BUILD_DIR)/$(PARTIAL_DIR)/cgc-extended-application.o: $(EXTENDED_APP) 195 | echo "The $(EXTENDED_APP_SZ) byte CGC Extended Application follows. Each team participating in CGC must have submitted this completed agreement including the Team Information, the Liability Waiver, the Site Visit Information Sheet and the Event Participation agreement." > $@.tmp 196 | cat $(EXTENDED_APP) >> $@.tmp 197 | $(OBJCOPY) --input binary --output cgc32-i386 --binary-architecture i386 $@.tmp $@ 198 | 199 | %.elf: %.o 200 | cp $< $@ 201 | cgc2elf $@ 202 | 203 | $(PATCHED_ELF_STUB): 204 | $(CC) -c -nostdlib -fno-builtin -nostdinc -o $(BUILD_DIR)/$(PATCHED_DIR)/syscall-stub.elf /usr/share/cb-testing/syscall-stub.c 205 | cgc2elf $(BUILD_DIR)/$(PATCHED_DIR)/syscall-stub.elf 206 | 207 | build-partial: 208 | ifneq ($(VULN_COUNT), 0) 209 | ifeq ($(BIN_COUNT), 0) 210 | ( for i in $(shell seq $(VULN_COUNT)); do \ 211 | echo $(VULN_COUNT) ; \ 212 | make PARTIAL_ID=$$i partial; \ 213 | done ) 214 | else 215 | ( for i in $(shell seq $(VULN_COUNT)); do \ 216 | echo $(VULN_COUNT) ; \ 217 | make -f ../Makefile PARTIAL_ID=$$i partial; \ 218 | done ) 219 | endif 220 | endif 221 | 222 | # Release rules 223 | release: prep $(RELEASE_PATH) 224 | 225 | $(RELEASE_PATH): $(RELEASE_OBJS) 226 | $(LD) $(LDFLAGS) -s -o $(RELEASE_PATH) -I$(BUILD_DIR)/$(RELEASE_DIR)/lib $^ $(LIBS) 227 | $(LD) $(LDFLAGS) -o $(RELEASE_DEBUG_PATH) -I$(BUILD_DIR)/$(RELEASE_DIR)/lib $^ $(LIBS) 228 | 229 | $(BUILD_DIR)/$(RELEASE_DIR)/%.o: %.c 230 | $(CC) -c $(RELEASE_CFLAGS) -o $@ $< 231 | 232 | $(BUILD_DIR)/$(RELEASE_DIR)/%.o: %.cc 233 | $(CXX) -c $(RELEASE_CFLAGS) $(CXXFLAGS) -o $@ $< 234 | 235 | # Partially patched binary rules 236 | partial: prep $(PARTIAL_PATH) 237 | 238 | $(PARTIAL_PATH): $(PARTIAL_OBJS) 239 | $(LD) $(LDFLAGS) -s -o $(PARTIAL_PATH) -I$(BUILD_DIR)/$(PARTIAL_DIR)/lib $^ $(LIBS) 240 | $(LD) $(LDFLAGS) -o $(PARTIAL_DEBUG_PATH) -I$(BUILD_DIR)/$(PARTIAL_DIR)/lib $^ $(LIBS) 241 | 242 | $(BUILD_DIR)/$(PARTIAL_DIR)/%.o: %.c 243 | $(CC) -c $(PARTIAL_CFLAGS) -o $@ $< 244 | 245 | $(BUILD_DIR)/$(PARTIAL_DIR)/%.o: %.cc 246 | $(CXX) -c $(PARTIAL_CFLAGS) $(CXXFLAGS) -o $@ $< 247 | 248 | # Patched rules 249 | patched: prep $(PATCHED_PATH) $(PATCHED_SO) 250 | 251 | patched-so: prep $(PATCHED_SO) 252 | 253 | $(PATCHED_PATH): $(PATCHED_OBJS) 254 | $(LD) $(LDFLAGS) -s -o $(PATCHED_PATH) $^ $(LIBS) 255 | $(LD) $(LDFLAGS) -o $(PATCHED_DEBUG_PATH) -I$(BUILD_DIR)/$(RELEASE_DIR)/lib $^ $(LIBS) 256 | 257 | $(BUILD_DIR)/$(PATCHED_DIR)/%.o: %.c 258 | $(CC) -c $(PATCHED_CFLAGS) -o $@ $< 259 | 260 | $(BUILD_DIR)/$(PATCHED_DIR)/%.o: %.cc 261 | $(CXX) -c $(PATCHED_CFLAGS) $(CXXFLAGS) -o $@ $< 262 | 263 | ifneq ("$(CXX_SRCS)", "") 264 | $(PATCHED_SO): 265 | @echo "SO build artifact not currently supported for C++ services" 266 | else 267 | $(PATCHED_SO): $(PATCHED_ELF_OBJS) $(PATCHED_ELF_STUB) 268 | $(LD_ELF) -shared -o $@ $^ 269 | endif 270 | 271 | generate-polls: 272 | if [ -f poller/for-release/machine.py ] && [ -f poller/for-release/state-graph.yaml ]; then generate-polls $(call get_poll_args) --count $(POLLS_RELEASE_COUNT) --seed $(POLLS_RELEASE_SEED) --depth $(POLLS_RELEASE_MAX_DEPTH) poller/for-release/machine.py poller/for-release/state-graph.yaml poller/for-release; fi 273 | if [ -f poller/for-testing/machine.py ] && [ -f poller/for-testing/state-graph.yaml ]; then generate-polls $(call get_poll_args) --count $(POLLS_TESTING_COUNT) --seed $(POLLS_TESTING_SEED) --depth $(POLLS_TESTING_MAX_DEPTH) poller/for-testing/machine.py poller/for-testing/state-graph.yaml poller/for-testing; fi 274 | 275 | check: generate-polls 276 | # Polls that the CB author intends to release the resulting network traffic during CQE 277 | if [ -d poller/for-release ]; then cb-test $(call get_test_args) --xml_dir poller/for-release/ --directory $(BIN_DIR) --log $(BUILD_DIR)/$(RELEASE_EXE).for-release.txt --cb $(call get_cb_bins) $(call get_pcap_args); fi 278 | if [ -d poller/for-release ]; then cb-test $(call get_test_args) --xml_dir poller/for-release/ --directory $(BIN_DIR) --log $(BUILD_DIR)/$(PATCHED_EXE).for-release.txt --cb $(call get_cb_patched_bins); fi 279 | 280 | # Polls that the CB author intends to NOT release the resulting network traffic during CQE 281 | if [ -d poller/for-testing ]; then cb-test $(call get_test_args) --xml_dir poller/for-testing/ --directory $(BIN_DIR) --log $(BUILD_DIR)/$(RELEASE_EXE).for-testing.txt --cb $(call get_cb_bins); fi 282 | if [ -d poller/for-testing ]; then cb-test $(call get_test_args) --xml_dir poller/for-testing/ --directory $(BIN_DIR) --log $(BUILD_DIR)/$(PATCHED_EXE).for-testing.txt --cb $(call get_cb_patched_bins); fi 283 | 284 | # POVs that should generate an identified crash for CQE when sent to the release CB but not the patched CB 285 | cb-test $(call get_test_args) --xml_dir $(POV_DIR) --directory $(BIN_DIR) --log $(BUILD_DIR)/$(RELEASE_EXE).pov.txt --failure_ok --should_core --cb $(call get_cb_bins) 286 | cb-test $(call get_test_args) --xml_dir $(POV_DIR) --directory $(BIN_DIR) --log $(BUILD_DIR)/$(PATCHED_EXE).pov.txt --failure_ok --cb $(call get_cb_patched_bins) 287 | 288 | check-ids: 289 | if [ -d poller/for-release ]; then cb-test $(call get_ids_args) $(call get_test_args) --enable_remote --xml_dir poller/for-release/ --directory $(BIN_DIR) --log $(BUILD_DIR)/$(RELEASE_EXE).for-release-ids.txt --cb $(call get_cb_bins); fi 290 | if [ -d poller/for-release ]; then cb-test $(call get_ids_args) $(call get_test_args) --enable_remote --xml_dir poller/for-release/ --directory $(BIN_DIR) --log $(BUILD_DIR)/$(PATCHED_EXE).for-release-ids.txt --cb $(call get_cb_patched_bins); fi 291 | if [ -d poller/for-testing ]; then cb-test $(call get_ids_args) $(call get_test_args) --enable_remote --xml_dir poller/for-testing/ --directory $(BIN_DIR) --log $(BUILD_DIR)/$(RELEASE_EXE).for-testing-ids.txt --cb $(call get_cb_bins); fi 292 | if [ -d poller/for-testing ]; then cb-test $(call get_ids_args) $(call get_test_args) --enable_remote --xml_dir poller/for-testing/ --directory $(BIN_DIR) --log $(BUILD_DIR)/$(PATCHED_EXE).for-testing-ids.txt --cb $(call get_cb_patched_bins); fi 293 | cb-test $(call get_ids_args) $(call get_test_args) --enable_remote --xml_dir $(POV_DIR) --directory $(BIN_DIR) --log $(BUILD_DIR)/$(RELEASE_EXE).pov-ids.txt --failure_ok --cb $(call get_cb_bins) 294 | cb-test $(call get_ids_args) $(call get_test_args) --enable_remote --xml_dir $(POV_DIR) --directory $(BIN_DIR) --log $(BUILD_DIR)/$(PATCHED_EXE).pov-ids.txt --failure_ok --cb $(call get_cb_patched_bins) 295 | 296 | check-remote: 297 | if [ -d poller/for-release ]; then cb-test $(call get_test_args) --enable_remote --xml_dir poller/for-release/ --directory $(BIN_DIR) --log $(BUILD_DIR)/$(RELEASE_EXE).for-release-remote.txt --cb $(call get_cb_bins); fi 298 | if [ -d poller/for-release ]; then cb-test $(call get_test_args) --enable_remote --xml_dir poller/for-release/ --directory $(BIN_DIR) --log $(BUILD_DIR)/$(PATCHED_EXE).for-release-remote.txt --cb $(call get_cb_patched_bins); fi 299 | if [ -d poller/for-testing ]; then cb-test $(call get_test_args) --enable_remote --xml_dir poller/for-testing/ --directory $(BIN_DIR) --log $(BUILD_DIR)/$(RELEASE_EXE).for-testing-remote.txt --cb $(call get_cb_bins); fi 300 | if [ -d poller/for-testing ]; then cb-test $(call get_test_args) --enable_remote --xml_dir poller/for-testing/ --directory $(BIN_DIR) --log $(BUILD_DIR)/$(PATCHED_EXE).for-testing-remote.txt --cb $(call get_cb_patched_bins); fi 301 | cb-test $(call get_test_args) --enable_remote --xml_dir $(POV_DIR) --directory $(BIN_DIR) --log $(BUILD_DIR)/$(RELEASE_EXE).pov-remote.txt --failure_ok --should_core --cb $(call get_cb_bins) 302 | cb-test $(call get_test_args) --enable_remote --xml_dir $(POV_DIR) --directory $(BIN_DIR) --log $(BUILD_DIR)/$(PATCHED_EXE).pov-remote.txt --failure_ok --cb $(call get_cb_patched_bins) 303 | 304 | clean-test: 305 | @-rm -f $(BUILD_POV_LOG) $(BUILD_POLL_LOG) 306 | 307 | clean: clean-test clean-binaries 308 | -rm -rf $(BUILD_DIR) $(BIN_DIR) $(PCAP_DIR) 309 | -rm -f test.log 310 | -rm -f poller/for-release/edges.png poller/for-release/nodes.png poller/for-release/counts.png 311 | -rm -f poller/for-release/gen_*.xml 312 | -rm -f poller/for-release/GEN_*.xml 313 | -rm -f poller/for-release/graph.dot 314 | -rm -f poller/for-testing/edges.png poller/for-testing/nodes.png poller/for-testing/counts.png 315 | -rm -f poller/for-testing/gen_*.xml 316 | -rm -f poller/for-testing/GEN_*.xml 317 | -rm -f poller/for-testing/graph.dot 318 | -rm -f poller/for-release/machine.pyc 319 | -rm -f poller/for-testing/machine.pyc 320 | -rm -f $(POV_DIR)/*.pov 321 | -if [ -d $(POV_DIR) ]; then rmdir --ignore-fail-on-non-empty $(POV_DIR); fi 322 | 323 | ifeq ($(strip $(BINS)),) 324 | build: prep release patched pov build-partial 325 | else 326 | build: prep build-binaries pov 327 | endif 328 | 329 | install: 330 | install -d $(CB_INSTALL_DIR)/$(BIN_DIR) 331 | install -d $(CB_INSTALL_DIR)/$(POV_DIR) 332 | if [ ! -z "$(POVS)" ]; then install -m 444 $(wildcard $(POV_DIR)/*.xml) $(CB_INSTALL_DIR)/$(POV_DIR) ; fi 333 | if [ ! -z "$(POV_BINS)" ] || [ ! -z "$(POVXML)" ]; then install -m 555 $(wildcard $(POV_DIR)/*.pov) $(CB_INSTALL_DIR)/$(POV_DIR) ; fi 334 | if [ -d ids ]; then install -d $(CB_INSTALL_DIR)/ids ; fi 335 | if [ -d ids ]; then install -m 444 $(wildcard ids/*.rules) $(CB_INSTALL_DIR)/ids ; fi 336 | if [ -d poller/for-release ]; then install -d $(CB_INSTALL_DIR)/poller/for-release ; fi 337 | if [ -d poller/for-release ]; then install -m 444 $(wildcard poller/for-release/*.xml) $(CB_INSTALL_DIR)/poller/for-release ; fi 338 | if [ -d poller/for-testing ]; then install -d $(CB_INSTALL_DIR)/poller/for-testing ; fi 339 | if [ -d poller/for-testing ]; then install -m 444 $(wildcard poller/for-testing/*.xml) $(CB_INSTALL_DIR)/poller/for-testing ; fi 340 | if [ -f $(PCAP_FILE_PATH) ]; then install -d $(CB_INSTALL_DIR)/$(PCAP_DIR) ; fi 341 | if [ -f $(PCAP_FILE_PATH) ]; then install -m 444 $(PCAP_FILE_PATH) $(CB_INSTALL_DIR)/$(PCAP_DIR) ; fi 342 | install -m 555 $(wildcard $(BIN_DIR)/$(EXE)*) $(CB_INSTALL_DIR)/$(BIN_DIR) 343 | 344 | test: build check 345 | 346 | .PHONY: install all clean clean-test patched prep release remake test build-partial $(BINS) $(POV_BINS) 347 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | cb-testing (10551-cfe-rc8) UNRELEASED; urgency=low 2 | 3 | * build 4 | 5 | -- Brian Caswell Tue, 10 Jan 2017 21:21:57 +0000 6 | 7 | cb-testing (1.0) unstable; urgency=low 8 | 9 | * Initial Release. 10 | 11 | -- bmc Thu, 17 Apr 2014 16:45:08 -0400 12 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 8 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: cb-testing 2 | Section: devel 3 | Priority: extra 4 | Maintainer: bmc 5 | Build-Depends: debhelper (>= 8.0.0) 6 | Standards-Version: 3.9.3 7 | Homepage: http://www.darpa.mil/cybergrandchallenge/ 8 | 9 | Package: cb-testing 10 | Architecture: all 11 | Depends: ${python:Depends}, ${misc:Depends}, python, python-lxml, cgc-service-launcher, openssh-client, tcpdump, cgc-service-launcher, poll-generator 12 | Description: Challenge Binary Testing tools 13 | Provide a simple framework for testing Cyber Grand Challenge binaries to verify 14 | the polling, proof-of-vulnerabilities, and traffic captures work as expected. 15 | . 16 | These are exemplar tools and are intended to showcase the functionality of CB 17 | testing. 18 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: cb-testing 3 | Source: 4 | 5 | Files: * 6 | Copyright: 2014 Brian Caswell 7 | License: MIT 8 | Permission is hereby granted, free of charge, to any person obtaining a 9 | copy of this software and associated documentation files (the "Software"), 10 | to deal in the Software without restriction, including without limitation 11 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | and/or sell copies of the Software, and to permit persons to whom the 13 | Software is furnished to do so, subject to the following conditions: 14 | . 15 | The above copyright notice and this permission notice shall be included 16 | in all copies or substantial portions of the Software. 17 | . 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | %: 4 | dh $@ 5 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /poll-validate: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | """ 4 | CB Testing tool 5 | 6 | Copyright (C) 2014 - Brian Caswell 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | 26 | This tool validates an XML against the replay DTD. 27 | """ 28 | 29 | import lxml.etree 30 | import sys 31 | 32 | errors = False 33 | 34 | dtd_path = '/usr/share/cgc-docs/replay.dtd' 35 | xml_line = '' 36 | dtd_line = '' % dtd_path 37 | 38 | def error(filename, message): 39 | sys.stderr.write("ERROR %s: %s\n" % (filename, message)) 40 | errors = True 41 | 42 | with open(dtd_path, 'r') as fh: 43 | dtd = lxml.etree.DTD(fh) 44 | 45 | for filename in sys.argv[1:]: 46 | xml = '' 47 | with open(filename, 'r') as fh: 48 | xml = fh.read() 49 | 50 | if not xml.startswith(xml_line): 51 | error(filename, "does not start with 'xml' tag. Expecting: %s" % xml_line) 52 | 53 | try: 54 | root = lxml.etree.XML(xml) 55 | except lxml.etree.XMLSyntaxError as err: 56 | error(filename, "parse error: %s" % str(err)) 57 | 58 | if root.getroottree().docinfo.doctype != dtd_line: 59 | error(filename, "invalid DOCTYPE. Expecting: %s" % dtd_line) 60 | 61 | if not dtd.validate(root): 62 | for parse_error in dtd.error_log: 63 | error(filename, "parse error (line %d): %s" % (parse_error.line, parse_error.message)) 64 | 65 | if errors: 66 | sys.exit(-1) 67 | -------------------------------------------------------------------------------- /poll-validate.md: -------------------------------------------------------------------------------- 1 | % POLL-VALIDATE(1) Cyber Grand Challenge Manuals 2 | % Brian Caswell 3 | % April 18, 2014 4 | 5 | # NAME 6 | 7 | poll-validate - Poll/POV validation utility 8 | 9 | # SYNOPSIS 10 | 11 | poll-validate *XML* [*XML* ...] 12 | 13 | # DESCRIPTION 14 | 15 | poll-validate is a utility to validate a set of POVs or Polls follow the Replay DTD. 16 | 17 | # EXAMPLE USES 18 | 19 | * poll-validate test-1.xml test-2.xml 20 | 21 | This will validate 'test-1.xml' and 'test-2.xml' against the replay DTD. 22 | 23 | # COPYRIGHT 24 | 25 | Copyright (C) 2014, Brian Caswell 26 | 27 | # SEE ALSO 28 | 29 | For the Replay DTD, see '/usr/share/cgc-docs/replay.dtd'. 30 | 31 | For more information relating to DARPA's Cyber Grand Challenge, please visit 32 | -------------------------------------------------------------------------------- /syscall-stub.c: -------------------------------------------------------------------------------- 1 | 2 | void abort(void) { 3 | __asm__ ("movl $0x1, %eax\n" 4 | "movl $0x1, %ebx\n" 5 | "int $0x80" 6 | ); 7 | } 8 | 9 | void _start(void) {abort();} 10 | void _terminate(void) {abort();} 11 | void transmit(void) {abort();} 12 | void receive(void) {abort();} 13 | void fdwait(void) {abort();} 14 | void allocate(void) {abort();} 15 | void deallocate(void) {abort();} 16 | void random(void) {abort();} 17 | -------------------------------------------------------------------------------- /tests/test_cbtest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | import os 4 | import sys 5 | import unittest 6 | import time 7 | import subprocess 8 | 9 | class CB_TEST(unittest.TestCase): 10 | def test_template(self): 11 | self.assertTrue("unit tests should be written for this package") 12 | 13 | if __name__ == '__main__': 14 | unittest.main() 15 | -------------------------------------------------------------------------------- /tests/test_prng.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Tests for ansi_x931_aes128.PRNG 5 | 6 | Copyright (C) 2015 - Brian Caswell 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in 16 | all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | THE SOFTWARE. 25 | 26 | """ 27 | 28 | import unittest 29 | import random 30 | import sys 31 | 32 | sys.path.append('./lib') 33 | from ansi_x931_aes128 import PRNG 34 | 35 | 36 | class PRNGTests(unittest.TestCase): 37 | def test_prng(self): 38 | expected = "648fa900fe421280d2e45d0c009d8ab24cb135890ed9ba675015cbb3cda9536270eb28ce1dd8f58d7a5bcfa3490cb8c3".decode('hex') 39 | i = PRNG('A' * 48) 40 | 41 | stream = '' 42 | while len(stream) < len(expected): 43 | size = random.randint(1, len(expected) - len(stream)) 44 | result = i.get(size) 45 | self.assertEqual(len(result), size) 46 | stream += result 47 | 48 | self.assertEqual(len(stream), len(expected)) 49 | self.assertEqual(stream, expected) 50 | 51 | def test_ansi_seeds(self): 52 | vectors = [ 53 | ['800000000000000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc593', '339cef70da546707b2944591890394a3'], 54 | ['c00000000000000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc594', 'cc96309772e727e71baf70c361e626ae'], 55 | ['e00000000000000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc595', '793b0b004ce8543b24d26bc76ef84c19'], 56 | ['f00000000000000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc596', '00a6100f3ac3c0afc7194d75863bb97d'], 57 | ['f80000000000000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc597', 'db6dcd4cdaffd704e4ac9ba46448771a'], 58 | ['fc0000000000000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc598', '29bb29e1ca7effe2807c674628ae97ff'], 59 | ['fe0000000000000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc599', '67290ea5d230e13e73e9223929078bd9'], 60 | ['ff0000000000000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc59a', '00a4362875f0ff7e2e58c616ca22a961'], 61 | ['ff8000000000000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc59b', '1c689c09f84beaa4785f7507ce99d909'], 62 | ['ffc000000000000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc59c', '5c83858da3c8d53ebce32fa44764a2c9'], 63 | ['ffe000000000000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc59d', '5e4f68f6d5beb7c7855518b34e2ba2f6'], 64 | ['fff000000000000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc59e', 'ed7fe3b42b8724ce68c070e61588d11a'], 65 | ['fff800000000000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc59f', 'ffa4846475cb9f83261f0a04fd11368e'], 66 | ['fffc00000000000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5a0', '3125f56ce4c048a5b33803c8020c8e6c'], 67 | ['fffe00000000000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5a1', '7cc527baa5b2b3add8e2198b326b8555'], 68 | ['ffff00000000000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5a2', '95810f62aee87acce306b4fafe30831b'], 69 | ['ffff80000000000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5a3', '69c8e966ee3edaa1f78022d65f23d21a'], 70 | ['ffffc0000000000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5a4', '5b5a4b89f521b3ba43b2f1fb226da412'], 71 | ['ffffe0000000000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5a5', '669763cdac9c776108bb0a3ac9e8717b'], 72 | ['fffff0000000000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5a6', '7420dfd43c1e1cdde2e97edc02c1c88a'], 73 | ['fffff8000000000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5a7', '0c1d2142b51720bdcff11ff41cb573cd'], 74 | ['fffffc000000000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5a8', '05b089e217efe8a06f25d8226f7075f8'], 75 | ['fffffe000000000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5a9', '12f408b060a676019a430173e9236802'], 76 | ['ffffff000000000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5aa', '84deccb0a2e30e1df4601f1eab0a5498'], 77 | ['ffffff800000000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5ab', '4af9217a6587bb66c72093f9fa52dfef'], 78 | ['ffffffc00000000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5ac', '6c552cd4e79b887c7f8a82a204bb5bc3'], 79 | ['ffffffe00000000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5ad', '835e991a3fef8a735574b3c8d6c18ee3'], 80 | ['fffffff00000000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5ae', '7d4a1bad6049ed9f6105b54081e0a47b'], 81 | ['fffffff80000000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5af', 'e4b2e5fd7c6ae9c7009b358dbfbbc40e'], 82 | ['fffffffc0000000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5b0', '601ed99fa91b1ee1e7b12e1b55cbbf39'], 83 | ['fffffffe0000000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5b1', '8c9e1f94fe91b8bc3bf62ca875595199'], 84 | ['ffffffff0000000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5b2', 'bc7f2371d7ace178ec967d3fd85fab35'], 85 | ['ffffffff8000000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5b3', '9e909514330bc5898cb6ce6c3a72798b'], 86 | ['ffffffffc000000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5b4', '9e8d2e53ea2b457941a6344b01b9e623'], 87 | ['ffffffffe000000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5b5', '9fd7cf92c009bd7823aa4b098245fe07'], 88 | ['fffffffff000000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5b6', 'f9d462b9fc073ef766623acc9e813d79'], 89 | ['fffffffff800000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5b7', 'bc8b053176ef602eab420c4c71c94b7d'], 90 | ['fffffffffc00000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5b8', 'd914ff8866033081be23c7a6357e88a1'], 91 | ['fffffffffe00000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5b9', '056d2d88ceccf5eb1e14f6950e4f98ca'], 92 | ['ffffffffff00000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5ba', '580f8e925dcb03206a0832e3cf956d44'], 93 | ['ffffffffff80000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5bb', '1f6cb3420f701353b16464869fd9777b'], 94 | ['ffffffffffc0000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5bc', '003130793596f2b1e2d5bde2b48ae312'], 95 | ['ffffffffffe0000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5bd', '267b9b7ef90a732f836de5b95dc3c5ab'], 96 | ['fffffffffff0000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5be', 'f41155bee8af3ee58e9dae215f8cd565'], 97 | ['fffffffffff8000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5bf', 'e5eac38320ce55f8a2aa8ba80d2a1814'], 98 | ['fffffffffffc000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5c0', '762d7889797c3de5cc043e7a523d4355'], 99 | ['fffffffffffe000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5c1', '07eabc9d857245eb77703c4c8a37a07a'], 100 | ['ffffffffffff000000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5c2', 'ee442d216c2bca9056f2841a302bc7db'], 101 | ['ffffffffffff800000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5c3', '8c6d73bd41af6120d542e0b96262c090'], 102 | ['ffffffffffffc00000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5c4', '6d6e205a2525666e46ae794096ff27e0'], 103 | ['ffffffffffffe00000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5c5', 'eaf9559b192a779cd5381802a07be6e9'], 104 | ['fffffffffffff00000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5c6', '8c611a402887c46010e3cd979708b225'], 105 | ['fffffffffffff80000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5c7', 'a83310b018994cbc81c7bc021f4d4258'], 106 | ['fffffffffffffc0000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5c8', '2bb0f59c40204ac4538b9857f90f89fb'], 107 | ['fffffffffffffe0000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5c9', '1fd58c060d2ac2a47a054d9e61cefe23'], 108 | ['ffffffffffffff0000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5ca', '8c609188f847c46ae07212fb7f72b237'], 109 | ['ffffffffffffff8000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5cb', '9c8377288804e9c72487a23cca4f1847'], 110 | ['ffffffffffffffc000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5cc', '2ca0336293178a7b1e3eecb073722fd9'], 111 | ['ffffffffffffffe000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5cd', '522fa2d0681b68c0409f14a7bbc10f35'], 112 | ['fffffffffffffff000000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5ce', '68bd7966a4b327ba5cd7454b71473441'], 113 | ['fffffffffffffff800000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5cf', '67b5ed9ccc8849e20ed1339cf527cf31'], 114 | ['fffffffffffffffc00000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5d0', 'a58ea39069a3f84d5f5f30b3d42269f1'], 115 | ['fffffffffffffffe00000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5d1', '11ee2c950e9b942d5a2d6bf9a0cce85c'], 116 | ['ffffffffffffffff00000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5d2', 'a704ba2bd785a1d3f9e97f58e93d6a53'], 117 | ['ffffffffffffffff80000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5d3', '473ff1fa63144c14a53f1a4a479593eb'], 118 | ['ffffffffffffffffc0000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5d4', '8f680d904097fffd37e0872d18e20f2d'], 119 | ['ffffffffffffffffe0000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5d5', 'b629d40bf2c60fc13bf0181af99fc166'], 120 | ['fffffffffffffffff0000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5d6', '5175a49b094152dba016e36cf8831533'], 121 | ['fffffffffffffffff8000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5d7', '451df25da209595f77c4cf2abcd54ece'], 122 | ['fffffffffffffffffc000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5d8', '44895ad13ac1a007a834f39d53ccd9a9'], 123 | ['fffffffffffffffffe000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5d9', '4aefd749fd865514f99ef0aecdf7a129'], 124 | ['ffffffffffffffffff000000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5da', '6bd592cca825397cfc494d4a02313971'], 125 | ['ffffffffffffffffff800000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5db', '2c3fc41c6f87074446b1469ab8948431'], 126 | ['ffffffffffffffffffc00000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5dc', 'd9e8e216af859e9a510ae08f41739af1'], 127 | ['ffffffffffffffffffe00000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5dd', 'bb93adbcf7be88c3660ef9ae61c4f138'], 128 | ['fffffffffffffffffff00000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5de', '89cffce3438b952ce14a5a6242f6b25a'], 129 | ['fffffffffffffffffff80000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5df', 'd3b28657e39893514bf2afd387e444e9'], 130 | ['fffffffffffffffffffc0000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5e0', 'fd2896cf0b953c003ba7f6ce609d914a'], 131 | ['fffffffffffffffffffe0000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5e1', 'd2a8614e8f8eb1e84cc072c1696ee752'], 132 | ['ffffffffffffffffffff0000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5e2', '015ed8675bfecb0615c15ee859b8e9c3'], 133 | ['ffffffffffffffffffff8000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5e3', 'efa41e228d14c7cff1573b90b453620f'], 134 | ['ffffffffffffffffffffc000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5e4', '669a55b7db37871ae198942ca4d6047c'], 135 | ['ffffffffffffffffffffe000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5e5', '704ad73a8444d8dc2989c95e300e0797'], 136 | ['fffffffffffffffffffff000000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5e6', '04846e9aaca38588cb532ae2a02223fb'], 137 | ['fffffffffffffffffffff800000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5e7', '2d7f5a4e3fdcc7af8e00673cf150aa18'], 138 | ['fffffffffffffffffffffc00000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5e8', 'f604a237f7f1625c18fe24fb3ada3869'], 139 | ['fffffffffffffffffffffe00000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5e9', 'a08dd841f9409a33791564bf6720d74f'], 140 | ['ffffffffffffffffffffff00000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5ea', 'd1fbb90f90d345f15060218bc0494a4b'], 141 | ['ffffffffffffffffffffff80000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5eb', 'd220cd3746368818f891f7ac95dc0c87'], 142 | ['ffffffffffffffffffffffc0000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5ec', '58ed87f98ecd9b1d0dd2bb0ab7f1ff82'], 143 | ['ffffffffffffffffffffffe0000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5ed', '3b204ee96831eb733eba7384ad4f593c'], 144 | ['fffffffffffffffffffffff0000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5ee', 'c025be20ab98e3a45fdd66bfedb695b4'], 145 | ['fffffffffffffffffffffff8000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5ef', 'e9e476e82c07f885991597df073a0b9d'], 146 | ['fffffffffffffffffffffffc000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5f0', 'c9769847c8d6c4bdf022e9264dab7162'], 147 | ['fffffffffffffffffffffffe000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5f1', 'ac985a7f8d050fa7e6d738a55ceff66d'], 148 | ['ffffffffffffffffffffffff000000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5f2', 'e4567a0da71d1d6ad87f77f78e6cc3f3'], 149 | ['ffffffffffffffffffffffff800000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5f3', 'c27c560471664f4f76b30a081c539f44'], 150 | ['ffffffffffffffffffffffffc00000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5f4', '43d68c5be6ffea3168278322b492f2be'], 151 | ['ffffffffffffffffffffffffe00000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5f5', 'ca94792e82530aa192938bd5bc9b3921'], 152 | ['fffffffffffffffffffffffff00000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5f6', '63b6d9ab7c6840ffe781fa74225f6f74'], 153 | ['fffffffffffffffffffffffff80000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5f7', '046b14b747e5cb15c04f1c6042a8540c'], 154 | ['fffffffffffffffffffffffffc0000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5f8', '0cca8944c609d8b94da06fccf362cd62'], 155 | ['fffffffffffffffffffffffffe0000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5f9', '48d2e16749f4629aa50d36e86505a62f'], 156 | ['ffffffffffffffffffffffffff0000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5fa', 'cde8d033e3c65ce2f342a8b00b575be7'], 157 | ['ffffffffffffffffffffffffff8000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5fb', '29628c81df38f4c64c76c7a18f25ed50'], 158 | ['ffffffffffffffffffffffffffc000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5fc', '58d1e32e8cd1653767014768f2b64b92'], 159 | ['ffffffffffffffffffffffffffe000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5fd', 'a7a0b9dddfde56be6fb2ca7a57bb6bca'], 160 | ['fffffffffffffffffffffffffff000007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5fe', '6e8efded58305528b83c086cfa1c00b8'], 161 | ['fffffffffffffffffffffffffff800007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc5ff', '8cd9ec6ab4f77088d737a4b88c819431'], 162 | ['fffffffffffffffffffffffffffc00007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc600', '22b8a64c9dd3f4f1966106a99bb51abf'], 163 | ['fffffffffffffffffffffffffffe00007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc601', 'fb327ac28841a17283b451a6c28090c8'], 164 | ['ffffffffffffffffffffffffffff00007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc602', 'b5284b41548d0774ac8cb2cacca6142c'], 165 | ['ffffffffffffffffffffffffffff80007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc603', 'de1a24deef977d23e837da08d80d41da'], 166 | ['ffffffffffffffffffffffffffffc0007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc604', '144824a70452915aeac6f3c51b1684fc'], 167 | ['ffffffffffffffffffffffffffffe0007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc605', '62076dd3bda3ad552cc849c768d8e454'], 168 | ['fffffffffffffffffffffffffffff0007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc606', '4d2b0a937c6db7c0754f45fb05ae1617'], 169 | ['fffffffffffffffffffffffffffff8007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc607', '18e1502a03f0b581bbdc325fab8dae89'], 170 | ['fffffffffffffffffffffffffffffc007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc608', '10499cbd013ba1b8ef09e17b9def52fb'], 171 | ['fffffffffffffffffffffffffffffe007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc609', '6589aa62b4e145ec9573428edc78256a'], 172 | ['ffffffffffffffffffffffffffffff007213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc60a', '7f3edb3044f72cd5de2599157e2b80de'], 173 | ['ffffffffffffffffffffffffffffff807213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc60b', '40b2071f7ade33926b4c702a294eb434'], 174 | ['ffffffffffffffffffffffffffffffc07213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc60c', '4a38a7c54858776d56e8289934fb6008'], 175 | ['ffffffffffffffffffffffffffffffe07213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc60d', '9a18a5c64ccba58ccbc535eb276afa41'], 176 | ['fffffffffffffffffffffffffffffff07213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc60e', 'cc225d6a3995b8e3e965055b61f2760b'], 177 | ['fffffffffffffffffffffffffffffff87213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc60f', '4e8ca55306c019332b8e3750d68bb78d'], 178 | ['fffffffffffffffffffffffffffffffc7213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc610', '23797319c2baeba81d4bd5e0d753469e'], 179 | ['fffffffffffffffffffffffffffffffe7213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc611', '1a261eb5776e0d4a8a7b5f0c892db74e'], 180 | ['ffffffffffffffffffffffffffffffff7213395b28586fe64026056638110b3c947529f603edb0cf6927f65edbbbc612', 'cb2eeb44945e9da22124e7868d69f55d'], 181 | ] 182 | for seed, want in vectors: 183 | seed = seed.decode('hex') 184 | want = want.decode('hex') 185 | prng = PRNG(seed) 186 | self.assertEquals(prng.get(len(want)), want) 187 | 188 | def test_mct_vector(self): 189 | seed = '35cc0ea481fc8a4f5f05c7d4667233b2' + 'f7d36762b9915f1ed585eb8e91700eb2' + '259e67249288597a4d61e7c0e690afae' 190 | seed = seed.decode('hex') 191 | 192 | mct_values = [ 193 | ['259e67249288597a4d61e7c0e690afaf', '2d4e2614df15a079eb40ebaf43dcb415', '15f013af5a8e9df9a8e37500edaeac43'], 194 | ['259e67249288597a4d61e7c0e690afb0', '9e8af85973f52bc3ef4c7e987f4c375b', 'a9d74bb1c90a222adc398546d64879cf'], 195 | ['259e67249288597a4d61e7c0e690afb1', '6d4b33fc46bdd8de2d1342f1b91cfaab', '0379e404042d58180764fb9e6c5d94bb'], 196 | ['259e67249288597a4d61e7c0e690afb2', '6d62c73f1f1dbd118f4b8f6d0840ac7d', '3c74603e036d28c79947ffb56fee4e51'], 197 | ] 198 | 199 | result = '' 200 | prng = PRNG(seed) 201 | for i in range(10000): 202 | result = prng.get(16) 203 | if i < len(mct_values): 204 | expected_dt, expected_v, expected_result = mct_values[i] 205 | self.assertEquals(prng.DT, expected_dt.decode('hex')) 206 | self.assertEquals(prng.V, expected_v.decode('hex')) 207 | self.assertEquals(result, expected_result.decode('hex')) 208 | 209 | want = '26a6b3d33b8e7e68b75d9630ec036314'.decode('hex') 210 | 211 | self.assertEquals(prng.DT, '259e67249288597a4d61e7c0e690d6be'.decode('hex')) 212 | self.assertEquals(result, want) 213 | 214 | if __name__ == '__main__': 215 | unittest.main() 216 | --------------------------------------------------------------------------------