Ejemplo n.º 1
0
class Ability(ns.ThreadedAbilityBase):
    _info = ns.AbilityInfo(
        name='Hello from a thread',
        description='Display an hello message and wait to be stopped to exit',
    )

    _option_list = [
        ns.StrOpt('msg',
                  default='Hi there',
                  comment='hello message to display'),
        ns.NumOpt('sleep_time',
                  default=2,
                  comment='Time to wait before displaying the hello message')
    ]

    def main(self):
        time.sleep(self.sleep_time)
        self._view.info('{}!'.format(self.msg).capitalize())
        self._view.warning('Hit Ctrl+c to stop me')
        self._wait()
        self._view.info('Ctrl+c received, exiting…')
        return 'Done'

    def howto(self):
        self._view.delimiter('Hello')
        self._view.info("""
        Display an hello message passed in argument after a defined time.
        It will then hang until receiving a ctrl+c interrupt.
        """)
Ejemplo n.º 2
0
class Ability(ns.AbilityBase):
    _info = ns.AbilityInfo(
        name='Call another Ability',
        description='Demonstrate how to call synchronously another ability',
    )
    _option_list = [
        ns.ChoiceOpt('option', ['normal', 'bypass_cache'],
                     default='normal',
                     comment='Define if cache must be bypassed when using '
                     'generators (except "nb")'),
        ns.StrOpt('msg',
                  default='I was called by another ability',
                  comment='Message we want to see in our called ability'),
        ns.NumOpt('nb', default=3, comment='Times to display everything'),
    ]

    _dependencies = [('abl_demo_opt', 'base', 'Demo options')]

    def main(self, **kwargs):
        # Parameters of the called ability can be set in different way
        abl = self.get_dependency('abl_demo_opt',
                                  nb=self.nb,
                                  ip_dst='RandIP6',
                                  msg=self.msg)
        abl.port_dst = 42
        abl.option = self.option
        abl.set_opt("path", "/bin/true")
        abl.start()
        self._view.info(abl.result())
Ejemplo n.º 3
0
class Ability(ns.ThreadedAbilityBase):
    _option_list = [
        ns.PathOpt(ns.OptNames.PATH_DST,
                   None,
                   'File to write the pcap to',
                   must_exist=False)
    ]

    _info = ns.AbilityInfo(name='Save to Pcap',
                           description='Save received Ether() frames to PCAP',
                           authors=[
                               'Florian Maury',
                           ],
                           tags=[ns.Tag.TCP_STACK_L1],
                           type=ns.AbilityType.COMPONENT)

    def main(self):
        if self.path_dst is None:
            self._view.error('Missing filename')
            return

        pcapwr = scapy.utils.PcapWriter(self.path_dst)

        try:
            while not self.is_stopped():
                if self._poll(0.1):
                    s = self._recv()
                    if s:
                        p = scapy.layers.l2.Ether(s)
                        pcapwr.write(p)
        except (IOError, EOFError):
            pass

        pcapwr.close()
Ejemplo n.º 4
0
class Ability(ns.ThreadedAbilityBase):
    _option_list = [
        ns.StrOpt('prefix', default='', comment='Prefix to the echo reply'),
        ns.StrOpt('client_info',
                  default='',
                  comment='Information about the client connected '
                          'to the socket')
    ]

    _info = ns.AbilityInfo(
        name='Echo Server',
        description='echoes back the received messages with a prefix and '
                    'the information about the connected client',
        authors=['Florian Maury', ],
        tags=[ns.Tag.EXAMPLE],
        type=ns.AbilityType.COMPONENT
    )

    def __init__(self, *args, **kwargs):
        super(Ability, self).__init__(*args, **kwargs)
        self._stop_evt = threading.Event()

    def stop(self):
        ns.ThreadedAbilityBase.stop(self)
        self._stop_evt.set()

    def main(self):
        while not self._stop_evt.is_set():
            try:
                if self._poll(0.1):
                    s = self._recv()
                    self._send(self.prefix.format(self.client_info) + s)
            except (IOError, EOFError):
                self._stop_evt.set()
Ejemplo n.º 5
0
class Ability(ns.AbilityBase):
    _info = ns.AbilityInfo(
        name='Displaying text',
        description='Demonstrate the output capabilities',
        tags=[ns.Tag.EXAMPLE],
    )

    _option_list = [
        ns.IpOpt(ns.OptNames.IP_DST, default='127.0.0.1', comment='an IP'),
    ]

    def main(self):
        self._view.success('Display in green')
        self._view.delimiter('A dashed line with title')  # with a fixed len
        self._view.delimiter()  # a dashed line with the same length
        self._view.warning('Display in yellow')
        self._view.error('A red IP: {}'.format(self.ip_dst))
        self._view.fail('Display in cyan')
        self._view.progress('Display in blue')
        self._view.debug('Display in purple')
        self._view.success('Display in your default terminal color')
        self._view.info('{}Display in bold{}'.format(
            self._view.start_effect('bold'), self._view.end_color()))
        self._view.info('{}Underline our text{}'.format(
            self._view.start_effect('underline'), self._view.end_color()))
        # To mix a color and an effect requires to memorize the previous
        # modification to apply it again after every call of endcolor()
        self._view.warning('Mixing a color and {} in the middle'.format(
            self._view.with_effect('underline', 'an effect',
                                   text.Log.colors['warning'])))
Ejemplo n.º 6
0
class Ability(ns.ThreadedAbilityBase):
    _option_list = []

    _info = ns.AbilityInfo(
        name='Demux',
        description='Demultiplex a series of datagrammes, based on a prefix',
        authors=[
            'Florian Maury',
        ],
        tags=[ns.Tag.EXAMPLE],
        type=ns.AbilityType.COMPONENT)

    def main(self, demux, quiet=True):
        if len([prefix for prefix in demux.keys() if len(prefix) != 1]) > 0:
            self._view.error('Prefixes must be one char long')

        try:
            while not self.is_stopped():
                if self._poll(0.1):
                    s = self._recv()
                    if s[0] in demux:
                        demux[s[0]].send(s[1:])
                    elif not quiet:
                        self._view.warning('Invalid prefix: {0:1s}'.format(
                            s[0]))
        except (IOError, EOFError):
            pass
Ejemplo n.º 7
0
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()
Ejemplo n.º 8
0
class Ability(ns.AbilityBase):

    _info = ns.AbilityInfo(
        name='TCP sync server',
        description='use the tcp_client ability to simply '
                    'send data over tcp in a synchronous way',
    )

    _option_list = [
        ns.PortOpt(ns.OptNames.PORT_DST,
                   default=2222,
                   comment='listening port on the TCP server'),
        ns.IpOpt(ns.OptNames.IP_DST,
                 default='127.0.0.1',
                 comment='server IP address'),
        ns.StrOpt('msg',
                  default='Hello from PacketWeaver\n',
                  comment='Message to send over TCP')
    ]

    _dependencies = ['tcpclnt']

    def main(self):
        inst = self.get_dependency('tcpclnt',
                                   protocol='IPv4',
                                   ip_dst=self.ip_dst,
                                   port_dst=self.port_dst)

        to_tcp, out_pipe = multiprocessing.Pipe()
        out_pipe_2, from_tcp = multiprocessing.Pipe()
        inst.add_in_pipe(out_pipe)
        inst.add_out_pipe(out_pipe_2)

        self._view.debug('Starting tcpclnt')
        inst.start()

        self._view.debug('Send msg')
        to_tcp.send(self.msg)
        r = from_tcp.recv()
        self._view.info('{}'.format(r))

        self._view.debug('Stop tcpclnt')
        inst.stop()
        inst.join()
        self._view.success('All done')

    def howto(self):
        self._view.delimiter('TCP synchronous server')
        self._view.info("""
        A simple ability that connect to a TCP server, send
        a string and await for a response.

        It can easily be tested against a netcat emulated server:
        1. the command "nc -lp 2222" will listen to the port 2222
        2. running the ability in another terminal will write your
            message to the netcat output
        3. writing a short text in the netcat view will send back
            a message, terminating the ability
        """)
