-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
e5e7392
commit 016f2e4
Showing
3 changed files
with
273 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
import csv | ||
import json | ||
import socket | ||
import os | ||
import platform | ||
import ipaddress | ||
from scapy.all import ARP, Ether, srp, IP, TCP, sr1 | ||
from concurrent.futures import ThreadPoolExecutor | ||
from utils import get_local_ip, get_user_input, colored_print | ||
from colorama import Fore, Style | ||
import argparse | ||
|
||
def scan_ip(ip, timeout=1): | ||
""" | ||
Sends an ARP request to a specific IP and waits for a response. | ||
:param ip: The IP address to scan. | ||
:param timeout: Timeout for the ARP request. | ||
:return: A dictionary containing the IP, MAC address, device name, and OS if available. | ||
""" | ||
if ':' in ip: | ||
# Skip IPv6 addresses, ARP is for IPv4 | ||
return None | ||
|
||
# Create ARP request for the given IP | ||
arp_request = ARP(pdst=ip) | ||
ether = Ether(dst="ff:ff:ff:ff:ff:ff") | ||
packet = ether / arp_request | ||
|
||
device_info = {'ip': ip} | ||
try: | ||
# Send the packet and wait for a response (timeout in seconds) | ||
result = srp(packet, timeout=timeout, verbose=0)[0] | ||
if result: | ||
device_info['mac'] = result[0][1].hwsrc | ||
device_info['name'] = get_device_name(ip) | ||
device_info['os'] = get_os(ip) | ||
return device_info | ||
except Exception as e: | ||
colored_print(f"Error scanning {ip}: {e}", "RED") | ||
return None | ||
|
||
def get_device_name(ip): | ||
""" | ||
Retrieves the device name using reverse DNS lookup. | ||
:param ip: The IP address to lookup. | ||
:return: Device name or 'Unknown' if not resolvable. | ||
""" | ||
try: | ||
return socket.gethostbyaddr(ip)[0] | ||
except socket.herror: | ||
return 'Unknown' | ||
|
||
def get_os(ip): | ||
""" | ||
Attempts to determine the operating system using TCP/IP stack analysis. | ||
:param ip: The IP address to scan. | ||
:return: OS guess based on TCP response. | ||
""" | ||
try: | ||
pkt = IP(dst=ip)/TCP(dport=80, flags='S') | ||
response = sr1(pkt, timeout=1, verbose=0) | ||
if response: | ||
if response.haslayer(TCP): | ||
if response[TCP].flags == 0x12: | ||
return 'Windows' | ||
elif response[TCP].flags == 0x14: | ||
return 'Linux/Unix' | ||
return 'Unknown' | ||
except Exception as e: | ||
colored_print(f"Error detecting OS for {ip}: {e}", "RED") | ||
return 'Unknown' | ||
|
||
def scan_network_concurrently(ip_range, timeout=1, threads=100): | ||
""" | ||
Scans the network range concurrently for active clients using ARP requests. | ||
:param ip_range: List of IP addresses to scan. | ||
:param timeout: Timeout for each ARP request. | ||
:param threads: Number of threads to use for scanning. | ||
:return: List of active clients with IP, MAC, device name, and OS information. | ||
""" | ||
with ThreadPoolExecutor(max_workers=threads) as executor: | ||
results = executor.map(lambda ip: scan_ip(ip) or None, ip_range) # TODO: Replace none with ipv6_scan and develop the function. | ||
return [client for client in results if client is not None] | ||
|
||
def export_results(results, file_format, filename): | ||
""" | ||
Export results to a file in the specified format (CSV or JSON). | ||
:param results: List of dictionaries containing IP, MAC, device name, and OS information. | ||
:param file_format: Format for exporting ('csv' or 'json'). | ||
:param filename: Output file name. | ||
""" | ||
if file_format == 'csv': | ||
with open(filename, 'w', newline='') as file: | ||
writer = csv.DictWriter(file, fieldnames=['ip', 'mac', 'name', 'os']) | ||
writer.writeheader() | ||
writer.writerows(results) | ||
elif file_format == 'json': | ||
with open(filename, 'w') as file: | ||
json.dump(results, file, indent=4) | ||
else: | ||
colored_print(f"Unsupported file format: {file_format}", "RED") | ||
|
||
if __name__ == "__main__": | ||
# Argument parsing for customization | ||
parser = argparse.ArgumentParser(description="Network Scanner") | ||
parser.add_argument('--timeout', type=float, default=1, help="Timeout for ARP requests (default: 1s)") | ||
parser.add_argument('--threads', type=int, default=100, help="Number of threads to use (default: 100)") | ||
parser.add_argument('--range', type=str, help="Custom IP range (e.g., 192.168.1.0/24)") | ||
parser.add_argument('--export', type=str, help="Export results to a file (csv or json) with the specified filename") | ||
|
||
args = parser.parse_args() | ||
|
||
# Get local IP to determine network range | ||
local_ip = get_local_ip() | ||
|
||
if not local_ip: | ||
colored_print("Could not determine local IP address.", "RED") | ||
else: | ||
# Get IP range from user or use default | ||
ip_range = get_user_input(args.range, local_ip) | ||
|
||
# Show scan range | ||
colored_print(f"Scanning network range: {ip_range[0].rsplit('.', 1)[0]}.0/24", "GREEN") | ||
|
||
# Scan network concurrently | ||
active_clients = scan_network_concurrently(ip_range, timeout=args.timeout, threads=args.threads) | ||
|
||
# Display results | ||
colored_print("\nActive clients on the network:", "YELLOW") | ||
print(f"{Fore.CYAN}IP Address\t\tMAC Address\t\tDevice Name\t\tOS{Style.RESET_ALL}") | ||
print("-" * 80) | ||
for client in active_clients: | ||
print(f"{Fore.GREEN}{client['ip']}\t\t{client['mac']}\t\t{client['name']}\t\t{client['os']}{Style.RESET_ALL}") | ||
|
||
if not active_clients: | ||
colored_print("No active clients found.", "RED") | ||
|
||
# Export results if requested | ||
if args.export: | ||
file_format = args.export.split('.')[-1] | ||
export_results(active_clients, file_format, args.export) | ||
colored_print(f"Results exported to {args.export}", "GREEN") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
# Network Scanner | ||
|
||
A Python-based network scanner that uses ARP requests to discover active clients on a local network. This tool includes features for device name and operating system detection, with options to export results to CSV or JSON files and support for IPv6 addresses. | ||
|
||
## Features | ||
- **Customizable Network Range**: Scan a specific IP range or use the local IP to derive the default range. | ||
- **Concurrency**: Utilizes Python's `ThreadPoolExecutor` for fast scanning with customizable thread count. | ||
- **Timeout Control**: Adjustable ARP request timeout to balance speed and accuracy. | ||
- **Export Results**: Save scan results to CSV or JSON files. | ||
- **Device Name Detection**: Retrieve device names using reverse DNS lookup. | ||
- **Operating System Detection**: Basic OS fingerprinting based on TCP/IP stack analysis. | ||
- **IPv6 Support**: Placeholder for future IPv6 scanning functionality. | ||
- **Colorful Output**: Clear, colorful output using the `colorama` library for easier result interpretation. | ||
- **Modular Design**: Core functionalities split into multiple files for maintainability and scalability. | ||
|
||
## Requirements | ||
- Python 3.x | ||
- `scapy` library | ||
- `colorama` library | ||
|
||
## Installation | ||
|
||
1. Clone the repository: | ||
```bash | ||
git clone https://github.com/sudo-arash/network-scanner.git | ||
cd network-scanner | ||
``` | ||
|
||
2. Install required dependencies: | ||
```bash | ||
pip install scapy colorama | ||
``` | ||
|
||
## Usage | ||
|
||
Run the `network_scanner.py` with the following customizable options: | ||
|
||
```bash | ||
python network_scanner.py [--timeout TIMEOUT] [--threads THREADS] [--range RANGE] [--export FILE] | ||
``` | ||
|
||
### Arguments: | ||
- `--timeout`: (Optional) Set the timeout for ARP requests (in seconds). Default is `1`. | ||
- `--threads`: (Optional) Number of threads to use for concurrent scanning. Default is `100`. | ||
- `--range`: (Optional) Specify a custom IP range to scan (e.g., `192.168.1.0/24`). If not provided, the script uses the local network IP range. | ||
- `--export`: (Optional) Export results to a file in CSV or JSON format (e.g., `results.csv` or `results.json`). | ||
|
||
### Examples: | ||
|
||
1. **Scan the default network range (based on local IP) with default settings**: | ||
```bash | ||
python network_scanner.py | ||
``` | ||
|
||
2. **Scan a custom IP range with a 2-second timeout and 50 threads**: | ||
```bash | ||
python network_scanner.py --range 192.168.1.0/24 --timeout 2 --threads 50 | ||
``` | ||
|
||
3. **Export results to a CSV file**: | ||
```bash | ||
python network_scanner.py --export results.csv | ||
``` | ||
|
||
## Project Structure | ||
|
||
``` | ||
network-scanner/ | ||
├── network_scanner.py # Main script with network scanning functionality | ||
├── utils.py # Utility functions for network scanning | ||
├── README.md # Project documentation | ||
``` | ||
## Future Enhancements | ||
- Implement full IPv6 scanning functionality. | ||
- Advanced network analysis features. | ||
- GUI for easier configuration and result viewing. | ||
## License | ||
This project is licensed under the MIT License. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import socket | ||
from colorama import Fore, Style | ||
|
||
def get_local_ip(): | ||
""" | ||
Returns the local IP address of the machine. | ||
""" | ||
try: | ||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | ||
s.settimeout(0) | ||
s.connect(('8.8.8.8', 1)) | ||
local_ip = s.getsockname()[0] | ||
s.close() | ||
return local_ip | ||
except Exception as e: | ||
colored_print(f"Error getting local IP: {e}", "RED") | ||
return None | ||
|
||
def get_user_input(custom_range, local_ip): | ||
""" | ||
Determines the IP range to scan. | ||
:param custom_range: User input for custom IP range. | ||
:param local_ip: Local IP to derive default range. | ||
:return: List of IP addresses to scan. | ||
""" | ||
if custom_range: | ||
# Parse custom range (assume /24 subnet) | ||
base_ip = custom_range.rsplit('.', 1)[0] | ||
return [f"{base_ip}.{i}" for i in range(1, 255)] | ||
else: | ||
# Use default range based on local IP | ||
ip_parts = local_ip.split('.') | ||
base_ip = f"{ip_parts[0]}.{ip_parts[1]}.{ip_parts[2]}." | ||
return [f"{base_ip}{i}" for i in range(1, 255)] | ||
|
||
def colored_print(message, color="WHITE"): | ||
""" | ||
Prints a message in the specified color. | ||
:param message: The message to print. | ||
:param color: The color for the message (options: RED, GREEN, YELLOW, CYAN, WHITE). | ||
""" | ||
color_mapping = { | ||
"RED": Fore.RED, | ||
"GREEN": Fore.GREEN, | ||
"YELLOW": Fore.YELLOW, | ||
"CYAN": Fore.CYAN, | ||
"WHITE": Fore.WHITE | ||
} | ||
print(f"{color_mapping.get(color, Fore.WHITE)}{message}{Style.RESET_ALL}") |