From 13033508937d66fe714969042a4fd9863843f2e5 Mon Sep 17 00:00:00 2001 From: lanjelot Date: Thu, 26 Mar 2020 15:54:45 +1000 Subject: [PATCH] New --groups option (fixes #124) --- README.md | 2 +- patator.py | 204 +++++++++++++++++++++++++++++++++++++---------------- 2 files changed, 146 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index dd253e1..b4d8b97 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ Currently it supports the following modules: * umbraco_crack : Crack Umbraco HMAC-SHA1 password hashes ``` -The name "Patator" comes from [this](https://www.youtube.com/watch?v=kU2yPJJdpag). +The name "Patator" comes from [this](https://www.youtube.com/watch?v=9sF9fTALhVA). Patator is NOT script-kiddie friendly, please read the full README inside [patator.py](patator.py) before reporting. diff --git a/patator.py b/patator.py index aaf4bf8..85ebef3 100755 --- a/patator.py +++ b/patator.py @@ -18,7 +18,7 @@ __email__ = 'patator@hsc.fr' __url__ = 'http://www.hsc.fr/ressources/outils/patator/' __git__ = 'https://github.com/lanjelot/patator' __twitter__ = 'https://twitter.com/lanjelot' -__version__ = '0.8' +__version__ = '0.8-dev' __license__ = 'GPLv2' __pyver__ = '%d.%d.%d' % sys.version_info[0:3] __banner__ = 'Patator %s (%s) with python-%s' % (__version__, __git__, __pyver__) @@ -76,7 +76,7 @@ Currently it supports the following modules: Future modules to be implemented: - rdp_login w/no NLA -The name "Patator" comes from http://www.youtube.com/watch?v=xoBkBvnTTjo +The name "Patator" comes from https://www.youtube.com/watch?v=9sF9fTALhVA * Why ? @@ -226,6 +226,20 @@ $ ./module host=FILE2 user=FILE1 password=FILE0 2=hosts.txt 1=logins.txt 0=passw 10.0.0.1 admin 123456 ... +By default Patator iterates over the cartesian product of all payload sets. Use +the --groups option to iterate over sets simultaneously instead. For example to +distribute all payloads among identical servers: +--------- +$ ./module name=FILE0.FILE1 resolver=FILE2 0=names.txt 1=domains.txt 2=ips.txt --groups 0,1:2 +ftp.abc.fr 8.8.8.8 +ftp.xyz.fr 8.8.4.4 +git.abc.fr 8.8.8.8 +git.xyz.fr 8.8.4.4 +www.abc.fr 8.8.8.8 +www.xyz.fr 8.8.4.4 + +The numbers of every keyword given on the command line must be specified. +Use ',' to iterate over the cartesian product of sets and use ':' to iterate over sets simultaneously. * Keywords @@ -909,8 +923,9 @@ import sys from time import localtime, gmtime, strftime, sleep, time from platform import system from functools import reduce +from operator import mul, itemgetter from select import select -from itertools import islice +from itertools import islice, cycle import string import random from decimal import Decimal @@ -928,6 +943,7 @@ import glob from xml.sax.saxutils import escape as xmlescape, quoteattr as xmlquoteattr from ssl import wrap_socket from binascii import hexlify, unhexlify +import mmap try: # python3+ from queue import Empty, Full @@ -1091,22 +1107,33 @@ def html_unescape(s): h = HTMLParser() return h.unescape(s) +def mapcount(filename): + lines = 0 + with open(filename, 'r+') as f: + buf = mmap.mmap(f.fileno(), 0) + readline = buf.readline + while readline(): + lines += 1 + return lines + # I rewrote itertools.product to avoid memory over-consumption when using large wordlists def product(xs, *rest): if len(rest) == 0: - for x in xs(): + for x in xs: yield [x] else: - for head in xs(): + for head in xs: for tail in product(*rest): yield [head] + tail -def chain(*iterables): - def xs(): - for iterable in iterables: +class chain: + def __init__(self, *iterables): + self.iterables = iterables + + def __iter__(self): + for iterable in self.iterables: for element in iterable: yield element - return xs class FileIter: def __init__(self, filename): @@ -1241,7 +1268,6 @@ class RangeIter: return self.size class ProgIter: - def __init__(self, prog): self.prog = prog @@ -1293,6 +1319,21 @@ class MyManager(SyncManager): ignore_ctrlc() super(MyManager, cls)._run_server(registry, address, authkey, serializer, writer) +def ppstr(s): + if isinstance(s, bytes): + s = B(s) + if not isinstance(s, str): + s = str(s) + return s.rstrip('\r\n') + +def flatten(l): + r = [] + for x in l: + if isinstance(x, (list, tuple)): + r.extend(map(ppstr, x)) + else: + r.append(ppstr(x)) + return r # }}} # Controller {{{ @@ -1407,19 +1448,20 @@ Please read the README inside for more examples and usage information. exe_grp = OptionGroup(parser, 'Execution') exe_grp.add_option('-x', dest='actions', action='append', default=[], metavar='arg', help='actions and conditions, see Syntax below') - exe_grp.add_option('--start', dest='start', type='int', default=0, metavar='N', help='start from offset N in the wordlist product') + exe_grp.add_option('--start', dest='start', type='int', default=0, metavar='N', help='start from offset N in the product of all payload sets') exe_grp.add_option('--stop', dest='stop', type='int', default=None, metavar='N', help='stop at offset N') exe_grp.add_option('--resume', dest='resume', metavar='r1[,rN]*', help='resume previous run') exe_grp.add_option('-e', dest='encodings', action='append', default=[], metavar='arg', help='encode everything between two tags, see Syntax below') exe_grp.add_option('-C', dest='combo_delim', default=':', metavar='str', help="delimiter string in combo files (default is ':')") exe_grp.add_option('-X', dest='condition_delim', default=',', metavar='str', help="delimiter string in conditions (default is ',')") - exe_grp.add_option('--allow-ignore-failures', action='store_true', default=False, dest='allow_ignore_failures', help="failures cannot be ignored with -x (this is by design to avoid false negatives) this option overrides this behavior") + exe_grp.add_option('--allow-ignore-failures', action='store_true', default=False, dest='allow_ignore_failures', help="failures cannot be ignored with -x (this is by design to avoid false negatives) this option overrides this safeguard") opt_grp = OptionGroup(parser, 'Optimization') - opt_grp.add_option('--rate-limit', dest='rate_limit', type='float', default=0, metavar='N', help='wait N seconds between each test (default is 0)') + opt_grp.add_option('--rate-limit', dest='rate_limit', type='float', default=0, metavar='N', help='wait N seconds between each attempt (default is 0)') opt_grp.add_option('--timeout', dest='timeout', type='int', default=0, metavar='N', help='wait N seconds for a response before retrying payload (default is 0)') opt_grp.add_option('--max-retries', dest='max_retries', type='int', default=4, metavar='N', help='skip payload after N retries (default is 4) (-1 for unlimited)') opt_grp.add_option('-t', '--threads', dest='num_threads', type='int', default=10, metavar='N', help='number of threads (default is 10)') + opt_grp.add_option('--groups', dest='groups', default='', metavar='', help="default is to iterate over the cartesian product of all payload sets, use this option to iterate over sets simultaneously instead (aka pitchfork), see syntax inside (default is '0,1..n')") log_grp = OptionGroup(parser, 'Logging') log_grp.add_option('-l', dest='log_dir', metavar='DIR', help="save output and response data into DIR ") @@ -1453,6 +1495,7 @@ Please read the README inside for more examples and usage information. self.payload = {} self.iter_keys = {} + self.iter_groups = {} self.enc_keys = [] self.module = module @@ -1514,7 +1557,8 @@ Please read the README inside for more examples and usage information. kargs.append((k, v)) iter_vals = [v for k, v in sorted(wlists.items())] - logger.debug('kargs: %s' % kargs) # [('host', 'NET0'), ('user', 'COMBO10'), ('password', 'COMBO11'), ('domain', 'MOD2')] + + logger.debug('kargs: %s' % kargs) # [('host', 'NET0'), ('user', 'COMBO10'), ('password', 'COMBO11'), ('name', 'google.MOD2')] logger.debug('iter_vals: %s' % iter_vals) # ['10.0.0.0/24', 'combos.txt', 'TLD'] for k, v in kargs: @@ -1567,10 +1611,26 @@ Please read the README inside for more examples and usage information. else: self.payload[k] = v - logger.debug('iter_keys: %s' % self.iter_keys) # { 0: ('NET', '10.0.0.0/24', ['host']), 1: ('COMBO', 'combos.txt', [(0, 'user'), (1, 'password')]), 2: ('MOD', 'TLD', ['name']) + if self.iter_keys: + if not opts.groups: + # default is to iterate over the cartesian product of all payload sets + opts.groups = ','.join(map(str, self.iter_keys)) + + for i, g in enumerate(opts.groups.split(':')): + ks = list(map(int, g.split(','))) + for k in ks: + if k not in self.iter_keys: + raise ValueError('Unknown keyword number %r' % k) + + self.iter_groups[i] = sorted(ks) + + logger.debug('iter_groups: %s' % self.iter_groups) # {0: [0, 1], 1: [2]} + logger.debug('iter_keys: %s' % self.iter_keys) # [(0, ('NET', '10.0.0.0/24', ['host'])), (1, ('COMBO', 'combos.txt', [(0, 'user'), (1, 'password')])), (2, ('MOD', 'TLD', ['name']))] logger.debug('enc_keys: %s' % self.enc_keys) # [('password', 'ENC', hex), ('header', 'B64', b64encode), ... - logger.debug('payload: %s' % self.payload) + logger.debug('payload: %s' % self.payload) # {'host': 'NET0', 'user': 'COMBO10', 'password': 'COMBO11', 'name': 'google.MOD2'} + self.iter_groups = sorted(self.iter_groups.items()) + self.iter_keys = sorted(self.iter_keys.items()) self.available_actions = [k for k, _ in self.builtin_actions + self.module.available_actions] self.module_actions = [k for k, _ in self.module.available_actions] @@ -1722,58 +1782,44 @@ Please read the README inside for more examples and usage information. global logger logger = Logger(log_queue) - iterables = [] - total_size = 1 - def abort(msg): logger.warn(msg) self.ns.quit_now = True - for _, (t, v, _) in self.iter_keys.items(): + psets = {} + for k, (t, v, _) in self.iter_keys: - if t in ('FILE', 'COMBO'): - size = 0 - files = [] + pset= [] + size = 0 + if t in ('FILE', 'COMBO'): for name in v.split(','): for fpath in sorted(glob.iglob(expand_path(name))): if not os.path.isfile(fpath): return abort("No such file '%s'" % fpath) - with open(fpath) as f: - for _ in f: - size += 1 - - files.append(FileIter(fpath)) - - iterable = chain(*files) + pset.append(FileIter(fpath)) + size += mapcount(fpath) elif t == 'NET': - subnets = [IP(n, make_net=True) for n in v.split(',')] - size = sum(len(s) for s in subnets) - iterable = chain(*subnets) + pset = [IP(n, make_net=True) for n in v.split(',')] + size = sum(len(subnet) for subnet in pset) elif t == 'MOD': elements, size = self.module.available_keys[v]() - iterable = chain(elements) + pset = [elements] elif t == 'RANGE': - size = 0 - ranges = [] - for r in v.split(','): typ, opt = r.split(':', 1) try: - it = RangeIter(typ, opt) - size += len(it) + ri = RangeIter(typ, opt) + size += len(ri) + pset.append(ri) except ValueError as e: return abort("Invalid range '%s' of type '%s', %s" % (opt, typ, e)) - ranges.append(it) - - iterable = chain(*ranges) - elif t == 'PROG': m = re.match(r'(.+),(\d+)$', v) if m: @@ -1783,19 +1829,48 @@ Please read the README inside for more examples and usage information. logger.debug('prog: %s, size: %s' % (prog, size)) - it = ProgIter(prog) - iterable, size = chain(it), int(size) + pset = [ProgIter(prog)] + size = int(size) else: return abort('Incorrect keyword %r' % t) - total_size *= size - iterables.append(iterable) + psets[k] = chain(*pset), size + + logger.debug('payload sets: %r' % psets) + + zipit = [] + if not psets: + total_size = 1 + zipit.append(['']) + + else: + group_sizes = {} + for i, ks in self.iter_groups: + group_sizes[i] = reduce(mul, (size for _, size in [psets[k] for k in ks])) + + logger.debug('group_sizes: %s' % group_sizes) + + total_size = max(group_sizes.values()) + biggest, _ = max(group_sizes.items(), key=itemgetter(1)) + + for i, ks in self.iter_groups: + r = [] + + for k in ks: + pset, _ = psets[k] + r.append(pset) - if not iterables: - iterables.append(chain([''])) + it = product(*r) + if i != biggest: + it = cycle(it) - if self.stop: + zipit.append(it) + + logger.debug('zipit: %s' % zipit) + logger.debug('total_size: %d' % total_size) + + if self.stop and total_size > self.stop: total_size = self.stop - self.start else: total_size -= self.start @@ -1809,13 +1884,18 @@ Please read the README inside for more examples and usage information. logger.headers() count = 0 - for pp in islice(product(*iterables), self.start, self.stop): + for pp in islice(zip(*zipit), self.start, self.stop): if self.ns.quit_now: break - cid = count % self.num_threads - prod = [str(p).rstrip('\r\n') for p in pp] + pp = flatten(pp) + logger.debug('pp: %s' % pp) + + prod = [''] * len(pp) + for _, ks in self.iter_groups: + for k in ks: + prod[k] = pp.pop(0) if self.resume: idx = count % len(self.resume) @@ -1831,6 +1911,7 @@ Please read the README inside for more examples and usage information. break try: + cid = count % self.num_threads task_queues[cid].put_nowait(prod) break except Full: @@ -1883,7 +1964,7 @@ Please read the README inside for more examples and usage information. payload = self.payload.copy() - for i, (t, _, keys) in self.iter_keys.items(): + for i, (t, _, keys) in self.iter_keys: if t == 'FILE': for k in keys: payload[k] = payload[k].replace('FILE%d' % i, prod[i]) @@ -1907,10 +1988,10 @@ Please read the README inside for more examples and usage information. payload[k] = re.sub(r'{0}(.+?){0}'.format(m), lambda m: e(b(m.group(1))), payload[k]) logger.debug('product: %s' % prod) - pp_prod = ':'.join(prod) + prod_str = ':'.join(prod) if self.check_free(payload): - report_queue.put(('skip', pp_prod, None, 0)) + report_queue.put(('skip', prod_str, None, 0)) continue try_count = 0 @@ -1959,7 +2040,7 @@ Please read the README inside for more examples and usage information. actions = {'fail': None} actions.update(self.lookup_actions(resp)) - report_queue.put((actions, pp_prod, resp, time() - start_time)) + report_queue.put((actions, prod_str, resp, time() - start_time)) for name in self.module_actions: if name in actions: @@ -2039,7 +2120,6 @@ Please read the README inside for more examples and usage information. p.done_count += 1 - def monitor_interaction(self): if on_windows(): @@ -2127,7 +2207,6 @@ Please read the README inside for more examples and usage information. total_count, total_size/num_threads, p.current)) - # }}} # Response_Base {{{ @@ -4858,6 +4937,9 @@ class TCP_fuzz: # }}} # Dummy Test {{{ +def generate_tst(): + return ['prd', 'dev'], 2 + class Dummy_test: '''Testing module''' @@ -4875,6 +4957,10 @@ class Dummy_test: ) available_actions = () + available_keys = { + 'TST': generate_tst, + } + Response = Response_Base def execute(self, data, data2='', delay='1'):