Ejemplo n.º 9
0
class Ability(ns.ThreadedAbilityBase):
    _option_list = [
        ns.BoolOpt('quiet',
                   default=True,
                   comment='Whether we should log stuff on error'),
    ]

    _info = ns.AbilityInfo(
        name='DNS Metadata Extractor',
        description=
        'Reads a Ether frame containing DNS and writes len(Ether to UDP/TCP) + Ether to UDP/TCP + DNS',
        authors=[
            'Florian Maury',
        ],
        tags=[ns.Tag.TCP_STACK_L5, ns.Tag.THREADED, ns.Tag.DNS],
        type=ns.AbilityType.COMPONENT)

    _dependencies = []

    @classmethod
    def check_preconditions(cls, module_factory):
        l = []
        if not HAS_SCAPY:
            l.append(
                'Scapy support missing or broken. Please install scapy or proceed to an update.'
            )
        l += super(Ability, cls).check_preconditions(module_factory)
        return l

    def main(self):
        try:
            while not self.is_stopped():
                if self._poll(0.1):
                    s = self._recv()
                    try:
                        m = l2.Ether(s)
                        data = str(m[scapy_dns.DNS])
                        if m.haslayer(scapy_inet.UDP):
                            m[scapy_inet.UDP].remove_payload()
                        else:
                            m[scapy_inet.UDP].remove_payload()
                        metadata = str(m)
                        self._send('{}{}{}'.format(
                            struct.pack('!H', len(metadata)), metadata, data))
                    except Exception as e:
                        if not self.quiet:
                            self._view.error('Unparsable frame. Dropping: ' +
                                             str(e))
                            print s
        except (IOError, EOFError):
            pass
Ejemplo n.º 10
0
class Ability(ns.ThreadedAbilityBase):
    _info = ns.AbilityInfo(
        name='Display piped string',
        type=ns.AbilityType.COMPONENT,
    )

    def main(self, **kwargs):
        # always check if we are asked to stop
        while not self.is_stopped():
            try:
                # setup semi-active polling of piped inputs
                if self._poll(0.1):
                    self._view.info("display> {}".format(self._recv()))
            except (IOError, EOFError):
                break
Ejemplo n.º 11
0
class Ability(ns.ThreadedAbilityBase):
    _info = ns.AbilityInfo(
        name='Debug Packets',
        description='Prints received packets',
        authors=['Florian Maury', ],
        tags=[ns.Tag.EXAMPLE],
        type=ns.AbilityType.COMPONENT
    )

    def main(self):
        while not self.is_stopped():
            try:
                if self._poll(0.1):
                    print(self._recv().encode('hex'))
            except (IOError, EOFError):
                break
Ejemplo n.º 12
0
class Ability(ns.AbilityBase):
    _info = ns.AbilityInfo(name='Ping a prefix', )

    _option_list = [
        ns.PrefixOpt('prefix', '127.0.0.1/24', 'Ping Destination Prefix'),
    ]

    _dependencies = [('ping', 'sstic', 'Ping a target')]

    def main(self, **kwargs):
        try:
            while True:
                target_ip = self.get_opt('prefix', bypass_cache=True)
                self.get_dependency('ping', ip_dst=target_ip).start()
        except StopIteration:
            pass
Ejemplo n.º 13
0
class Ability(ns.AbilityBase):
    _info = ns.AbilityInfo(name='Ping a target', )

    _option_list = [
        ns.IpOpt('ip_dst', default='8.8.8.8', comment='Ping Destination IP'),
    ]

    def main(self):
        cmd = '/bin/ping6' if self.ip_dst.find(':') != -1 else '/bin/ping'
        rc = subprocess.call([cmd, '-c 1', '-w 1', self.ip_dst],
                             stdout=os.open('/dev/null', os.O_WRONLY),
                             stderr=os.open('/dev/null', os.O_WRONLY))

        if rc == 0:
            self._view.success('{} is UP'.format(self.ip_dst))
        else:
            self._view.warning('{} is DOWN'.format(self.ip_dst))
Ejemplo n.º 14
0
class Ability(ns.ThreadedAbilityBase):
    _option_list = [
        ns.PathOpt(ns.OptNames.PATH_DST,
                   default=None,
                   comment='File to write the pcap to',
                   must_exist=False,
                   optional=True)
    ]

    _info = ns.AbilityInfo(name='Save to Pcap',
                           description='Save received Ether() frames to PCAP',
                           authors=[
                               'Florian Maury',
                           ],
                           tags=[ns.Tag.TCP_STACK_L1],
                           type=ns.AbilityType.COMPONENT)

    def main(self):
        if self.path_dst is None:
            self._view.error('Missing filename')
            return

        pcapwr = scapy.utils.PcapWriter(self.path_dst)

        try:
            while not self.is_stopped():
                if self._poll(0.1):
                    s = self._recv()
                    if s:
                        p = scapy.layers.l2.Ether(s)
                        pcapwr.write(p)
        except (IOError, EOFError):
            pass

        pcapwr.close()

    @classmethod
    def check_preconditions(cls, module_factory):
        l_dep = []
        if not HAS_SCAPY:
            l_dep.append('Scapy support missing or broken. '
                         'Please install it or proceed to an update.')
        l_dep += super(Ability, cls).check_preconditions(module_factory)
        return l_dep
Ejemplo n.º 15
0
class Ability(ns.ThreadedAbilityBase):
    _info = ns.AbilityInfo(name='Chain abilities', )
    _dependencies = [('abl_invert', 'base', 'Invert piped string'),
                     ('abl_display', 'base', 'Display piped string')]
    _option_list = [
        ns.StrOpt('msg', default='default', comment='Message to invert')
    ]

    def main(self, **kwargs):
        abl_inv = self.get_dependency('abl_invert')
        abl_disp = self.get_dependency('abl_display')

        # build communication channels
        to_abl, p_out = Pipe()
        abl_inv.add_in_pipe(p_out)
        abl_inv | abl_disp

        # run our controlled abilities
        self._view.debug('[main abl] Starting our two abilities')
        abl_inv.start()
        abl_disp.start()

        # ask to operate our message
        to_abl.send(self.msg)
        self._view.debug('[main abl] Sending "{}" to be inverted'.format(
            self.msg))

        # close all
        self._view.debug('[main abl] Hit ctrl+c to stop'.format(self.msg))
        self._wait()
        abl_inv.stop()
        abl_disp.stop()
        abl_inv.join()
        abl_disp.join()
        self._view.debug("[main abl] End")

    def howto(self):
        self._view.delimiter('Chain ability howto')
        self._view.info("""
        This ability invert and display its string input value. It is done
        using two abilities, one that invert the string, and another that
        display it.
        """)
Ejemplo n.º 16
0
class Ability(ns.AbilityBase):
    _info = ns.AbilityInfo(
        name='Detect leaks',
        description='Just a quick demo that when a module is missing, '
        'you can use this ability.')

    _option_list = []

    @classmethod
    def check_preconditions(cls, module_factory):
        l_dep = []
        if not HAS_UNKNOWN:
            l_dep.append('Numpy support missing or broken. '
                         'Please install Numpy or proceed to an update.')
        l_dep += super(Ability, cls).check_preconditions(module_factory)
        return l_dep

    def main(self):
        pass
