From 21f6c94f1b21f053734da87a7b513c1904c1b0bd Mon Sep 17 00:00:00 2001 From: Matthew Haigh Date: Thu, 14 Mar 2019 06:40:40 -0700 Subject: [PATCH 01/18] initial POC complete --- fakenet/diverters/linutil.py | 20 ++++++++++++++++---- fakenet/diverters/linux.py | 9 ++++++--- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/fakenet/diverters/linutil.py b/fakenet/diverters/linutil.py index 145f0a3..8da92ae 100644 --- a/fakenet/diverters/linutil.py +++ b/fakenet/diverters/linutil.py @@ -54,11 +54,19 @@ class LinuxDiverterNfqueue(object): The results are undefined if start() or stop() are called multiple times. """ - def __init__(self, qno, chain, table, callback): + def __init__(self, qno, chain, table, callback, iface=None): self.logger = logging.getLogger('Diverter') # e.g. iptables <-I> -t -j NFQUEUE --queue-num <0>' - fmt = 'iptables %s %s -t %s -j NFQUEUE --queue-num %d' + cmd = 'iptables ' + if iface: + if chain in ['OUTPUT', 'POSTROUTING']: + cmd += ('-o %s ' % (iface)) + elif chain in ['INPUT', 'PREROUTING']: + cmd += ('-i %s ' % (iface)) + else: + raise NotImplementedError('Unanticipated chain %s' % (chain)) + fmt = cmd + ' %s %s -t %s -j NFQUEUE --queue-num %d' # Specifications self.qno = qno @@ -615,8 +623,12 @@ def scan_for_default_gw(fields): return dgw - def linux_redir_icmp(self): - fmt = 'iptables -t nat %s OUTPUT -p icmp -j REDIRECT' + def linux_redir_icmp(self, iface=None): + cmd = 'iptables' + if iface: + cmd += (' -o %s' % (iface)) + fmt = cmd + ' -t nat %s OUTPUT -p icmp -j REDIRECT' + rule = IptCmdTemplate(fmt) ret = rule.add() return (ret == 0), rule diff --git a/fakenet/diverters/linux.py b/fakenet/diverters/linux.py index b3695c6..752639b 100644 --- a/fakenet/diverters/linux.py +++ b/fakenet/diverters/linux.py @@ -95,6 +95,8 @@ def init_diverter_linux(self): self.outgoing_trans_cbs.append(self.maybe_redir_ip) def startCallback(self): + only_this_iface = 'enp2s5' + if not self.check_privileged(): self.logger.error('The Linux Diverter requires administrative ' + 'privileges') @@ -145,7 +147,7 @@ def startCallback(self): self.pdebug(DNFQUEUE, ('Creating NFQUEUE object for chain %s / ' + 'table %s / queue # %d => %s') % (hk.chain, hk.table, qno, str(hk.callback))) - q = LinuxDiverterNfqueue(qno, hk.chain, hk.table, hk.callback) + q = LinuxDiverterNfqueue(qno, hk.chain, hk.table, hk.callback, only_this_iface) self.nfqueues.append(q) ok = q.start() if not ok: @@ -170,7 +172,8 @@ def startCallback(self): if self.is_configured('linuxredirectnonlocal'): self.pdebug(DMISC, 'Processing LinuxRedirectNonlocal') - specified_ifaces = self.getconfigval('linuxredirectnonlocal') + # specified_ifaces = self.getconfigval('linuxredirectnonlocal') + specified_ifaces = [only_this_iface] self.pdebug(DMISC, 'Processing linuxredirectnonlocal on ' + 'interfaces: %s' % (specified_ifaces)) ok, rules = self.linux_iptables_redir_nonlocal(specified_ifaces) @@ -185,7 +188,7 @@ def startCallback(self): self.stop() sys.exit(1) - ok, rule = self.linux_redir_icmp() + ok, rule = self.linux_redir_icmp(only_this_iface) if not ok: self.logger.error('Failed to redirect ICMP') self.stop() From 73d2b2a4112cdbf165c7a2d318667b62cab02f96 Mon Sep 17 00:00:00 2001 From: Matthew Haigh Date: Mon, 18 Mar 2019 07:34:23 -0700 Subject: [PATCH 02/18] implemented restrict interfaces feature --- fakenet/configs/default.ini | 14 ++++----- fakenet/diverters/fnconfig.py | 3 ++ fakenet/diverters/linutil.py | 50 ++++++++++-------------------- fakenet/diverters/linux.py | 42 +++++++++++++------------ fakenet/fakenet.py | 41 ++++++++++++++++++------ fakenet/listeners/BITSListener.py | 15 ++++++--- fakenet/listeners/DNSListener.py | 5 +-- fakenet/listeners/FTPListener.py | 5 +-- fakenet/listeners/HTTPListener.py | 6 ++-- fakenet/listeners/IRCListener.py | 3 +- fakenet/listeners/POPListener.py | 3 +- fakenet/listeners/ProxyListener.py | 50 +++++++++++++++++------------- fakenet/listeners/RawListener.py | 3 +- fakenet/listeners/SMTPListener.py | 3 +- fakenet/listeners/TFTPListener.py | 3 +- 15 files changed, 137 insertions(+), 109 deletions(-) diff --git a/fakenet/configs/default.ini b/fakenet/configs/default.ini index 33b1a11..e413db6 100644 --- a/fakenet/configs/default.ini +++ b/fakenet/configs/default.ini @@ -48,14 +48,12 @@ NetworkMode: Auto # IPTABLES iptables firewall rule activity (Linux only) DebugLevel: Off -# MultiHost mode only: Specify what interfaces the Linux Diverter should create -# an iptables rule for to redirect traffic destined for other hosts to the -# local networking stack. This allows FakeNet-NG to log and handle packets -# from remote machines that are destined for non-local IP addresses that are -# either hard-coded or were returned in responses from the DNSListener. Use '*' -# (no quotes) or 'any' (no quotes, case-sensitive!) to indicate that this rule -# should be applied to all interfaces. Comment out to leave unconfigured. -LinuxRedirectNonlocal: * +# Restrict which interfaces on which Fakenet-NG will intercept and handle +# packets. Specify (only) one interface and Fakenet-NG will ignore all other +# interfaces. This feature only applies to interfaces on different subnets. +# Specify interface by name only (ex: eth0). To disable, set to "Off" +#LinuxRestrictInterface: Off +LinuxRestrictInterface: enp2s4 # Set LinuxFlushIptables to Yes to have the Linux Diverter flush all iptables # rules before adding its FakeNet-NG-specific rules to iptables. FakeNet-NG diff --git a/fakenet/diverters/fnconfig.py b/fakenet/diverters/fnconfig.py index 5a97061..f65c9ec 100644 --- a/fakenet/diverters/fnconfig.py +++ b/fakenet/diverters/fnconfig.py @@ -75,6 +75,9 @@ def is_clear(self, opt): return (self.is_configured(opt) and self._fuzzy_false(self._dict[opt.lower()])) + def is_activated(self, opt): + return not self.is_clear(opt) + def getconfigval(self, opt, default=None): return self._dict[opt.lower()] if self.is_configured(opt) else default diff --git a/fakenet/diverters/linutil.py b/fakenet/diverters/linutil.py index 8da92ae..be1c073 100644 --- a/fakenet/diverters/linutil.py +++ b/fakenet/diverters/linutil.py @@ -59,6 +59,7 @@ def __init__(self, qno, chain, table, callback, iface=None): # e.g. iptables <-I> -t -j NFQUEUE --queue-num <0>' cmd = 'iptables ' + if iface: if chain in ['OUTPUT', 'POSTROUTING']: cmd += ('-o %s ' % (iface)) @@ -66,6 +67,7 @@ def __init__(self, qno, chain, table, callback, iface=None): cmd += ('-i %s ' % (iface)) else: raise NotImplementedError('Unanticipated chain %s' % (chain)) + fmt = cmd + ' %s %s -t %s -j NFQUEUE --queue-num %d' # Specifications @@ -367,9 +369,9 @@ def linux_get_next_nfqueue_numbers(self, n): return next_qnos - def linux_iptables_redir_nonlocal(self, specified_ifaces): - """Linux-specific iptables processing for 'LinuxRedirectNonlocal' - configuration item. + def linux_iptables_redir_iface(self, fn_iface): + """Linux-specific iptables processing for interface-based redirect + rules. returns: tuple(bool, list(IptCmdTemplate)) @@ -377,40 +379,22 @@ def linux_iptables_redir_nonlocal(self, specified_ifaces): need to be undone. """ - local_ifaces = self._linux_get_ifaces() - all_iface_aliases = ['any', '*'] - acceptable_ifaces = local_ifaces + all_iface_aliases iptables_rules = [] + if fn_iface: + fmt = 'iptables -t nat %s PREROUTING -i {} -j REDIRECT'.format( + fn_iface) + else: + fmt = 'iptables -t nat %s PREROUTING -j REDIRECT' - # Catch cases where the user isn't going to get what they expect - # because iptables does not err for non-existent ifaces... - if not set(specified_ifaces).issubset(acceptable_ifaces): - # And indicate ALL interfaces that do not appear to exist - for iface in specified_ifaces: - if iface not in acceptable_ifaces: - self.logger.error(('Interface %s not found for nonlocal ' + - 'packet redirection, must be one of ' + - '%s') % (iface, str(acceptable_ifaces))) - return (False, []) - - for iface in specified_ifaces: - fmt, args = '', list() - if iface in all_iface_aliases: - # Handle */any case by omitting -i switch and corresponding arg - fmt = 'iptables -t nat %s PREROUTING -j REDIRECT' - else: - fmt = 'iptables -t nat %s PREROUTING -i %s -j REDIRECT' - args = [iface] - - rule = IptCmdTemplate(fmt, args) - ret = rule.add() + rule = IptCmdTemplate(fmt) + ret = rule.add() - if ret != 0: - self.logger.error('Failed to create PREROUTING/REDIRECT ' + - 'rule for %s, stopping...' % (iface)) - return (False, iptables_rules) + if ret != 0: + self.logger.error('Failed to create PREROUTING/REDIRECT ' + + 'rule for %s, stopping...' % (iface)) + return (False, iptables_rules) - iptables_rules.append(rule) + iptables_rules.append(rule) return (True, iptables_rules) diff --git a/fakenet/diverters/linux.py b/fakenet/diverters/linux.py index 752639b..41142fd 100644 --- a/fakenet/diverters/linux.py +++ b/fakenet/diverters/linux.py @@ -38,8 +38,9 @@ def init_diverter_linux(self): # String list configuration item that is specific to the Linux # Diverter, will not be parsed by DiverterBase, and needs to be # accessed as an array in the future. - slists = ['linuxredirectnonlocal', ] - self.reconfigure(portlists=[], stringlists=slists) + + #slists = [] + #self.reconfigure(portlists=[], stringlists=slists) self.logger.info('Running in %s mode' % (self.network_mode)) @@ -95,7 +96,6 @@ def init_diverter_linux(self): self.outgoing_trans_cbs.append(self.maybe_redir_ip) def startCallback(self): - only_this_iface = 'enp2s5' if not self.check_privileged(): self.logger.error('The Linux Diverter requires administrative ' + @@ -142,12 +142,18 @@ def startCallback(self): self.pdebug(DNFQUEUE, 'Enumerating queue numbers and hook ' + 'specifications to create NFQUEUE objects') + self.pdebug(DMISC, 'Processing LinuxRestrictInterface config %s' % self.getconfigval('linuxrestrictinterface')) + if self.is_activated('linuxrestrictinterface'): + fn_iface = self.getconfigval('linuxrestrictinterface') + else: + fn_iface = None self.nfqueues = list() for qno, hk in zip(qnos, callbacks): self.pdebug(DNFQUEUE, ('Creating NFQUEUE object for chain %s / ' + 'table %s / queue # %d => %s') % (hk.chain, hk.table, qno, str(hk.callback))) - q = LinuxDiverterNfqueue(qno, hk.chain, hk.table, hk.callback, only_this_iface) + q = LinuxDiverterNfqueue(qno, hk.chain, hk.table, hk.callback, + fn_iface) self.nfqueues.append(q) ok = q.start() if not ok: @@ -170,25 +176,21 @@ def startCallback(self): self.logger.error('Failed to flush DNS cache. Local machine ' 'may use cached DNS results.') - if self.is_configured('linuxredirectnonlocal'): - self.pdebug(DMISC, 'Processing LinuxRedirectNonlocal') - # specified_ifaces = self.getconfigval('linuxredirectnonlocal') - specified_ifaces = [only_this_iface] - self.pdebug(DMISC, 'Processing linuxredirectnonlocal on ' + - 'interfaces: %s' % (specified_ifaces)) - ok, rules = self.linux_iptables_redir_nonlocal(specified_ifaces) + self.pdebug(DMISC, 'Processing interface redirection on ' + + 'interface: %s' % (fn_iface)) + ok, rules = self.linux_iptables_redir_iface(fn_iface) - # Irrespective of whether this failed, we want to add any - # successful iptables rules to the list so that stop() will be able - # to remove them using linux_remove_iptables_rules(). - self.rules_added += rules + # Irrespective of whether this failed, we want to add any + # successful iptables rules to the list so that stop() will be able + # to remove them using linux_remove_iptables_rules(). + self.rules_added += rules - if not ok: - self.logger.error('Failed to process LinuxRedirectNonlocal') - self.stop() - sys.exit(1) + if not ok: + self.logger.error('Failed to process interface redirection') + self.stop() + sys.exit(1) - ok, rule = self.linux_redir_icmp(only_this_iface) + ok, rule = self.linux_redir_icmp(fn_iface) if not ok: self.logger.error('Failed to redirect ICMP') self.stop() diff --git a/fakenet/fakenet.py b/fakenet/fakenet.py index 8b38e9b..444b0e1 100644 --- a/fakenet/fakenet.py +++ b/fakenet/fakenet.py @@ -159,6 +159,18 @@ def start(self): if self.diverter_config['networkmode'].lower() == 'auto': self.diverter_config['networkmode'] = 'multihost' + fn_addr = '0.0.0.0' + if self.diverter_config['networkmode'].lower() == 'multihost': + if self.diverter_config['linuxrestrictinterface'].lower() != 'off': + fn_iface = self.diverter_config['linuxrestrictinterface'] + ifaces = linux_get_ifaces(netifaces.AF_INET) + if fn_iface in ifaces: + fn_addr = ifaces[fn_iface] + else: + self.logger.error('Invalid interface %s specified. Proceeding without interface restriction. Exiting.', fn_iface) + sys.exit(1) + + from diverters.linux import Diverter self.diverter = Diverter(self.diverter_config, self.listeners_config, ip_addrs, self.logging_level) @@ -172,7 +184,6 @@ def start(self): for listener_name in self.listeners_config: listener_config = self.listeners_config[listener_name] - # Anonymous listener if not 'listener' in listener_config: self.logger.info('Anonymous %s listener on %s port %s...', listener_name, listener_config['protocol'], listener_config['port']) @@ -190,7 +201,7 @@ def start(self): else: listener_provider_instance = listener_provider( - listener_config, listener_name, self.logging_level) + config=listener_config, name=listener_name, local_ip=fn_addr, logging_level=self.logging_level) # Store listener provider object self.running_listener_providers.append(listener_provider_instance) @@ -251,19 +262,29 @@ def get_ips(ipvers): else: raise ValueError('get_ips only supports IP versions 4 and 6') + for spec in specs: + ifaces = linux_get_ifaces(spec) + if ifaces is not None: + results.append(ifaces.values()) + + return results + +def linux_get_ifaces(spec=netifaces.AF_INET): + + results = {} + for iface in netifaces.interfaces(): - for spec in specs: - addrs = netifaces.ifaddresses(iface) - # If an interface only has an IPv4 or IPv6 address, then 6 or 4 - # respectively will be absent from the keys in the interface - # addresses dictionary. - if spec in addrs: - for link in addrs[spec]: + + addrs = netifaces.ifaddresses(iface) + if spec in addrs: + for link in addrs[spec]: + if 'addr' in link: - results.append(link['addr']) + results[iface] = link['addr'] return results + def main(): print """ diff --git a/fakenet/listeners/BITSListener.py b/fakenet/listeners/BITSListener.py index 0980b90..e6b01e0 100644 --- a/fakenet/listeners/BITSListener.py +++ b/fakenet/listeners/BITSListener.py @@ -447,15 +447,22 @@ def taste(self, data, dport): continue return confidence - - def __init__(self, config={}, name='BITSListener', - logging_level=logging.DEBUG, running_listeners=None): + + def __init__( + self, + config={}, + name='BITSListener', + local_ip='0.0.0.0', + logging_level=logging.DEBUG, + running_listeners=None + ): + self.logger = logging.getLogger(name) self.logger.setLevel(logging_level) self.config = config self.name = name - self.local_ip = '0.0.0.0' + self.local_ip = local_ip self.server = None self.running_listeners = running_listeners self.NAME = 'BITS' diff --git a/fakenet/listeners/DNSListener.py b/fakenet/listeners/DNSListener.py index 1b1b287..0de19bb 100644 --- a/fakenet/listeners/DNSListener.py +++ b/fakenet/listeners/DNSListener.py @@ -26,7 +26,8 @@ def taste(self, data, dport): def __init__( self, config={}, - name='DNSListener', + name='DNSListener', + local_ip='0.0.0.0', logging_level=logging.INFO, ): @@ -34,7 +35,7 @@ def __init__( self.logger.setLevel(logging_level) self.config = config - self.local_ip = '0.0.0.0' + self.local_ip = local_ip self.server = None self.name = 'DNS' self.port = self.config.get('port', 53) diff --git a/fakenet/listeners/FTPListener.py b/fakenet/listeners/FTPListener.py index 9551b8e..e8b5463 100644 --- a/fakenet/listeners/FTPListener.py +++ b/fakenet/listeners/FTPListener.py @@ -226,7 +226,8 @@ def taste(self, data, dport): def __init__(self, config, - name='FTPListener', + name='FTPListener', + local_ip='0.0.0.0', logging_level=logging.INFO, running_listeners=None, diverter=None @@ -237,7 +238,7 @@ def __init__(self, self.config = config self.name = name - self.local_ip = '0.0.0.0' + self.local_ip = local_ip self.server = None self.running_listeners = running_listeners self.diverter = diverter diff --git a/fakenet/listeners/HTTPListener.py b/fakenet/listeners/HTTPListener.py index 381cff5..d9367f9 100644 --- a/fakenet/listeners/HTTPListener.py +++ b/fakenet/listeners/HTTPListener.py @@ -56,7 +56,8 @@ def __init__( self, config={}, name='HTTPListener', - logging_level=logging.DEBUG, + local_ip='0.0.0.0', + logging_level=logging.DEBUG, ): self.logger = logging.getLogger(name) @@ -64,7 +65,7 @@ def __init__( self.config = config self.name = name - self.local_ip = '0.0.0.0' + self.local_ip = local_ip self.server = None self.name = 'HTTP' self.port = self.config.get('port', 80) @@ -83,7 +84,6 @@ def __init__( def start(self): self.logger.debug('Starting...') - self.server = ThreadedHTTPServer((self.local_ip, int(self.config.get('port'))), ThreadedHTTPRequestHandler) self.server.logger = self.logger self.server.config = self.config diff --git a/fakenet/listeners/IRCListener.py b/fakenet/listeners/IRCListener.py index cbe839a..d39d613 100644 --- a/fakenet/listeners/IRCListener.py +++ b/fakenet/listeners/IRCListener.py @@ -68,6 +68,7 @@ def taste(self, data, dport): def __init__(self, config, name='IRCListener', + local_ip='0.0.0.0', logging_level=logging.INFO, ): @@ -76,7 +77,7 @@ def __init__(self, self.config = config self.name = name - self.local_ip = '0.0.0.0' + self.local_ip = local_ip self.server = None self.name = 'IRC' diff --git a/fakenet/listeners/POPListener.py b/fakenet/listeners/POPListener.py index fedc57a..d8fe19d 100644 --- a/fakenet/listeners/POPListener.py +++ b/fakenet/listeners/POPListener.py @@ -45,6 +45,7 @@ def taste(self, data, dport): def __init__(self, config, name='POPListener', + local_ip='0.0.0.0', logging_level=logging.INFO, ): @@ -53,7 +54,7 @@ def __init__(self, self.config = config self.name = name - self.local_ip = '0.0.0.0' + self.local_ip = local_ip self.server = None self.name = 'POP' self.port = self.config.get('port', 110) diff --git a/fakenet/listeners/ProxyListener.py b/fakenet/listeners/ProxyListener.py index 33b193d..4384425 100644 --- a/fakenet/listeners/ProxyListener.py +++ b/fakenet/listeners/ProxyListener.py @@ -15,7 +15,9 @@ import os BUF_SZ = 1024 -IP = '0.0.0.0' +# When interface restriction is on, the proxy cannot connect to the Diverter +# via localhost so the configured IP must be preserved. +LOCAL_FN_IP = '0.0.0.0' class ProxyListener(object): @@ -23,8 +25,9 @@ class ProxyListener(object): def __init__( self, config={}, - name ='ProxyListener', - logging_level=logging.DEBUG, + name ='ProxyListener', + local_ip='0.0.0.0', + logging_level=logging.DEBUG, ): self.logger = logging.getLogger(name) @@ -32,6 +35,9 @@ def __init__( self.config = config self.name = name + self.local_ip = local_ip + global LOCAL_FN_IP + LOCAL_FN_IP = local_ip self.server = None self.udp_fwd_table = dict() @@ -50,14 +56,14 @@ def start(self): self.logger.debug('Starting TCP ...') - self.server = ThreadedTCPServer((IP, + self.server = ThreadedTCPServer((self.local_ip, int(self.config.get('port'))), ThreadedTCPRequestHandler) elif proto == 'UDP': self.logger.debug('Starting UDP ...') - self.server = ThreadedUDPServer((IP, + self.server = ThreadedUDPServer((self.local_ip, int(self.config.get('port'))), ThreadedUDPRequestHandler) self.server.fwd_table = self.udp_fwd_table @@ -78,7 +84,7 @@ def start(self): self.server_thread.daemon = True self.server_thread.start() server_ip, server_port = self.server.server_address - self.logger.info("%s Server(%s:%d) thread: %s" % (proto, server_ip, + self.logger.info("%s Server(%s:%d) thread: %s" % (proto, server_ip, server_port, self.server_thread.name)) def stop(self): @@ -112,7 +118,7 @@ def run(self): try: self.sock.connect((self.ip, self.port)) while True: - readable, writable, exceptional = select.select([self.sock], + readable, writable, exceptional = select.select([self.sock], [], [], .001) if not self.remote_q.empty(): data = self.remote_q.get() @@ -133,7 +139,7 @@ class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer): class ThreadedUDPServer(SocketServer.ThreadingMixIn, SocketServer.UDPServer): daemon_threads = True -def get_top_listener(config, data, listeners, diverter, orig_src_ip, +def get_top_listener(config, data, listeners, diverter, orig_src_ip, orig_src_port, proto): @@ -210,21 +216,21 @@ def handle(self): orig_src_ip = self.client_address[0] orig_src_port = self.client_address[1] - top_listener = get_top_listener(self.server.config, data, - self.server.listeners, self.server.diverter, + top_listener = get_top_listener(self.server.config, data, + self.server.listeners, self.server.diverter, orig_src_ip, orig_src_port, 'TCP') if top_listener: - self.server.logger.debug('Likely listener: %s' % + self.server.logger.debug('Likely listener: %s' % top_listener.name) - listener_sock = ThreadedTCPClientSocket('localhost', - top_listener.port, listener_q, remote_q, + listener_sock = ThreadedTCPClientSocket(LOCAL_FN_IP, + top_listener.port, listener_q, remote_q, self.server.config, self.server.logger) listener_sock.daemon = True listener_sock.start() remote_sock.setblocking(0) - # ssl has no 'peek' option, so we need to process the first + # ssl has no 'peek' option, so we need to process the first # packet that is already consumed from the socket if ssl_remote_sock: ssl_remote_sock.setblocking(0) @@ -262,7 +268,7 @@ def handle(self): data = self.request[0] remote_sock = self.request[1] - self.server.logger.debug('Received UDP packet from %s.' % + self.server.logger.debug('Received UDP packet from %s.' % self.client_address[0]) if data: @@ -276,16 +282,16 @@ def handle(self): orig_src_ip = self.client_address[0] orig_src_port = self.client_address[1] - top_listener = get_top_listener(self.server.config, data, - self.server.listeners, self.server.diverter, + top_listener = get_top_listener(self.server.config, data, + self.server.listeners, self.server.diverter, orig_src_ip, orig_src_port, 'UDP') if top_listener: sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - sock.bind(('localhost', 0)) + sock.bind((LOCAL_FN_IP, 0)) - sock.sendto(data, ('localhost', int(top_listener.port))) + sock.sendto(data, (LOCAL_FN_IP, int(top_listener.port))) reply = sock.recv(BUF_SZ) self.server.logger.info('Received %d bytes.', len(data)) sock.close() @@ -305,18 +311,18 @@ def hexdump_table(data, length=16): def main(): - logging.basicConfig(format='%(asctime)s [%(name)15s] %(message)s', + logging.basicConfig(format='%(asctime)s [%(name)15s] %(message)s', datefmt='%m/%d/%y %I:%M:%S %p', level=logging.DEBUG) global listeners listeners = load_plugins() - TCP_server = ThreadedTCPServer((IP, int(sys.argv[1])), + TCP_server = ThreadedTCPServer((sys.argv[1], int(sys.argv[2])), ThreadedTCPRequestHandler) TCP_server_thread = threading.Thread(target=TCP_server.serve_forever) TCP_server_thread.daemon = True TCP_server_thread.start() tcp_server_ip, tcp_server_port = TCP_server.server_address - logger.info("TCP Server(%s:%d) thread: %s" % (tcp_server_ip, + logger.info("TCP Server(%s:%d) thread: %s" % (tcp_server_ip, tcp_server_port, TCP_server_thread.name)) try: diff --git a/fakenet/listeners/RawListener.py b/fakenet/listeners/RawListener.py index 0fbe619..d584896 100644 --- a/fakenet/listeners/RawListener.py +++ b/fakenet/listeners/RawListener.py @@ -19,6 +19,7 @@ def taste(self, data, dport): def __init__(self, config, name='RawListener', + local_ip='0.0.0.0', logging_level=logging.INFO, ): @@ -27,7 +28,7 @@ def __init__(self, self.config = config self.name = name - self.local_ip = '0.0.0.0' + self.local_ip = local_ip self.server = None self.name = 'Raw' self.port = self.config.get('port', 1337) diff --git a/fakenet/listeners/SMTPListener.py b/fakenet/listeners/SMTPListener.py index 8c33225..ae56d34 100644 --- a/fakenet/listeners/SMTPListener.py +++ b/fakenet/listeners/SMTPListener.py @@ -37,6 +37,7 @@ def __init__( self, config, name='SMTPListener', + local_ip='0.0.0.0', logging_level=logging.INFO, ): @@ -45,7 +46,7 @@ def __init__( self.config = config self.name = name - self.local_ip = '0.0.0.0' + self.local_ip = local_ip self.server = None self.name = 'SMTP' self.port = self.config.get('port', 25) diff --git a/fakenet/listeners/TFTPListener.py b/fakenet/listeners/TFTPListener.py index c209800..88ba5af 100644 --- a/fakenet/listeners/TFTPListener.py +++ b/fakenet/listeners/TFTPListener.py @@ -69,6 +69,7 @@ def taste(self, data, dport): def __init__(self, config, name='TFTPListener', + local_ip='0.0.0.0', logging_level=logging.INFO, ): @@ -77,7 +78,7 @@ def __init__(self, self.config = config self.name = name - self.local_ip = '0.0.0.0' + self.local_ip = local_ip self.server = None self.name = 'TFTP' self.port = self.config.get('port', 69) From 7097ce9438db96c0b240dd2f8e44f6fe80847bde Mon Sep 17 00:00:00 2001 From: Matthew Haigh Date: Mon, 18 Mar 2019 07:41:35 -0700 Subject: [PATCH 03/18] fixed default config and added to template config for testing --- fakenet/configs/default.ini | 4 ++-- test/template.ini | 13 +++++-------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/fakenet/configs/default.ini b/fakenet/configs/default.ini index e413db6..6f8ce4d 100644 --- a/fakenet/configs/default.ini +++ b/fakenet/configs/default.ini @@ -52,8 +52,8 @@ DebugLevel: Off # packets. Specify (only) one interface and Fakenet-NG will ignore all other # interfaces. This feature only applies to interfaces on different subnets. # Specify interface by name only (ex: eth0). To disable, set to "Off" -#LinuxRestrictInterface: Off -LinuxRestrictInterface: enp2s4 +LinuxRestrictInterface: Off + # Set LinuxFlushIptables to Yes to have the Linux Diverter flush all iptables # rules before adding its FakeNet-NG-specific rules to iptables. FakeNet-NG diff --git a/test/template.ini b/test/template.ini index 286524c..3de4b7e 100644 --- a/test/template.ini +++ b/test/template.ini @@ -31,14 +31,11 @@ NetworkMode: SingleHost # in an unacceptable overhead cost, hence this setting. DebugLevel: NFQUEUE,IPTALBS,NONLOC,GENPKTV,PCAP -# MultiHost mode only: Specify what interfaces the Linux Diverter should create -# an iptables rule for to redirect traffic destined for other hosts to the -# local networking stack. This allows FakeNet-NG to log and handle packets -# from remote machines that are destined for non-local IP addresses that are -# either hard-coded or were returned in responses from the DNSListener. Use '*' -# (no quotes) or 'any' (no quotes, case-sensitive!) to indicate that this rule -# should be applied to all interfaces. Comment out to leave unconfigured. -LinuxRedirectNonlocal: * +# Restrict which interfaces on which Fakenet-NG will intercept and handle +# packets. Specify (only) one interface and Fakenet-NG will ignore all other +# interfaces. This feature only applies to interfaces on different subnets. +# Specify interface by name only (ex: eth0). To disable, set to "Off" +LinuxRestrictInterface: Off # Set LinuxFlushIptables to Yes to have the Linux Diverter flush all iptables # rules before adding its FakeNet-NG-specific rules to iptables. FakeNet-NG From 9f11ab50c803c22b5d14a7a9ab075c6a7bf3b93d Mon Sep 17 00:00:00 2001 From: Matthew Haigh Date: Wed, 20 Mar 2019 07:03:54 -0700 Subject: [PATCH 04/18] addressed errors --- fakenet/configs/default.ini | 46 +++++++++---- fakenet/diverters/fnconfig.py | 3 - fakenet/diverters/linutil.py | 86 ++++++++++++++--------- fakenet/diverters/linux.py | 22 +++--- fakenet/fakenet.py | 106 +++++++++++++++++------------ fakenet/listeners/BITSListener.py | 4 +- fakenet/listeners/DNSListener.py | 5 +- fakenet/listeners/FTPListener.py | 5 +- fakenet/listeners/HTTPListener.py | 7 +- fakenet/listeners/IRCListener.py | 5 +- fakenet/listeners/POPListener.py | 5 +- fakenet/listeners/ProxyListener.py | 15 ++-- fakenet/listeners/RawListener.py | 5 +- fakenet/listeners/SMTPListener.py | 5 +- fakenet/listeners/TFTPListener.py | 5 +- 15 files changed, 181 insertions(+), 143 deletions(-) diff --git a/fakenet/configs/default.ini b/fakenet/configs/default.ini index 6f8ce4d..6ff7f5b 100644 --- a/fakenet/configs/default.ini +++ b/fakenet/configs/default.ini @@ -46,14 +46,13 @@ NetworkMode: Auto # NFQUEUE NetfilterQueue activity (Linux only) # PROCFS Procfs read/write activity (Linux only) # IPTABLES iptables firewall rule activity (Linux only) -DebugLevel: Off +DebugLevel: IPTABLES, NFQUEUE # Restrict which interfaces on which Fakenet-NG will intercept and handle # packets. Specify (only) one interface and Fakenet-NG will ignore all other # interfaces. This feature only applies to interfaces on different subnets. # Specify interface by name only (ex: eth0). To disable, set to "Off" -LinuxRestrictInterface: Off - +LinuxRestrictInterface: enp2s4 # Set LinuxFlushIptables to Yes to have the Linux Diverter flush all iptables # rules before adding its FakeNet-NG-specific rules to iptables. FakeNet-NG @@ -139,6 +138,8 @@ BlackListPortsUDP: 67, 68, 137, 138, 443, 1900, 5355 # # The following settings are available for all listeners: # * Enabled - specify whether or not the listener is enabled. +# * IPAddress - specify IP address to bind to. Comment out or leave +# blank to bind to all interface # * Port - TCP or UDP port to listen on. # * Protocol - TCP or UDP # * Listener - Listener name to handle traffic. @@ -202,23 +203,26 @@ BlackListPortsUDP: 67, 68, 137, 138, 443, 1900, 5355 # between 1 and 15 characters (inclusive). [ProxyTCPListener] -Enabled: True -Protocol: TCP -Listener: ProxyListener -Port: 38926 -Listeners: HTTPListener, RawListener, FTPListener, DNSListener, POPListener, SMTPListener, TFTPListener, IRCListener, BITSListener -Hidden: False +Enabled: True +Protocol: TCP +Listener: ProxyListener +IPAddress: 192.168.204.140 +Port: 38926 +Listeners: HTTPListener, RawListener, FTPListener, DNSListener, POPListener, SMTPListener, TFTPListener, IRCListener, BITSListener +Hidden: False [ProxyUDPListener] -Enabled: True -Protocol: UDP -Listener: ProxyListener -Port: 38926 -Listeners: RawListener, DNSListener, TFTPListener, FTPListener -Hidden: False +Enabled: True +Protocol: UDP +Listener: ProxyListener +IPAddress: 192.168.204.140 +Port: 38926 +Listeners: RawListener, DNSListener, TFTPListener, FTPListener +Hidden: False [Forwarder] Enabled: False +IPAddress: 192.168.204.140 Port: 8080 Protocol: TCP ProcessWhiteList: chrome.exe @@ -226,6 +230,7 @@ Hidden: False [RawTCPListener] Enabled: True +IPAddress: 192.168.204.140 Port: 1337 Protocol: TCP Listener: RawListener @@ -235,6 +240,7 @@ Hidden: False [RawUDPListener] Enabled: True +IPAddress: 192.168.204.140 Port: 1337 Protocol: UDP Listener: RawListener @@ -244,6 +250,7 @@ Hidden: False [FilteredListener] Enabled: False +IPAddress: 192.168.204.140 Port: 31337 Protocol: TCP Listener: RawListener @@ -255,6 +262,7 @@ Hidden: False [DNS Server] Enabled: True +IPAddress: 192.168.204.140 Port: 53 Protocol: UDP Listener: DNSListener @@ -266,6 +274,7 @@ Hidden: False [HTTPListener80] Enabled: True +IPAddress: 192.168.204.140 Port: 80 Protocol: TCP Listener: HTTPListener @@ -279,6 +288,7 @@ Hidden: False [HTTPListener443] Enabled: True +IPAddress: 192.168.204.140 Port: 443 Protocol: TCP Listener: HTTPListener @@ -290,6 +300,7 @@ Hidden: False [SMTPListener] Enabled: True +IPAddress: 192.168.204.140 Port: 25 Protocol: TCP Listener: SMTPListener @@ -298,6 +309,7 @@ Hidden: False [FTPListener21] Enabled: True +IPAddress: 192.168.204.140 Port: 21 Protocol: TCP Listener: FTPListener @@ -310,12 +322,14 @@ ServerName: !gethostname [FTPListenerPASV] Enabled: True +IPAddress: 192.168.204.140 Port: 60000-60010 Protocol: TCP Hidden: False [IRCServer] Enabled: True +IPAddress: 192.168.204.140 Port: 6667 Protocol: TCP Listener: IRCListener @@ -327,6 +341,7 @@ Hidden: False [TFTPListener] Enabled: True +IPAddress: 192.168.204.140 Port: 69 Protocol: UDP Listener: TFTPListener @@ -336,6 +351,7 @@ TFTPFilePrefix: tftp [POPServer] Enabled: True +IPAddress: 192.168.204.140 Port: 110 Protocol: TCP Listener: POPListener diff --git a/fakenet/diverters/fnconfig.py b/fakenet/diverters/fnconfig.py index f65c9ec..5a97061 100644 --- a/fakenet/diverters/fnconfig.py +++ b/fakenet/diverters/fnconfig.py @@ -75,9 +75,6 @@ def is_clear(self, opt): return (self.is_configured(opt) and self._fuzzy_false(self._dict[opt.lower()])) - def is_activated(self, opt): - return not self.is_clear(opt) - def getconfigval(self, opt, default=None): return self._dict[opt.lower()] if self.is_configured(opt) else default diff --git a/fakenet/diverters/linutil.py b/fakenet/diverters/linutil.py index be1c073..a22132c 100644 --- a/fakenet/diverters/linutil.py +++ b/fakenet/diverters/linutil.py @@ -23,9 +23,9 @@ class IptCmdTemplate(object): LinUtilMixin.linux_{capture,restore}_iptables(). """ - def __init__(self, fmt, args=[], add='-I', rem='-D', add_idx=0, rem_idx=0): - self._addcmd = fmt % tuple(args[0:add_idx] + [add] + args[add_idx:]) - self._remcmd = fmt % tuple(args[0:add_idx] + [rem] + args[rem_idx:]) + def __init__(self): + self._addcmd = None + self._remcmd = None def gen_add_cmd(self): return self._addcmd @@ -39,6 +39,51 @@ def add(self): def remove(self): return subprocess.call(self._remcmd.split()) + def iptables_format(self, chain, iface, argfmt): + """Format iptables command line with optional interface restriction. + + Parameters + ---------- + chain : string + One of 'OUTPUT', 'POSTROUTING', 'INPUT', or 'PREROUTING', used for + deciding the correct flag (-i versus -o) + iface : string or NoneType + Name of interface to restrict the rule to (e.g. 'eth0'), or None + argfmt : string + Format string for remaining iptables arguments. This format string will + not be included in format string evaluation but is appended as-is to + the iptables command. + """ + flag_iface = '' + if iface: + if chain in ['OUTPUT', 'POSTROUTING']: + flag_iface = '-o' + elif chain in ['INPUT', 'PREROUTING']: + flag_iface = '-i' + else: + raise NotImplementedError('Unanticipated chain %s' % (chain)) + + add_cmd = 'iptables -I {chain} {flag_if} {iface} {fmt}' + add_cmd = add_cmd.format(chain=chain, flag_if=flag_iface, + iface=(iface or ''), fmt=argfmt) + rem_cmd = 'iptables -D {chain} {flag_if} {iface} {fmt}' + rem_cmd = rem_cmd.format(chain=chain, flag_if=flag_iface, + iface=(iface or ''), fmt=argfmt) + return add_cmd, rem_cmd + + def nfqueue_init(self, chain, qno, table, iface=None): + fmt = '-t {} -j NFQUEUE --queue-num {}'.format(table, qno) + self._addcmd, self._remcmd = self.iptables_format(chain, iface, fmt) + + def redir_iface_init(self, iface=None): + fmt = '-t nat -j REDIRECT' + self._addcmd, self._remcmd = self.iptables_format('PREROUTING', iface, + fmt) + + def redir_icmp_init(self, iface=None): + fmt = '-t nat -p icmp -j REDIRECT' + self._addcmd, self._remcmd = self.iptables_format('OUTPUT', iface, fmt) + class LinuxDiverterNfqueue(object): """NetfilterQueue object wrapper. @@ -57,24 +102,12 @@ class LinuxDiverterNfqueue(object): def __init__(self, qno, chain, table, callback, iface=None): self.logger = logging.getLogger('Diverter') - # e.g. iptables <-I> -t -j NFQUEUE --queue-num <0>' - cmd = 'iptables ' - - if iface: - if chain in ['OUTPUT', 'POSTROUTING']: - cmd += ('-o %s ' % (iface)) - elif chain in ['INPUT', 'PREROUTING']: - cmd += ('-i %s ' % (iface)) - else: - raise NotImplementedError('Unanticipated chain %s' % (chain)) - - fmt = cmd + ' %s %s -t %s -j NFQUEUE --queue-num %d' - # Specifications self.qno = qno self.chain = chain self.table = table - self._rule = IptCmdTemplate(fmt, [self.chain, self.table, self.qno]) + self._rule = IptCmdTemplate() + self._rule.nfqueue_init(self.chain, self.qno, self.table, iface) self._callback = callback self._nfqueue = netfilterqueue.NetfilterQueue() self._sk = None @@ -369,7 +402,7 @@ def linux_get_next_nfqueue_numbers(self, n): return next_qnos - def linux_iptables_redir_iface(self, fn_iface): + def linux_iptables_redir_iface(self, iface): """Linux-specific iptables processing for interface-based redirect rules. @@ -380,13 +413,8 @@ def linux_iptables_redir_iface(self, fn_iface): """ iptables_rules = [] - if fn_iface: - fmt = 'iptables -t nat %s PREROUTING -i {} -j REDIRECT'.format( - fn_iface) - else: - fmt = 'iptables -t nat %s PREROUTING -j REDIRECT' - - rule = IptCmdTemplate(fmt) + rule = IptCmdTemplate() + rule.redir_iface_init(iface) ret = rule.add() if ret != 0: @@ -608,12 +636,8 @@ def scan_for_default_gw(fields): return dgw def linux_redir_icmp(self, iface=None): - cmd = 'iptables' - if iface: - cmd += (' -o %s' % (iface)) - fmt = cmd + ' -t nat %s OUTPUT -p icmp -j REDIRECT' - - rule = IptCmdTemplate(fmt) + rule = IptCmdTemplate() + rule.redir_icmp_init(iface) ret = rule.add() return (ret == 0), rule diff --git a/fakenet/diverters/linux.py b/fakenet/diverters/linux.py index 41142fd..cae1250 100644 --- a/fakenet/diverters/linux.py +++ b/fakenet/diverters/linux.py @@ -35,12 +35,6 @@ def __init__(self, diverter_config, listeners_config, ip_addrs, def init_diverter_linux(self): """Linux-specific Diverter initialization.""" - # String list configuration item that is specific to the Linux - # Diverter, will not be parsed by DiverterBase, and needs to be - # accessed as an array in the future. - - #slists = [] - #self.reconfigure(portlists=[], stringlists=slists) self.logger.info('Running in %s mode' % (self.network_mode)) @@ -96,7 +90,6 @@ def init_diverter_linux(self): self.outgoing_trans_cbs.append(self.maybe_redir_ip) def startCallback(self): - if not self.check_privileged(): self.logger.error('The Linux Diverter requires administrative ' + 'privileges') @@ -138,22 +131,25 @@ def startCallback(self): 'netfilter queue numbers') sys.exit(1) + fn_iface = None + if (self.is_configured('linuxrestrictinterface') and + not self.is_clear('linuxrestrictinterface')): + self.pdebug(DMISC, 'Processing LinuxRestrictInterface config %s' % + self.getconfigval('linuxrestrictinterface')) + fn_iface = self.getconfigval('linuxrestrictinterface') + self.pdebug(DNFQUEUE, 'Next available NFQUEUE numbers: ' + str(qnos)) self.pdebug(DNFQUEUE, 'Enumerating queue numbers and hook ' + 'specifications to create NFQUEUE objects') - self.pdebug(DMISC, 'Processing LinuxRestrictInterface config %s' % self.getconfigval('linuxrestrictinterface')) - if self.is_activated('linuxrestrictinterface'): - fn_iface = self.getconfigval('linuxrestrictinterface') - else: - fn_iface = None + self.nfqueues = list() for qno, hk in zip(qnos, callbacks): self.pdebug(DNFQUEUE, ('Creating NFQUEUE object for chain %s / ' + 'table %s / queue # %d => %s') % (hk.chain, hk.table, qno, str(hk.callback))) q = LinuxDiverterNfqueue(qno, hk.chain, hk.table, hk.callback, - fn_iface) + fn_iface) self.nfqueues.append(q) ok = q.start() if not ok: diff --git a/fakenet/fakenet.py b/fakenet/fakenet.py index 444b0e1..a63884a 100644 --- a/fakenet/fakenet.py +++ b/fakenet/fakenet.py @@ -23,6 +23,7 @@ import platform from optparse import OptionParser +from collections import namedtuple ############################################################################### # Listener services @@ -137,9 +138,12 @@ def start(self): # Select platform specific diverter platform_name = platform.system() + iface_ip_info = IfaceIpInfo() + ip_addrs = dict() - ip_addrs[4] = get_ips([4]) # Get IPv4 addrs - ip_addrs[6] = get_ips([6]) # Get IPv6 addrs + ip_addrs[4] = iface_ip_info.get_ips([4]) + ip_addrs[6] = iface_ip_info.get_ips([6]) + fn_addr = '0.0.0.0' if platform_name == 'Windows': @@ -159,17 +163,22 @@ def start(self): if self.diverter_config['networkmode'].lower() == 'auto': self.diverter_config['networkmode'] = 'multihost' - fn_addr = '0.0.0.0' if self.diverter_config['networkmode'].lower() == 'multihost': - if self.diverter_config['linuxrestrictinterface'].lower() != 'off': + if (self.diverter_config['linuxrestrictinterface'].lower() + != 'off'): fn_iface = self.diverter_config['linuxrestrictinterface'] - ifaces = linux_get_ifaces(netifaces.AF_INET) - if fn_iface in ifaces: - fn_addr = ifaces[fn_iface] + if fn_iface in iface_ip_info.ifaces: + # default to first interfaces + fn_addr = iface_ip_info.get_ips([4], fn_iface)[0] else: - self.logger.error('Invalid interface %s specified. Proceeding without interface restriction. Exiting.', fn_iface) + self.logger.error('Invalid interface %s specified.' + + 'Proceeding without interface restriction.' + + 'Exiting.', fn_iface) sys.exit(1) + print('get_ips: %r' % (ip_addrs)) + print('fn_addr: %r' % (fn_addr)) + from diverters.linux import Diverter self.diverter = Diverter(self.diverter_config, self.listeners_config, ip_addrs, self.logging_level) @@ -184,6 +193,7 @@ def start(self): for listener_name in self.listeners_config: listener_config = self.listeners_config[listener_name] + listener_config['ipaddr'] = fn_addr # Anonymous listener if not 'listener' in listener_config: self.logger.info('Anonymous %s listener on %s port %s...', listener_name, listener_config['protocol'], listener_config['port']) @@ -201,7 +211,7 @@ def start(self): else: listener_provider_instance = listener_provider( - config=listener_config, name=listener_name, local_ip=fn_addr, logging_level=self.logging_level) + config=listener_config, name=listener_name, logging_level=self.logging_level) # Store listener provider object self.running_listener_providers.append(listener_provider_instance) @@ -242,47 +252,57 @@ def stop(self): if self.diverter: self.diverter.stop() -def get_ips(ipvers): - """Return IP addresses bound to local interfaces including loopbacks. - - Parameters - ---------- - ipvers : list - IP versions desired (4, 6, or both); ensures the netifaces semantics - (e.g. netiface.AF_INET) are localized to this function. - """ - specs = [] - results = [] - - for ver in ipvers: - if ver == 4: - specs.append(netifaces.AF_INET) - elif ver == 6: - specs.append(netifaces.AF_INET6) - else: - raise ValueError('get_ips only supports IP versions 4 and 6') - for spec in specs: - ifaces = linux_get_ifaces(spec) - if ifaces is not None: - results.append(ifaces.values()) +class IfaceIpInfo(): + """Make netifaces queryable via listcomps of namedtuples""" - return results + IfaceIp = namedtuple('IfaceIp', 'iface ip ver') -def linux_get_ifaces(spec=netifaces.AF_INET): - - results = {} + _ver_to_spec = {4: netifaces.AF_INET, 6: netifaces.AF_INET6} + _valid_ipvers = [4, 6] - for iface in netifaces.interfaces(): + def __init__(self): + self.ifaces = netifaces.interfaces() + self.ips = [] - addrs = netifaces.ifaddresses(iface) + for iface in self.ifaces: + addrs = netifaces.ifaddresses(iface) + for ipver in self._valid_ipvers: + self._tabulate_iface(iface, addrs, ipver) + + def _tabulate_iface(self, iface, addrs, ipver): + spec = self._ver_to_spec[ipver] if spec in addrs: for link in addrs[spec]: - - if 'addr' in link: - results[iface] = link['addr'] - - return results + self._tabulate_link(iface, link, ipver) + + def _tabulate_link(self, iface, link, ipver): + if 'addr' in link: + addr = link['addr'] + self.ips.append(self.IfaceIp(iface, addr, ipver)) + + def get_ips(self, ipvers, iface=None): + """Return IP addresses bound to local interfaces including loopbacks. + + Parameters + ---------- + ipvers : list(int) + IP versions desired (4, 6, or both) + iface : str or NoneType + Optional interface to limit the query + returns: + list(str): IP addresses as requested + """ + if not all(ver in self._valid_ipvers for ver in ipvers): + raise ValueError('Only IP versions 4 and 6 are supported') + + if iface and (iface not in self.ifaces): + raise ValueError('Unrecognized iface %s' % (iface)) + + downselect = [i for i in self.ips if i.ver in ipvers] + if iface: + downselect = [i for i in downselect if i.iface == iface] + return [i.ip for i in downselect] def main(): diff --git a/fakenet/listeners/BITSListener.py b/fakenet/listeners/BITSListener.py index e6b01e0..9f1af2a 100644 --- a/fakenet/listeners/BITSListener.py +++ b/fakenet/listeners/BITSListener.py @@ -452,7 +452,6 @@ def __init__( self, config={}, name='BITSListener', - local_ip='0.0.0.0', logging_level=logging.DEBUG, running_listeners=None ): @@ -462,7 +461,7 @@ def __init__( self.config = config self.name = name - self.local_ip = local_ip + self.local_ip = config.get('ipaddr') self.server = None self.running_listeners = running_listeners self.NAME = 'BITS' @@ -478,7 +477,6 @@ def __init__( def start(self): self.logger.debug('Starting...') - self.server = ThreadedHTTPServer((self.local_ip, int(self.config.get('port'))), SimpleBITSRequestHandler) self.server.logger = self.logger self.server.bits_file_prefix = self.bits_file_prefix diff --git a/fakenet/listeners/DNSListener.py b/fakenet/listeners/DNSListener.py index 0de19bb..e6b366c 100644 --- a/fakenet/listeners/DNSListener.py +++ b/fakenet/listeners/DNSListener.py @@ -27,7 +27,6 @@ def __init__( self, config={}, name='DNSListener', - local_ip='0.0.0.0', logging_level=logging.INFO, ): @@ -35,7 +34,7 @@ def __init__( self.logger.setLevel(logging_level) self.config = config - self.local_ip = local_ip + self.local_ip = config.get('ipaddr') self.server = None self.name = 'DNS' self.port = self.config.get('port', 53) @@ -47,7 +46,7 @@ def __init__( self.logger.debug(' %10s: %s', key, value) def start(self): - + # Start UDP listener if self.config['protocol'].lower() == 'udp': self.logger.debug('Starting UDP ...') diff --git a/fakenet/listeners/FTPListener.py b/fakenet/listeners/FTPListener.py index e8b5463..640726a 100644 --- a/fakenet/listeners/FTPListener.py +++ b/fakenet/listeners/FTPListener.py @@ -225,9 +225,8 @@ def taste(self, data, dport): return confidence def __init__(self, - config, + config, name='FTPListener', - local_ip='0.0.0.0', logging_level=logging.INFO, running_listeners=None, diverter=None @@ -238,7 +237,7 @@ def __init__(self, self.config = config self.name = name - self.local_ip = local_ip + self.local_ip = config.get('ipaddr') self.server = None self.running_listeners = running_listeners self.diverter = diverter diff --git a/fakenet/listeners/HTTPListener.py b/fakenet/listeners/HTTPListener.py index d9367f9..1d78f60 100644 --- a/fakenet/listeners/HTTPListener.py +++ b/fakenet/listeners/HTTPListener.py @@ -55,8 +55,7 @@ def taste(self, data, dport): def __init__( self, config={}, - name='HTTPListener', - local_ip='0.0.0.0', + name='HTTPListener', logging_level=logging.DEBUG, ): @@ -65,10 +64,10 @@ def __init__( self.config = config self.name = name - self.local_ip = local_ip + self.local_ip = config.get('ipaddr') self.server = None self.name = 'HTTP' - self.port = self.config.get('port', 80) + self.port = self.config.get('port', 80) self.logger.debug('Initialized with config:') for key, value in config.iteritems(): diff --git a/fakenet/listeners/IRCListener.py b/fakenet/listeners/IRCListener.py index d39d613..7b27adc 100644 --- a/fakenet/listeners/IRCListener.py +++ b/fakenet/listeners/IRCListener.py @@ -68,7 +68,6 @@ def taste(self, data, dport): def __init__(self, config, name='IRCListener', - local_ip='0.0.0.0', logging_level=logging.INFO, ): @@ -77,7 +76,7 @@ def __init__(self, self.config = config self.name = name - self.local_ip = local_ip + self.local_ip = config.get('ipaddr') self.server = None self.name = 'IRC' @@ -92,7 +91,7 @@ def __init__(self, def start(self): self.logger.debug('Starting...') - + self.server = ThreadedTCPServer((self.local_ip, int(self.config['port'])), ThreadedTCPRequestHandler) self.banner = self.genBanner() diff --git a/fakenet/listeners/POPListener.py b/fakenet/listeners/POPListener.py index d8fe19d..661c9c6 100644 --- a/fakenet/listeners/POPListener.py +++ b/fakenet/listeners/POPListener.py @@ -45,7 +45,6 @@ def taste(self, data, dport): def __init__(self, config, name='POPListener', - local_ip='0.0.0.0', logging_level=logging.INFO, ): @@ -54,7 +53,7 @@ def __init__(self, self.config = config self.name = name - self.local_ip = local_ip + self.local_ip = config.get('ipaddr') self.server = None self.name = 'POP' self.port = self.config.get('port', 110) @@ -67,7 +66,7 @@ def __init__(self, def start(self): self.logger.debug('Starting...') - + self.server = ThreadedTCPServer((self.local_ip, int(self.config['port'])), ThreadedTCPRequestHandler) if self.config.get('usessl') == 'Yes': diff --git a/fakenet/listeners/ProxyListener.py b/fakenet/listeners/ProxyListener.py index 4384425..591969f 100644 --- a/fakenet/listeners/ProxyListener.py +++ b/fakenet/listeners/ProxyListener.py @@ -15,9 +15,6 @@ import os BUF_SZ = 1024 -# When interface restriction is on, the proxy cannot connect to the Diverter -# via localhost so the configured IP must be preserved. -LOCAL_FN_IP = '0.0.0.0' class ProxyListener(object): @@ -26,7 +23,6 @@ def __init__( self, config={}, name ='ProxyListener', - local_ip='0.0.0.0', logging_level=logging.DEBUG, ): @@ -35,9 +31,7 @@ def __init__( self.config = config self.name = name - self.local_ip = local_ip - global LOCAL_FN_IP - LOCAL_FN_IP = local_ip + self.local_ip = config.get('ipaddr') self.server = None self.udp_fwd_table = dict() @@ -77,6 +71,7 @@ def start(self): self.server.config = self.config self.server.logger = self.logger + self.server.local_ip = self.local_ip self.server.running_listeners = None self.server.diverter = None self.server_thread = threading.Thread( @@ -223,7 +218,7 @@ def handle(self): if top_listener: self.server.logger.debug('Likely listener: %s' % top_listener.name) - listener_sock = ThreadedTCPClientSocket(LOCAL_FN_IP, + listener_sock = ThreadedTCPClientSocket(self.server.local_ip, top_listener.port, listener_q, remote_q, self.server.config, self.server.logger) listener_sock.daemon = True @@ -289,9 +284,9 @@ def handle(self): if top_listener: sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - sock.bind((LOCAL_FN_IP, 0)) + sock.bind((self.server.local_ip, 0)) - sock.sendto(data, (LOCAL_FN_IP, int(top_listener.port))) + sock.sendto(data, (self.server.local_ip, int(top_listener.port))) reply = sock.recv(BUF_SZ) self.server.logger.info('Received %d bytes.', len(data)) sock.close() diff --git a/fakenet/listeners/RawListener.py b/fakenet/listeners/RawListener.py index d584896..d9481b0 100644 --- a/fakenet/listeners/RawListener.py +++ b/fakenet/listeners/RawListener.py @@ -19,7 +19,6 @@ def taste(self, data, dport): def __init__(self, config, name='RawListener', - local_ip='0.0.0.0', logging_level=logging.INFO, ): @@ -28,7 +27,7 @@ def __init__(self, self.config = config self.name = name - self.local_ip = local_ip + self.local_ip = config.get('ipaddr') self.server = None self.name = 'Raw' self.port = self.config.get('port', 1337) @@ -43,7 +42,7 @@ def start(self): # Start listener if self.config.get('protocol') != None: - + if self.config['protocol'].lower() == 'tcp': self.logger.debug('Starting TCP ...') self.server = ThreadedTCPServer((self.local_ip, int(self.config['port'])), ThreadedTCPRequestHandler) diff --git a/fakenet/listeners/SMTPListener.py b/fakenet/listeners/SMTPListener.py index ae56d34..8510391 100644 --- a/fakenet/listeners/SMTPListener.py +++ b/fakenet/listeners/SMTPListener.py @@ -37,7 +37,6 @@ def __init__( self, config, name='SMTPListener', - local_ip='0.0.0.0', logging_level=logging.INFO, ): @@ -46,7 +45,7 @@ def __init__( self.config = config self.name = name - self.local_ip = local_ip + self.local_ip = config.get('ipaddr') self.server = None self.name = 'SMTP' self.port = self.config.get('port', 25) @@ -59,7 +58,7 @@ def __init__( def start(self): self.logger.debug('Starting...') - + self.server = ThreadedTCPServer((self.local_ip, int(self.config['port'])), ThreadedTCPRequestHandler) if self.config.get('usessl') == 'Yes': diff --git a/fakenet/listeners/TFTPListener.py b/fakenet/listeners/TFTPListener.py index 88ba5af..b4947f0 100644 --- a/fakenet/listeners/TFTPListener.py +++ b/fakenet/listeners/TFTPListener.py @@ -69,7 +69,6 @@ def taste(self, data, dport): def __init__(self, config, name='TFTPListener', - local_ip='0.0.0.0', logging_level=logging.INFO, ): @@ -78,7 +77,7 @@ def __init__(self, self.config = config self.name = name - self.local_ip = local_ip + self.local_ip = config.get('ipaddr') self.server = None self.name = 'TFTP' self.port = self.config.get('port', 69) @@ -97,7 +96,7 @@ def __init__(self, def start(self): self.logger.info('Starting...') - + # Start listener self.server = ThreadedUDPServer((self.local_ip, int(self.config['port'])), ThreadedUDPRequestHandler) From 337db3297c860b279c4f16bc6efa047ae8ffd94b Mon Sep 17 00:00:00 2001 From: Matthew Haigh Date: Wed, 20 Mar 2019 08:08:53 -0700 Subject: [PATCH 05/18] fixed lost pep8 changes --- fakenet/configs/default.ini | 21 ++------------------- fakenet/diverters/linutil.py | 11 +++++------ fakenet/diverters/linux.py | 4 ++-- 3 files changed, 9 insertions(+), 27 deletions(-) diff --git a/fakenet/configs/default.ini b/fakenet/configs/default.ini index 6ff7f5b..cffb1e0 100644 --- a/fakenet/configs/default.ini +++ b/fakenet/configs/default.ini @@ -46,13 +46,13 @@ NetworkMode: Auto # NFQUEUE NetfilterQueue activity (Linux only) # PROCFS Procfs read/write activity (Linux only) # IPTABLES iptables firewall rule activity (Linux only) -DebugLevel: IPTABLES, NFQUEUE +DebugLevel: Off # Restrict which interfaces on which Fakenet-NG will intercept and handle # packets. Specify (only) one interface and Fakenet-NG will ignore all other # interfaces. This feature only applies to interfaces on different subnets. # Specify interface by name only (ex: eth0). To disable, set to "Off" -LinuxRestrictInterface: enp2s4 +LinuxRestrictInterface: Off # Set LinuxFlushIptables to Yes to have the Linux Diverter flush all iptables # rules before adding its FakeNet-NG-specific rules to iptables. FakeNet-NG @@ -138,8 +138,6 @@ BlackListPortsUDP: 67, 68, 137, 138, 443, 1900, 5355 # # The following settings are available for all listeners: # * Enabled - specify whether or not the listener is enabled. -# * IPAddress - specify IP address to bind to. Comment out or leave -# blank to bind to all interface # * Port - TCP or UDP port to listen on. # * Protocol - TCP or UDP # * Listener - Listener name to handle traffic. @@ -206,7 +204,6 @@ BlackListPortsUDP: 67, 68, 137, 138, 443, 1900, 5355 Enabled: True Protocol: TCP Listener: ProxyListener -IPAddress: 192.168.204.140 Port: 38926 Listeners: HTTPListener, RawListener, FTPListener, DNSListener, POPListener, SMTPListener, TFTPListener, IRCListener, BITSListener Hidden: False @@ -215,14 +212,12 @@ Hidden: False Enabled: True Protocol: UDP Listener: ProxyListener -IPAddress: 192.168.204.140 Port: 38926 Listeners: RawListener, DNSListener, TFTPListener, FTPListener Hidden: False [Forwarder] Enabled: False -IPAddress: 192.168.204.140 Port: 8080 Protocol: TCP ProcessWhiteList: chrome.exe @@ -230,7 +225,6 @@ Hidden: False [RawTCPListener] Enabled: True -IPAddress: 192.168.204.140 Port: 1337 Protocol: TCP Listener: RawListener @@ -240,7 +234,6 @@ Hidden: False [RawUDPListener] Enabled: True -IPAddress: 192.168.204.140 Port: 1337 Protocol: UDP Listener: RawListener @@ -250,7 +243,6 @@ Hidden: False [FilteredListener] Enabled: False -IPAddress: 192.168.204.140 Port: 31337 Protocol: TCP Listener: RawListener @@ -262,7 +254,6 @@ Hidden: False [DNS Server] Enabled: True -IPAddress: 192.168.204.140 Port: 53 Protocol: UDP Listener: DNSListener @@ -274,7 +265,6 @@ Hidden: False [HTTPListener80] Enabled: True -IPAddress: 192.168.204.140 Port: 80 Protocol: TCP Listener: HTTPListener @@ -288,7 +278,6 @@ Hidden: False [HTTPListener443] Enabled: True -IPAddress: 192.168.204.140 Port: 443 Protocol: TCP Listener: HTTPListener @@ -300,7 +289,6 @@ Hidden: False [SMTPListener] Enabled: True -IPAddress: 192.168.204.140 Port: 25 Protocol: TCP Listener: SMTPListener @@ -309,7 +297,6 @@ Hidden: False [FTPListener21] Enabled: True -IPAddress: 192.168.204.140 Port: 21 Protocol: TCP Listener: FTPListener @@ -322,14 +309,12 @@ ServerName: !gethostname [FTPListenerPASV] Enabled: True -IPAddress: 192.168.204.140 Port: 60000-60010 Protocol: TCP Hidden: False [IRCServer] Enabled: True -IPAddress: 192.168.204.140 Port: 6667 Protocol: TCP Listener: IRCListener @@ -341,7 +326,6 @@ Hidden: False [TFTPListener] Enabled: True -IPAddress: 192.168.204.140 Port: 69 Protocol: UDP Listener: TFTPListener @@ -351,7 +335,6 @@ TFTPFilePrefix: tftp [POPServer] Enabled: True -IPAddress: 192.168.204.140 Port: 110 Protocol: TCP Listener: POPListener diff --git a/fakenet/diverters/linutil.py b/fakenet/diverters/linutil.py index a22132c..d546480 100644 --- a/fakenet/diverters/linutil.py +++ b/fakenet/diverters/linutil.py @@ -50,9 +50,9 @@ def iptables_format(self, chain, iface, argfmt): iface : string or NoneType Name of interface to restrict the rule to (e.g. 'eth0'), or None argfmt : string - Format string for remaining iptables arguments. This format string will - not be included in format string evaluation but is appended as-is to - the iptables command. + Format string for remaining iptables arguments. This format string + will not be included in format string evaluation but is appended + as-is to the iptables command. """ flag_iface = '' if iface: @@ -73,7 +73,7 @@ def iptables_format(self, chain, iface, argfmt): def nfqueue_init(self, chain, qno, table, iface=None): fmt = '-t {} -j NFQUEUE --queue-num {}'.format(table, qno) - self._addcmd, self._remcmd = self.iptables_format(chain, iface, fmt) + self._addcmd, self._remcmd = self.iptables_format(chain, iface, fmt) def redir_iface_init(self, iface=None): fmt = '-t nat -j REDIRECT' @@ -82,7 +82,7 @@ def redir_iface_init(self, iface=None): def redir_icmp_init(self, iface=None): fmt = '-t nat -p icmp -j REDIRECT' - self._addcmd, self._remcmd = self.iptables_format('OUTPUT', iface, fmt) + self._addcmd, self._remcmd = self.iptables_format('OUTPUT', iface, fmt) class LinuxDiverterNfqueue(object): @@ -759,4 +759,3 @@ def linux_endpoint_owned_by_processes(self, ipver, proto_name, ip, port, (ip, port, t)) return False - diff --git a/fakenet/diverters/linux.py b/fakenet/diverters/linux.py index cae1250..dbaa771 100644 --- a/fakenet/diverters/linux.py +++ b/fakenet/diverters/linux.py @@ -135,14 +135,14 @@ def startCallback(self): if (self.is_configured('linuxrestrictinterface') and not self.is_clear('linuxrestrictinterface')): self.pdebug(DMISC, 'Processing LinuxRestrictInterface config %s' % - self.getconfigval('linuxrestrictinterface')) + self.getconfigval('linuxrestrictinterface')) fn_iface = self.getconfigval('linuxrestrictinterface') self.pdebug(DNFQUEUE, 'Next available NFQUEUE numbers: ' + str(qnos)) self.pdebug(DNFQUEUE, 'Enumerating queue numbers and hook ' + 'specifications to create NFQUEUE objects') - + self.nfqueues = list() for qno, hk in zip(qnos, callbacks): self.pdebug(DNFQUEUE, ('Creating NFQUEUE object for chain %s / ' + From 98d0f0faa5d9a3ef075323839380fb7c3e89e42b Mon Sep 17 00:00:00 2001 From: Matthew Haigh Date: Wed, 20 Mar 2019 10:09:01 -0700 Subject: [PATCH 06/18] formatting cleanup --- fakenet/fakenet.py | 8 ++------ fakenet/listeners/BITSListener.py | 2 +- fakenet/listeners/DNSListener.py | 10 +++++----- fakenet/listeners/FTPListener.py | 10 +++++----- fakenet/listeners/HTTPListener.py | 7 +++---- fakenet/listeners/IRCListener.py | 10 ++++------ fakenet/listeners/ProxyListener.py | 14 +++++++------- fakenet/listeners/SMTPListener.py | 7 +++---- fakenet/listeners/TFTPListener.py | 12 +++++------- 9 files changed, 35 insertions(+), 45 deletions(-) diff --git a/fakenet/fakenet.py b/fakenet/fakenet.py index a63884a..bc07930 100644 --- a/fakenet/fakenet.py +++ b/fakenet/fakenet.py @@ -168,7 +168,7 @@ def start(self): != 'off'): fn_iface = self.diverter_config['linuxrestrictinterface'] if fn_iface in iface_ip_info.ifaces: - # default to first interfaces + # default to first interface fn_addr = iface_ip_info.get_ips([4], fn_iface)[0] else: self.logger.error('Invalid interface %s specified.' @@ -176,10 +176,6 @@ def start(self): + 'Exiting.', fn_iface) sys.exit(1) - print('get_ips: %r' % (ip_addrs)) - print('fn_addr: %r' % (fn_addr)) - - from diverters.linux import Diverter self.diverter = Diverter(self.diverter_config, self.listeners_config, ip_addrs, self.logging_level) @@ -211,7 +207,7 @@ def start(self): else: listener_provider_instance = listener_provider( - config=listener_config, name=listener_name, logging_level=self.logging_level) + listener_config, listener_name, self.logging_level) # Store listener provider object self.running_listener_providers.append(listener_provider_instance) diff --git a/fakenet/listeners/BITSListener.py b/fakenet/listeners/BITSListener.py index 9f1af2a..838526a 100644 --- a/fakenet/listeners/BITSListener.py +++ b/fakenet/listeners/BITSListener.py @@ -458,7 +458,7 @@ def __init__( self.logger = logging.getLogger(name) self.logger.setLevel(logging_level) - + self.config = config self.name = name self.local_ip = config.get('ipaddr') diff --git a/fakenet/listeners/DNSListener.py b/fakenet/listeners/DNSListener.py index e6b366c..0ae663b 100644 --- a/fakenet/listeners/DNSListener.py +++ b/fakenet/listeners/DNSListener.py @@ -24,15 +24,15 @@ def taste(self, data, dport): return confidence + 2 def __init__( - self, - config={}, + self, + config={}, name='DNSListener', - logging_level=logging.INFO, + logging_level=logging.INFO, ): self.logger = logging.getLogger(name) self.logger.setLevel(logging_level) - + self.config = config self.local_ip = config.get('ipaddr') self.server = None @@ -46,7 +46,7 @@ def __init__( self.logger.debug(' %10s: %s', key, value) def start(self): - + # Start UDP listener if self.config['protocol'].lower() == 'udp': self.logger.debug('Starting UDP ...') diff --git a/fakenet/listeners/FTPListener.py b/fakenet/listeners/FTPListener.py index 640726a..3b90a29 100644 --- a/fakenet/listeners/FTPListener.py +++ b/fakenet/listeners/FTPListener.py @@ -222,19 +222,19 @@ def taste(self, data, dport): if data.startswith(command): return confidence + 1 - return confidence + return confidence - def __init__(self, + def __init__(self, config, name='FTPListener', - logging_level=logging.INFO, - running_listeners=None, + logging_level=logging.INFO, + running_listeners=None, diverter=None ): self.logger = logging.getLogger(name) self.logger.setLevel(logging_level) - + self.config = config self.name = name self.local_ip = config.get('ipaddr') diff --git a/fakenet/listeners/HTTPListener.py b/fakenet/listeners/HTTPListener.py index 1d78f60..5eed2e7 100644 --- a/fakenet/listeners/HTTPListener.py +++ b/fakenet/listeners/HTTPListener.py @@ -53,21 +53,20 @@ def taste(self, data, dport): }) def __init__( - self, - config={}, + self, + config={}, name='HTTPListener', logging_level=logging.DEBUG, ): self.logger = logging.getLogger(name) self.logger.setLevel(logging_level) - self.config = config self.name = name self.local_ip = config.get('ipaddr') self.server = None self.name = 'HTTP' - self.port = self.config.get('port', 80) + self.port = self.config.get('port', 80) self.logger.debug('Initialized with config:') for key, value in config.iteritems(): diff --git a/fakenet/listeners/IRCListener.py b/fakenet/listeners/IRCListener.py index 7b27adc..24abbd8 100644 --- a/fakenet/listeners/IRCListener.py +++ b/fakenet/listeners/IRCListener.py @@ -65,15 +65,14 @@ def taste(self, data, dport): return confidence - def __init__(self, - config, - name='IRCListener', - logging_level=logging.INFO, + def __init__(self, + config, + name='IRCListener', + logging_level=logging.INFO, ): self.logger = logging.getLogger(name) self.logger.setLevel(logging_level) - self.config = config self.name = name self.local_ip = config.get('ipaddr') @@ -91,7 +90,6 @@ def __init__(self, def start(self): self.logger.debug('Starting...') - self.server = ThreadedTCPServer((self.local_ip, int(self.config['port'])), ThreadedTCPRequestHandler) self.banner = self.genBanner() diff --git a/fakenet/listeners/ProxyListener.py b/fakenet/listeners/ProxyListener.py index 591969f..9dbfdcc 100644 --- a/fakenet/listeners/ProxyListener.py +++ b/fakenet/listeners/ProxyListener.py @@ -20,9 +20,9 @@ class ProxyListener(object): def __init__( - self, - config={}, - name ='ProxyListener', + self, + config={}, + name='ProxyListener', logging_level=logging.DEBUG, ): @@ -52,7 +52,7 @@ def start(self): self.server = ThreadedTCPServer((self.local_ip, int(self.config.get('port'))), ThreadedTCPRequestHandler) - + elif proto == 'UDP': self.logger.debug('Starting UDP ...') @@ -68,7 +68,7 @@ def start(self): else: self.logger.error('Protocol is not defined') return - + self.server.config = self.config self.server.logger = self.logger self.server.local_ip = self.local_ip @@ -93,7 +93,7 @@ def acceptListeners(self, listeners): def acceptDiverter(self, diverter): self.server.diverter = diverter - + class ThreadedTCPClientSocket(threading.Thread): @@ -217,7 +217,7 @@ def handle(self): if top_listener: self.server.logger.debug('Likely listener: %s' % - top_listener.name) + top_listener.name) listener_sock = ThreadedTCPClientSocket(self.server.local_ip, top_listener.port, listener_q, remote_q, self.server.config, self.server.logger) diff --git a/fakenet/listeners/SMTPListener.py b/fakenet/listeners/SMTPListener.py index 8510391..5789377 100644 --- a/fakenet/listeners/SMTPListener.py +++ b/fakenet/listeners/SMTPListener.py @@ -34,9 +34,9 @@ def taste(self, data, dport): return confidence def __init__( - self, - config, - name='SMTPListener', + self, + config, + name='SMTPListener', logging_level=logging.INFO, ): @@ -58,7 +58,6 @@ def __init__( def start(self): self.logger.debug('Starting...') - self.server = ThreadedTCPServer((self.local_ip, int(self.config['port'])), ThreadedTCPRequestHandler) if self.config.get('usessl') == 'Yes': diff --git a/fakenet/listeners/TFTPListener.py b/fakenet/listeners/TFTPListener.py index b4947f0..618639b 100644 --- a/fakenet/listeners/TFTPListener.py +++ b/fakenet/listeners/TFTPListener.py @@ -66,15 +66,14 @@ def taste(self, data, dport): return confidence - def __init__(self, - config, - name='TFTPListener', - logging_level=logging.INFO, + def __init__(self, + config, + name='TFTPListener', + logging_level=logging.INFO, ): self.logger = logging.getLogger(name) - self.logger.setLevel(logging_level) - + self.logger.setLevel(logging_level) self.config = config self.name = name self.local_ip = config.get('ipaddr') @@ -96,7 +95,6 @@ def __init__(self, def start(self): self.logger.info('Starting...') - # Start listener self.server = ThreadedUDPServer((self.local_ip, int(self.config['port'])), ThreadedUDPRequestHandler) From 6c234ca99a4a0ea7a19b2551207d3aa98c7a40f4 Mon Sep 17 00:00:00 2001 From: Matthew Haigh Date: Wed, 20 Mar 2019 11:09:36 -0700 Subject: [PATCH 07/18] fixed localhost issue for windows --- fakenet/listeners/ProxyListener.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fakenet/listeners/ProxyListener.py b/fakenet/listeners/ProxyListener.py index 9dbfdcc..22104d6 100644 --- a/fakenet/listeners/ProxyListener.py +++ b/fakenet/listeners/ProxyListener.py @@ -72,6 +72,8 @@ def start(self): self.server.config = self.config self.server.logger = self.logger self.server.local_ip = self.local_ip + if self.local_ip == '0.0.0.0': + self.server.local_ip = 'localhost' self.server.running_listeners = None self.server.diverter = None self.server_thread = threading.Thread( From 9ee3f07de6b05a05b17ffe62dc2d262711b2b90e Mon Sep 17 00:00:00 2001 From: Matthew Haigh Date: Wed, 20 Mar 2019 11:42:35 -0700 Subject: [PATCH 08/18] fixed long string in fakenet.py --- fakenet/fakenet.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/fakenet/fakenet.py b/fakenet/fakenet.py index bc07930..9b1bbf0 100644 --- a/fakenet/fakenet.py +++ b/fakenet/fakenet.py @@ -171,9 +171,10 @@ def start(self): # default to first interface fn_addr = iface_ip_info.get_ips([4], fn_iface)[0] else: - self.logger.error('Invalid interface %s specified.' - + 'Proceeding without interface restriction.' - + 'Exiting.', fn_iface) + self.logger.error( + 'Invalid interface %s specified. Proceeding ' + 'without interface restriction. Exiting.', + fn_iface) sys.exit(1) from diverters.linux import Diverter From 7f8cb75376a83afd40cd90bdf198db8078a9cb80 Mon Sep 17 00:00:00 2001 From: Matthew Haigh Date: Wed, 20 Mar 2019 12:26:14 -0700 Subject: [PATCH 09/18] documented need to turn off LinuxFlushIptables --- fakenet/configs/default.ini | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fakenet/configs/default.ini b/fakenet/configs/default.ini index cffb1e0..6674722 100644 --- a/fakenet/configs/default.ini +++ b/fakenet/configs/default.ini @@ -51,7 +51,10 @@ DebugLevel: Off # Restrict which interfaces on which Fakenet-NG will intercept and handle # packets. Specify (only) one interface and Fakenet-NG will ignore all other # interfaces. This feature only applies to interfaces on different subnets. -# Specify interface by name only (ex: eth0). To disable, set to "Off" +# Specify interface by name only (ex: eth0). To disable, set to "Off". In +# order to run multiple instance of Fakenet-NG on different interfaces within +# the same guest, LinuxFlushIptables must be turned off to avoid the latest +# instance flushing the rules associated with other instances. LinuxRestrictInterface: Off # Set LinuxFlushIptables to Yes to have the Linux Diverter flush all iptables From 90ee970e8951aa05d8c78bf95cf8c7b1a89bad0d Mon Sep 17 00:00:00 2001 From: Matthew Haigh Date: Wed, 20 Mar 2019 13:47:05 -0700 Subject: [PATCH 10/18] fixed conflict error from previous merge --- fakenet/diverters/linux.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/fakenet/diverters/linux.py b/fakenet/diverters/linux.py index 0a305c8..27299fa 100644 --- a/fakenet/diverters/linux.py +++ b/fakenet/diverters/linux.py @@ -173,7 +173,7 @@ def startCallback(self): self.logger.error('Failed to flush DNS cache. Local machine ' 'may use cached DNS results.') - ok, rule = self.linux_redir_icmp() + ok, rule = self.linux_redir_icmp(fn_iface) if not ok: self.logger.error('Failed to redirect ICMP') self.stop() @@ -194,14 +194,6 @@ def startCallback(self): self.stop() sys.exit(1) - ok, rule = self.linux_redir_icmp(fn_iface) - if not ok: - self.logger.error('Failed to redirect ICMP') - self.stop() - sys.exit(1) - - self.rules_added.append(rule) - return True def stopCallback(self): From 106340a1b5bb13df0f25f0d547b38c46961fcc5f Mon Sep 17 00:00:00 2001 From: Matthew Haigh Date: Thu, 21 Mar 2019 04:42:43 -0700 Subject: [PATCH 11/18] updated description of feature in template.ini --- test/template.ini | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/template.ini b/test/template.ini index 3de4b7e..37a5a48 100644 --- a/test/template.ini +++ b/test/template.ini @@ -34,7 +34,10 @@ DebugLevel: NFQUEUE,IPTALBS,NONLOC,GENPKTV,PCAP # Restrict which interfaces on which Fakenet-NG will intercept and handle # packets. Specify (only) one interface and Fakenet-NG will ignore all other # interfaces. This feature only applies to interfaces on different subnets. -# Specify interface by name only (ex: eth0). To disable, set to "Off" +# Specify interface by name only (ex: eth0). To disable, set to "Off". In +# order to run multiple instance of Fakenet-NG on different interfaces within +# the same guest, LinuxFlushIptables must be turned off to avoid the latest +# instance flushing the rules associated with other instances. LinuxRestrictInterface: Off # Set LinuxFlushIptables to Yes to have the Linux Diverter flush all iptables From 21c9c10adeeca97b51ea92816377ff0edc8f8f9c Mon Sep 17 00:00:00 2001 From: Michael Bailey Date: Fri, 22 Mar 2019 08:10:13 -0700 Subject: [PATCH 12/18] Tied iptables-restore to LinuxFlushIptables resolving rule cleanup conflicts --- fakenet/configs/default.ini | 12 +++++------- fakenet/diverters/linutil.py | 12 ++++++++++-- fakenet/diverters/linux.py | 3 ++- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/fakenet/configs/default.ini b/fakenet/configs/default.ini index 6674722..278542b 100644 --- a/fakenet/configs/default.ini +++ b/fakenet/configs/default.ini @@ -54,16 +54,14 @@ DebugLevel: Off # Specify interface by name only (ex: eth0). To disable, set to "Off". In # order to run multiple instance of Fakenet-NG on different interfaces within # the same guest, LinuxFlushIptables must be turned off to avoid the latest -# instance flushing the rules associated with other instances. +# instance flushing the rules associated with other instances or restoring +# rules to an incorrect state upon exit. LinuxRestrictInterface: Off # Set LinuxFlushIptables to Yes to have the Linux Diverter flush all iptables -# rules before adding its FakeNet-NG-specific rules to iptables. FakeNet-NG -# will restore all old rules when it exits, unless its termination is -# interrupted. If you disable this setting, and you accidentally interrupt the -# termination of FakeNet-NG (such as by hitting Ctrl+C more than once), then be -# prepared for network mayhem as the Diverter may receive each packet multiple -# times due to duplicate NFQUEUE rules. +# rules before adding its FakeNet-NG-specific rules to iptables. This setting +# also restores rules via `iptables-restore` when it exits, unless its +# termination is interrupted. LinuxFlushIptables: Yes # Incorporated so that users of the binary release may make this work for diff --git a/fakenet/diverters/linutil.py b/fakenet/diverters/linutil.py index d546480..879634e 100644 --- a/fakenet/diverters/linutil.py +++ b/fakenet/diverters/linutil.py @@ -34,10 +34,16 @@ def gen_remove_cmd(self): return self._remcmd def add(self): - return subprocess.call(self._addcmd.split()) + self.pdebug(DIPTBLS, 'Adding iptables rule: %s' % (self._addcmd)) + ret = subprocess.call(self._addcmd.split()) + self.pdebug(DIPTBLS, 'iptables returned %d' % (ret)) + return ret def remove(self): - return subprocess.call(self._remcmd.split()) + self.pdebug(DIPTBLS, 'Removing iptables rule: %s' % (self._remcmd)) + ret = subprocess.call(self._remcmd.split()) + self.pdebug(DIPTBLS, 'iptables returned %d' % (ret)) + return ret def iptables_format(self, chain, iface, argfmt): """Format iptables command line with optional interface restriction. @@ -317,6 +323,8 @@ def linux_capture_iptables(self): def linux_restore_iptables(self): ret = None + self.pdebug(DIPTBLS, 'Restoring iptables') + try: p = subprocess.Popen(['iptables-restore'], stdin=subprocess.PIPE) p.communicate(self.iptables_captured) diff --git a/fakenet/diverters/linux.py b/fakenet/diverters/linux.py index 27299fa..650dd86 100644 --- a/fakenet/diverters/linux.py +++ b/fakenet/diverters/linux.py @@ -218,7 +218,8 @@ def stopCallback(self): if self.single_host_mode and self.is_set('modifylocaldns'): self.linux_restore_local_dns() - self.linux_restore_iptables() + if self.is_set('linuxflushiptables'): + self.linux_restore_iptables() return True From b3e19ac9ce208671b75b76e407f26e4e36e14fae Mon Sep 17 00:00:00 2001 From: Matthew Haigh Date: Fri, 22 Mar 2019 09:58:32 -0700 Subject: [PATCH 13/18] fixed singlehost error and pep8 errors --- fakenet/diverters/linux.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/fakenet/diverters/linux.py b/fakenet/diverters/linux.py index 650dd86..2be2ce3 100644 --- a/fakenet/diverters/linux.py +++ b/fakenet/diverters/linux.py @@ -132,10 +132,11 @@ def startCallback(self): sys.exit(1) fn_iface = None - if (self.is_configured('linuxrestrictinterface') and - not self.is_clear('linuxrestrictinterface')): + if ((not self.single_host_mode) and + self.is_configured('linuxrestrictinterface') and not + self.is_clear('linuxrestrictinterface')): self.pdebug(DMISC, 'Processing LinuxRestrictInterface config %s' % - self.getconfigval('linuxrestrictinterface')) + self.getconfigval('linuxrestrictinterface')) fn_iface = self.getconfigval('linuxrestrictinterface') self.pdebug(DNFQUEUE, 'Next available NFQUEUE numbers: ' + str(qnos)) @@ -158,7 +159,7 @@ def startCallback(self): sys.exit(1) if self.single_host_mode: - + if self.is_set('fixgateway'): if not self.linux_get_default_gw(): self.linux_set_default_gw() @@ -170,14 +171,14 @@ def startCallback(self): cmd = self.getconfigval('linuxflushdnscommand') ret = subprocess.call(cmd.split()) if ret != 0: - self.logger.error('Failed to flush DNS cache. Local machine ' - 'may use cached DNS results.') - + self.logger.error('Failed to flush DNS cache. Local ' + 'machine may use cached DNS results.') + ok, rule = self.linux_redir_icmp(fn_iface) if not ok: self.logger.error('Failed to redirect ICMP') self.stop() - sys.exit(1) + sys.exit(1) self.rules_added.append(rule) self.pdebug(DMISC, 'Processing interface redirection on ' + From 4e9faa288f359b46cc1673431f176ab2116ae218 Mon Sep 17 00:00:00 2001 From: Matthew Haigh Date: Fri, 22 Mar 2019 11:09:51 -0700 Subject: [PATCH 14/18] removed error print statements --- fakenet/diverters/linutil.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fakenet/diverters/linutil.py b/fakenet/diverters/linutil.py index 879634e..98d6028 100644 --- a/fakenet/diverters/linutil.py +++ b/fakenet/diverters/linutil.py @@ -34,15 +34,15 @@ def gen_remove_cmd(self): return self._remcmd def add(self): - self.pdebug(DIPTBLS, 'Adding iptables rule: %s' % (self._addcmd)) +# self.pdebug(DIPTBLS, 'Adding iptables rule: %s' % (self._addcmd)) ret = subprocess.call(self._addcmd.split()) - self.pdebug(DIPTBLS, 'iptables returned %d' % (ret)) +# self.pdebug(DIPTBLS, 'iptables returned %d' % (ret)) return ret def remove(self): - self.pdebug(DIPTBLS, 'Removing iptables rule: %s' % (self._remcmd)) +# self.pdebug(DIPTBLS, 'Removing iptables rule: %s' % (self._remcmd)) ret = subprocess.call(self._remcmd.split()) - self.pdebug(DIPTBLS, 'iptables returned %d' % (ret)) +# self.pdebug(DIPTBLS, 'iptables returned %d' % (ret)) return ret def iptables_format(self, chain, iface, argfmt): From 24d9e97f74a096774e3870fb331f619a71a805eb Mon Sep 17 00:00:00 2001 From: Michael Bailey Date: Fri, 22 Mar 2019 14:55:08 -0700 Subject: [PATCH 15/18] Remove my errant pdebugs for now; catch no IP exception --- fakenet/diverters/linutil.py | 10 ++-------- fakenet/fakenet.py | 9 +++++++-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/fakenet/diverters/linutil.py b/fakenet/diverters/linutil.py index 98d6028..60d5650 100644 --- a/fakenet/diverters/linutil.py +++ b/fakenet/diverters/linutil.py @@ -34,16 +34,10 @@ def gen_remove_cmd(self): return self._remcmd def add(self): -# self.pdebug(DIPTBLS, 'Adding iptables rule: %s' % (self._addcmd)) - ret = subprocess.call(self._addcmd.split()) -# self.pdebug(DIPTBLS, 'iptables returned %d' % (ret)) - return ret + return subprocess.call(self._addcmd.split()) def remove(self): -# self.pdebug(DIPTBLS, 'Removing iptables rule: %s' % (self._remcmd)) - ret = subprocess.call(self._remcmd.split()) -# self.pdebug(DIPTBLS, 'iptables returned %d' % (ret)) - return ret + return subprocess.call(self._remcmd.split()) def iptables_format(self, chain, iface, argfmt): """Format iptables command line with optional interface restriction. diff --git a/fakenet/fakenet.py b/fakenet/fakenet.py index 0fe1d09..2d24fa4 100644 --- a/fakenet/fakenet.py +++ b/fakenet/fakenet.py @@ -168,8 +168,13 @@ def start(self): != 'off'): fn_iface = self.diverter_config['linuxrestrictinterface'] if fn_iface in iface_ip_info.ifaces: - # default to first interface - fn_addr = iface_ip_info.get_ips([4], fn_iface)[0] + try: + # default to first link + fn_addr = iface_ip_info.get_ips([4], fn_iface)[0] + except LookupError as e: + self.logger.error('Couldn\'t get IP for %s' % + (fn_iface)) + sys.exit(1) else: self.logger.error( 'Invalid interface %s specified. Proceeding ' From 3a77bec457b697a67e30737ffc4beb8fb78c8934 Mon Sep 17 00:00:00 2001 From: Michael Bailey Date: Fri, 22 Mar 2019 17:09:32 -0700 Subject: [PATCH 16/18] Refactor iptables command template class --- fakenet/diverters/linutil.py | 77 ++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 39 deletions(-) diff --git a/fakenet/diverters/linutil.py b/fakenet/diverters/linutil.py index 60d5650..37e5e6f 100644 --- a/fakenet/diverters/linutil.py +++ b/fakenet/diverters/linutil.py @@ -13,33 +13,18 @@ from . import diverterbase -class IptCmdTemplate(object): +class IptCmdTemplateBase(object): """For managing insertion and removal of iptables rules. Construct and execute iptables command lines to add (-I or -A) and remove - (-D) rules. - - The removal half of this is now redundant with - LinUtilMixin.linux_{capture,restore}_iptables(). + (-D) rules in the abstract. Base only handles iptables -I/-D and -i/-o args """ - def __init__(self): + def __init(self): self._addcmd = None self._remcmd = None - def gen_add_cmd(self): - return self._addcmd - - def gen_remove_cmd(self): - return self._remcmd - - def add(self): - return subprocess.call(self._addcmd.split()) - - def remove(self): - return subprocess.call(self._remcmd.split()) - - def iptables_format(self, chain, iface, argfmt): + def _iptables_format(self, chain, iface, argfmt): """Format iptables command line with optional interface restriction. Parameters @@ -63,26 +48,43 @@ def iptables_format(self, chain, iface, argfmt): else: raise NotImplementedError('Unanticipated chain %s' % (chain)) - add_cmd = 'iptables -I {chain} {flag_if} {iface} {fmt}' - add_cmd = add_cmd.format(chain=chain, flag_if=flag_iface, - iface=(iface or ''), fmt=argfmt) - rem_cmd = 'iptables -D {chain} {flag_if} {iface} {fmt}' - rem_cmd = rem_cmd.format(chain=chain, flag_if=flag_iface, - iface=(iface or ''), fmt=argfmt) - return add_cmd, rem_cmd + self._addcmd = 'iptables -I {chain} {flag_if} {iface} {fmt}' + self._addcmd = self._addcmd.format(chain=chain, flag_if=flag_iface, + iface=(iface or ''), fmt=argfmt) + self._remcmd = 'iptables -D {chain} {flag_if} {iface} {fmt}' + self._remcmd = self._remcmd.format(chain=chain, flag_if=flag_iface, + iface=(iface or ''), fmt=argfmt) - def nfqueue_init(self, chain, qno, table, iface=None): + def add(self): + if not self._addcmd: + raise ValueError('Iptables rule addition command not initialized') + return subprocess.call(self._addcmd.split()) + + def remove(self): + if not self._remcmd: + raise ValueError('Iptables rule removal command not initialized') + return subprocess.call(self._remcmd.split()) + + +class IptCmdTemplateNfq(IptCmdTemplateBase): + """For constructing and executing NFQUEUE iptables rules""" + def __init__(self, chain, qno, table, iface=None): fmt = '-t {} -j NFQUEUE --queue-num {}'.format(table, qno) - self._addcmd, self._remcmd = self.iptables_format(chain, iface, fmt) + self._iptables_format(chain, iface, fmt) - def redir_iface_init(self, iface=None): + +class IptCmdTemplateRedir(IptCmdTemplateBase): + """For constructing and executing REDIRECT iptables rules""" + def __init__(self, iface=None): fmt = '-t nat -j REDIRECT' - self._addcmd, self._remcmd = self.iptables_format('PREROUTING', iface, - fmt) + self._iptables_format('PREROUTING', iface, fmt) + - def redir_icmp_init(self, iface=None): +class IptCmdTemplateIcmpRedir(IptCmdTemplateBase): + """For constructing and executing ICMP REDIRECT iptables rules""" + def __init__(self, iface=None): fmt = '-t nat -p icmp -j REDIRECT' - self._addcmd, self._remcmd = self.iptables_format('OUTPUT', iface, fmt) + self._iptables_format('OUTPUT', iface, fmt) class LinuxDiverterNfqueue(object): @@ -106,8 +108,7 @@ def __init__(self, qno, chain, table, callback, iface=None): self.qno = qno self.chain = chain self.table = table - self._rule = IptCmdTemplate() - self._rule.nfqueue_init(self.chain, self.qno, self.table, iface) + self._rule = IptCmdTemplateNfq(self.chain, self.qno, self.table, iface) self._callback = callback self._nfqueue = netfilterqueue.NetfilterQueue() self._sk = None @@ -415,8 +416,7 @@ def linux_iptables_redir_iface(self, iface): """ iptables_rules = [] - rule = IptCmdTemplate() - rule.redir_iface_init(iface) + rule = IptCmdTemplateRedir(iface) ret = rule.add() if ret != 0: @@ -638,8 +638,7 @@ def scan_for_default_gw(fields): return dgw def linux_redir_icmp(self, iface=None): - rule = IptCmdTemplate() - rule.redir_icmp_init(iface) + rule = IptCmdTemplateIcmpRedir(iface) ret = rule.add() return (ret == 0), rule From e3b9bf46659f9dd7f04b2003a00f02d06c42ace7 Mon Sep 17 00:00:00 2001 From: Michael Bailey Date: Fri, 22 Mar 2019 17:38:25 -0700 Subject: [PATCH 17/18] Fixing LinuxRestrictInterface comment verbiage --- fakenet/configs/default.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fakenet/configs/default.ini b/fakenet/configs/default.ini index 278542b..475bc6e 100644 --- a/fakenet/configs/default.ini +++ b/fakenet/configs/default.ini @@ -48,7 +48,7 @@ NetworkMode: Auto # IPTABLES iptables firewall rule activity (Linux only) DebugLevel: Off -# Restrict which interfaces on which Fakenet-NG will intercept and handle +# Restrict which interface on which Fakenet-NG will intercept and handle # packets. Specify (only) one interface and Fakenet-NG will ignore all other # interfaces. This feature only applies to interfaces on different subnets. # Specify interface by name only (ex: eth0). To disable, set to "Off". In From db6ca22081d6e121a88c40c8feadfe03f3ba3d08 Mon Sep 17 00:00:00 2001 From: Michael Bailey Date: Fri, 22 Mar 2019 17:38:32 -0700 Subject: [PATCH 18/18] Version bump for LinuxRestrictInterface feature --- CHANGELOG.txt | 4 ++++ fakenet/fakenet.py | 2 +- setup.py | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 5cde353..f901c6c 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,7 @@ +Version 1.4.5 +------------- +* LinuxRestrictInterface feature for MultiHost mode + Version 1.4.4 ------------- * Moved ICMP redirection to singlehost only. diff --git a/fakenet/fakenet.py b/fakenet/fakenet.py index 2d24fa4..dd5a762 100644 --- a/fakenet/fakenet.py +++ b/fakenet/fakenet.py @@ -317,7 +317,7 @@ def main(): | | / ____ \| . \| |____| |\ | |____ | | | |\ | |__| | |_|/_/ \_\_|\_\______|_| \_|______| |_| |_| \_|\_____| - Version 1.4.4 + Version 1.4.5 _____________________________________________________________ Developed by FLARE Team _____________________________________________________________ diff --git a/setup.py b/setup.py index 60aace5..612e0b2 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ setup( name='FakeNet NG', - version='1.4.4', + version='1.4.5', description="", long_description="", author="FireEye FLARE Team with credit to Peter Kacherginsky as the original developer",