class Ability(ns.ThreadedAbilityBase): _option_list = [ ns.NICOpt(ns.OptNames.OUTPUT_INTERFACE, None, 'NIC to send traffic on') ] _info = ns.AbilityInfo( name='Send Raw Frames', description='Reads L2 Frames from the pipe and writes them on the specified NIC', authors=['Florian Maury', ], tags=[ns.Tag.TCP_STACK_L1], type=ns.AbilityType.COMPONENT ) @classmethod def check_preconditions(cls, module_factory): l = [] if not ns.HAS_PCAPY: l.append('Pcapy support missing or broken. Please install pcapy or proceed to an update.') l += super(Ability, cls).check_preconditions(module_factory) return l def main(self): thr, stop_evt = ns.send_raw_traffic(self.outerface, self._poll, self._recv) self._wait() stop_evt.set() thr.join()
class Ability(ns.ThreadedAbilityBase): _option_list = [ ns.StrOpt('bpf', default='', comment='Filter to apply to received frames'), ns.NICOpt(ns.OptNames.INPUT_INTERFACE, default=None, comment='NIC to sniff on') ] _info = ns.AbilityInfo( name='Sniff Frames', description='Sniff frames and send them in the pipe', authors=[ 'Florian Maury', ], tags=[ns.Tag.TCP_STACK_L1], type=ns.AbilityType.COMPONENT) @classmethod def check_preconditions(cls, module_factory): l_dep = [] if not ns.HAS_PCAPY: l_dep.append('Pcapy support missing or broken. ' 'Please install pcapy or proceed to an update.') l_dep += super(Ability, cls).check_preconditions(module_factory) return l_dep def main(self): l_threads = [] for out in self._builtin_out_pipes: thr, cap_stop_evt, _ = pcap_lib.start_capture( self.interface, self.bpf, out) l_threads.append((thr, cap_stop_evt)) self._wait() for t in l_threads: t[1].set() t[0].join()
class Ability(ns.ThreadedAbilityBase): _option_list = [ ns.NICOpt(ns.OptNames.INPUT_INTERFACE, default=None, comment='Sniffed interface'), ns.NICOpt(ns.OptNames.OUTPUT_INTERFACE, default=None, comment='Injection interface', optional=True), ns.MacOpt(ns.OptNames.MAC_SRC, default=None, comment='Source Mac', optional=True), ns.MacOpt(ns.OptNames.MAC_DST, default=None, comment='Destination Mac', optional=True), ns.IpOpt(ns.OptNames.IP_SRC, default=None, comment='Source IP', optional=True), ns.IpOpt(ns.OptNames.IP_DST, default=None, comment='Destination IP', optional=True), ns.PortOpt(ns.OptNames.PORT_SRC, default=None, comment='Source Port', optional=True), ns.PortOpt(ns.OptNames.PORT_DST, default=None, comment='Destination Port', optional=True), ns.OptionTemplateEntry( lambda x: 0 == len( [e for e in x.lower() if e not in "0123456789abcdef"]), ns.StrOpt('ether_type', default='0800', comment='Filter by ether_type (hexa)', optional=True)), ns.ChoiceOpt(ns.OptNames.L4PROTOCOL, ['tcp', 'udp'], comment='L4 Protocol over IP', optional=True), ns.StrOpt('bridge', default=None, comment="""Specify the bridge to use for sniffing. If the bridge does not exist, it will be created and the input and output interfaces will be bridged together.""", optional=True), ns.BoolOpt('mux', default=False, comment="""True if messages to send are prefixed with either \\x00 or \\xFF. If a prefix is used, \\x00 means the message is to be sent through the sniffing interface (supposedly back to the sender, but who knows?!). If the prefix values \\xFF, then the message is sent through the output interface. If no prefix are used and this option values False, then messages are always sent through the output interface. """), ns.BoolOpt('bidirectional', default=False, comment='Whether communications must be intercepted ' 'in one way or both ways.'), ns.BoolOpt('quiet', default=True, comment='Whether to log errors.'), ] _info = ns.AbilityInfo( name='Message Interceptor', description=""" This module sniffs some frames and reports them in the in_pkt channel. Original frames might be dropped and new frames can be injected back in. If an outerface is specified, the interface and the outerface are bridged together and intercepted frames are dropped.""", authors=[ 'Florian Maury', ], tags=[ ns.Tag.INTRUSIVE, ], type=ns.AbilityType.COMPONENT) _dependencies = ['netfilter', 'capture', 'sendraw', 'demux'] @classmethod def check_preconditions(cls, module_factory): l_dep = [] if not ns.HAS_PYROUTE2: l_dep.append('PyRoute2 support missing or broken. ' 'Please install pyroute2 or proceed to an update.') l_dep += super(Ability, cls).check_preconditions(module_factory) return l_dep def _check_parameter_consistency(self): """ Check whether all provided parameters are sensible, including whether related parameters have consistent values :return: bool, True if parameter values are consistent """ if (self.port_src is not None or self.port_dst is not None) \ and self.protocol is None: self._view.error('If src port or dst port are defined, ' 'a protocol must be specified.') return False if self.outerface is None and self.mux is True: self._view.error('Message are supposed to be prefixed, ' 'but output interface is unspecified!?') return False if self.interface is None: self._view.error('An input channel must be defined.') return False if self.interface is not None \ and self.outerface is not None \ and self.interface == self.outerface: self._view.error( 'Input interface and output interface cannot be the same. ' 'If you are sniffing and T-mode and you want to inject traffic' ' back, please instanciate your own send_packet ability') return False br_name = ns.in_bridge(self.interface) if (br_name is not None and self.bridge is not None and br_name != self.bridge): self._view.error( 'Input interface is already in a different bridge. ' 'You might be breaking something here :)') return False if ns.is_bridge(self.interface): self._view.error('A bridge cannot be enslaved to another bridge. ' 'Input interface is a bridge.') return False if self.outerface is not None and ns.is_bridge(self.outerface): self._view.error('A bridge cannot be enslaved to another bridge. ' 'Output interface is a bridge.') return False return True def _build_bpf(self, mac_src, mac_dst, ether_type, ip_src, ip_dst, proto, port_src, port_dst): """ Builds a BPF from the provided parameters :param mac_src: Source MAC address (may be None) :param mac_dst: Destination MAC address (may be None) :param ip_src: Source IP address (may be None) :param ip_dst: Destination IP address (may be None) :param proto: Protocol (either "udp" or "tcp" or None) :param port_src: Source Port (may be None) :param port_dst: Destination Port (may be None) :param bidirectional: Bool telling whether the connection must be extracted in one way or in both ways :return: the BPF expression as a string """ bpf = set() bpf.add('ether proto 0x{}'.format(ether_type)) if self.bidirectional: if mac_src is not None and mac_dst is not None: bpf.add('(ether src {} and ether dst {}) ' 'or (ether src {} and ether dst {})'.format( mac_src, mac_dst, mac_dst, mac_src)) elif mac_src is not None and mac_dst is None: bpf.add('ether {}'.format(mac_src)) elif mac_dst is not None and mac_src is None: bpf.add('ether {}'.format(mac_src)) if ip_src is not None and ip_dst is not None: bpf.add('(src host {} and dst host {}) ' 'or (src host {} and dst host {})'.format( ip_src, ip_dst, ip_dst, ip_src)) elif ip_src is not None and ip_dst is None: bpf.add('host {}'.format(ip_src)) elif ip_dst is not None and ip_src is None: bpf.add('host {}'.format(ip_dst)) if proto is not None: bpf.add(proto) if port_src is not None and port_dst is not None: bpf.add('(src port {} and dst port {}) ' 'or (src port {} and dst port {})'.format( port_src, port_dst, port_dst, port_src)) elif port_src is not None and port_dst is None: bpf.add('port {}'.format(port_src)) elif port_dst is not None and port_src is None: bpf.add('port {}'.format(port_dst)) else: if not isinstance(mac_src, type(None)): bpf.add('ether src {}'.format(mac_src)) if not isinstance(mac_dst, type(None)): bpf.add('ether dst {}'.format(mac_dst)) if not isinstance(ip_src, type(None)): bpf.add('src host {}'.format(ip_src)) bpf.add('ip or ip6') if not isinstance(ip_dst, type(None)): bpf.add('dst host {}'.format(ip_dst)) bpf.add('ip or ip6') if not isinstance(proto, type(None)): bpf.add(proto) if not isinstance(port_src, type(None)): bpf.add('src port {}'.format(port_src)) if not isinstance(port_dst, type(None)): bpf.add('dst port {}'.format(port_dst)) return '({})'.format(') and ('.join(list(bpf))) def main(self): if not self._check_parameter_consistency(): self._view.warning('Inconsistent parameters') return bpf_expr = self._build_bpf(self.mac_src, self.mac_dst, self.ether_type, self.ip_src, self.ip_dst, self.protocol, self.port_src, self.port_dst) if self.outerface is not None: # Bridge only the output NIC at the moment, # to create the bridge but not let the traffic go through bridge_name = ns.bridge_iface_together(self.outerface, bridge=self.bridge) # Configure the firewall to drop relevant frames/packets fw_abl = self.get_dependency('netfilter', interface=self.interface, outerface=self.outerface, mac_src=self.mac_src, mac_dst=self.mac_dst, ip_src=self.ip_src, ip_dst=self.ip_dst, protocol=self.protocol, port_src=self.port_src, port_dst=self.port_dst) fw_abl.start() # Configure the sniffing ability sniff_abl = self.get_dependency('capture', bpf=bpf_expr, interface=bridge_name) self._transfer_out(sniff_abl) sniff_abl.start() # Configure the sending ability, if a pipe is provided was_source = self._is_source() if not was_source: if self.mux is True: out1, in1 = multiprocessing.Pipe() send_raw_abl1 = self.get_dependency( 'sendraw', outerface=self.interface) send_raw_abl1.add_in_pipe(in1) send_raw_abl1.start() out2, in2 = multiprocessing.Pipe() send_raw_abl2 = self.get_dependency( 'sendraw', outerface=self.outerface) send_raw_abl2.add_in_pipe(in2) send_raw_abl2.start() demux_abl = self.get_dependency('demux') self._transfer_in(demux_abl) demux_abl.start(demux={ '\x00': out1, '\xFF': out2 }, quiet=self.quiet, deepcopy=False) else: send_raw_abl = self.get_dependency( 'sendraw', outerface=self.outerface) self._transfer_in(send_raw_abl) send_raw_abl.start() else: send_raw_abl = None # Finally adds the input NIC to the bridge, now that relevant # packets are dropped, to let through all irrelevant packets ns.bridge_iface_together(self.interface, bridge=bridge_name) # Wait for the stop event self._wait() # Stopping Ability sniff_abl.stop() sniff_abl.join() if not was_source: if self.mux is True: demux_abl.stop() send_raw_abl1.stop() send_raw_abl2.stop() demux_abl.join() send_raw_abl1.join() send_raw_abl2.join() else: send_raw_abl.stop() send_raw_abl.join() fw_abl.stop() fw_abl.join() ns.unbridge(bridge_name) else: # We are only acting on a single interface # Configure the sniffing ability sniff_abl = self.get_dependency('capture', bpf=bpf_expr, interface=self.interface) self._transfer_out(sniff_abl) sniff_abl.start() was_source = self._is_source() if not was_source: send_raw_abl = self.get_dependency('sendraw', outerface=self.interface) self._transfer_in(send_raw_abl) send_raw_abl.start() # Wait for the stop event self._wait() # Stopping Ability sniff_abl.stop() sniff_abl.join() if not was_source: send_raw_abl.stop() send_raw_abl.join()
class Ability(ns.ThreadedAbilityBase): _option_list = [ ns.PathOpt('fake_zone', must_exist=True, readable=True, is_dir=False), ns.PathOpt('policy_zone', must_exist=True, readable=True, is_dir=False), ns.IpOpt(ns.OptNames.IP_SRC, default=None, optional=True), ns.IpOpt(ns.OptNames.IP_DST, default=None, optional=True), ns.PortOpt(ns.OptNames.PORT_DST, optional=True, default=53), ns.NICOpt(ns.OptNames.INPUT_INTERFACE), ns.NICOpt(ns.OptNames.OUTPUT_INTERFACE, default=None, optional=True), ns.BoolOpt('quiet', default=True) ] _info = ns.AbilityInfo( name='DNSProxy', description='Replacement for DNSProxy', authors=['Florian Maury', ], tags=[ns.Tag.TCP_STACK_L5, ns.Tag.THREADED, ns.Tag.DNS], type=ns.AbilityType.STANDALONE ) _dependencies = [ 'mitm', ('dnsproxysrv', 'base', 'DNSProxy Server'), ('scapy_splitter', 'base', 'DNS Metadata Extractor'), ('scapy_unsplitter', 'base', 'DNS Metadata Reverser'), ] def main(self): dns_srv_abl = self.get_dependency( 'dnsproxysrv', fake_zone=self.fake_zone, policy_zone=self.policy_zone, quiet=self.quiet ) mitm_abl = self.get_dependency( 'mitm', interface=self.interface, outerface=self.outerface, ip_src=self.ip_src, ip_dst=self.ip_dst, port_dst=self.port_dst, protocol='udp', mux=True ) scapy_dns_metadata_splitter = self.get_dependency('scapy_splitter', quiet=self.quiet) scapy_dns_metadata_reverser = self.get_dependency('scapy_unsplitter', quiet=self.quiet) mitm_abl | scapy_dns_metadata_splitter | dns_srv_abl | scapy_dns_metadata_reverser | mitm_abl self._start_wait_and_stop( [dns_srv_abl, mitm_abl, scapy_dns_metadata_reverser, scapy_dns_metadata_splitter] ) def howto(self): print("""This DNS proxy intercepts DNS requests at OSI layer 2. For each intercepted request, this proxy can either fake an answer and send it to the requester or forward the request to the original recipient. Fake answers are authoritative, and they may contain DNS records for IPv4 or IPv6 addresses, denials of existence (nxdomain or empty answers), errors (SERVFAIL or truncated answer), NS records, MX records, and in fact whatever record you can think of. Special handling is done for denials of existence, NS records, MX records, and SRV records, to either synthesize a SOA record or add the corresponding glues whenever available. Whether to fake answers is instructed through a DNS master file that contains policies. Policies are formated as TXT records whose first word is the mnemonic of a record type (A, AAAA, NS, etc.) or the "ANY" keyword. ANY means all types of records. The second word is the policy decision. It can be one of the following: * PASSTHRU: the request is forwarded, unaltered, to the original destination. * NODATA: the request is answered with the indication that there is no such DNS record for this record type at the requested domain name. * NXDOMAIN: the request is answered with the indication that the requested domain name does not exist and that no records can be found at this name and under it. This policy only works only with the keyword "ANY". * SERVFAIL: the request is answered with the indication that the server is unable to provide a valid answer at the moment. This will generally force implementations to retry the request against another server, whenever possible. * TCP: the request is answered with an empty answer and the indication that the complete answer would truncated. This will force RFC-compliant implementation to retry the request over TCP. TCP is currently unsupported by this ability. * FAKE: the request is answered with fake data as specified in the fake zone, as described hereunder. The policy zone file must contain records whose owner name is fully qualified domain names. For instance, to fake a request for the IPv4 address of ssi.gouv.fr, one would write in the policy file: ssi.gouv.fr. IN TXT "A FAKE" The policy zone file can use wildcards to cover all domain names under some domain name. For instance, to let through all requests for all domain names under the fr TLD, one would write: *.fr IN TXT "ANY PASSTHRU" The wildcard matching is similar to that of the DNS. That means that if both previous policies are in the policy file, all requests for any records and names under the fr TLD would be let through, save for a request for the IPv4 of ssi.gouv.fr. If two policies are defined for a given name (be it an ANY policy and a record type-specific policy or two ANY policies or even two exact match policies), the first record to match is used. Thus, one can write a default policy using the wildcard expression "*.". For instance, to answer that there is no record for any NAPTR record, whatever the requested name is, and unless there is an explicit other policy to apply, one would write: *. IN TXT "NAPTR NODATA" If no policy can be found for a domain name and a record type, the request is dropped. If the received request cannot be parsed into a valid DNS message, the "packet" is let through. We think this is a reasonable behaviour, because it might not be at all a DNS request. The fake zone file is also a DNS master file containing all the records required to synthesize the fake answer, as instructed by the policy. For instance, according to the previously described policy for ssi.gouv.fr IPv4, one would have to write something among the likes of: ssi.gouv.fr. 3600 IN A 127.0.0.1 This would instruct this ability to answer "ssi.gouv.fr. A?" requests with a fake answer with a TTL of 1 hour, and an IPv4 address equal to 127.0.0.1. All domain names in the fake zone file must also be fully-qualified, and wildcards also apply, as described before. For example, the following files could be used: --- Policy file: --- *. IN TXT "NAPTR NODATA" *.fr. IN TXT "ANY PASSTHRU" ssi.gouv.fr. IN TXT "A FAKE" ssi.gouv.fr. IN TXT "AAAA FAKE" --- Fake zone file: --- ssi.gouv.fr. 3600 IN A 127.0.0.1 ssi.gouv.fr. 7200 IN AAAA 2001:db8::1 --- The IP parameters and the destination port serves to better target the requests for which to answer fake records. The input NIC is the network card connected to the victim. The output NIC is optional; it may be specified if the real DNS server is connected to a different card than the victim. """)
class Ability(ns.ThreadedAbilityBase): _option_list = [ ns.NICOpt(ns.OptNames.INPUT_INTERFACE, default=None, comment='Input interface', optional=True), ns.NICOpt(ns.OptNames.OUTPUT_INTERFACE, default=None, comment='Output interface', optional=True), ns.MacOpt(ns.OptNames.MAC_SRC, default=None, comment='Source Mac', optional=True), ns.MacOpt(ns.OptNames.MAC_DST, default=None, comment='Destination Mac', optional=True), ns.IpOpt(ns.OptNames.IP_SRC, default=None, comment='Source IP', optional=True), ns.IpOpt(ns.OptNames.IP_DST, default=None, comment='Destination IP', optional=True), ns.PortOpt(ns.OptNames.PORT_SRC, default=None, comment='Source Port', optional=True), ns.PortOpt(ns.OptNames.PORT_DST, default=None, comment='Destination Port', optional=True), ns.ChoiceOpt(ns.OptNames.L4PROTOCOL, ['tcp', 'udp'], comment='L4 Protocol over IP', optional=True), ] _info = ns.AbilityInfo( name='Netfilter Config', description='Configure Ebtables and IPtables rules to drop ' 'specified traffic', authors=['Florian Maury', ], tags=[ns.Tag.TCP_STACK_L2, ns.Tag.TCP_STACK_L3], type=ns.AbilityType.COMPONENT ) @classmethod def check_preconditions(cls, module_factory): l_dep = [] if not ns.HAS_IPTC and not ns.HAS_IPTABLES: l_dep.append( 'IPTC support missing or broken and IPtables CLI missing too. ' 'Please install python-iptables, install iptables or proceed ' 'to an update.') l_dep += super(Ability, cls).check_preconditions(module_factory) return l_dep def _configure_firewall_rules(self, iface, oface, mac_src, mac_dst, ip_src, ip_dst, proto, port_src, port_dst): """ Sets the firewall rules to drop traffic that is intercepted! :param mac_src: Source MAC address (may be None) :param mac_dst: Destination MAC address (may be None) :param ip_src: Source IP address (may be None) :param ip_dst: Destination IP address (may be None) :param proto: Protocol (either "udp" or "tcp" or None) :param port_src: Source Port (may be None) :param port_dst: Destination Port (may be None) :return: the BPF expression as a string """ if not isinstance(mac_src, type(None))\ or not isinstance(mac_dst, type(None)): ns.drop_frames(iface, oface, mac_src, mac_dst) if ( not isinstance(ip_src, type(None)) or not isinstance(ip_dst, type(None)) or not isinstance(proto, type(None)) or not isinstance(port_src, type(None)) or not isinstance(port_dst, type(None)) ): ns.drop_packets(iface, oface, ip_src, ip_dst, proto, port_src, port_dst, bridge=True) def _unconfigure_firewall_rules(self, iface, oface, mac_src, mac_dst, ip_src, ip_dst, proto, port_src, port_dst): """ Sets the firewall rules to drop traffic that is intercepted! :param mac_src: Source MAC address (may be None) :param mac_dst: Destination MAC address (may be None) :param ip_src: Source IP address (may be None) :param ip_dst: Destination IP address (may be None) :param proto: Protocol (either "udp" or "tcp" or None) :param port_src: Source Port (may be None) :param port_dst: Destination Port (may be None) :return: the BPF expression as a string """ if not isinstance(mac_src, type(None)) \ or not isinstance(mac_dst, type(None)): ns.undrop_frames(iface, oface, mac_src, mac_dst) if ( not isinstance(ip_src, type(None)) or not isinstance(ip_dst, type(None)) or not isinstance(proto, type(None)) or not isinstance(port_src, type(None)) or not isinstance(port_dst, type(None)) ): ns.undrop_packets(iface, oface, ip_src, ip_dst, proto, port_src, port_dst, bridge=True) def main(self): self._configure_firewall_rules( self.interface, self.outerface, self.mac_src, self.mac_dst, self.ip_src, self.ip_dst, self.protocol, self.port_src, self.port_dst, ) self._wait() self._unconfigure_firewall_rules( self.interface, self.outerface, self.mac_src, self.mac_dst, self.ip_src, self.ip_dst, self.protocol, self.port_src, self.port_dst, )