Ejemplo n.º 17
0
class Ability(ns.ThreadedAbilityBase):
    _option_list = [
        ns.PathOpt(ns.OptNames.PATH_SRC,
                   None,
                   'Pcap file from which the packets are read',
                   must_exist=True,
                   readable=True)
    ]

    _info = ns.AbilityInfo(name='Read from Pcap',
                           description='Read frames from PCAP',
                           authors=[
                               'Florian Maury',
                           ],
                           tags=[ns.Tag.TCP_STACK_L1],
                           type=ns.AbilityType.COMPONENT)

    def main(self):
        if self.path_src is None:
            self._view.error('Missing filename')
            return

        pcaprd = scapy.utils.PcapReader(self.path_src)

        p = True
        while p:
            p = pcaprd.read_packet(size=65535)
            if p:
                try:
                    self._send(str(p))
                except (IOError, EOFError):
                    break
        pcaprd.close()

    @classmethod
    def check_preconditions(cls, module_factory):
        l = []
        if not HAS_SCAPY:
            l.append('Scapy support missing or broken.')
        l += super(Ability, cls).check_preconditions(module_factory)
        return l
Ejemplo n.º 18
0
class Ability(ns.ThreadedAbilityBase):
    _info = ns.AbilityInfo(
        name='Demo info',
        description='Demonstrate all available info metadata',
        tags=['my_custom_tag', ns.Tag.EXAMPLE],
        authors=['John Doe ([email protected])',
                 'Bob Eau ([email protected])'],
        references=[
            ['PacketWeaver online documentation',
             'https://packetweaver.readthedocs.io/'],
            ['PacketWeaver source code',
             'https://github.com/ANSSI-FR/packetweaver']
                    ],
        diffusion='public',
        reliability=ns.Reliability.RELIABLE,
        type=ns.AbilityType.STANDALONE
    )

    def main(self):
        self._view.info('Demonstrates the info metadata '
                        '(run > info to display them).')
Ejemplo n.º 19
0
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()
Ejemplo n.º 20
0
class Ability(ns.ThreadedAbilityBase):
    _info = ns.AbilityInfo(
        name='Invert piped string',
        type=ns.AbilityType.COMPONENT,
    )

    def main(self, **kwargs):
        # always check if we are asked to stop
        while not self.is_stopped():
            try:
                # setup semi-active polling of piped inputs
                if self._poll(0.1):
                    s = self._recv()
                    if s == s[::-1]:
                        self._view.warning('passing the palindrome "{}" '
                                           'to be displayed'.format(s))
                    else:
                        self._view.info(
                            'passing "{}" to be displayed'.format(s))
                    # forward to the following piped abilities
                    self._send(s[::-1])
            except (IOError, EOFError):
                break
Ejemplo n.º 21
0
class Ability(ns.ThreadedAbilityBase):
    _option_list = [
        ns.StrOpt('cacert_file', '/etc/ssl/certs/ca-certificates.crt',
                  'Path of a file containing the list of trusted CAs'),
        ns.StrOpt('alpn',
                  None,
                  'Application-Layer Protocol Negotiation value (as a CSV)',
                  optional=True),
        ns.StrOpt(
            'cipher_suites',
            ':'.join([  # List from ANSSI TLS guide v.1.1 p.51
                'ECDHE-ECDSA-AES256-GCM-SHA384', 'ECDHE-RSA-AES256-GCM-SHA384',
                'ECDHE-ECDSA-AES128-GCM-SHA256', 'ECDHE-RSA-AES128-GCM-SHA256',
                'ECDHE-ECDSA-AES256-SHA384', 'ECDHE-RSA-AES256-SHA384',
                'ECDHE-ECDSA-AES128-SHA256', 'ECDHE-RSA-AES128-SHA256',
                'ECDHE-ECDSA-CAMELLIA256-SHA384',
                'ECDHE-RSA-CAMELLIA256-SHA384',
                'ECDHE-ECDSA-CAMELLIA128-SHA256',
                'ECDHE-RSA-CAMELLIA128-SHA256', 'DHE-RSA-AES256-GCM-SHA384',
                'DHE-RSA-AES128-GCM-SHA256', 'DHE-RSA-AES256-SHA256',
                'DHE-RSA-AES128-SHA256', 'AES256-GCM-SHA384',
                'AES128-GCM-SHA256', 'AES256-SHA256', 'AES128-SHA256',
                'CAMELLIA128-SHA256'
            ]),
            'Proposed Ordered Cipher Suite List'),
        ns.BoolOpt('compress', False, 'Should TLS compression be used?'),
        ns.ChoiceOpt(
            'version',
            ['SSLv3', 'TLSv1', 'TLSv1.1', 'TLSv1.2'],
            default='TLSv1.2',
            comment='SSL/TLS protocol version',
        ),
        ns.StrOpt('cert_file', None, 'Client Certificate',
                  optional=True),  # To be set only in case of mutual authn
        ns.StrOpt('key_file', None, 'Client Private Key',
                  optional=True),  # To be set only in case of mutual authn
        ns.ChoiceOpt('protocol', ['IPv4', 'IPv6'], comment='IPv4 or IPv6'),
        ns.IpOpt(ns.OptNames.IP_SRC, None, 'Local (Source) IP', optional=True),
        ns.IpOpt(ns.OptNames.IP_DST, '127.0.0.1', 'Remote (Destination) IP'),
        ns.StrOpt('hostname', None, 'Remote Name (dnsName)', optional=True),
        ns.PortOpt(ns.OptNames.PORT_SRC, 0,
                   'Local (Source) Port (0 = Random Port)'),
        ns.PortOpt(ns.OptNames.PORT_DST, 0, 'Remote (Destination) Port'),
        ns.OptionTemplateEntry(lambda x: 0 <= x <= 10,
                               ns.NumOpt('timeout', 5, 'Connect Timeout'))
    ]

    _info = ns.AbilityInfo(
        name='TLS Client',
        description='Connects then sends and receives TLS records',
        authors=['Florian Maury'],
        tags=[ns.Tag.TCP_STACK_L4],
        type=ns.AbilityType.COMPONENT)

    def __init__(self, *args, **kwargs):
        super(Ability, self).__init__(*args, **kwargs)
        self._stop_evt = threading.Event()

    def stop(self):
        super(Ability, self).stop()
        self._stop_evt.set()

    def _serve(self, ssl_sock):
        to_read = [ssl_sock] + self._builtin_in_pipes
        to_write = [ssl_sock] + self._builtin_out_pipes
        ready_to_read = []
        ready_to_write = []

        while not self._stop_evt.is_set():
            # Waiting for sockets to be ready
            readable, writable, errored = select.select(
                to_read, to_write, [], 0.1)
            # Adding the sockets that are ready to the list of the already ready sockets
            ready_to_write += writable
            to_write = [x for x in to_write if x not in ready_to_write]
            ready_to_read += readable
            to_read = [x for x in to_read if x not in ready_to_read]

            if len(ready_to_read) > 0:
                # For each socket that is ready to be read
                for s in ready_to_read:
                    if s is ssl_sock and all([
                            out in ready_to_write
                            for out in self._builtin_out_pipes
                    ]):
                        try:
                            msg = ssl_sock.recv(65535)
                            if len(msg) == 0:
                                raise EOFError
                            self._send(msg)
                            to_read.append(ssl_sock)
                            to_write += self._builtin_out_pipes
                        except:
                            self.stop()
                        finally:
                            ready_to_read.pop(ready_to_read.index(s))
                            for out in self._builtin_out_pipes:
                                ready_to_write.pop(ready_to_write.index(out))
                    elif s in self._builtin_in_pipes and ssl_sock in ready_to_write:
                        try:
                            ssl_sock.send(self._recv())
                            to_read += self._builtin_in_pipes
                            to_write.append(ssl_sock)
                        except:
                            self.stop()
                        finally:
                            for p in self._builtin_in_pipes:
                                ready_to_read.pop(ready_to_read.index(p))
                            ready_to_write.pop(ready_to_write.index(ssl_sock))

    def main(self):
        # Check Python version
        py_ver = sys.version_info
        if (py_ver.major < 2 or (py_ver.major == 2 and
                                 (py_ver.minor < 7 or
                                  (py_ver.minor >= 7 and py_ver.micro < 10)))):
            raise Exception(
                'Your version of Python and Python-ssl are too old. Please upgrade to more "current" versions'
            )

        if self._is_sink() or self._is_source():
            raise Exception(
                'This ability must be connected through pipes to other abilities!'
            )

        # Set up SSL/TLS context
        tls_version_table = {
            'SSLv3': ssl.PROTOCOL_SSLv23,
            'TLSv1': ssl.PROTOCOL_TLSv1,
            'TLSv1.1': ssl.PROTOCOL_TLSv1_1,
            'TLSv1.2': ssl.PROTOCOL_TLSv1_2,
        }

        tls_version = tls_version_table[self.version]

        ctx = ssl.SSLContext(tls_version)
        if not isinstance(self.alpn, type(None)):
            ctx.set_alpn_protocols(','.join(self.alpn))
        ctx.set_ciphers(self.cipher_suites)
        ctx.load_verify_locations(cafile=self.cacert_file)

        if isinstance(self.key_file, type(None)) ^ isinstance(
                self.cert_file, type(None)):
            raise Exception(
                'Both key_file and cert_file must be set or none of them.')
        if not isinstance(self.key_file, type(None)):
            ctx.load_cert_chain(self.cert_file, self.key_file)

        if self.protocol == 'IPv4':
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        else:
            s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)

        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)

        if isinstance(self.hostname, type(None)):
            ssl_sock = ctx.wrap_socket(s)
        else:
            ssl_sock = ctx.wrap_socket(s, server_hostname=self.hostname)

        ssl_sock.bind(
            ('' if isinstance(self.ip_src, type(None)) else self.ip_src,
             self.port_src))
        ssl_sock.connect((self.ip_dst, self.port_dst))

        self._serve(ssl_sock)

        try:
            s = ssl_sock.unwrap()
            s.shutdown(socket.SHUT_RDWR)
        except:
            pass
        finally:
            s.close()
