def mac_sniffer(st=10): IFACE = "mon0" SNIFF_TIME = st t = AsyncSniffer(iface=IFACE, prn=store_packets) t.start() waiter(SNIFF_TIME) t.stop()
def start_scapy_scan(self) -> List[bool]: """ This function starts AsyncSniffer and scapy scan, finally stops AsyncSniffer. """ no_error = False _targets = self._targets info("Start AsyncSniffer using Scapy...") sniffer = AsyncSniffer( iface=self.iface, lfilter=lambda p: ((ARP in p and p.psrc in _targets) or (IP in p and p.src in _targets)), prn=self.scapy_match, ) sniffer.start() try: results = self.scapy_scan() except Exception as e: error = e else: no_error = True finally: info("Stop AsyncSniffer, scan end.") sniffer.stop() if no_error: return results else: raise error
def collect_devices(iface, src_mac, timeout=5) -> List[EthernetDevice]: global PING_RESULT PING_RESULT = {} devices = [] filter_exp = 'ether dst host {0} and ether[16:2] == 0x01cc'.format( src_mac) command_line = message_helper.build_eth_command( dest_mac="ff:ff:ff:ff:ff:ff", src_mac=src_mac, message_type=PING_PKT, message_bytes=[] ) async_sniffer = AsyncSniffer( iface=iface, prn=handle_ping_receive_packet, filter=filter_exp ) async_sniffer.start() time.sleep(.1) sendp(command_line, iface=iface, verbose=0, count=1) time.sleep(timeout) async_sniffer.stop() for key in PING_RESULT.keys(): devices.append(EthernetDevice(key)) return devices
class Sniffer: """Sniff incoming packets for SYN/ACK responses.""" def __init__(self, scan_filter, timeout): """Initialize Sniffer.""" self.packets = list() self.sniffer = AsyncSniffer( filter=scan_filter, prn=self.packets.append, ) self.timeout = timeout def start(self): """Start sniffing.""" self.sniffer.start() def stop(self): """Stop sniffing.""" time.sleep(self.timeout) self.sniffer.stop() def open_ports(self): """Return open ports.""" live_ports = set() for packet in self.packets: if packet["TCP"].flags == "SA": live_ports.add(packet.sport) return sorted(live_ports)
def run(self): a = AsyncSniffer(iface=self.iface, prn=self.callback) a.start() c = ProgressBar() while self.do_run: c.update(len(self.found)) sleep(1) # be nice to the cpu a.stop()
def init_test_sniffer(): """ For testing """ # packets = AsyncSniffer(monitor=True, count=0) packets = AsyncSniffer(count=0) packets.start() input("Press enter to stop sniffing: ") packets.stop() packets.join()
def campaign1(s, HOST, TCP_PORT, UDP_PORT, client_ip): conn, addr = s.accept() conn_ip = addr[0] if (conn_ip != client_ip): return if (conn): try: sniffer = AsyncSniffer(iface='lo', prn=printPacket, filter='dst port {}'.format(UDP_PORT)) sniffer.start() time.sleep(1) #Initialization check: check = conn.recv(255) print("[-] {}".format(check)) response = 'yes' conn.sendall(response) time.sleep(1) #UDP packet: request = conn.recv(255) print("[-] {}".format(request)) #Start UDP server try: sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind((HOST, UDP_PORT)) print('[+] UDP server started') response = 'yes' except: response = 'no' raise try: conn.sendall(response) data, address = sock.recvfrom(1024) time.sleep(1) print('[*] Received: {}'.format(data)) result = 'Success, report is: \n{}'.format(info) sock.sendto(result, address) sniffer.stop() except: result = 'Failed, report is: \n{}'.format(info) sock.sendto(result, address) sniffer.stop() except: print('[+] Error connecting to client') s.shutdown(socket.SHUT_RDWR) s.close() raise s.shutdown(socket.SHUT_RDWR) s.close()
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 test_scapy_sniffer(self): send_data = [ Ether(src="99:54:8f:91:12:f6", dst="44:35:a2:a6:d0:bd") / IPv6(src="fe80::1", dst="fe80::2") / ICMPv6EchoRequest(), Ether(src="44:35:a2:a6:d0:bd", dst="99:54:8f:91:12:f6") / IPv6(src="fe80::2", dst="fe80::1") / ICMPv6EchoReply(), Ether(src="1f:db:ed:9c:26:6e", dst="18:39:3c:e8:1f:ad") / IPv6(src="2001:db8:8a5a:4020:f160:162:c83d:527d", dst="2001:db8:2932:29c9:35eb:9377:ae68:634d") / UDP(sport=31245, dport=8788) / b"abcdef", Ether(src="18:39:3c:e8:1f:ad", dst="1f:db:ed:9c:26:6e") / IPv6(src="2001:db8:2932:29c9:35eb:9377:ae68:634d", dst="2001:db8:8a5a:4020:f160:162:c83d:527d") / UDP(sport=8788, dport=31245) / b"12345", ] sniffer = AsyncSniffer() sniffer.start() self.spawn.expect(r"Initializing L2listen with arguments") self.spawn.expect(r"Bound socket to '(.*)'\s") remote = self.spawn.match.group(1) # wait a bit to avoid race where socket is not bound yet time.sleep(.5) for packet in send_data: time.sleep(.001) # some spacing for test stability self.assertEqual(len(raw(packet)), self.comm_sock.sendto(raw(packet), remote)) for packet in send_data: self.spawn.expect(r"Receiving on L2listen with arguments") results = sniffer.stop() self.assertIsNotNone(results) self.assertEqual(len(send_data), len(results)) for pkt in send_data: self.assertIn(Ether(raw(pkt)), results)
class DeviceSniffer: def __init__(self, user_querier: UserQuerier, interface="mon0"): self.async_sniffer = AsyncSniffer(prn=self.handle_packet, store=False, iface=interface, monitor=True) self.device_dectect_stream = Subject() self.user_querier = user_querier def __del__(self): self.device_dectect_stream.on_completed() @staticmethod def is_probe_request(packet: Packet): return packet.type == 0 and packet.subtype == 4 def handle_packet(self, packet: Packet): if not DeviceSniffer.is_probe_request(packet): return try: target_ssid = packet.getlayer(Dot11Elt).getfieldval("info").decode( "utf-8") if len(target_ssid) == 0: return source_mac_addr = packet.addr2.upper() userid = self.user_querier.get_userid(target_ssid, source_mac_addr) if userid is not None: self.device_dectect_stream.on_next(userid) except Exception as err: self.device_dectect_stream.on_error(err) def get_observable(self) -> Subject: return self.device_dectect_stream def start(self): self.async_sniffer.start() def stop(self): self.async_sniffer.stop()
def make_pcaps(): #don't think I'll need more but make an hour worth of #one minute pcap files for i in range(60): #if we need we can narrow the interface we use to sniff by changing #the args to AsyncSniffer() t = AsyncSniffer() t.start() time.sleep(60) z = t.stop() wrpcap(f"test_{i}.pcap", z)
class DashSensor(Sensor): """Uses an AsyncSniffer to watch for ARP packets generated by an Amazon Dash button when the button is pressed. """ def __init__(self, publishers, params): """Initializes and starts the background scanning for Dash Button ARP packets, publishing the MAC address to the destination when one is detected. """ super().__init__(publishers, params) self.log.info("Configuing Dash Scanner") self.devices = get_sequential_param_pairs(params, "MAC", "Destination") if self.poll > 0: raise ValueError("DashSensor is not a polling sensor!") self.sniffer = AsyncSniffer(prn=self.arp_received, filter="arp", store=0, count=0) self.sniffer.start() def arp_received(self, pkt): """Called by the sniffer when an ARP packet is received. If it's from a device that we have a configured MAC address, we publish the MAC address to the configured destination. """ # 1 = who-has, 2 = is-at if ARP in pkt and pkt[ARP].op in (1, 2): mac = pkt[ARP].hwsrc if mac in self.devices: self.log.info("Dash button pressed for %s publishing to %s", mac, self.devices[mac]) self._send(mac, self.devices[mac]) def cleanup(self): """Stops and waits for the sniffer to exit.""" self.sniffer.stop() self.sniffer.join()
def __StartCap(self): global basesettings # Used to count how many packets have been recieved throughout capture global pkt_count pkt_count = 1 # Determine if user specified a run time or a run period if basesettings['RunTime'] != '': # After calculating how long the program is specified to run for # the program starts an asynchronous sniffer that captures packets # in a seperate thread. dt_now, dt_run = self.__CalcRuntime() Asniff = AsyncSniffer(iface=basesettings['Interface'], prn=self.__DisplayPackets) Asniff.start() while dt_now <= dt_run: dt_now, str_now, date_now = self.__GetTimeNow() Asniff.stop() print(colored("\nScan has stopped!", 'green')) elif basesettings['RunPeriod'] != '': # After calculating how long the program is specified to run for # the program starts an asynchronous sniffer that captures packets # in a seperate thread. dt_now, dt_run = self.__CalcPeriod() Asniff = AsyncSniffer(iface=basesettings['Interface'], prn=self.__DisplayPackets) Asniff.start() while dt_now <= dt_run: dt_now, str_now, date_now = self.__GetTimeNow() Asniff.stop() print(colored("\nScan has stopped!", 'green'))
def send_and_exp_any_dhcp6(cls, send_pkt, client=True, ipv6_src=None): sniffer = AsyncSniffer(iface=cls.tap) sniffer.start() time.sleep(cls.pre_sniffer_wait) cls._sendp(send_pkt, client=client, ipv6_src=ipv6_src) time.sleep(cls.post_sniffer_wait) return [ pkt for pkt in sniffer.stop() if cls._contains_dhcp(pkt) and # filter out sent packet (UDP not in pkt or raw(pkt[UDP].payload) != raw(send_pkt)) and # filter out ICMPv6 since error notifications can also contain # the sent packets and we are not interested in error # notifications ICMPv6DestUnreach not in pkt ]
class Aggregator(): def __init__(self): self.value = 0 self.sniffer = None self.lock = Lock() def sniffer_callback(self, pkt): self.lock.acquire() self.value += len(pkt) self.lock.release() def sniffer_activate(self): self.sniffer = AsyncSniffer(prn=self.sniffer_callback) self.sniffer.start() def sniffer_deactivate(self): self.sniffer.stop() def flush(self): self.lock.acquire() res = self.value self.value = 0 self.lock.release() return res
def sniff_routine() -> PacketList: sniffer = AsyncSniffer(prn=lambda x: x.summary(), filter='ip') while True: start = input("Type start to start capturing\n") if start == "start": print("Starting to capture...") break sniffer.start() while True: stop = input("To stop capturing type stop\n") if stop == "stop": break sniff_result = sniffer.stop() wrpcap("output/output.cap", sniff_result) return sniff_result
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)", 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: str, state: DHCPState, dhcp_desc: DHCPDescriptor = 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: 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: 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") 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=int(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: str) -> 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: str): """ 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) 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 = "" if Dot1Q in packet: vlan = str(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_router_opt = self._get_option(packet, "router") if dhcp_router_opt is not None: router_ip_addr = ip_address(dhcp_router_opt) self.dhcp_gw_info.update_ip(router_ip_addr, vlan) else: router_ip_addr = None 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=packet[IP].src, 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 DHCPClient: THREAD_YIELD_TIME = .1 def __init__(self, dhcp_store: MutableMapping[str, DHCPDescriptor], gw_info: UplinkGatewayInfo, dhcp_wait: Condition, iface: str = "dhcp0"): """ 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)", 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 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) def stop(self): self._sniffer.stop() def send_dhcp_packet(self, mac: MacAddress, state: DHCPState, dhcp_desc: DHCPDescriptor = 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: """ rel_ciaddr = None # generate DHCP request packet if state == DHCPState.DISCOVER: dhcp_opts = [("message-type", "discover")] dhcp_desc = DHCPDescriptor(mac, "", DHCPState.DISCOVER) self._msg_xid = self._msg_xid + 1 pkt_xid = self._msg_xid elif state == DHCPState.REQUEST: dhcp_opts = [("message-type", "request"), ("requested_addr", dhcp_desc.ip), ("server_id", dhcp_desc.server_ip)] dhcp_desc.state = DHCPState.REQUEST pkt_xid = dhcp_desc.xid elif state == DHCPState.RELEASE: dhcp_opts = [("message-type", "release"), ("server_id", dhcp_desc.server_ip)] dhcp_desc.state = DHCPState.RELEASE self._msg_xid = self._msg_xid + 1 pkt_xid = self._msg_xid rel_ciaddr = dhcp_desc.ip else: LOG.warning("Unknown egress request mac %s state %s", str(mac), state) return dhcp_opts.append("end") with self._dhcp_notify: self.dhcp_client_state[mac.as_redis_key()] = dhcp_desc LOG.debug("SEND %s mac %s hex %s xid %s", state.name, str(mac), mac, self._msg_xid) pkt = Ether(src=str(mac), dst="ff:ff:ff:ff:ff:ff") 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=rel_ciaddr) pkt /= DHCP(options=dhcp_opts) LOG.debug("DHCP pkt %s", pkt.summary()) sendp(pkt, iface=self._dhcp_interface, verbose=0) def get_dhcp_desc(self, mac: MacAddress) -> Optional[DHCPDescriptor]: """ Get DHCP description for given MAC. Args: mac: Mac address of the client Returns: Current DHCP info. """ key = mac.as_redis_key() if key in self.dhcp_client_state: return self.dhcp_client_state[key] LOG.debug("lookup error for %s", str(mac)) return None def release_ip_address(self, mac: MacAddress): """ Release DHCP allocated IP. Args: mac: MAC address of the IP allocated. Returns: None """ if mac.as_redis_key() not in self.dhcp_client_state: LOG.error("Unallocated DHCP release for MAC: %s", str(mac)) return dhcp_desc = self.dhcp_client_state[mac.as_redis_key()] self.send_dhcp_packet(mac, DHCPState.RELEASE, dhcp_desc) def manage_dhcp_state(self): """ monitor DHCP client state. TODO: Handle IP address lease revoke. """ @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): mac_addr = create_mac_from_sid(packet[Ether].dst) mac_addr_key = mac_addr.as_redis_key() with self._dhcp_notify: if mac_addr_key in self.dhcp_client_state: 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 = None dhcp_router_opt = self._get_option(packet, "router") if dhcp_router_opt is not None: router_ip_addr = ip_address(dhcp_router_opt) else: router_ip_addr = None lease_time = self._get_option(packet, "lease_time") dhcp_state = DHCPDescriptor(mac_addr, ip_offered, state, str(ip_subnet), packet[IP].src, router_ip_addr, lease_time, packet[BOOTP].xid) LOG.info("Record mac %s IP %s", mac_addr_key, dhcp_state) self.dhcp_client_state[mac_addr_key] = dhcp_state self.dhcp_gw_info.update_ip(router_ip_addr) 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, 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 LOG.debug("DHCP type %s", packet[DHCP].options[0][1]) # Match DHCP offer if packet[DHCP].options[0][1] == int(DHCPState.OFFER): LOG.debug("Offer %s (%s) ", packet[IP].src, packet[Ether].src) self._process_dhcp_pkt(packet, DHCPState.OFFER) # Match DHCP ack elif packet[DHCP].options[0][1] == int(DHCPState.ACK): LOG.debug("Acked %s (%s) ", packet[IP].src, packet[Ether].src) self._process_dhcp_pkt(packet, DHCPState.ACK)
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()
class PacketReader: """ This class needs root privileges to run """ def __init__(self, interface: str): self.interface = interface self.store = [] self.sniffer = None self.last_index = 0 def _store(self, packet): logger.debug('read packet {}'.format(packet.summary())) self.store.append(packet) def sniff(self, count=0): """ start async sniffer """ logger.info("starting to sniff packets") # TODO filter all outcoming packets self.sniffer = AsyncSniffer(session=TCPSession, prn=self._store, iface=self.interface, count=count, store=False) self.sniffer.start() def get_packets(self) -> list: """ get new packets since last call of this function """ logger.debug("getting new packets with index at {}".format( self.last_index)) index = self.last_index self.last_index = len(self.store) logger.debug("new index is at {}".format(self.last_index)) return self.store[index:] def end(self): logger.info("stopping reader") if not self.sniffer or not self.sniffer.running: logger.error("sniffer not started") return self.sniffer.stop() def remove_old_packets(self): """ remove all already read packets by `get_packets` !! Warning - you will loose the references to those packets !! """ logger.debug("removing old packets with index at {}".format( self.last_index)) del self.store[:self.last_index] def started(self) -> bool: return True if self.sniffer else False def packet_q(self) -> int: """ how many packets is currently in store, not processed """ return len(self.store) - self.last_index
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 == 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 == 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 == 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 * 4) 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 == 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 * 4) 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)
else: sMynet=sys.argv[1] sInt=sys.argv[2] ## Show info iAddressCount = len([str(ip) for ip in ipaddress.IPv4Network(sMynet)]) print('[!] Scanning ' + sMynet + ' ('+str(iAddressCount)+' IP\'s)') ## Start sniffer in separate thread arrResponses = [] oSniffer = AsyncSniffer(prn=lambda x: arrResponses.append(x), store=False, filter="arp and arp[6:2] == 2") oSniffer.start() ## Send all packets sendp(Ether(dst='ff:ff:ff:ff:ff:ff')/ARP(pdst = sMynet), iface = sInt, verbose = False) print('[!] Waiting 2 seconds for the responses to find their way back') time.sleep(2) oSniffer.stop() ## Parsing arrLiveSystems = [] arrMacList = [] for arrReceived in arrResponses: sMac = arrReceived.hwsrc sIP = arrReceived.psrc if sMac not in arrMacList: print(arrReceived.summary().replace(' / Padding','')) arrMacList.append(sMac) arrLiveSystems.append((sMac,sIP)) if len(arrLiveSystems) == 0: print('[-] No systems found for network ' + sMynet) else: print('[+] Found ' + str(len(arrLiveSystems)) + ' IP address(es) on network ' + sMynet) if len(sys.argv) != 3: input('Press enter to close') '''
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)
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()