├── requirements.txt ├── .github └── FUNDING.yml ├── LICENSE ├── README.md └── netprobe.py /requirements.txt: -------------------------------------------------------------------------------- 1 | argparse==1.4.0 2 | rich==10.9.0 3 | scapy==2.4.5 4 | socket==0.1.1 5 | table==0.0.9 -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: HalilDeniz 4 | patreon: denizhalil 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Halil Ibrahim Deniz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NetProbe: Network Probe 2 | 3 | **NetProbe** is a tool you can use to scan for devices on your network. The program sends ARP requests to any IP address on your network and lists the IP addresses, MAC addresses, manufacturers, and device models of the responding devices. 4 | 5 |

6 |
7 | Buy Me A Coffee 8 |

9 | 10 | 11 | ## Features 12 | 13 | - Scan for devices on a specified IP address or subnet 14 | - Display the IP address, MAC address, manufacturer, and device model of discovered devices 15 | - Live tracking of devices (optional) 16 | - Save scan results to a file (optional) 17 | - Filter by manufacturer (e.g., 'Apple') (optional) 18 | - Filter by IP range (e.g., '192.168.1.0/24') (optional) 19 | - Scan rate in seconds (default: 5) (optional) 20 | 21 | 22 | ## Contact 23 | If you have any questions, suggestions, or feedback about the program, please feel free to reach out to me through any of the following platforms: 24 | 25 | 26 | 27 | - Mywebsite: [Denizhalil](https://denizhalil.com) 28 | - LinkedIn: [LinkedIn](https://www.linkedin.com/in/halil-ibrahim-deniz/) 29 | - TryHackMe: [TryHackMe](https://tryhackme.com/p/halilovic) 30 | - Instagram: [Instagram](https://www.instagram.com/deniz.halil333/) 31 | - YouTube: [YouTube](https://www.youtube.com/c/HalilDeniz) 32 | - Email: halildeniz313@gmail.com 33 | 34 | 35 | ## License 36 | 37 | This program is released under the MIT [LICENSE](LICENSE). See LICENSE for more information. 38 | 39 | ## 💰 You can help me by Donating 40 | Thank you for considering supporting me! Your support enables me to dedicate more time and effort to creating useful tools like DNSWatch and developing new projects. By contributing, you're not only helping me improve existing tools but also inspiring new ideas and innovations. Your support plays a vital role in the growth of this project and future endeavors. Together, let's continue building and learning. Thank you!" 41 | [![BuyMeACoffee](https://img.shields.io/badge/Buy%20Me%20a%20Coffee-ffdd00?style=for-the-badge&logo=buy-me-a-coffee&logoColor=black)](https://buymeacoffee.com/halildeniz) 42 | [![Patreon](https://img.shields.io/badge/Patreon-F96854?style=for-the-badge&logo=patreon&logoColor=white)](https://patreon.com/denizhalil) 43 | 44 | 45 | -------------------------------------------------------------------------------- /netprobe.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | from rich.console import Console 5 | from rich.table import Table 6 | from scapy.all import ARP, Ether, srp 7 | import requests 8 | import threading 9 | import time 10 | import ipaddress 11 | 12 | class NetProbe: 13 | def __init__(self): 14 | self.console = Console() 15 | 16 | def scan(self, target, iface): 17 | arp = ARP(pdst=target) 18 | ether = Ether(dst="ff:ff:ff:ff:ff:ff") 19 | packet = ether / arp 20 | try: 21 | result = srp(packet, timeout=3, verbose=0, iface=iface)[0] 22 | except PermissionError: 23 | self.console.print("[bold red]Error:[/] You do not have sufficient privileges. Try running the program with 'sudo'.") 24 | exit() 25 | except OSError as e: 26 | if "No such device" in str(e): 27 | self.console.print(f"[bold red]Error:[/] Interface '{iface}' does not exist. \nPlease provide a valid interface name.") 28 | exit() 29 | else: 30 | raise 31 | 32 | devices = [] 33 | for sent, received in result: 34 | mac_address = received.hwsrc 35 | manufacturer = self.get_device_info(mac_address) 36 | packet_size = len(sent) + len(received) 37 | devices.append({'ip': received.psrc, 'mac': mac_address, 'manufacturer': manufacturer, 'packet_size': packet_size}) 38 | 39 | return devices 40 | 41 | def get_device_info(self, mac_address): 42 | try: 43 | url = f"https://api.macvendors.com/{mac_address}" 44 | response = requests.get(url) 45 | if response.status_code == 200: 46 | manufacturer = response.text.strip() 47 | else: 48 | manufacturer = "Unknown" 49 | except requests.exceptions.RequestException: 50 | manufacturer = "Unknown" 51 | 52 | return manufacturer 53 | 54 | def apply_filters(self, devices, manufacturer_filter, ip_range_filter): 55 | filtered_devices = [] 56 | for device in devices: 57 | if (not manufacturer_filter or device['manufacturer'].lower() == manufacturer_filter.lower()) and (not ip_range_filter or self.ip_in_range(device['ip'], ip_range_filter)): 58 | filtered_devices.append(device) 59 | return filtered_devices 60 | 61 | def ip_in_range(self, ip, ip_range): 62 | try: 63 | ip_network = ip_range.split('/')[0] 64 | subnet_mask = int(ip_range.split('/')[1]) 65 | return ipaddress.ip_address(ip) in ipaddress.ip_network(ip_network, strict=False) 66 | except ValueError: 67 | return False 68 | 69 | def display_live_updates(self, targets, interfaces, output_file=None, manufacturer_filter=None, ip_range_filter=None, scan_rate=5): 70 | known_devices = [] 71 | while True: 72 | for target in targets: 73 | for iface in interfaces: 74 | devices = self.scan(target, iface) 75 | 76 | # Save newly discovered devices 77 | new_devices = [d for d in devices if d not in known_devices] 78 | if new_devices and output_file: 79 | self.save_results(new_devices, output_file) 80 | known_devices.extend(new_devices) 81 | 82 | filtered_devices = self.apply_filters(devices, manufacturer_filter, ip_range_filter) 83 | 84 | self.console.clear() 85 | table = Table(show_header=True, header_style="bold magenta") 86 | table.add_column("IP Address", style="bold red") 87 | table.add_column("MAC Address", style="bold blue") 88 | table.add_column("Packet Size", style="bold green") 89 | table.add_column("Manufacturer", style="bold yellow") 90 | 91 | for device in filtered_devices: 92 | table.add_row(device['ip'], device['mac'], str(device['packet_size']), device['manufacturer']) 93 | 94 | self.console.print(table) 95 | time.sleep(scan_rate) 96 | 97 | def save_results(self, devices, output_file): 98 | with open(output_file, 'a') as f: # 'a' for append mode 99 | for device in devices: 100 | f.write(f"IP Address: {device['ip']}\n") 101 | f.write(f"MAC Address: {device['mac']}\n") 102 | f.write(f"Manufacturer: {device['manufacturer']}\n") 103 | f.write(f"Packet Size: {device['packet_size']} bytes\n") 104 | f.write("\n") 105 | 106 | def main(self): 107 | try: 108 | args = self.get_arguments() 109 | targets = args.target 110 | interfaces = args.interface 111 | live_tracking = args.live 112 | output_file = args.output 113 | manufacturer_filter = args.manufacturer 114 | ip_range_filter = args.ip_range 115 | scan_rate = args.scan_rate 116 | 117 | devices = [] 118 | for target in targets: 119 | for iface in interfaces: 120 | devices.extend(self.scan(target, iface)) 121 | 122 | table = Table(show_header=True, header_style="bold magenta") 123 | table.add_column("IP Address", style="bold red") 124 | table.add_column("MAC Address", style="bold blue") 125 | table.add_column("Packet Size", style="bold green") 126 | table.add_column("Manufacturer", style="bold yellow") 127 | 128 | filtered_devices = self.apply_filters(devices, manufacturer_filter, ip_range_filter) 129 | 130 | for device in filtered_devices: 131 | table.add_row(device['ip'], device['mac'], str(device['packet_size']), device['manufacturer']) 132 | 133 | self.console.print(table) 134 | 135 | if output_file: 136 | self.save_results(filtered_devices, output_file) 137 | self.console.print(f"\n[bold green]Results saved to: {output_file}") 138 | 139 | if live_tracking: 140 | t = threading.Thread(target=self.display_live_updates, args=(targets, interfaces, output_file, manufacturer_filter, ip_range_filter, scan_rate)) 141 | t.daemon = True # This makes sure the thread will exit when the main program exits 142 | t.start() 143 | while t.is_alive(): # This loop will keep the main thread running while the display thread is running 144 | time.sleep(1) 145 | except KeyboardInterrupt: 146 | self.console.print("\n[bold red]Program interrupted by user. Exiting...") 147 | 148 | def get_arguments(self): 149 | parser = argparse.ArgumentParser(description='NetProbe: Network Scanner Tool') 150 | parser.add_argument("-t", "--target", metavar="", help="Target IP address or subnet (default: 192.168.1.0/24)", default="192.168.1.0/24", nargs='+', required=True) 151 | parser.add_argument("-i", "--interface", metavar="", help="Interface to use (default: None)", default=None, nargs='+', required=True) 152 | parser.add_argument("-l", "--live", action="store_true", help="Enable live tracking of devices") 153 | parser.add_argument("-o", "--output", metavar="", help="Output file to save the results") 154 | parser.add_argument("-m", "--manufacturer", metavar="", help="Filter by manufacturer (e.g., 'Apple')") 155 | parser.add_argument("-r", "--ip-range", metavar="", help="Filter by IP range (e.g., '192.168.1.0/24')") 156 | parser.add_argument("-s", "--scan-rate", metavar="", help="Scan rate in seconds (default: 5)", type=int, default=5) 157 | return parser.parse_args() 158 | 159 | 160 | if __name__ == "__main__": 161 | net_probe = NetProbe() 162 | net_probe.main() 163 | --------------------------------------------------------------------------------