Ejemplo n.º 22
0
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,
        )
Ejemplo n.º 23
0
class Ability(ns.ThreadedAbilityBase):
    _option_list = [
        ns.BoolOpt('quiet',
                   default=True,
                   comment='Whether we should log stuff on error'),
    ]

    _info = ns.AbilityInfo(
        name='DNS Metadata Reverser',
        description='Reads a DNS message + metadata + demux token '
        'and writes demux token + Ether/DNS',
        authors=[
            'Florian Maury',
        ],
        tags=[ns.Tag.TCP_STACK_L5, ns.Tag.THREADED, ns.Tag.DNS],
        type=ns.AbilityType.COMPONENT)

    _dependencies = []

    @classmethod
    def check_preconditions(cls, module_factory):
        l_dep = []
        if not HAS_SCAPY:
            l_dep.append('Scapy support missing or broken. '
                         'Please install scapy or proceed to an update.')
        l_dep += super(Ability, cls).check_preconditions(module_factory)
        return l_dep

    def _forge_scapy_response(self, scapy_msg):
        new_msg = l2.Ether(src=scapy_msg[l2.Ether].dst,
                           dst=scapy_msg[l2.Ether].src)
        if scapy_msg.haslayer(scapy_inet.IP):
            new_msg /= scapy_inet.IP(src=scapy_msg[scapy_inet.IP].dst,
                                     dst=scapy_msg[scapy_inet.IP].src)
        else:
            new_msg /= scapy_inet.IPv6(src=scapy_msg[scapy_inet6.IPv6].dst,
                                       dst=scapy_msg[scapy_inet6.IPv6].src)

        new_msg /= scapy_inet.UDP(sport=scapy_msg[scapy_inet.UDP].dport,
                                  dport=scapy_msg[scapy_inet.UDP].sport)

        return new_msg

    def main(self):
        try:
            while not self.is_stopped():
                if self._poll(0.1):
                    s = self._recv()
                    try:
                        demux_tok, metadata_len = struct.unpack('!cH', s[:3])
                        metadata = s[3:metadata_len + 3]
                        data = s[metadata_len + 3:]

                        parsed_metadata = l2.Ether(metadata)
                        parsed_data = scapy_dns.DNS(data)

                        if demux_tok == '\x00':
                            forged_metadata = self._forge_scapy_response(
                                parsed_metadata)
                            m = forged_metadata / parsed_data
                            self._send(demux_tok + str(m))
                        elif demux_tok == '\xFF':
                            if parsed_metadata.haslayer(scapy_inet.IP):
                                del parsed_metadata[scapy_inet.IP].chksum
                            else:
                                del parsed_metadata[scapy_inet6.IPv6].chksum
                            del parsed_metadata[scapy_inet.UDP].chksum
                            m = parsed_metadata / parsed_data
                            self._send(demux_tok + str(m))
                        else:
                            if not self.quiet:
                                self._view.error('Invalid demux token: {:x}.'
                                                 ' Dropping.'.format(
                                                     ord(demux_tok)))
                    except Exception as e:
                        if not self.quiet:
                            self._view.error('Unparsable frame. Dropping: ' +
                                             str(e))
                            print(s)
        except (IOError, EOFError):
            pass
