switched to multiprocesses (instead of threads) to have the new --timeout option work with signals

pull/10/head
lanjelot 9 years ago
parent 32f7feebac
commit ad3871eae7

@ -622,9 +622,24 @@ TODO
# logging {{{
import logging
class CrossProcessLogger(logging.Logger):
def getEffectiveLevel(self):
return ns.logger_level
logging.setLoggerClass(CrossProcessLogger)
logging._levelNames[logging.ERROR] = 'FAIL'
logger = logging.getLogger('patator')
from multiprocessing.managers import SyncManager, current_process
import signal
manager = SyncManager()
manager.start(lambda: signal.signal(signal.SIGINT, signal.SIG_IGN))
ns = manager.Namespace()
ns.logger_level = logger.level
class TXTFormatter(logging.Formatter):
def __init__(self, indicatorsfmt):
self.resultfmt = '%(asctime)s %(name)-7s %(levelname)7s - ' + ' '.join('%%(%s)%ss' % (k, v) for k, v in indicatorsfmt) + ' | %(candidate)-34s | %(num)5s | %(mesg)s'
@ -636,7 +651,7 @@ class TXTFormatter(logging.Formatter):
self._fmt = self.resultfmt
else:
if record.levelno == 10: # DEBUG
self._fmt = '%(asctime)s %(name)-7s %(levelname)7s [%(threadName)s] %(message)s'
self._fmt = '%(asctime)s %(name)-7s %(levelname)7s [%(processName)s] %(message)s'
else:
self._fmt = '%(asctime)s %(name)-7s %(levelname)7s - %(message)s'
@ -760,7 +775,6 @@ from sys import exc_info, exit, version_info, maxint
from time import localtime, strftime, sleep
from timeit import default_timer
from functools import reduce
from threading import Thread, active_count
from select import select
from itertools import islice
import string
@ -773,14 +787,15 @@ import socket
import subprocess
import hashlib
from collections import defaultdict
from multiprocessing import Process, active_children, Queue
try:
# python3+
from queue import Queue, Empty, Full
from queue import Empty, Full
from urllib.parse import quote, urlencode, urlparse, urlunparse, parse_qsl, quote_plus
from io import StringIO
except ImportError:
# python2.6+
from Queue import Queue, Empty, Full
from Queue import Empty, Full
from urllib import quote, urlencode, quote_plus
from urlparse import urlparse, urlunparse, parse_qsl
from cStringIO import StringIO
@ -1014,9 +1029,23 @@ class ProgIter:
p = subprocess.Popen(self.prog.split(' '), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return p.stdout
class TimeoutError(Exception):
pass
def raise_timeout(signum, frame):
if signum == signal.SIGALRM:
raise TimeoutError('timed out')
# }}}
# Controller {{{
ns.actions = {}
ns.free_list = []
ns.paused = False
ns.quit_now = False
ns.start_time = 0
ns.total_size = 1
class Controller:
builtin_actions = (
@ -1135,8 +1164,9 @@ Please read the README inside for more examples and usage information.
exe_grp.add_option('-X', dest='condition_delim', default=',', metavar='str', help="delimiter string in conditions (default is ',')")
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 tests (default is 0)')
opt_grp.add_option('--max-retries', dest='max_retries', type='int', default=4, metavar='N', help='skip payload after N failures (default is 4) (-1 for unlimited)')
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('--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)')
log_grp = OptionGroup(parser, 'Logging')
@ -1155,9 +1185,9 @@ Please read the README inside for more examples and usage information.
opts, args = parser.parse_args(argv[1:])
if opts.debug:
logger.setLevel(logging.DEBUG)
ns.logger_level = logging.DEBUG
else:
logger.setLevel(logging.INFO)
ns.logger_level = logging.INFO
if not len(args) > 0:
parser.print_usage()
@ -1167,12 +1197,6 @@ Please read the README inside for more examples and usage information.
return opts, args
def __init__(self, module, argv):
self.actions = {}
self.free_list = []
self.paused = False
self.start_time = 0
self.total_size = 1
self.quit_now = False
self.thread_report = []
self.thread_progress = []
@ -1181,14 +1205,18 @@ Please read the README inside for more examples and usage information.
self.enc_keys = []
self.module = module
opts, args = self.parse_usage(argv)
self.combo_delim = opts.combo_delim
self.condition_delim = opts.condition_delim
self.rate_limit = opts.rate_limit
self.timeout = opts.timeout
self.max_retries = opts.max_retries
self.num_threads = opts.num_threads
self.start, self.stop, self.resume = opts.start, opts.stop, opts.resume
self.start, self.stop = opts.start, opts.stop
self.resume = [int(i) for i in opts.resume.split(',')] if opts.resume else None
self.output = Output(self.module.Response.indicatorsfmt, argv, opts.log_dir, opts.auto_log)
@ -1271,11 +1299,12 @@ Please read the README inside for more examples and usage information.
for x in opts.actions:
self.update_actions(x)
logger.debug('actions: %s' % self.actions)
logger.debug('actions: %s' % ns.actions)
def update_actions(self, arg):
actions, conditions = arg.split(':', 1)
ns_actions = ns.actions
actions, conditions = arg.split(':', 1)
for action in actions.split(','):
conds = [c.split('=', 1) for c in conditions.split(self.condition_delim)]
@ -1288,14 +1317,16 @@ Please read the README inside for more examples and usage information.
if name not in self.available_actions:
raise NotImplementedError('Unsupported action: %s' % name)
if name not in self.actions:
self.actions[name] = []
if name not in ns_actions:
ns_actions[name] = []
self.actions[name].append((conds, opts))
ns_actions[name].append((conds, opts))
ns.actions = ns_actions
def lookup_actions(self, resp):
actions = {}
for action, conditions in self.actions.items():
for action, conditions in ns.actions.items():
for condition, opts in conditions:
for key, val in condition:
if key[-1] == '!':
@ -1310,7 +1341,7 @@ Please read the README inside for more examples and usage information.
def check_free(self, payload):
# free_list: 'host=10.0.0.1', 'user=anonymous', 'host=10.0.0.7,user=test', ...
for m in self.free_list:
for m in ns.free_list:
args = m.split(',', 1)
for arg in args:
k, v = arg.split('=', 1)
@ -1322,8 +1353,8 @@ Please read the README inside for more examples and usage information.
return False
def register_free(self, payload, opts):
self.free_list.append(','.join('%s=%s' % (k, payload[k]) for k in opts.split('+')))
logger.debug('free_list updated: %s' % self.free_list)
ns.free_list += [','.join('%s=%s' % (k, payload[k]) for k in opts.split('+'))]
logger.debug('free_list updated: %s' % ns.free_list)
def fire(self):
logger.info('Starting %s at %s' % (__banner__, strftime('%Y-%m-%d %H:%M %Z', localtime())))
@ -1332,13 +1363,14 @@ Please read the README inside for more examples and usage information.
self.start_threads()
self.monitor_progress()
except KeyboardInterrupt:
self.quit_now = True
ns.quit_now = True
except:
self.quit_now = True
ns.quit_now = True
logger.exception(exc_info()[1])
try:
while active_count() > 1:
while len(active_children()) > 1:
logger.debug('active: %s' % active_children())
sleep(.1)
self.report_progress()
except KeyboardInterrupt:
@ -1349,19 +1381,19 @@ Please read the README inside for more examples and usage information.
skip_count = sum(p.skip_count for p in self.thread_progress)
fail_count = sum(p.fail_count for p in self.thread_progress)
total_time = default_timer() - self.start_time
total_time = default_timer() - ns.start_time
speed_avg = done_count / total_time
if self.total_size >= maxint:
self.total_size = -1
if ns.total_size >= maxint:
ns.total_size = -1
self.show_final()
logger.info('Hits/Done/Skip/Fail/Size: %d/%d/%d/%d/%d, Avg: %d r/s, Time: %s' % (
hits_count, done_count, skip_count, fail_count, self.total_size, speed_avg,
hits_count, done_count, skip_count, fail_count, ns.total_size, speed_avg,
pprint_seconds(total_time, '%dh %dm %ds')))
if self.quit_now:
if ns.quit_now:
resume = []
for i, p in enumerate(self.thread_progress):
c = p.done_count + p.skip_count
@ -1391,20 +1423,24 @@ Please read the README inside for more examples and usage information.
# consumers
for num in range(self.num_threads):
report_queue = Queue(maxsize=1000)
t = Thread(target=self.consume, args=(task_queues[num], report_queue))
t = Process(name='Consumer-%d' % num, target=self.consume, args=(task_queues[num], report_queue))
t.daemon = True
t.start()
self.thread_report.append(report_queue)
self.thread_progress.append(Progress())
# producer
t = Thread(target=self.produce, args=(task_queues,))
t = Process(name='Producer', target=self.produce, args=(task_queues,))
t.daemon = True
t.start()
def produce(self, task_queues):
signal.signal(signal.SIGINT, signal.SIG_IGN)
iterables = []
total_size = 1
for _, (t, v, _) in self.iter_keys.items():
if t in ('FILE', 'COMBO'):
@ -1458,27 +1494,31 @@ Please read the README inside for more examples and usage information.
else:
raise NotImplementedError("Incorrect keyword '%s'" % t)
self.total_size *= size
total_size *= size
iterables.append(iterable)
if not iterables:
iterables.append(chain(['']))
if self.stop:
self.total_size = self.stop - self.start
total_size = self.stop - self.start
else:
self.total_size -= self.start
total_size -= self.start
if self.resume:
self.resume = [int(i) for i in self.resume.split(',')]
self.total_size -= sum(self.resume)
total_size -= sum(self.resume)
self.output.headers()
self.start_time = default_timer()
ns.total_size = total_size
ns.start_time = default_timer()
count = 0
for pp in islice(product(*iterables), self.start, self.stop):
if ns.quit_now:
break
cid = count % self.num_threads
prod = [str(p).strip('\r\n') for p in pp]
@ -1492,8 +1532,8 @@ Please read the README inside for more examples and usage information.
continue
while True:
if self.quit_now:
return
if ns.quit_now:
break
try:
task_queues[cid].put_nowait(prod)
@ -1504,20 +1544,29 @@ Please read the README inside for more examples and usage information.
count += 1
for q in task_queues:
q.put(None)
q.cancel_join_thread()
if not ns.quit_now:
q.put(None)
logger.debug('producer exits')
def consume(self, task_queue, report_queue):
module = self.module()
signal.signal(signal.SIGALRM, raise_timeout)
signal.signal(signal.SIGINT, signal.SIG_IGN)
def shutdown():
logger.debug('thread exits')
report_queue.cancel_join_thread()
if hasattr(module, '__del__'):
module.__del__()
logger.debug('consumer exits')
while True:
if self.quit_now:
shutdown()
return
if ns.quit_now:
return shutdown()
try:
prod = task_queue.get_nowait()
@ -1526,8 +1575,7 @@ Please read the README inside for more examples and usage information.
continue
if prod is None:
shutdown()
return
return shutdown()
payload = self.payload.copy()
@ -1566,14 +1614,13 @@ Please read the README inside for more examples and usage information.
while True:
while self.paused and not self.quit_now:
while ns.paused and not ns.quit_now:
sleep(1)
if self.quit_now:
shutdown()
return
if ns.quit_now:
return shutdown()
if self.rate_limit:
if self.rate_limit > 0:
sleep(self.rate_limit)
if try_count <= self.max_retries or self.max_retries < 0:
@ -1584,16 +1631,16 @@ Please read the README inside for more examples and usage information.
logger.debug('payload: %s [try %d/%d]' % (payload, try_count, self.max_retries+1))
try:
signal.alarm(self.timeout)
resp = module.execute(**payload)
except:
e_type, e_value, _ = exc_info()
mesg = '%s %s' % (e_type, e_value.args)
mesg = '%s %s' % exc_info()[:2]
logger.debug('except: %s' % mesg)
#logger.exception(exc_info()[1])
logger.exception(exc_info()[1])
logger.debug('except: %s' % mesg)
resp = self.module.Response('xxx', mesg)
resp = self.module.Response('xxx', mesg, timing=default_timer()-start_time)
if hasattr(module, 'reset'):
module.reset()
@ -1601,6 +1648,8 @@ Please read the README inside for more examples and usage information.
sleep(try_count * .1)
continue
finally:
signal.alarm(0)
else:
actions = {'fail': None}
@ -1625,7 +1674,7 @@ Please read the README inside for more examples and usage information.
break
def monitor_progress(self):
while active_count() > 1 and not self.quit_now:
while len(active_children()) > 1 and not ns.quit_now:
self.report_progress()
self.monitor_interaction()
@ -1633,7 +1682,7 @@ Please read the README inside for more examples and usage information.
for i, pq in enumerate(self.thread_report):
p = self.thread_progress[i]
for _ in range(pq.maxsize):
while True:
try:
actions, current, resp, seconds = pq.get_nowait()
@ -1678,7 +1727,7 @@ Please read the README inside for more examples and usage information.
p.done_count += 1
if 'quit' in actions:
self.quit_now = True
ns.quit_now = True
def monitor_interaction(self):
@ -1700,20 +1749,20 @@ Please read the README inside for more examples and usage information.
''')
elif command == 'q':
self.quit_now = True
ns.quit_now = True
elif command == 'p':
self.paused = not self.paused
logger.info(self.paused and 'Paused' or 'Unpaused')
ns.paused = not ns.paused
logger.info(ns.paused and 'Paused' or 'Unpaused')
elif command == 'd':
logger.setLevel(logging.DEBUG)
ns.logger_level = logging.DEBUG
elif command == 'D':
logger.setLevel(logging.INFO)
ns.logger_level = logging.INFO
elif command == 'a':
logger.info(self.actions)
logger.info(repr(ns.actions))
elif command.startswith('x'):
_, arg = command.split(' ', 1)
@ -1723,34 +1772,39 @@ Please read the README inside for more examples and usage information.
logger.warn('usage: x actions:conditions')
else: # show progress
total_count = sum(p.done_count+p.skip_count for p in self.thread_progress)
speed_avg = self.num_threads / (sum(sum(p.seconds) / len(p.seconds) for p in self.thread_progress) / self.num_threads)
if self.total_size >= maxint:
thread_progress = self.thread_progress
num_threads = self.num_threads
total_size = ns.total_size
total_count = sum(p.done_count+p.skip_count for p in thread_progress)
speed_avg = num_threads / (sum(sum(p.seconds) / len(p.seconds) for p in thread_progress) / num_threads)
if total_size >= maxint:
etc_time = 'inf'
remain_time = 'inf'
else:
remain_seconds = (self.total_size - total_count) / speed_avg
remain_seconds = (total_size - total_count) / speed_avg
remain_time = pprint_seconds(remain_seconds, '%02d:%02d:%02d')
etc_seconds = datetime.now() + timedelta(seconds=remain_seconds)
etc_time = etc_seconds.strftime('%H:%M:%S')
logger.info('Progress: {0:>3}% ({1}/{2}) | Speed: {3:.0f} r/s | ETC: {4} ({5} remaining) {6}'.format(
total_count * 100/self.total_size,
total_count * 100/total_size,
total_count,
self.total_size,
total_size,
speed_avg,
etc_time,
remain_time,
self.paused and '| Paused' or ''))
ns.paused and '| Paused' or ''))
if command == 'f':
for i, p in enumerate(self.thread_progress):
for i, p in enumerate(thread_progress):
total_count = p.done_count + p.skip_count
logger.info(' {0:>3}: {1:>3}% ({2}/{3}) {4}'.format(
'#%d' % (i+1),
int(100*total_count/(1.0*self.total_size/self.num_threads)),
int(100*total_count/(1.0*total_size/num_threads)),
total_count,
self.total_size/self.num_threads,
total_size/num_threads,
p.current))
# }}}
@ -1790,7 +1844,7 @@ class Response_Base:
('egrep', 'search for regex in mesg'),
)
indicatorsfmt = [('code', -5), ('size', -4), ('time', 6)]
indicatorsfmt = [('code', -5), ('size', -4), ('time', 7)]
def __init__(self, code, mesg, timing=0, trace=None):
self.code = code
@ -2385,6 +2439,9 @@ class SMB_Connection(TCP_Connection):
def close(self):
self.fp.get_socket().close()
class Response_SMB(Response_Base):
indicatorsfmt = [('code', -8), ('size', -4), ('time', 6)]
class SMB_login(TCP_Cache):
'''Brute-force SMB'''
@ -2403,8 +2460,7 @@ class SMB_login(TCP_Cache):
)
available_options += TCP_Cache.available_options
class Response(Response_Base):
indicatorsfmt = [('code', -8), ('size', -4), ('time', 6)]
Response = Response_SMB
# ripped from medusa smbnt.c
error_map = {
@ -2437,7 +2493,7 @@ class SMB_login(TCP_Cache):
return SMB_Connection(fp)
def execute(self, host, port='139', user=None, password=None, password_hash=None, domain='', persistent='1'):
def execute(self, host, port='139', user=None, password='', password_hash=None, domain='', persistent='1'):
with Timing() as timing:
fp, _ = self.bind(host, port)
@ -2448,8 +2504,11 @@ class SMB_login(TCP_Cache):
fp.login_standard('', '') # to get workgroup or domain (Primary Domain)
else:
with Timing() as timing:
if password is None:
lmhash, nthash = password_hash.split(':')
if password_hash:
if ':' in password_hash:
lmhash, nthash = password_hash.split(':')
else:
lmhash, nthash = 'aad3b435b51404eeaad3b435b51404ee', password_hash
fp.login(user, '', domain, lmhash, nthash)
else:
@ -3023,6 +3082,9 @@ try:
except ImportError:
warnings.append('cx_Oracle')
class Response_Oracle(Response_Base):
indicatorsfmt = [('code', -9), ('size', -4), ('time', 6)]
class Oracle_login:
'''Brute-force Oracle'''
@ -3041,8 +3103,7 @@ class Oracle_login:
)
available_actions = ()
class Response(Response_Base):
indicatorsfmt = [('code', -9), ('size', -4), ('time', 6)]
Response = Response_Oracle
def execute(self, host, port='1521', user='', password='', sid='', service_name=''):

Loading…
Cancel
Save