Created
June 17, 2025 22:56
-
-
Save jlmalone/78c10e98e9036752dc87f6b208bb69e9 to your computer and use it in GitHub Desktop.
Find Devices on Network with Special emphasis of Macs and Mac Minis on a local network
This file contains hidden or 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
#!/bin/bash | |
# | |
# find_macs.sh - Local Network Mac & Mac Mini Scanner | |
# | |
# Author: Joseph Malone / jlmalone | |
# Date: 2025-06-18 | |
# Version: 1.0 | |
# | |
# Description: | |
# This script scans the local network to identify all active devices. | |
# It uses nmap, Bonjour (mDNS), and ARP lookups to gather information. | |
# Its primary goal is to make an educated guess on which devices are | |
# Apple Macs or Mac Minis based on their MAC address, hostname, | |
# and advertised services. | |
# | |
# Usage: | |
# Run with administrator privileges: | |
# sudo ./find_macs.sh | |
# | |
# For detailed help and explanation: | |
# ./find_macs.sh --help | |
# | |
# Dependencies: | |
# - macOS (relies on 'route', 'ifconfig', and 'dns-sd') | |
# - nmap (must be installed, e.g., 'brew install nmap') | |
# | |
################################################################################ | |
# --- Help Function --- | |
# Provides detailed information to the user on how the script works. | |
show_help() { | |
cat << EOF | |
Mac & Mac Mini Network Scanner (v1.1) | |
This script scans your local network to find active devices and makes an | |
educated guess to identify Apple Mac and Mac Mini computers. | |
USAGE: | |
sudo ./find_macs.sh | |
./find_macs.sh -h | --help | |
DESCRIPTION: | |
The script performs several steps to identify devices: | |
1. Finds your computer's local subnet (e.g., 192.168.1.0/24). | |
2. Uses 'nmap' to perform a fast "ping scan" to find all active IP addresses. | |
3. Uses 'dns-sd' (Bonjour) to listen for Macs advertising SSH services. | |
4. For each active IP, it uses 'arp' and 'host' to find its MAC address | |
and hostname. | |
5. It then analyzes this information to flag potential Macs. | |
HOW IT IDENTIFIES MACS: | |
A device is considered a likely Mac if: | |
- Its MAC address starts with a prefix registered to Apple, Inc. | |
- Its hostname contains "Mac" (e.g., "joes-macbook-pro.local"). | |
- It is discovered via Bonjour advertising an SSH service. | |
A device is a strong candidate for a Mac Mini if its hostname also | |
contains "Mini". | |
REQUIREMENTS: | |
- macOS: The script is designed for macOS and uses specific commands. | |
- nmap: A powerful network scanner. Install with Homebrew: | |
brew install nmap | |
- sudo: Administrator privileges are required for the 'arp' command to | |
reliably retrieve MAC addresses for all devices on the network. | |
EOF | |
} | |
# --- Argument Parsing --- | |
# If the user asks for help, show it and exit immediately. | |
if [[ "$1" == "-h" || "$1" == "--help" ]]; then | |
show_help | |
exit 0 | |
fi | |
# Use "strict mode" to make the script more robust. | |
set -euo pipefail | |
# --- Configuration & Setup --- | |
C_GREEN=$(tput setaf 2) | |
C_YELLOW=$(tput setaf 3) | |
C_BOLD=$(tput bold) | |
C_RESET=$(tput sgr0) | |
# --- Pre-flight Checks --- | |
# 1. Check for root privileges. | |
if [[ $EUID -ne 0 ]]; then | |
echo "This script uses the 'arp' command for reliable MAC address lookups." | |
echo "Please run with sudo: ${C_BOLD}sudo $0${C_RESET}" | |
exit 1 | |
fi | |
# 2. Check if nmap is installed. | |
if ! command -v nmap &> /dev/null; then | |
echo "Error: nmap is not installed. Install it with 'brew install nmap'." | |
exit 1 | |
fi | |
# --- Network Discovery --- | |
echo "Determining active network..." | |
# Get the active network interface and subnet. | |
interface=$(route -n get default | grep 'interface:' | awk '{print $2}') | |
if [ -z "$interface" ]; then | |
echo "Error: No active network interface found." | |
exit 1 | |
fi | |
subnet=$(ifconfig "$interface" | grep 'inet ' | awk '{print $2}' | cut -d. -f1-3).0/24 | |
if [ -z "$subnet" ]; then | |
echo "Error: Could not determine subnet." | |
exit 1 | |
fi | |
echo "Scanning network subnet: $subnet" | |
# --- Host Scanning --- | |
# Create temporary files that we will clean up automatically on exit. | |
HOSTS_FILE=$(mktemp) | |
BONJOUR_FILE=$(mktemp) | |
trap 'rm -f "$HOSTS_FILE" "$BONJOUR_FILE"' EXIT | |
# 1. Perform a quick ping scan to find active hosts. | |
echo "Finding active hosts with nmap..." | |
nmap -sn "$subnet" -oG - | grep "Up" | awk '{print $2}' > "$HOSTS_FILE" | |
# 2. Use dns-sd to find macOS devices advertising SSH (via Bonjour/mDNS). | |
echo "Scanning for macOS devices with SSH via Bonjour (6 seconds)..." | |
dns-sd -B _ssh._tcp local > "$BONJOUR_FILE" 2>/dev/null & | |
dns_sd_pid=$! | |
sleep 6 | |
kill "$dns_sd_pid" 2>/dev/null | |
wait "$dns_sd_pid" 2>/dev/null # Suppress "Terminated" message | |
# --- Analysis and Reporting --- | |
echo | |
echo "${C_BOLD}Active Devices Found:${C_RESET}" | |
echo "---------------------" | |
if [ ! -s "$HOSTS_FILE" ]; then | |
echo "No active hosts found on the network." | |
exit 0 | |
fi | |
# Iterate through discovered hosts. | |
while IFS= read -r ip; do | |
# Get MAC address and hostname from the ARP table. | |
arp_info=$(arp -n "$ip" | grep -v 'incomplete') | |
mac=$(echo "$arp_info" | awk '{print $4}' | grep -E '([0-9a-f]{1,2}:){5}[0-9a-f]{1,2}') | |
hostname_from_arp=$(echo "$arp_info" | awk '{print $2}' | grep -v '?') | |
hostname=${hostname_from_arp:-"unknown"} | |
# Check Bonjour for a more definitive macOS hostname. | |
bonjour_hostname=$(grep " $ip\.$" "$BONJOUR_FILE" | awk '{print $7}' | head -n 1) | |
if [[ -n "$bonjour_hostname" ]]; then | |
hostname="$bonjour_hostname.local" | |
fi | |
echo "${C_BOLD}IP: $ip${C_RESET}" | |
if [[ -n "$mac" ]]; then | |
echo "MAC: $mac" | |
fi | |
if [[ "$hostname" != "unknown" ]]; then | |
echo "Hostname: $hostname" | |
fi | |
# --- Mac Identification Logic --- | |
is_mac=0 | |
is_mac_mini=0 | |
# 1. Check MAC address against known Apple Organizationally Unique Identifiers (OUIs). | |
# Source: https://gitlab.com/wireshark/wireshark/-/raw/master/manuf | |
if [[ -n "$mac" ]]; then | |
mac_prefix=$(echo "$mac" | cut -c 1-8 | tr '[:lower:]' '[:upper:]') | |
case "$mac_prefix" in | |
"00:03:93"|"00:05:02"|"00:0A:27"|"00:0A:95"|"00:0D:93"|"00:10:FA"|"00:11:24"|"00:14:51"|"00:16:CB"|"00:17:F2"|"00:19:E3"|"00:1B:63"|"00:1C:B3"|"00:1D:4F"|"00:1E:52"|"00:1E:C2"|"00:1F:5B"|"00:1F:F3"|"00:21:E9"|"00:22:41"|"00:23:12"|"00:23:32"|"00:23:6C"|"00:23:DF"|"00:24:36"|"00:25:00"|"00:25:4B"|"00:25:BC"|"00:26:08"|"00:26:4F"|"00:26:B0"|"00:26:BB"|"00:3E:E1"|"00:A0:40"|"00:C6:10"|"00:F4:B9"|"04:0C:CE"|"04:1E:64"|"04:26:05"|"04:48:9A"|"04:54:53"|"04:DB:56"|"04:E5:36"|"08:00:07"|"08:74:02"|"0C:1A:10"|"0C:30:21"|"0C:4D:E9"|"0C:74:C2"|"10:1C:0C"|"10:40:F3"|"10:9A:DD"|"14:10:9F"|"14:5A:05"|"14:8F:C6"|"14:99:E2"|"18:20:32"|"18:34:51"|"18:81:0E"|"18:9E:FC"|"18:AF:61"|"18:AF:8F"|"1C:1A:C0"|"1C:36:BB"|"1C:5C:F2"|"1C:91:4F"|"20:78:F0"|"20:C9:D0"|"24:1E:EB"|"24:A0:74"|"24:A2:E1"|"28:0B:5C"|"28:37:37"|"28:5A:EB"|"28:6A:BA"|"28:CF:DA"|"28:E0:2C"|"28:E7:CF"|"28:F0:76"|"2C:29:97"|"2C:61:F6"|"2C:BE:08"|"2C:F0:A2"|"30:10:E4"|"30:35:AD"|"30:63:6B"|"30:90:AB"|"30:F7:C5"|"34:12:98"|"34:36:3B"|"34:51:C9"|"34:A8:4E"|"34:C0:59"|"34:E2:FD"|"38:0F:4A"|"38:4F:F0"|"38:60:77"|"38:71:DE"|"38:86:A5"|"38:B5:4D"|"3C:07:54"|"3C:2F:3A"|"3C:AB:8E"|"3C:D0:F8"|"3C:E0:72"|"40:3C:FC"|"40:98:AD"|"40:A6:D9"|"40:D3:2D"|"44:00:10"|"44:2A:60"|"44:48:9A"|"44:D8:84"|"44:F4:59"|"48:3B:38"|"48:43:7C"|"48:60:BC"|"48:74:6E"|"48:A1:C5"|"48:D7:05"|"4C:32:75"|"4C:57:CA"|"4C:7C:5F"|"50:32:37"|"50:EA:D6"|"54:26:96"|"54:4E:90"|"54:72:4F"|"54:9F:13"|"54:AE:27"|"54:E4:3A"|"58:1F:AA"|"58:40:4E"|"58:55:CA"|"58:B0:35"|"5C:09:47"|"5C:59:48"|"5C:8D:4E"|"5C:96:9D"|"5C:97:F3"|"5C:A8:6A"|"5C:F5:DA"|"5C:F9:38"|"60:03:08"|"60:19:71"|"60:30:D4"|"60:67:20"|"60:92:17"|"60:C5:47"|"60:D9:C7"|"60:F8:1D"|"60:FA:CD"|"64:31:50"|"64:5A:04"|"64:7B:D3"|"64:A3:CB"|"64:A5:C3"|"64:B9:E8"|"68:3E:34"|"68:5B:35"|"68:64:4B"|"68:96:7B"|"68:A8:6D"|"68:D9:3C"|"68:DB:CA"|"6C:29:92"|"6C:40:08"|"6C:70:9F"|"6C:94:F8"|"6C:C9:22"|"70:11:24"|"70:3E:AC"|"70:56:81"|"70:70:0D"|"70:A2:B3"|"70:CD:60"|"70:DE:E2"|"70:F0:87"|"74:81:14"|"74:E1:A2"|"78:31:C1"|"78:3A:84"|"78:6C:1C"|"78:7E:61"|"78:88:6A"|"78:A3:E4"|"78:CA:39"|"7C:04:D0"|"7C:11:BE"|"7C:6D:62"|"7C:C3:A1"|"7C:C5:37"|"7C:D1:C3"|"7C:F0:5F"|"80:00:6E"|"80:49:71"|"80:92:9F"|"80:E6:50"|"80:EA:96"|"84:29:99"|"84:38:35"|"84:78:8B"|"84:85:0A"|"84:8E:0C"|"84:B1:97"|"84:FC:FE"|"88:1F:A1"|"88:63:DF"|"88:66:5A"|"88:C6:63"|"8C:00:6D"|"8C:29:37"|"8C:58:77"|"8C:7B:9D"|"8C:85:90"|"8C:87:32"|"8C:FA:BA"|"90:72:40"|"90:84:0D"|"90:B2:1F"|"90:DD:5D"|"94:E9:6A"|"98:00:C6"|"98:01:A7"|"98:03:D8"|"98:46:0A"|"98:5A:EB"|"98:9E:63"|"98:B8:E3"|"98:D6:BB"|"98:E0:D9"|"98:F0:AB"|"9C:04:EB"|"9C:20:7B"|"9C:35:EB"|"9C:F3:87"|"A0:3B:E3"|"A0:4E:A7"|"A0:99:9B"|"A0:ED:CD"|"A4:5E:60"|"A4:B1:97"|"A4:E9:75"|"A8:20:66"|"A8:5C:2C"|"A8:60:B6"|"A8:66:7F"|"A8:86:DD"|"A8:88:08"|"A8:96:8A"|"A8:BB:C5"|"A8:FA:D8"|"AC:18:26"|"AC:3C:0B"|"AC:61:EA"|"AC:87:A3"|"AC:BC:32"|"AC:E3:42"|"AC:FD:EC"|"B0:34:95"|"B0:65:BD"|"B0:9F:BA"|"B0:C0:90"|"B4:43:0D"|"B4:F0:AB"|"B8:09:8A"|"B8:16:19"|"B8:44:D9"|"B8:53:AC"|"B8:63:4D"|"B8:78:2E"|"B8:8D:12"|"B8:C7:5D"|"B8:E8:56"|"B8:F6:B1"|"BC:3B:AF"|"BC:4C:C4"|"BC:52:B7"|"BC:67:A6"|"BC:9F:EF"|"C0:1A:DA"|"C0:63:94"|"C0:84:7A"|"C0:B6:58"|"C0:CE:CD"|"C4:2C:03"|"C4:61:8B"|"C8:1F:77"|"C8:2A:14"|"C8:33:4B"|"C8:6F:1D"|"C8:85:50"|"C8:B5:B7"|"C8:D0:83"|"CC:08:E0"|"CC:20:E8"|"CC:29:F5"|"CC:44:63"|"CC:78:5F"|"CC:C7:60"|"D0:03:4B"|"D0:23:DB"|"D0:4F:7E"|"D0:81:7A"|"D0:A6:37"|"D0:D2:B0"|"D0:E1:82"|"D4:61:2E"|"D4:61:9D"|"D4:A3:3D"|"D8:00:4D"|"D8:1D:72"|"D8:30:62"|"D8:96:95"|"D8:A7:56"|"D8:D1:CB"|"DC:08:0F"|"DC:2B:2A"|"DC:86:D8"|"DC:A9:04"|"E0:33:8E"|"E0:5F:45"|"E0:75:7D"|"E0:AC:CB"|"E0:B9:BA"|"E0:C9:7A"|"E0:F5:C6"|"E4:2B:34"|"E4:30:22"|"E4:50:EB"|"E4:8B:7F"|"E4:98:D6"|"E4:B2:FB"|"E4:C6:3D"|"E8:04:0B"|"E8:06:88"|"E8:80:2E"|"EC:35:86"|"EC:85:2F"|"F0:18:98"|"F0:24:75"|"F0:78:16"|"F0:98:9D"|"F0:99:BF"|"F0:B4:79"|"F0:C1:F1"|"F0:D1:A9"|"F0:DC:E2"|"F4:1B:A1"|"F4:37:B7"|"F4:F1:5A"|"F4:F9:51"|"F8:27:93"|"F8:38:80"|"F8:4D:89"|"F8:D0:BD"|"F8:E0:79"|"FC:1B:1F"|"FC:25:3F"|"FC:D8:48") | |
is_mac=1 | |
;; | |
esac | |
fi | |
# 2. Check hostname for common Mac patterns. | |
if [[ "$hostname" =~ [Mm]ac ]]; then | |
is_mac=1 | |
if [[ "$hostname" =~ [Mm]ini ]]; then | |
is_mac_mini=1 | |
fi | |
fi | |
# --- Final Verdict --- | |
if [[ $is_mac -eq 1 ]]; then | |
echo "${C_GREEN}Verdict: Likely a Mac device${C_RESET}" | |
if [[ $is_mac_mini -eq 1 ]]; then | |
echo "${C_YELLOW}Note: Strong candidate for Mac Mini (hostname contains 'Mini')${C_RESET}" | |
fi | |
fi | |
echo "---------------------" | |
done < "$HOSTS_FILE" | |
echo "Scan complete." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
https://aistudio.google.com/app/prompts?state=%7B%22ids%22:%5B%221m2YCAo2rUUPLSMME-1Ak1J9lqk-iyS-2%22%5D,%22action%22:%22open%22,%22userId%22:%22105903347225150947554%22,%22resourceKeys%22:%7B%7D%7D&usp=sharing