Ejemplo n.º 24
0
class Ability(ns.ThreadedAbilityBase):
    _option_list = [
        ns.ChoiceOpt('protocol', ['IPv4', 'IPv6'], comment='IPv4 or IPv6'),
        ns.IpOpt(ns.OptNames.IP_DST, '127.0.0.1', 'Binding IP'),
        ns.PortOpt(ns.OptNames.PORT_DST, 0, 'Binding Port'),
        ns.NumOpt('backlog_size', 10, 'Backlog size provided to listen()'),
        ns.NumOpt('timeout', 30, 'Timeout for sockets'),
        ns.CallbackOpt(
            ns.OptNames.CALLBACK,
            comment='Callback returning an ability to handle a new connection'
        ),
        ns.StrOpt(
            'client_info_name',
            None,
            'Name of the service ability option that will contain the information about the client that is at the other end of the TCP connection',
            optional=True)
    ]

    _info = ns.AbilityInfo(
        name='TCP Server',
        description=
        'Binds to a port, accept connections and starts new abilities to handle them',
        authors=[
            'Florian Maury',
        ],
        tags=[ns.Tag.TCP_STACK_L4],
        type=ns.AbilityType.COMPONENT)

    def __init__(self, *args, **kwargs):
        super(Ability, self).__init__(*args, **kwargs)
        self._stop_evt = threading.Event()

    def stop(self):
        super(Ability, self).stop()
        self._stop_evt.set()

    def _accept_new_connection(self, s):
        # accepting the connection
        clt_sock, clt_info = s.accept()

        # Getting the service ability
        new_abl = self.callback()

        # Giving to the service ability the information about the client
        if not isinstance(self.client_info_name, type(None)):
            new_abl.set_opt(self.client_info_name,
                            '{}:{}'.format(clt_info[0], clt_info[1]))

        # Creating the pipes
        in_pipe_in, in_pipe_out = multiprocessing.Pipe()
        out_pipe_in, out_pipe_out = multiprocessing.Pipe()
        new_abl.add_in_pipe(in_pipe_out)
        new_abl.add_out_pipe(out_pipe_in)

        # Starting the service ability
        new_abl.start()

        return clt_sock, in_pipe_in, out_pipe_out, new_abl

    def _serve(self, server_sock):
        to_read = [server_sock]
        to_write = []
        ready_to_read = []
        ready_to_write = []
        service_abilities = []

        while not self._stop_evt.is_set():
            # Waiting for sockets to be ready
            readable, writable, errored = select.select(
                to_read, to_write, [], 0.1)
            # Adding the sockets that are ready to the list of the already ready sockets
            ready_to_write += writable
            to_write = [x for x in to_write if x not in ready_to_write]
            ready_to_read += readable
            to_read = [x for x in to_read if x not in ready_to_read]

            if len(ready_to_read) > 0:
                # For each socket that is ready to be read
                for s in ready_to_read:
                    if s is server_sock:
                        # This socket is the server_sock (the one we can run accept upon)
                        new_sock, new_in_pipe, new_out_pipe, new_abl = self._accept_new_connection(
                            s)
                        to_read.append(new_sock)
                        to_read.append(new_out_pipe)
                        to_read.append(s)
                        to_write.append(new_sock)
                        to_write.append(new_in_pipe)
                        service_abilities.append(
                            (new_abl, new_sock, new_in_pipe, new_out_pipe))
                        ready_to_read.pop(ready_to_read.index(s))
                    else:
                        # The socket is one of the socket connected to a client

                        # StopIteration should not happen because we know that the element must be present
                        # We also know that there should be only one answer so calling on next is efficient
                        # Finally, we use a generator expression because it is more efficient (only generates up to the
                        # first matching occurrence. A list expression would have iterated over the whole list
                        abl, sock, in_pipe, out_pipe = next(
                            (srv for srv in service_abilities
                             if s is srv[1] or s is srv[3]))
                        if s is sock and in_pipe in ready_to_write:
                            try:
                                in_pipe.send(s.recv(65535))
                                to_read.append(s)
                                to_write.append(in_pipe)
                            except:
                                abl.stop()
                                sock.close()
                                in_pipe.close()
                                out_pipe.close()
                                abl.join()
                            finally:
                                ready_to_write.pop(
                                    ready_to_write.index(in_pipe))
                                ready_to_read.pop(ready_to_read.index(sock))

                        elif s is out_pipe and sock in ready_to_write:
                            try:
                                sock.send(out_pipe.recv())
                                to_read.append(out_pipe)
                                to_write.append(sock)
                            except:
                                abl.stop()
                                sock.close()
                                in_pipe.close()
                                out_pipe.close()
                                abl.join()
                            finally:
                                ready_to_write.pop(ready_to_write.index(sock))
                                ready_to_read.pop(
                                    ready_to_read.index(out_pipe))

        for abl, sock, in_pipe, out_pipe in service_abilities:
            abl.stop()
            sock.close()
            in_pipe.close()
            out_pipe.close()
            abl.join()

    def main(self):
        if self.protocol == 'IPv4':
            server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        else:
            server_sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)

        server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)

        server_sock.bind(
            ('' if isinstance(self.ip_dst, type(None)) else self.ip_dst,
             self.port_dst))

        server_sock.listen(self.backlog_size)
        server_sock.settimeout(self.timeout)

        self._serve(server_sock)

        server_sock.close()
