Skip to content

Commit fd8ec3d

Browse files
authored
Merge pull request #413 from JustGuardian/master
add --update-db option to update local MAC address prefix database
2 parents d85d4b4 + 69c6704 commit fd8ec3d

5 files changed

Lines changed: 226 additions & 2 deletions

File tree

tools/fetch_oui.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
#!/usr/bin/env python3
2+
import argparse
3+
import csv
4+
import requests
5+
import sys
6+
import os
7+
from datetime import datetime
8+
9+
# IEEE registry URLs
10+
IEEE_REG_URLS = {
11+
"OUI": "https://standards-oui.ieee.org/oui/oui.csv",
12+
"MAM": "https://standards-oui.ieee.org/oui28/mam.csv",
13+
"OUI36": "https://standards-oui.ieee.org/oui36/oui36.csv",
14+
"IAB": "https://standards-oui.ieee.org/iab/iab.csv",
15+
}
16+
17+
DEFAULT_FILENAME = "ieee-oui.txt"
18+
19+
20+
def fetch_csv(url, verbose=False):
21+
"""Download CSV content from a URL."""
22+
headers = {"User-Agent": "Mozilla/5.0 (compatible; FetchOUI/1.0; +https://github.com/kimocoder/wifite2)"}
23+
if verbose:
24+
print(f"→ Fetching {url}")
25+
response = requests.get(url, headers=headers, timeout=30)
26+
if not response.ok:
27+
raise RuntimeError(f"Failed to fetch {url}: {response.status_code} {response.reason}")
28+
if len(response.content) == 0:
29+
raise RuntimeError(f"Empty response from {url}")
30+
if verbose:
31+
print(f" Downloaded {len(response.content)} bytes")
32+
return response.text
33+
34+
35+
def parse_and_write_csv(csv_content, outfile, key, verbose=False):
36+
"""Parse CSV content and write MAC/Vendor to file."""
37+
reader = csv.DictReader(csv_content.splitlines())
38+
outfile.write(f"\n#\n# Start of IEEE {key} registry data\n#\n")
39+
count = 0
40+
41+
for row in reader:
42+
# Columns differ slightly between registries, so we handle gracefully
43+
mac = row.get("Assignment") or row.get("Registry") or ""
44+
vendor = row.get("Organization Name") or row.get("Organization") or ""
45+
vendor = vendor.strip()
46+
if mac and vendor:
47+
outfile.write(f"{mac}\t{vendor}\n")
48+
count += 1
49+
50+
outfile.write(f"#\n# End of IEEE {key} registry data. {count} entries.\n#\n")
51+
if verbose:
52+
print(f" Wrote {count} entries for {key}")
53+
return count
54+
55+
56+
def main():
57+
parser = argparse.ArgumentParser(
58+
description="Fetch the IEEE OUI (manufacturer) registries and write MAC/vendor mappings to a text file."
59+
)
60+
parser.add_argument("-f", metavar="FILE", default=DEFAULT_FILENAME,
61+
help=f"Output filename (default: {DEFAULT_FILENAME})")
62+
parser.add_argument("-v", action="store_true", help="Verbose output")
63+
args = parser.parse_args()
64+
65+
filename = args.f
66+
verbose = args.v
67+
68+
# Delete old file if exists
69+
if os.path.exists(filename):
70+
if verbose:
71+
print(f"Deleting existing {filename}")
72+
os.remove(filename)
73+
74+
# Open new output file
75+
if verbose:
76+
print(f"Opening {filename} for output")
77+
78+
with open(filename, "w", encoding="utf-8") as outfile:
79+
date_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
80+
outfile.write(f"# IEEE OUI Vendor List\n# Generated {date_str}\n")
81+
82+
total_entries = 0
83+
for key, url in sorted(IEEE_REG_URLS.items()):
84+
if verbose:
85+
print(f"\nProcessing IEEE {key} registry data from {url}")
86+
try:
87+
content = fetch_csv(url, verbose)
88+
total_entries += parse_and_write_csv(content, outfile, key, verbose)
89+
except Exception as e:
90+
print(f"Error processing {key}: {e}", file=sys.stderr)
91+
92+
print(f"\nTotal of {total_entries} MAC/Vendor mappings written to {filename}")
93+
94+
95+
if __name__ == "__main__":
96+
main()

wifite/args.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,11 @@ def _add_command_args(commands):
518518
action='store_true',
519519
dest='crack_handshake',
520520
help=Color.s('Show commands to crack a captured handshake'))
521+
522+
commands.add_argument('--update-db',
523+
action='store_true',
524+
dest='update_db',
525+
help=Color.s('Update the local MAC address prefix database from IEEE registries'))
521526

522527

523528
if __name__ == '__main__':

wifite/config.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ class Configuration(object):
4646
show_bssids = None
4747
show_cracked = None
4848
show_ignored = None
49+
update_db = None
50+
db_filename = None
4951
show_manufacturers = None
5052
skip_crack = None
5153
target_bssid = None
@@ -215,6 +217,8 @@ def initialize(cls, load_interface=True):
215217
cls.show_ignored = False
216218
cls.check_handshake = None
217219
cls.crack_handshake = False
220+
cls.update_db = False
221+
cls.db_filename = 'ieee-oui.txt'
218222

219223
# A list to cache all checked commands (e.g. `which hashcat` will execute only once)
220224
cls.existing_commands = {}
@@ -267,7 +271,8 @@ def load_from_arguments(cls):
267271
cls.check_handshake = args.check_handshake
268272
if args.crack_handshake:
269273
cls.crack_handshake = True
270-
274+
if args.update_db:
275+
cls.update_db = True
271276
@classmethod
272277
def validate(cls):
273278
if cls.use_pmkid_only and cls.wps_only:

wifite/util/dbupdater.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
#!/usr/bin/env python3
2+
import os
3+
import sys
4+
import csv
5+
import requests
6+
from datetime import datetime
7+
from typing import List
8+
9+
# keep the same import style as your example (adjust to your project layout)
10+
from ..config import Configuration
11+
from ..util.color import Color
12+
from ..util.process import Process
13+
14+
class DBUpdater:
15+
"""Updates a local database of MAC address prefixes to vendor names from IEEE registries.
16+
"""
17+
18+
# Registry URLs (same idea as your original script)
19+
SOURCES = {
20+
"OUI": "https://standards-oui.ieee.org/oui/oui.csv",
21+
"MAM": "https://standards-oui.ieee.org/oui28/mam.csv",
22+
"OUI36": "https://standards-oui.ieee.org/oui36/oui36.csv",
23+
"IAB": "https://standards-oui.ieee.org/iab/iab.csv",
24+
}
25+
26+
DEFAULT_FILENAME = "ieee-oui.txt"
27+
28+
@classmethod
29+
def run(cls):
30+
31+
Configuration.initialize(False)
32+
33+
filename = Configuration.db_filename
34+
verbose = bool(Configuration.verbose)
35+
36+
if os.path.exists(filename):
37+
up_to_date, last_updated = cls.is_up_to_date(filename)
38+
39+
if up_to_date:
40+
Color.pl('{+} {G}Database is up to date ({C}%s{G}). Last update date: {C}%s{W}' % (filename, last_updated))
41+
return
42+
if verbose:
43+
Color.pl('{!} {O}Deleting existing {R}%s{W}' % filename)
44+
os.remove(filename)
45+
46+
try:
47+
total_written = cls.update_all(filename, verbose=verbose)
48+
except KeyboardInterrupt:
49+
Color.pl('\n{!} {O}Interrupted by user{W}')
50+
return
51+
52+
Color.pl('\n\n{+} {G}Done{W} - Total entries written: {C}%d{W}' % total_written)
53+
54+
@ classmethod
55+
def update_all(cls, filename: str, verbose: bool = False) -> int:
56+
"""Loop selected sources, fetch, parse and append to filename. Returns count written."""
57+
written_total = 0
58+
with open(filename, "w", encoding="utf-8") as outfile:
59+
date_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
60+
outfile.write(f"# Registry Vendor List\n# Generated {date_str}\n")
61+
for key in cls.SOURCES.keys():
62+
url = cls.SOURCES.get(key)
63+
Color.pl('\n{+} Processing {C}%s{W} from {O}%s{W}' % (key, url))
64+
try:
65+
csv_content = cls.fetch_csv(url, verbose=verbose)
66+
written = cls.parse_and_write_csv(csv_content, outfile, key, verbose=verbose)
67+
written_total += written
68+
except Exception as e:
69+
print(f"Error processing {key}: {e}", file=sys.stderr)
70+
return written_total
71+
72+
73+
@classmethod
74+
def fetch_csv(cls, url: str, verbose: bool = False) -> str:
75+
"""Download CSV content (boilerplate; uses requests)."""
76+
headers = {"User-Agent": "Mozilla/5.0 (compatible; FetchOUI/1.0; +https://github.com/kimocoder/wifite2)"}
77+
if verbose:
78+
Color.pl(' → Fetching %s' % url)
79+
response = requests.get(url, headers=headers, timeout=30)
80+
if not response.ok:
81+
raise RuntimeError(f"Failed to fetch {url}: {response.status_code} {response.reason}")
82+
if len(response.content) == 0:
83+
raise RuntimeError(f"Empty response from {url}")
84+
return response.text
85+
86+
@classmethod
87+
def parse_and_write_csv(cls, csv_content: str, outfile, key: str, verbose: bool = False) -> int:
88+
"""Parse CSV content and write MAC\tVendor lines to outfile (boilerplate)."""
89+
reader = csv.DictReader(csv_content.splitlines())
90+
outfile.write(f"\n#\n# Start of {key} registry data\n#\n")
91+
count = 0
92+
for row in reader:
93+
mac = row.get("Assignment") or row.get("Registry") or ""
94+
vendor = row.get("Organization Name") or row.get("Organization") or ""
95+
vendor = (vendor or "").strip()
96+
if mac and vendor:
97+
outfile.write(f"{mac}\t{vendor}\n")
98+
count += 1
99+
outfile.write(f"#\n# End of {key} registry data. {count} entries.\n#\n")
100+
101+
Color.p(' Wrote {C}%d{W} entries from source: {C}%s{W}' % (count, key))
102+
return count
103+
104+
def is_up_to_date(filename: str) -> bool:
105+
106+
mtime = os.path.getmtime(filename)
107+
last_update = datetime.fromtimestamp(mtime).strftime("%Y-%m-%d %H:%M:%S")
108+
age_seconds = datetime.now().timestamp() - mtime
109+
return age_seconds < (7 * 24 * 3600), last_update #if file is older than 7 days it is not up to date
110+
111+
112+
if __name__ == '__main__':
113+
DBUpdater.run()

wifite/wifite.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ def start(self):
4343
from .model.result import CrackResult
4444
from .model.handshake import Handshake
4545
from .util.crack import CrackHelper
46-
46+
from .util.dbupdater import DBUpdater
47+
4748
if Configuration.show_cracked:
4849
CrackResult.display('cracked')
4950

@@ -55,6 +56,10 @@ def start(self):
5556

5657
elif Configuration.crack_handshake:
5758
CrackHelper.run()
59+
60+
elif Configuration.update_db:
61+
62+
DBUpdater.run()
5863

5964
else:
6065
Configuration.get_monitor_mode_interface()

0 commit comments

Comments
 (0)