Ejemplo n.º 25
0
class Ability(ns.ThreadedAbilityBase):
    _option_list = [
        ns.StrOpt(
            'cacert_file', '/etc/ssl/certs/ca-certificates.crt',
            'Path of a file containing the list of trusted CAs', optional=True
        ),
        ns.StrOpt('alpn', None, 'Application-Layer Protocol Negotiation value (as a CSV)', optional=True),
        ns.StrOpt('cipher_suites', ':'.join([  # List from ANSSI TLS guide v.1.1 p.51
            'ECDHE-ECDSA-AES256-GCM-SHA384',
            'ECDHE-RSA-AES256-GCM-SHA384',
            'ECDHE-ECDSA-AES128-GCM-SHA256',
            'ECDHE-RSA-AES128-GCM-SHA256',
            'ECDHE-ECDSA-AES256-SHA384',
            'ECDHE-RSA-AES256-SHA384',
            'ECDHE-ECDSA-AES128-SHA256',
            'ECDHE-RSA-AES128-SHA256',
            'ECDHE-ECDSA-CAMELLIA256-SHA384',
            'ECDHE-RSA-CAMELLIA256-SHA384',
            'ECDHE-ECDSA-CAMELLIA128-SHA256',
            'ECDHE-RSA-CAMELLIA128-SHA256',
            'DHE-RSA-AES256-GCM-SHA384',
            'DHE-RSA-AES128-GCM-SHA256',
            'DHE-RSA-AES256-SHA256',
            'DHE-RSA-AES128-SHA256',
            'AES256-GCM-SHA384',
            'AES128-GCM-SHA256',
            'AES256-SHA256',
            'AES128-SHA256',
            'CAMELLIA128-SHA256'
        ]), 'Proposed Ordered Cipher Suite List'),
        ns.BoolOpt('compress', False, 'Should TLS compression be used?'),
        ns.ChoiceOpt(
            'version', ['SSLv3', 'TLSv1', 'TLSv1.1', 'TLSv1.2'], default='TLSv1.2', comment='SSL/TLS protocol version',
        ),
        ns.StrOpt('cert_file', '/etc/ssl/certs/ssl-cert-snakeoil.pem', 'Server Certificate'),
        ns.StrOpt('key_file', '/etc/ssl/private/ssl-cert-snakeoil.key', 'Server Private Key'),
        ns.ChoiceOpt('protocol', ['IPv4', 'IPv6'], comment='IPv4 or IPv6'),
        ns.IpOpt(ns.OptNames.IP_DST, '127.0.0.1', 'Binding IP'),
        ns.PortOpt(ns.OptNames.PORT_DST, 0, 'Binding Port'),
        ns.NumOpt('backlog_size', 10, 'Backlog size provided to listen()'),
        ns.NumOpt('timeout', 30, 'Timeout for sockets'),
        ns.CallbackOpt(ns.OptNames.CALLBACK, comment='Callback returning a service ability to handle a new connection'),
        ns.StrOpt('client_info_name', 'client_info',
            'Name of the service ability option that will contain the information about the client that is at the other end of the TCP connection'
        )
    ]

    _info = ns.AbilityInfo(
        name='TLS Server',
        description='Binds to a port, accept TLS connections and starts new abilities to handle them',
        authors=['Florian Maury',],
        tags=[ns.Tag.TCP_STACK_L4],
        type=ns.AbilityType.COMPONENT
    )

    def __init__(self, *args, **kwargs):
        super(Ability, self).__init__(*args, **kwargs)
        self._stop_evt = threading.Event()

    def stop(self):
        super(Ability, self).stop()
        self._stop_evt.set()

    def _accept_new_connection(self, s):
        # accepting the connection
        clt_sock, clt_info = s.accept()

        # Getting the service ability
        new_abl = self.callback()

        # Giving to the service ability the informations about the client
        new_abl.set_opt(self.client_info_name, '{}:{}'.format(clt_info[0], clt_info[1]))

        # Creating the pipes
        in_pipe_in, in_pipe_out = multiprocessing.Pipe()
        out_pipe_in, out_pipe_out = multiprocessing.Pipe()
        new_abl.add_in_pipe(in_pipe_out)
        new_abl.add_out_pipe(out_pipe_in)

        # Starting the service ability
        new_abl.start()

        return clt_sock, in_pipe_in, out_pipe_out, new_abl

    def _serve(self, server_sock):
        to_read = [server_sock]
        to_write = []
        ready_to_read = []
        ready_to_write = []
        service_abilities = []

        while not self._stop_evt.is_set():
            # Waiting for sockets to be ready
            readable, writable, errored = select.select(to_read, to_write, [], 0.1)
            # Adding the sockets that are ready to the list of the already ready sockets
            ready_to_write += writable
            to_write = [x for x in to_write if x not in ready_to_write]
            ready_to_read += readable
            to_read = [x for x in to_read if x not in ready_to_read]

            if len(ready_to_read) > 0:
                # For each socket that is ready to be read
                for s in ready_to_read:
                    if s is server_sock:
                        # This socket is the server_sock (the one we can run accept upon)
                        new_sock, new_in_pipe, new_out_pipe, new_abl = self._accept_new_connection(s)
                        to_read.append(new_sock)
                        to_read.append(new_out_pipe)
                        to_read.append(s)
                        to_write.append(new_sock)
                        to_write.append(new_in_pipe)
                        service_abilities.append((new_abl, new_sock, new_in_pipe, new_out_pipe))
                        ready_to_read.pop(ready_to_read.index(s))
                    else:
                        # The socket is one of the socket connected to a client

                        # StopIteration should not happen because we know that the element must be present
                        # We also know that there should be only one answer so calling on next is efficient
                        # Finally, we use a generator expression because it is more efficient (only generates up to the
                        # first matching occurrence. A list expression would have iterated over the whole list
                        abl, sock, in_pipe, out_pipe = next(
                            (srv for srv in service_abilities if s is srv[1] or s is srv[3])
                        )
                        if s is sock and in_pipe in ready_to_write:
                            try:
                                in_pipe.send(s.recv(65535))
                                to_read.append(s)
                                to_write.append(in_pipe)
                            except:
                                abl.stop()
                                sock.close()
                                in_pipe.close()
                                out_pipe.close()
                                abl.join()
                            finally:
                                ready_to_write.pop(ready_to_write.index(in_pipe))
                                ready_to_read.pop(ready_to_read.index(sock))

                        elif s is out_pipe and sock in ready_to_write:
                            try:
                                sock.send(out_pipe.recv())
                                to_read.append(out_pipe)
                                to_write.append(sock)
                            except:
                                abl.stop()
                                sock.close()
                                in_pipe.close()
                                out_pipe.close()
                                abl.join()
                            finally:
                                ready_to_write.pop(ready_to_write.index(sock))
                                ready_to_read.pop(ready_to_read.index(out_pipe))

        for abl, sock, in_pipe, out_pipe in service_abilities:
            abl.stop()
            sock.close()
            in_pipe.close()
            out_pipe.close()
            abl.join()

    def main(self):
        # Check Python version
        py_ver = sys.version_info
        if (
            py_ver.major < 2
            or (
                py_ver.major == 2
                and (
                    py_ver.minor < 7
                    or (py_ver.minor >= 7 and py_ver.micro < 10)
                )
            )
        ):
            raise Exception('Your version of Python and Python-ssl are too old. Please upgrade to more "current" versions')

        # Set up SSL/TLS context
        tls_version_table = {
            'SSLv3': ssl.PROTOCOL_SSLv23,
            'TLSv1': ssl.PROTOCOL_TLSv1,
            'TLSv1.1': ssl.PROTOCOL_TLSv1_1,
            'TLSv1.2': ssl.PROTOCOL_TLSv1_2,
        }

        tls_version = tls_version_table[self.version]

        ctx = ssl.SSLContext(tls_version)
        if not isinstance(self.alpn, type(None)):
            ctx.set_alpn_protocols(','.join(self.alpn))
        ctx.set_ciphers(self.cipher_suites)
        if not isinstance(self.cacert_file, type(None)):
            ctx.load_verify_locations(cafile=self.cacert_file)

        ctx.load_cert_chain(self.cert_file, self.key_file)

        if self.protocol == 'IPv4':
            server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        else:
            server_sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)

        server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)

        ssl_sock = ctx.wrap_socket(server_sock, server_side=True)

        ssl_sock.bind(('' if isinstance(self.ip_dst, type(None)) else self.ip_dst, self.port_dst))

        ssl_sock.listen(self.backlog_size)
        ssl_sock.settimeout(self.timeout)

        self._serve(ssl_sock)

        try:
            server_sock = ssl_sock.unwrap()
            server_sock.shutdown(socket.SHUT_RDWR)
        except:
            pass
        finally:
            server_sock.close()
Ejemplo n.º 26
0
class Ability(ns.ThreadedAbilityBase):
    _option_list = [
        ns.PathOpt(
            'fake_zone',
            must_exist=True,
            readable=True,
            is_dir=False,
            comment=
            "Zone file containing the names for which a lie must be returned."
        ),
        ns.PathOpt(
            'policy_zone',
            must_exist=True,
            readable=True,
            is_dir=False,
            optional=True,
            comment=
            "Zone file containing the policy to apply to incoming requests. If no match is found, there is an implicit FAKE policy"
        ),
        ns.BoolOpt(
            'resolver',
            default=False,
            comment=
            'Whether we are spoofing a resolver (or an authoritative server'),
        ns.BoolOpt(
            'authentic',
            default=False,
            comment=
            'If resolver is true, our answers are flagged as authentic, unless checking is disabled'
        ),
        ns.BoolOpt('quiet',
                   default=True,
                   comment='Whether we should log stuff on error'),
    ]

    _info = ns.AbilityInfo(
        name='DNSProxy Server',
        description='DNSProxy "server", which answers lies',
        authors=[
            'Florian Maury',
        ],
        tags=[ns.Tag.TCP_STACK_L5, ns.Tag.THREADED, ns.Tag.DNS],
        type=ns.AbilityType.COMPONENT)

    _dependencies = []

    FAKE_POLICY = 0
    SERVFAIL_POLICY = 1
    NXDOMAIN_POLICY = 2
    NODATA_POLICY = 3
    TCP_POLICY = 4
    PASSTHRU_POLICY = 5

    DECISION_DICT = {
        NODATA_POLICY:
        lambda self, *args, **kwargs: self._send_nodata(*args, **kwargs),
        NXDOMAIN_POLICY:
        lambda self, *args, **kwargs: self._send_nxdomain(*args, **kwargs),
        SERVFAIL_POLICY:
        lambda self, *args, **kwargs: self._send_servfail(*args, **kwargs),
        TCP_POLICY:
        lambda self, *args, **kwargs: self._send_truncated(*args, **kwargs),
        PASSTHRU_POLICY:
        lambda self, *args, **kwargs: self._send_passthru(*args, **kwargs),
        FAKE_POLICY:
        lambda self, *args, **kwargs: self._send_fake(*args, **kwargs),
    }

    @classmethod
    def check_preconditions(cls, module_factory):
        l = []
        if not HAS_DNSPYTHON:
            l.append(
                'DNSPython support missing or broken. Please install dnspython or proceed to an update.'
            )
        l += super(Ability, cls).check_preconditions(module_factory)
        return l

    def _parse_zones(self):
        try:
            pz = zone_parser.from_file(self.policy_zone,
                                       origin='.',
                                       relativize=False,
                                       check_origin=False)
        except:
            if not self.quiet:
                self._view.error('Invalid policy zone file')
            return None, None

        try:
            fz = zone_parser.from_file(self.fake_zone,
                                       origin='.',
                                       relativize=False,
                                       check_origin=False)
        except:
            if not self.quiet:
                self._view.error('Invalid fake zone file')
            return None, None
        return fz, pz

    def _find_zone_match(self, zone, name, rdtype):
        try:
            while name != dns.name.root:
                found_rrset = None
                try:
                    found_rrset = zone.find_rrset(name, rdtype)
                except KeyError:
                    pass
                if found_rrset is None:
                    try:
                        found_rrset = zone.find_rrset(name,
                                                      dns.rdatatype.CNAME)
                    except KeyError:
                        pass
                if found_rrset is None:
                    try:
                        found_rrset = zone.find_rrset(name,
                                                      dns.rdatatype.DNAME)
                    except KeyError:
                        pass
                if found_rrset is not None:
                    return found_rrset
                if name.labels[0] == '*':
                    name = name.parent()
                name = dns.name.Name(['*'] + list(name.parent().labels))
        except dns.name.NoParent:
            pass
        raise KeyError('No matching record for {} {}'.format(name, rdtype))

    def _set_flags(self, dns_msg):
        dns_msg.flags |= dns.flags.QR
        if self.resolver:
            if dns_msg.flags & dns.flags.RD != 0:
                dns_msg.flags |= dns.flags.RA

            # If flag AD and authentic are both set, then nothing happens, and this is all good
            if self.authentic:
                if dns_msg.ednsflags & dns.flags.DO != 0 and dns_msg.flags & dns.flags.CD == 0:
                    dns_msg.flags |= dns.flags.AD
            else:
                dns_msg.flags &= (2**11) - 1 - dns.flags.AD  # Revert AD byte

        else:
            dns_msg.flags |= dns.flags.AA

    def _fake_answer(self, fz, metadata, dns_msg, fake_rrset):
        qdrrset = dns_msg.question[0]
        self._set_flags(dns_msg)

        if qdrrset.rdtype in [
                dns.rdatatype.NS, dns.rdatatype.MX, dns.rdatatype.SRV
        ]:
            if qdrrset.rdtype in [dns.rdatatype.NS, dns.rdatatype.SRV]:
                names = [rr.target for rr in fake_rrset.items]
            elif qdrrset.rdtype == dns.rdatatype.MX:
                names = [rr.exchange for rr in fake_rrset.items]

            addr_rrset = []
            for name in names:
                try:
                    addr_rrset.append(
                        self._find_zone_match(fz, name, dns.rdatatype.A))
                except KeyError:
                    pass
                try:
                    addr_rrset.append(
                        self._find_zone_match(fz, name, dns.rdatatype.AAAA))
                except KeyError:
                    pass
            dns_msg.additional = addr_rrset

        dns_msg.answer.append(fake_rrset)
        self._send('\x00{}{}{}'.format(struct.pack('!H', len(metadata)),
                                       metadata, dns_msg.to_wire()))

    def _send_empty_response(self, metadata, parsed, rcode):
        parsed.set_rcode(rcode)
        parsed.answer = []
        parsed.authority = []
        parsed.additional = []
        self._send('\x00{}{}{}'.format(struct.pack('!H', len(metadata)),
                                       metadata, str(parsed.to_wire())))

    def _send_negative_answer(self, fz, metadata, parsed, rcode):
        self._set_flags(parsed)
        name = parsed.question[0].name
        forged_soa = dns.rrset.from_text(
            name, 3600 * 3, dns.rdataclass.IN, dns.rdatatype.from_text('SOA'),
            'ns1.{} hostmaster.{} 1 {} {} {} {}'.format(
                name.to_text(), name.to_text(), 3 * 3600, 3600, 86400 * 7,
                3 * 3600))
        parsed.set_rcode(rcode)
        parsed.answer = []
        parsed.authority = [forged_soa]
        parsed.additional = []
        self._send('\x00{}{}{}'.format(struct.pack('!H', len(metadata)),
                                       metadata, str(parsed.to_wire())))

    def _send_nodata(self, fz, metadata=None, parsed=None):
        if parsed is None or metadata is None:
            if not self.quiet:
                self._view.error('Parsed packet unavailable. Dropping')
            return
        self._send_negative_answer(fz, metadata, parsed, 0)

    def _send_nxdomain(self, fz, metadata=None, parsed=None):
        if parsed is None or metadata is None:
            if not self.quiet:
                self._view.error('Parsed packet unavailable. Dropping')
            return
        self._send_negative_answer(fz, metadata, parsed, 3)

    def _send_servfail(self, fz, metadata=None, parsed=None):
        if parsed is None or metadata is None:
            if not self.quiet:
                self._view.error('Parsed packet unavailable. Dropping')
            return

        self._set_flags(parsed)
        self._send_empty_response(metadata, parsed, 2)

    def _send_truncated(self, fz, metadata=None, parsed=None):
        if parsed is None or metadata is None:
            if not self.quiet:
                self._view.error('Parsed DNS message unavailable. Dropping')
            return

        self._set_flags(parsed)
        parsed.flags |= dns.flags.TC
        self._send_empty_response(metadata, parsed, 0)

    def _send_passthru(self, fz, metadata=None, raw=None, parsed=None):
        if (raw is None and parsed is None) or metadata is None:
            if not self.quiet:
                self._view.error('Raw DNS message unavailable. Dropping')

        self._send('\xFF{}{}{}'.format(
            struct.pack('!H', len(metadata)), metadata,
            raw if raw is not None else str(parsed.to_wire())))

    def _send_fake(self, fz, metadata=None, parsed=None):
        if parsed is None or metadata is None:
            if not self.quiet:
                self._view.error('Parsed DNS message unavailable. Dropping')
            return

        try:
            rrset = self._find_zone_match(fz, parsed.question[0].name,
                                          parsed.question[0].rdtype)
            if rrset.name.to_text().startswith('*'):
                rrset.name = parsed.question[0].name
            self._fake_answer(fz, metadata, parsed, rrset)
        except KeyError:
            if not self.quiet:
                self._view.error(
                    'Fake policy but not matching fake record. Dropping')
            return

    def _find_policy(self, pz, rrset):
        try:
            policy_rrset = self._find_zone_match(pz, rrset.name,
                                                 dns.rdatatype.TXT)
            verdict = None
            for item in policy_rrset.items:
                for string in item.strings:
                    rrtype, policy = string.split(' ')
                    if rrtype == 'ANY' or dns.rdatatype.from_text(
                            rrtype) == rrset.rdtype:
                        if policy == 'NXDOMAIN':
                            if rrtype == 'ANY':
                                verdict = self.NXDOMAIN_POLICY
                            elif verdict is None:
                                verdict = self.NODATA_POLICY
                        elif policy == 'NODATA':
                            verdict = self.NODATA_POLICY
                        elif policy == 'SERVFAIL':
                            verdict = self.SERVFAIL_POLICY
                        elif policy == 'PASSTHRU':
                            verdict = self.PASSTHRU_POLICY
                        elif policy == 'TCP':
                            verdict = self.TCP_POLICY
                        else:
                            # Policy == FAKE or whatever else
                            verdict = self.FAKE_POLICY
                    if verdict is not None:
                        return verdict
            return None
        except Exception as e:
            print e
            return None

    def _handle_query(self, metadata, data, fz, pz):
        try:
            dns_msg = message_parser.from_wire(data)
        except (message_parser.ShortHeader, message_parser.TrailingJunk,
                dns.name.BadLabelType):
            self._view.error(
                'Error while parsing DNS message. Pass Thru policy applied.')
            self.DECISION_DICT[self.PASSTHRU_POLICY](self,
                                                     fz,
                                                     metadata=metadata,
                                                     raw=data)
            return

        # Figuring out which policy to apply
        verdict = self._find_policy(pz, dns_msg.question[0])
        if verdict is None:
            self._view.error('Could not determine a verdict. Dropping.')
            return
        self.DECISION_DICT[verdict](self,
                                    fz,
                                    metadata=metadata,
                                    parsed=dns_msg)

    def main(self):
        fz, pz = self._parse_zones()
        if fz is None or pz is None:
            return

        try:
            while not self.is_stopped():
                if self._poll(0.05):
                    s = self._recv()
                    metadata_len, = struct.unpack('!H', s[:2])
                    metadata = s[2:metadata_len + 2]
                    data = s[metadata_len + 2:]
                    self._handle_query(metadata, data, fz, pz)
        except (IOError, EOFError):
            pass
Ejemplo n.º 27
0
class Ability(ns.ThreadedAbilityBase):
    _option_list = [
        ns.ChoiceOpt('protocol', ['IPv4', 'IPv6'], comment='IPv4 or IPv6'),
        ns.IpOpt(ns.OptNames.IP_SRC, None, 'Local (Source) IP', optional=True),
        ns.IpOpt(ns.OptNames.IP_DST, '127.0.0.1', 'Remote (Destination) IP'),
        ns.PortOpt(ns.OptNames.PORT_SRC, 0,
                   'Local (Source) Port (0 = Random Port)'),
        ns.PortOpt(ns.OptNames.PORT_DST, 0, 'Remote (Destination) Port'),
        ns.OptionTemplateEntry(lambda x: 0 <= x <= 10,
                               ns.NumOpt('timeout', 5, 'Connect Timeout'))
    ]

    _info = ns.AbilityInfo(name='TCP Client',
                           description='Sends and receives segments',
                           authors=[
                               'Florian Maury',
                           ],
                           tags=[ns.Tag.TCP_STACK_L4],
                           type=ns.AbilityType.COMPONENT)

    @staticmethod
    def _forward_outgoing(sock, stop_evt, poller, receiver):
        while not stop_evt.is_set():
            if poller(0.1):
                try:
                    s = receiver()
                except EOFError:
                    break
                sock.send(str(s))

    @staticmethod
    def _forward_incoming(sock, stop_evt, sender, stopper):
        # Timeout is set back to 0.1 second for polling purposes
        sock.settimeout(0.1)
        while not stop_evt.is_set():
            try:
                s = sock.recv(65535)
            except socket.timeout:
                continue
            if len(s) == 0:
                # Socket is closed!
                break
            sender(s)
        stopper()

    def main(self):
        if self._is_sink() or self._is_source():
            raise Exception(
                'This ability must be connected through pipes to other abilities!'
            )

        if self.protocol == 'IPv4':
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        else:
            s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)

        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)

        s.bind(('' if isinstance(self.ip_src, type(None)) else self.ip_src,
                self.port_src))
        s.settimeout(self.timeout)
        s.connect((self.ip_dst, self.port_dst))

        stop_evt = threading.Event()

        out_thr = threading.Thread(target=self._forward_outgoing,
                                   args=(s, stop_evt, self._poll, self._recv))
        out_thr.start()

        in_thr = threading.Thread(target=self._forward_incoming,
                                  args=(s, stop_evt, self._send, self.stop))
        in_thr.start()

        self._wait()

        stop_evt.set()

        out_thr.join()
        in_thr.join()
        s.close()
Ejemplo n.º 28
0
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.
""")
Ejemplo n.º 29
0
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()
Ejemplo n.º 30
0
class Ability(ns.AbilityBase):
    _info = ns.AbilityInfo(
        name='Demo options',
        description='Demonstrate all available options',
        tags=[ns.Tag.EXAMPLE],
    )

    _option_list = [
        ns.ChoiceOpt('option', ['normal', 'bypass_cache'],
                     default='normal',
                     comment='Define if cache must be bypassed '
                     'when using generators (except "nb")'),
        ns.NumOpt('nb', default=3, comment='Times to display everything'),
        ns.IpOpt(ns.OptNames.IP_DST,
                 default='127.0.0.1',
                 comment='use as default the standardized dst_ip option name'),
        ns.StrOpt('msg', default='my message', comment='A string message'),
        ns.PortOpt(ns.OptNames.PORT_DST,
                   default=2222,
                   comment='A string message'),
        ns.MacOpt(ns.OptNames.MAC_SRC,
                  default='Mac00',
                  comment='Source MAC address'),
        ns.BoolOpt('a_bool', default=True, comment='A True/False value'),
        ns.PathOpt('path',
                   default='pw.ini',
                   comment='Path to an existing file')  # must_exist=True),
    ]

    def display(self):
        for i in range(self.nb):
            self._view.delimiter('Round {}'.format(i + 1))
            self._view.info('[{}] - {} - {}'.format(self.mac_src, self.ip_dst,
                                                    self.port_dst))
            self._view.progress('{}'.format(self.msg))
            self._view.debug('{}'.format(self.a_bool))
            self._view.warning('{} (abs: {})'.format(
                self.path, os.path.abspath(self.path)))
            self._view.delimiter()
            self._view.info('')

    def display_bypass_cache(self):
        for i in range(self.nb):
            self._view.delimiter('Round {}'.format(i + 1))
            self._view.info('[{}] - {} - {}'.format(
                self.get_opt('mac_src', bypass_cache=True),
                self.get_opt('ip_dst', bypass_cache=True),
                self.get_opt('port_dst', bypass_cache=True),
            ))
            self._view.progress('{}'.format(
                self.get_opt('msg', bypass_cache=True)))
            self._view.debug('{}'.format(
                self.get_opt('a_bool', bypass_cache=True)))
            self._view.warning('{} (abs: {})'.format(
                self.get_opt('path', bypass_cache=True),
                os.path.abspath(self.get_opt('path', bypass_cache=True))))
            self._view.delimiter()
            self._view.info('')

    def main(self):
        if self.nb <= 0:
            self._view.error(
                'The number must be greater than 0 ({} given)'.format(self.nb))
            return
        elif self.nb > 2000:
            self._view.warning('{} rounds is quite a lot! '
                               'Please try with a lower number.'.format(
                                   self.nb))
            return

        if self.option == 'normal':
            self.display()
        elif self.option == 'bypass_cache':
            self.display_bypass_cache()

        self._view.success('Done!')
        return 'Done'

    def howto(self):
        self._view.delimiter('Module option demonstration')
        self._view.info("""
        This ability make use of all the PacketWeaver framework supported
        options.

        Their names are either specified using a label, or a predefined value
        using a OptNames.VAL . The latter solution is preferred as it helps
        getting a clean input interface across different abilities.

        You may play with the different options, modifying their value with
        either:
        - a fixed value
        - a fixed value randomly drawn (e.g RandIP4() for the dst_ip)
        - a random generator (e.g RandIP4)

        The ability will display their value three times so you can see how
        they behave.
        """)