Documentation, code-formatting, and refactoring.
* Added some docs, updated existing docs. * Use single-quotes for strings when possible. * Color.pexception() prints exception and stack trace.
This commit is contained in:
@@ -65,7 +65,7 @@ class Aircrack(Dependency):
|
||||
hex_chars.append(byt)
|
||||
byt_int = int(byt, 16)
|
||||
if byt_int < 32 or byt_int > 127 or ascii_key is None:
|
||||
ascii_key = None # Not printable
|
||||
ascii_key = None # Not printable
|
||||
else:
|
||||
ascii_key += chr(byt_int)
|
||||
|
||||
@@ -91,17 +91,17 @@ if __name__ == '__main__':
|
||||
Configuration.initialize(False)
|
||||
|
||||
ivs_file = 'tests/files/wep-crackable.ivs'
|
||||
print("Running aircrack on %s ..." % ivs_file)
|
||||
print('Running aircrack on %s ...' % ivs_file)
|
||||
|
||||
aircrack = Aircrack(ivs_file)
|
||||
while aircrack.is_running():
|
||||
sleep(1)
|
||||
|
||||
assert aircrack.is_cracked(), "Aircrack should have cracked %s" % ivs_file
|
||||
print("aircrack process completed.")
|
||||
assert aircrack.is_cracked(), 'Aircrack should have cracked %s' % ivs_file
|
||||
print('aircrack process completed.')
|
||||
|
||||
(hexkey, asciikey) = aircrack.get_key_hex_ascii()
|
||||
print("aircrack found HEX key: (%s) and ASCII key: (%s)" % (hexkey, asciikey))
|
||||
print('aircrack found HEX key: (%s) and ASCII key: (%s)' % (hexkey, asciikey))
|
||||
assert hexkey == '75:6E:63:6C:65', 'hexkey was "%s", expected "75:6E:63:6C:65"' % hexkey
|
||||
assert asciikey == 'uncle', 'asciikey was "%s", expected "uncle"' % asciikey
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ class WEPAttackType(object):
|
||||
self.name = name
|
||||
self.value = value
|
||||
return
|
||||
raise Exception("Attack number %d not found" % var)
|
||||
raise Exception('Attack number %d not found' % var)
|
||||
elif type(var) is str:
|
||||
for (name,value) in WEPAttackType.__dict__.items():
|
||||
if type(value) is int:
|
||||
@@ -44,12 +44,12 @@ class WEPAttackType(object):
|
||||
self.name = name
|
||||
self.value = value
|
||||
return
|
||||
raise Exception("Attack name %s not found" % var)
|
||||
raise Exception('Attack name %s not found' % var)
|
||||
elif type(var) == WEPAttackType:
|
||||
self.name = var.name
|
||||
self.value = var.value
|
||||
else:
|
||||
raise Exception("Attack type not supported")
|
||||
raise Exception('Attack type not supported')
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
@@ -65,13 +65,13 @@ class Aireplay(Thread, Dependency):
|
||||
Starts aireplay process.
|
||||
Args:
|
||||
target - Instance of Target object, AP to attack.
|
||||
attack_type - str, e.g. "fakeauth", "arpreplay", etc.
|
||||
attack_type - str, e.g. 'fakeauth', 'arpreplay', etc.
|
||||
client_mac - MAC address of an associated client.
|
||||
'''
|
||||
super(Aireplay, self).__init__() # Init the parent Thread
|
||||
|
||||
self.target = target
|
||||
self.output_file = Configuration.temp("aireplay_%s.output" % attack_type)
|
||||
self.output_file = Configuration.temp('aireplay_%s.output' % attack_type)
|
||||
self.attack_type = WEPAttackType(attack_type).value
|
||||
self.error = None
|
||||
self.status = None
|
||||
@@ -90,7 +90,7 @@ class Aireplay(Thread, Dependency):
|
||||
|
||||
def stop(self):
|
||||
''' Stops aireplay process '''
|
||||
if hasattr(self, "pid") and self.pid and self.pid.poll() is None:
|
||||
if hasattr(self, 'pid') and self.pid and self.pid.poll() is None:
|
||||
self.pid.interrupt()
|
||||
|
||||
def get_output(self):
|
||||
@@ -104,7 +104,7 @@ class Aireplay(Thread, Dependency):
|
||||
time.sleep(0.1)
|
||||
if not os.path.exists(self.output_file): continue
|
||||
# Read output file & clear output file
|
||||
with open(self.output_file, "r+") as fid:
|
||||
with open(self.output_file, 'r+') as fid:
|
||||
lines = fid.read()
|
||||
self.stdout += lines
|
||||
fid.seek(0)
|
||||
@@ -114,51 +114,51 @@ class Aireplay(Thread, Dependency):
|
||||
from ..util.color import Color
|
||||
Color.pl('\n{P} [?] aireplay output:\n %s{W}' % lines.strip().replace('\n', '\n '))
|
||||
|
||||
for line in lines.split("\n"):
|
||||
line = line.replace("\r", "").strip()
|
||||
if line == "": continue
|
||||
if "Notice: got a deauth/disassoc packet" in line:
|
||||
self.error = "Not associated (needs fakeauth)"
|
||||
for line in lines.split('\n'):
|
||||
line = line.replace('\r', '').strip()
|
||||
if line == '': continue
|
||||
if 'Notice: got a deauth/disassoc packet' in line:
|
||||
self.error = 'Not associated (needs fakeauth)'
|
||||
|
||||
if self.attack_type == WEPAttackType.fakeauth:
|
||||
# Look for fakeauth status. Potential Output lines:
|
||||
# (START): 00:54:58 Sending Authentication Request (Open System)
|
||||
if "Sending Authentication Request " in line:
|
||||
if 'Sending Authentication Request ' in line:
|
||||
self.status = None # Reset
|
||||
# (????): Please specify an ESSID (-e).
|
||||
elif "Please specify an ESSID" in line:
|
||||
elif 'Please specify an ESSID' in line:
|
||||
self.status = None
|
||||
# (FAIL): 00:57:43 Got a deauthentication packet! (Waiting 3 seconds)
|
||||
elif "Got a deauthentication packet!" in line:
|
||||
elif 'Got a deauthentication packet!' in line:
|
||||
self.status = False
|
||||
# (PASS): 20:17:25 Association successful :-) (AID: 1)
|
||||
# (PASS): 20:18:55 Reassociation successful :-) (AID: 1)
|
||||
elif "association successful :-)" in line.lower():
|
||||
elif 'association successful :-)' in line.lower():
|
||||
self.status = True
|
||||
elif self.attack_type == WEPAttackType.chopchop:
|
||||
# Look for chopchop status. Potential output lines:
|
||||
|
||||
# (START) Read 178 packets...
|
||||
read_re = re.compile(r"Read (\d+) packets")
|
||||
read_re = re.compile(r'Read (\d+) packets')
|
||||
matches = read_re.match(line)
|
||||
if matches:
|
||||
self.status = "Waiting for packet (read %s)..." % matches.group(1)
|
||||
self.status = 'Waiting for packet (read %s)...' % matches.group(1)
|
||||
|
||||
# Sent 1912 packets, current guess: 70...
|
||||
sent_re = re.compile(r"Sent (\d+) packets, current guess: (\w+)...")
|
||||
sent_re = re.compile(r'Sent (\d+) packets, current guess: (\w+)...')
|
||||
matches = sent_re.match(line)
|
||||
if matches:
|
||||
self.status = "Generating .xor (%s)... current guess: %s" % (self.xor_percent, matches.group(2))
|
||||
|
||||
self.status = 'Generating .xor (%s)... current guess: %s' % (self.xor_percent, matches.group(2))
|
||||
|
||||
# (DURING) Offset 52 (54% done) | xor = DE | pt = E0 | 152 frames written in 2782ms
|
||||
offset_re = re.compile(r"Offset.*\(\s*(\d+%) done\)")
|
||||
offset_re = re.compile(r'Offset.*\(\s*(\d+%) done\)')
|
||||
matches = offset_re.match(line)
|
||||
if matches:
|
||||
self.xor_percent = matches.group(1)
|
||||
self.status = "Generating .xor (%s)..." % self.xor_percent
|
||||
self.status = 'Generating .xor (%s)...' % self.xor_percent
|
||||
|
||||
# (DONE) Saving keystream in replay_dec-0516-202246.xor
|
||||
saving_re = re.compile(r"Saving keystream in (.*\.xor)")
|
||||
saving_re = re.compile(r'Saving keystream in (.*\.xor)')
|
||||
matches = saving_re.match(line)
|
||||
if matches:
|
||||
self.status = matches.group(1)
|
||||
@@ -171,17 +171,17 @@ class Aireplay(Thread, Dependency):
|
||||
# Parse fragment output, update self.status
|
||||
|
||||
# (START) Read 178 packets...
|
||||
read_re = re.compile(r"Read (\d+) packets")
|
||||
read_re = re.compile(r'Read (\d+) packets')
|
||||
matches = read_re.match(line)
|
||||
if matches:
|
||||
self.status = "Waiting for packet (read %s)..." % matches.group(1)
|
||||
self.status = 'Waiting for packet (read %s)...' % matches.group(1)
|
||||
|
||||
# 01:08:15 Waiting for a data packet...
|
||||
if 'Waiting for a data packet' in line:
|
||||
self.status = 'waiting for packet'
|
||||
|
||||
|
||||
# Read 207 packets...
|
||||
trying_re = re.compile(r"Trying to get (\d+) bytes of a keystream")
|
||||
trying_re = re.compile(r'Trying to get (\d+) bytes of a keystream')
|
||||
matches = trying_re.match(line)
|
||||
if matches:
|
||||
self.status = 'trying to get %sb of a keystream' % matches.group(1)
|
||||
@@ -195,7 +195,7 @@ class Aireplay(Thread, Dependency):
|
||||
self.status = 'sending another packet'
|
||||
|
||||
# XX:XX:XX Trying to get 1500 bytes of a keystream
|
||||
trying_re = re.compile(r"Trying to get (\d+) bytes of a keystream")
|
||||
trying_re = re.compile(r'Trying to get (\d+) bytes of a keystream')
|
||||
matches = trying_re.match(line)
|
||||
if matches:
|
||||
self.status = 'trying to get %sb of a keystream' % matches.group(1)
|
||||
@@ -209,7 +209,7 @@ class Aireplay(Thread, Dependency):
|
||||
self.status = 'relayed packet was our'
|
||||
|
||||
# XX:XX:XX Saving keystream in fragment-0124-161129.xor
|
||||
saving_re = re.compile(r"Saving keystream in (.*\.xor)")
|
||||
saving_re = re.compile(r'Saving keystream in (.*\.xor)')
|
||||
matches = saving_re.match(line)
|
||||
if matches:
|
||||
self.status = 'saving keystream to %s' % matches.group(1)
|
||||
@@ -220,14 +220,14 @@ class Aireplay(Thread, Dependency):
|
||||
# Parse Packets Sent & PacketsPerSecond. Possible output lines:
|
||||
# Read 55 packets (got 0 ARP requests and 0 ACKs), sent 0 packets...(0 pps)
|
||||
# Read 4467 packets (got 1425 ARP requests and 1417 ACKs), sent 1553 packets...(100 pps)
|
||||
read_re = re.compile(r"Read (\d+) packets \(got (\d+) ARP requests and (\d+) ACKs\), sent (\d+) packets...\((\d+) pps\)")
|
||||
read_re = re.compile(r'Read (\d+) packets \(got (\d+) ARP requests and (\d+) ACKs\), sent (\d+) packets...\((\d+) pps\)')
|
||||
matches = read_re.match(line)
|
||||
if matches:
|
||||
pps = matches.group(5)
|
||||
if pps == "0":
|
||||
self.status = "Waiting for packet..."
|
||||
if pps == '0':
|
||||
self.status = 'Waiting for packet...'
|
||||
else:
|
||||
self.status = "Replaying @ %s/sec" % pps
|
||||
self.status = 'Replaying @ %s/sec' % pps
|
||||
pass
|
||||
|
||||
def __del__(self):
|
||||
@@ -248,10 +248,10 @@ class Aireplay(Thread, Dependency):
|
||||
# Interface is required at this point
|
||||
Configuration.initialize()
|
||||
if Configuration.interface is None:
|
||||
raise Exception("Wireless interface must be defined (-i)")
|
||||
raise Exception('Wireless interface must be defined (-i)')
|
||||
|
||||
cmd = ["aireplay-ng"]
|
||||
cmd.append("--ignore-negative-one")
|
||||
cmd = ['aireplay-ng']
|
||||
cmd.append('--ignore-negative-one')
|
||||
|
||||
if client_mac is None and len(target.clients) > 0:
|
||||
# Client MAC wasn't specified, but there's an associated client. Use that.
|
||||
@@ -263,87 +263,87 @@ class Aireplay(Thread, Dependency):
|
||||
|
||||
if attack_type == WEPAttackType.fakeauth:
|
||||
cmd.extend([
|
||||
"--fakeauth", "30", # Fake auth every 30 seconds
|
||||
"-Q", # Send re-association packets
|
||||
"-a", target.bssid
|
||||
'--fakeauth', '30', # Fake auth every 30 seconds
|
||||
'-Q', # Send re-association packets
|
||||
'-a', target.bssid
|
||||
])
|
||||
if target.essid_known:
|
||||
cmd.extend(["-e", target.essid])
|
||||
cmd.extend(['-e', target.essid])
|
||||
elif attack_type == WEPAttackType.replay:
|
||||
cmd.extend([
|
||||
"--arpreplay",
|
||||
"-b", target.bssid,
|
||||
"-x", str(Configuration.wep_pps)
|
||||
'--arpreplay',
|
||||
'-b', target.bssid,
|
||||
'-x', str(Configuration.wep_pps)
|
||||
])
|
||||
if client_mac:
|
||||
cmd.extend(["-h", client_mac])
|
||||
cmd.extend(['-h', client_mac])
|
||||
|
||||
elif attack_type == WEPAttackType.chopchop:
|
||||
cmd.extend([
|
||||
"--chopchop",
|
||||
"-b", target.bssid,
|
||||
"-x", str(Configuration.wep_pps),
|
||||
#"-m", "60", # Minimum packet length (bytes)
|
||||
#"-n", "82", # Maximum packet length
|
||||
"-F" # Automatically choose first packet
|
||||
'--chopchop',
|
||||
'-b', target.bssid,
|
||||
'-x', str(Configuration.wep_pps),
|
||||
#'-m', '60', # Minimum packet length (bytes)
|
||||
#'-n', '82', # Maximum packet length
|
||||
'-F' # Automatically choose first packet
|
||||
])
|
||||
if client_mac:
|
||||
cmd.extend(["-h", client_mac])
|
||||
cmd.extend(['-h', client_mac])
|
||||
|
||||
elif attack_type == WEPAttackType.fragment:
|
||||
cmd.extend([
|
||||
"--fragment",
|
||||
"-b", target.bssid,
|
||||
"-x", str(Configuration.wep_pps),
|
||||
"-m", "100", # Minimum packet length (bytes)
|
||||
"-F" # Automatically choose first packet
|
||||
'--fragment',
|
||||
'-b', target.bssid,
|
||||
'-x', str(Configuration.wep_pps),
|
||||
'-m', '100', # Minimum packet length (bytes)
|
||||
'-F' # Automatically choose first packet
|
||||
])
|
||||
if client_mac:
|
||||
cmd.extend(["-h", client_mac])
|
||||
cmd.extend(['-h', client_mac])
|
||||
|
||||
elif attack_type == WEPAttackType.caffelatte:
|
||||
if len(target.clients) == 0:
|
||||
# Unable to carry out caffe-latte attack
|
||||
raise Exception("Client is required for caffe-latte attack")
|
||||
raise Exception('Client is required for caffe-latte attack')
|
||||
cmd.extend([
|
||||
"--caffe-latte",
|
||||
"-b", target.bssid,
|
||||
"-h", target.clients[0].station
|
||||
'--caffe-latte',
|
||||
'-b', target.bssid,
|
||||
'-h', target.clients[0].station
|
||||
])
|
||||
|
||||
elif attack_type == WEPAttackType.p0841:
|
||||
cmd.extend([
|
||||
"--arpreplay",
|
||||
"-b", target.bssid,
|
||||
"-c", "ff:ff:ff:ff:ff:ff",
|
||||
"-x", str(Configuration.wep_pps),
|
||||
"-F", # Automatically choose first packet
|
||||
"-p", "0841"
|
||||
'--arpreplay',
|
||||
'-b', target.bssid,
|
||||
'-c', 'ff:ff:ff:ff:ff:ff',
|
||||
'-x', str(Configuration.wep_pps),
|
||||
'-F', # Automatically choose first packet
|
||||
'-p', '0841'
|
||||
])
|
||||
if client_mac:
|
||||
cmd.extend(["-h", client_mac])
|
||||
cmd.extend(['-h', client_mac])
|
||||
|
||||
elif attack_type == WEPAttackType.hirte:
|
||||
if client_mac is None:
|
||||
# Unable to carry out hirte attack
|
||||
raise Exception("Client is required for hirte attack")
|
||||
raise Exception('Client is required for hirte attack')
|
||||
cmd.extend([
|
||||
"--cfrag",
|
||||
"-h", client_mac
|
||||
'--cfrag',
|
||||
'-h', client_mac
|
||||
])
|
||||
elif attack_type == WEPAttackType.forgedreplay:
|
||||
if client_mac is None or replay_file is None:
|
||||
raise Exception("Client_mac and Replay_File are required for arp replay")
|
||||
raise Exception('Client_mac and Replay_File are required for arp replay')
|
||||
cmd.extend([
|
||||
"--arpreplay",
|
||||
"-b", target.bssid,
|
||||
"-h", client_mac,
|
||||
"-r", replay_file,
|
||||
"-F", # Automatically choose first packet
|
||||
"-x", str(Configuration.wep_pps)
|
||||
'--arpreplay',
|
||||
'-b', target.bssid,
|
||||
'-h', client_mac,
|
||||
'-r', replay_file,
|
||||
'-F', # Automatically choose first packet
|
||||
'-x', str(Configuration.wep_pps)
|
||||
])
|
||||
else:
|
||||
raise Exception("Unexpected attack type: %s" % attack_type)
|
||||
raise Exception('Unexpected attack type: %s' % attack_type)
|
||||
|
||||
cmd.append(Configuration.interface)
|
||||
return cmd
|
||||
@@ -388,18 +388,18 @@ class Aireplay(Thread, Dependency):
|
||||
def deauth(target_bssid, essid=None, client_mac=None, num_deauths=None, timeout=2):
|
||||
num_deauths = num_deauths or Configuration.num_deauths
|
||||
deauth_cmd = [
|
||||
"aireplay-ng",
|
||||
"-0", # Deauthentication
|
||||
'aireplay-ng',
|
||||
'-0', # Deauthentication
|
||||
str(num_deauths),
|
||||
"--ignore-negative-one",
|
||||
"-a", target_bssid, # Target AP
|
||||
"-D" # Skip AP detection
|
||||
'--ignore-negative-one',
|
||||
'-a', target_bssid, # Target AP
|
||||
'-D' # Skip AP detection
|
||||
]
|
||||
if client_mac is not None:
|
||||
# Station-specific deauth
|
||||
deauth_cmd.extend(["-c", client_mac])
|
||||
deauth_cmd.extend(['-c', client_mac])
|
||||
if essid:
|
||||
deauth_cmd.extend(["-e", essid])
|
||||
deauth_cmd.extend(['-e', essid])
|
||||
deauth_cmd.append(Configuration.interface)
|
||||
proc = Process(deauth_cmd)
|
||||
while proc.poll() is None:
|
||||
@@ -450,23 +450,3 @@ if __name__ == '__main__':
|
||||
t = WEPAttackType(t)
|
||||
print(t.name, type(t.name), t.value)
|
||||
|
||||
from ..model.target import Target
|
||||
fields = 'A4:2B:8C:16:6B:3A, 2015-05-27 19:28:44, 2015-05-27 19:28:46, 6, 54e, WEP, WEP, , -58, 2, 0, 0. 0. 0. 0, 9, Test Router Please Ignore, '.split(',')
|
||||
t = Target(fields)
|
||||
|
||||
'''
|
||||
aireplay = Aireplay(t, 'replay')
|
||||
while aireplay.is_running():
|
||||
from time import sleep
|
||||
sleep(0.1)
|
||||
stdout, stderr = aireplay.get_output()
|
||||
print("STDOUT>", stdout)
|
||||
print("STDERR>", stderr)
|
||||
'''
|
||||
|
||||
'''
|
||||
forge = Aireplay.forge_packet('/tmp/replay_dec-0605-060243.xor', \
|
||||
'A4:2B:8C:16:6B:3A', \
|
||||
'00:C0:CA:4E:CA:E0')
|
||||
print(forge)
|
||||
'''
|
||||
|
||||
@@ -75,7 +75,7 @@ class Airmon(Dependency):
|
||||
''' Prints menu '''
|
||||
print(AirmonIface.menu_header())
|
||||
for idx, iface in enumerate(self.interfaces, start=1):
|
||||
Color.pl(" {G}%d{W}. %s" % (idx, iface))
|
||||
Color.pl(' {G}%d{W}. %s' % (idx, iface))
|
||||
|
||||
def get(self, index):
|
||||
''' Gets interface at index (starts at 1) '''
|
||||
@@ -166,10 +166,10 @@ class Airmon(Dependency):
|
||||
iface_name = iface
|
||||
driver = None
|
||||
|
||||
# Remember this as the "base" interface.
|
||||
# Remember this as the 'base' interface.
|
||||
Airmon.base_interface = iface_name
|
||||
|
||||
Color.p("{+} enabling {G}monitor mode{W} on {C}%s{W}... " % iface_name)
|
||||
Color.p('{+} enabling {G}monitor mode{W} on {C}%s{W}... ' % iface_name)
|
||||
|
||||
airmon_output = Process(['airmon-ng', 'start', iface_name]).stdout()
|
||||
|
||||
@@ -180,22 +180,22 @@ class Airmon(Dependency):
|
||||
enabled_iface = Airmon.start_bad_driver(iface_name)
|
||||
|
||||
if enabled_iface is None:
|
||||
Color.pl("{R}failed{W}")
|
||||
Color.pl('{R}failed{W}')
|
||||
|
||||
monitor_interfaces = Iwconfig.get_interfaces(mode='Monitor')
|
||||
|
||||
# Assert that there is an interface in monitor mode
|
||||
if len(monitor_interfaces) == 0:
|
||||
Color.pl("{R}failed{W}")
|
||||
raise Exception("Cannot find any interfaces in Mode:Monitor")
|
||||
Color.pl('{R}failed{W}')
|
||||
raise Exception('Cannot find any interfaces in Mode:Monitor')
|
||||
|
||||
# Assert that the interface enabled by airmon-ng is in monitor mode
|
||||
if enabled_iface not in monitor_interfaces:
|
||||
Color.pl("{R}failed{W}")
|
||||
raise Exception("Cannot find %s with Mode:Monitor" % enabled_iface)
|
||||
Color.pl('{R}failed{W}')
|
||||
raise Exception('Cannot find %s with Mode:Monitor' % enabled_iface)
|
||||
|
||||
# No errors found; the device 'enabled_iface' was put into Mode:Monitor.
|
||||
Color.pl("{G}enabled {C}%s{W}" % enabled_iface)
|
||||
Color.pl('{G}enabled {C}%s{W}' % enabled_iface)
|
||||
|
||||
return enabled_iface
|
||||
|
||||
@@ -216,7 +216,7 @@ class Airmon(Dependency):
|
||||
|
||||
@staticmethod
|
||||
def stop(iface):
|
||||
Color.p("{!} {R}disabling {O}monitor mode{O} on {R}%s{O}... " % iface)
|
||||
Color.p('{!} {R}disabling {O}monitor mode{O} on {R}%s{O}... ' % iface)
|
||||
|
||||
airmon_output = Process(['airmon-ng', 'stop', iface]).stdout()
|
||||
|
||||
@@ -307,7 +307,7 @@ class Airmon(Dependency):
|
||||
choice = 1
|
||||
else:
|
||||
# Multiple interfaces found
|
||||
question = Color.s("{+} select interface ({G}1-%d{W}): " % (count))
|
||||
question = Color.s('{+} select interface ({G}1-%d{W}): ' % (count))
|
||||
choice = raw_input(question)
|
||||
|
||||
iface = a.get(choice)
|
||||
@@ -368,26 +368,26 @@ class Airmon(Dependency):
|
||||
|
||||
@staticmethod
|
||||
def put_interface_up(iface):
|
||||
Color.p("{!} {O}putting interface {R}%s up{O}..." % (iface))
|
||||
Color.p('{!} {O}putting interface {R}%s up{O}...' % (iface))
|
||||
Ifconfig.up(iface)
|
||||
Color.pl(" {G}done{W}")
|
||||
Color.pl(' {G}done{W}')
|
||||
|
||||
@staticmethod
|
||||
def start_network_manager():
|
||||
Color.p("{!} {O}restarting {R}NetworkManager{O}...")
|
||||
Color.p('{!} {O}restarting {R}NetworkManager{O}...')
|
||||
|
||||
if Process.exists('service'):
|
||||
cmd = 'service network-manager start'
|
||||
proc = Process(cmd)
|
||||
(out, err) = proc.get_output()
|
||||
if proc.poll() != 0:
|
||||
Color.pl(" {R}Error executing {O}%s{W}" % cmd)
|
||||
if out is not None and out.strip() != "":
|
||||
Color.pl("{!} {O}STDOUT> %s{W}" % out)
|
||||
if err is not None and err.strip() != "":
|
||||
Color.pl("{!} {O}STDERR> %s{W}" % err)
|
||||
Color.pl(' {R}Error executing {O}%s{W}' % cmd)
|
||||
if out is not None and out.strip() != '':
|
||||
Color.pl('{!} {O}STDOUT> %s{W}' % out)
|
||||
if err is not None and err.strip() != '':
|
||||
Color.pl('{!} {O}STDERR> %s{W}' % err)
|
||||
else:
|
||||
Color.pl(" {G}done{W} ({C}%s{W})" % cmd)
|
||||
Color.pl(' {G}done{W} ({C}%s{W})' % cmd)
|
||||
return
|
||||
|
||||
if Process.exists('systemctl'):
|
||||
@@ -395,20 +395,20 @@ class Airmon(Dependency):
|
||||
proc = Process(cmd)
|
||||
(out, err) = proc.get_output()
|
||||
if proc.poll() != 0:
|
||||
Color.pl(" {R}Error executing {O}%s{W}" % cmd)
|
||||
if out is not None and out.strip() != "":
|
||||
Color.pl("{!} {O}STDOUT> %s{W}" % out)
|
||||
if err is not None and err.strip() != "":
|
||||
Color.pl("{!} {O}STDERR> %s{W}" % err)
|
||||
Color.pl(' {R}Error executing {O}%s{W}' % cmd)
|
||||
if out is not None and out.strip() != '':
|
||||
Color.pl('{!} {O}STDOUT> %s{W}' % out)
|
||||
if err is not None and err.strip() != '':
|
||||
Color.pl('{!} {O}STDERR> %s{W}' % err)
|
||||
else:
|
||||
Color.pl(" {G}done{W} ({C}%s{W})" % cmd)
|
||||
Color.pl(' {G}done{W} ({C}%s{W})' % cmd)
|
||||
return
|
||||
else:
|
||||
Color.pl(" {R}can't restart NetworkManager: {O}systemctl{R} or {O}service{R} not found{W}")
|
||||
Color.pl(' {R}cannot restart NetworkManager: {O}systemctl{R} or {O}service{R} not found{W}')
|
||||
|
||||
if __name__ == '__main__':
|
||||
Airmon.terminate_conflicting_processes()
|
||||
iface = Airmon.ask()
|
||||
(disabled_iface, enabled_iface) = Airmon.stop(iface)
|
||||
print("Disabled:", disabled_iface)
|
||||
print("Enabled:", enabled_iface)
|
||||
print('Disabled:', disabled_iface)
|
||||
print('Enabled:', enabled_iface)
|
||||
|
||||
@@ -27,7 +27,7 @@ class Airodump(Dependency):
|
||||
if interface is None:
|
||||
interface = Configuration.interface
|
||||
if interface is None:
|
||||
raise Exception("Wireless interface must be defined (-i)")
|
||||
raise Exception('Wireless interface must be defined (-i)')
|
||||
self.interface = interface
|
||||
|
||||
self.targets = []
|
||||
@@ -206,22 +206,22 @@ class Airodump(Dependency):
|
||||
|
||||
hit_clients = False
|
||||
for row in csv_reader:
|
||||
# Each "row" is a list of fields for a target/client
|
||||
# Each 'row' is a list of fields for a target/client
|
||||
|
||||
if len(row) == 0: continue
|
||||
|
||||
if row[0].strip() == 'BSSID':
|
||||
# This is the "header" for the list of Targets
|
||||
# This is the 'header' for the list of Targets
|
||||
hit_clients = False
|
||||
continue
|
||||
|
||||
elif row[0].strip() == 'Station MAC':
|
||||
# This is the "header" for the list of Clients
|
||||
# This is the 'header' for the list of Clients
|
||||
hit_clients = True
|
||||
continue
|
||||
|
||||
if hit_clients:
|
||||
# The current row corresponds to a "Client" (computer)
|
||||
# The current row corresponds to a 'Client' (computer)
|
||||
try:
|
||||
client = Client(row)
|
||||
except (IndexError, ValueError) as e:
|
||||
@@ -239,7 +239,7 @@ class Airodump(Dependency):
|
||||
break
|
||||
|
||||
else:
|
||||
# The current row corresponds to a "Target" (router)
|
||||
# The current row corresponds to a 'Target' (router)
|
||||
try:
|
||||
target = Target(row)
|
||||
targets.append(target)
|
||||
|
||||
@@ -23,7 +23,7 @@ class Bully(Attack, Dependency):
|
||||
self.total_timeouts = 0
|
||||
self.total_failures = 0
|
||||
self.locked = False
|
||||
self.state = "{O}Waiting for beacon{W}"
|
||||
self.state = '{O}Waiting for beacon{W}'
|
||||
self.start_time = time.time()
|
||||
|
||||
self.cracked_pin = self.cracked_key = self.cracked_bssid = self.cracked_essid = None
|
||||
@@ -35,17 +35,17 @@ class Bully(Attack, Dependency):
|
||||
|
||||
if Process.exists('stdbuf'):
|
||||
self.cmd.extend([
|
||||
"stdbuf", "-o0" # No buffer. See https://stackoverflow.com/a/40453613/7510292
|
||||
'stdbuf', '-o0' # No buffer. See https://stackoverflow.com/a/40453613/7510292
|
||||
])
|
||||
|
||||
self.cmd.extend([
|
||||
"bully",
|
||||
"--bssid", target.bssid,
|
||||
"--channel", target.channel,
|
||||
"--detectlock", # Detect WPS lockouts unreported by AP
|
||||
"--force",
|
||||
"-v", "4",
|
||||
"--pixiewps",
|
||||
'bully',
|
||||
'--bssid', target.bssid,
|
||||
'--channel', target.channel,
|
||||
'--detectlock', # Detect WPS lockouts unreported by AP
|
||||
'--force',
|
||||
'-v', '4',
|
||||
'--pixiewps',
|
||||
Configuration.interface
|
||||
])
|
||||
|
||||
@@ -58,7 +58,7 @@ class Bully(Attack, Dependency):
|
||||
skip_wps=True,
|
||||
output_file_prefix='wps_pin') as airodump:
|
||||
# Wait for target
|
||||
self.pattack("Waiting for target to appear...")
|
||||
self.pattack('Waiting for target to appear...')
|
||||
self.target = self.wait_for_target(airodump)
|
||||
|
||||
# Start bully
|
||||
@@ -111,7 +111,7 @@ class Bully(Attack, Dependency):
|
||||
raise e
|
||||
|
||||
if self.crack_result is None:
|
||||
self.pattack("{R}Failed{W}", newline=True)
|
||||
self.pattack('{R}Failed{W}', newline=True)
|
||||
|
||||
|
||||
def pattack(self, message, newline=False):
|
||||
@@ -119,12 +119,12 @@ class Bully(Attack, Dependency):
|
||||
time_left = Configuration.wps_pixie_timeout - self.running_time()
|
||||
|
||||
Color.clear_entire_line()
|
||||
Color.pattack("WPS",
|
||||
Color.pattack('WPS',
|
||||
self.target,
|
||||
'Pixie-Dust',
|
||||
'{W}[{C}%s{W}] %s' % (Timer.secs_to_str(time_left), message))
|
||||
if newline:
|
||||
Color.pl("")
|
||||
Color.pl('')
|
||||
|
||||
|
||||
def running_time(self):
|
||||
@@ -136,13 +136,13 @@ class Bully(Attack, Dependency):
|
||||
|
||||
meta_statuses = []
|
||||
if self.total_timeouts > 0:
|
||||
meta_statuses.append("{O}Timeouts:%d{W}" % self.total_timeouts)
|
||||
meta_statuses.append('{O}Timeouts:%d{W}' % self.total_timeouts)
|
||||
|
||||
if self.total_failures > 0:
|
||||
meta_statuses.append("{O}WPSFail:%d{W}" % self.total_failures)
|
||||
meta_statuses.append('{O}WPSFail:%d{W}' % self.total_failures)
|
||||
|
||||
if self.locked:
|
||||
meta_statuses.append("{R}Locked{W}")
|
||||
meta_statuses.append('{R}Locked{W}')
|
||||
|
||||
if len(meta_statuses) > 0:
|
||||
main_status += ' (%s)' % ', '.join(meta_statuses)
|
||||
@@ -151,13 +151,13 @@ class Bully(Attack, Dependency):
|
||||
|
||||
|
||||
def parse_line_thread(self):
|
||||
for line in iter(self.bully_proc.pid.stdout.readline, b""):
|
||||
if line == "": continue
|
||||
line = line.replace("\r", "").replace("\n", "").strip()
|
||||
for line in iter(self.bully_proc.pid.stdout.readline, b''):
|
||||
if line == '': continue
|
||||
line = line.replace('\r', '').replace('\n', '').strip()
|
||||
|
||||
if Configuration.verbose > 1:
|
||||
Color.pe('\n{P} [bully:stdout] %s' % line)
|
||||
|
||||
|
||||
self.state = self.parse_state(line)
|
||||
|
||||
self.crack_result = self.parse_crack_result(line)
|
||||
@@ -189,9 +189,9 @@ class Bully(Attack, Dependency):
|
||||
|
||||
if self.cracked_pin is not None:
|
||||
# Mention the PIN & that we're not done yet.
|
||||
self.pattack("{G}Cracked PIN: {C}%s{W}" % self.cracked_pin, newline=True)
|
||||
self.pattack('{G}Cracked PIN: {C}%s{W}' % self.cracked_pin, newline=True)
|
||||
|
||||
self.state = "{G}Finding PSK...{C}"
|
||||
self.state = '{G}Finding PSK...{C}'
|
||||
time.sleep(2)
|
||||
|
||||
###########################
|
||||
@@ -201,13 +201,13 @@ class Bully(Attack, Dependency):
|
||||
self.cracked_key = key_re.group(1)
|
||||
|
||||
if not self.crack_result and self.cracked_pin and self.cracked_key:
|
||||
self.pattack("{G}Cracked PSK: {C}%s{W}" % self.cracked_key, newline=True)
|
||||
self.pattack('{G}Cracked PSK: {C}%s{W}' % self.cracked_key, newline=True)
|
||||
self.crack_result = CrackResultWPS(
|
||||
self.target.bssid,
|
||||
self.target.essid,
|
||||
self.cracked_pin,
|
||||
self.cracked_key)
|
||||
Color.pl("")
|
||||
Color.pl('')
|
||||
self.crack_result.dump()
|
||||
|
||||
return self.crack_result
|
||||
@@ -220,14 +220,14 @@ class Bully(Attack, Dependency):
|
||||
got_beacon = re.search(r".*Got beacon for '(.*)' \((.*)\)", line)
|
||||
if got_beacon:
|
||||
# group(1)=ESSID, group(2)=BSSID
|
||||
state = "Got beacon"
|
||||
state = 'Got beacon'
|
||||
|
||||
# [+] Last State = 'NoAssoc' Next pin '48855501'
|
||||
last_state = re.search(r".*Last State = '(.*)'\s*Next pin '(.*)'", line)
|
||||
if last_state:
|
||||
# group(1)=result, group(2)=PIN
|
||||
pin = last_state.group(2)
|
||||
state = "Trying PIN {C}%s{W} (%s)" % (pin, last_state.group(1))
|
||||
state = 'Trying PIN {C}%s{W} (%s)' % (pin, last_state.group(1))
|
||||
|
||||
# [+] Tx( Auth ) = 'Timeout' Next pin '80241263'
|
||||
mx_result_pin = re.search(r".*[RT]x\(\s*(.*)\s*\) = '(.*)'\s*Next pin '(.*)'", line)
|
||||
@@ -238,42 +238,42 @@ class Bully(Attack, Dependency):
|
||||
result = mx_result_pin.group(2) # NoAssoc, WPSFail, Pin1Bad, Pin2Bad
|
||||
pin = mx_result_pin.group(3)
|
||||
|
||||
if result == "Timeout":
|
||||
if result == 'Timeout':
|
||||
self.total_timeouts += 1
|
||||
result = "{O}%s{W}" % result
|
||||
elif result == "WPSFail":
|
||||
result = '{O}%s{W}' % result
|
||||
elif result == 'WPSFail':
|
||||
self.total_failures += 1
|
||||
result = "{O}%s{W}" % result
|
||||
elif result == "NoAssoc":
|
||||
result = "{O}%s{W}" % result
|
||||
result = '{O}%s{W}' % result
|
||||
elif result == 'NoAssoc':
|
||||
result = '{O}%s{W}' % result
|
||||
else:
|
||||
result = "{R}%s{W}" % result
|
||||
result = '{R}%s{W}' % result
|
||||
|
||||
result = "{P}%s{W}:%s" % (m_state.strip(), result.strip())
|
||||
state = "Trying PIN {C}%s{W} (%s)" % (pin, result)
|
||||
result = '{P}%s{W}:%s' % (m_state.strip(), result.strip())
|
||||
state = 'Trying PIN {C}%s{W} (%s)' % (pin, result)
|
||||
|
||||
# [!] WPS lockout reported, sleeping for 43 seconds ...
|
||||
re_lockout = re.search(r".*WPS lockout reported, sleeping for (\d+) seconds", line)
|
||||
if re_lockout:
|
||||
self.locked = True
|
||||
sleeping = re_lockout.group(1)
|
||||
state = "{R}WPS Lock-out: {O}Waiting %s seconds{W}" % sleeping
|
||||
state = '{R}WPS Lock-out: {O}Waiting %s seconds{W}' % sleeping
|
||||
|
||||
# [Pixie-Dust] WPS pin not found
|
||||
re_pin_not_found = re.search(r".*\[Pixie-Dust\] WPS pin not found", line)
|
||||
if re_pin_not_found:
|
||||
state = "{R}Failed: {O}Bully says 'WPS pin not found'{W}"
|
||||
state = '{R}Failed: {O}Bully says "WPS pin not found"{W}'
|
||||
|
||||
# [+] Running pixiewps with the information, wait ...
|
||||
re_running_pixiewps = re.search(r".*Running pixiewps with the information", line)
|
||||
if re_running_pixiewps:
|
||||
state = "{G}Running pixiewps...{W}"
|
||||
state = '{G}Running pixiewps...{W}'
|
||||
|
||||
return state
|
||||
|
||||
|
||||
def stop(self):
|
||||
if hasattr(self, "pid") and self.pid and self.pid.poll() is None:
|
||||
if hasattr(self, 'pid') and self.pid and self.pid.poll() is None:
|
||||
self.pid.interrupt()
|
||||
|
||||
|
||||
@@ -283,7 +283,7 @@ class Bully(Attack, Dependency):
|
||||
|
||||
@staticmethod
|
||||
def get_psk_from_pin(target, pin):
|
||||
# Fetches PSK from a Target assuming "pin" is the correct PIN
|
||||
# Fetches PSK from a Target assuming 'pin' is the correct PIN
|
||||
'''
|
||||
bully --channel 1 --bssid 34:21:09:01:92:7C --pin 01030365 --bruteforce wlan0mon
|
||||
PIN : '01030365'
|
||||
@@ -319,7 +319,7 @@ if __name__ == '__main__':
|
||||
fields = '34:21:09:01:92:7C,2015-05-27 19:28:44,2015-05-27 19:28:46,1,54,WPA2,CCMP TKIP,PSK,-58,2,0,0.0.0.0,9,AirLink89300,'.split(',')
|
||||
target = Target(fields)
|
||||
psk = Bully.get_psk_from_pin(target, '01030365')
|
||||
print("psk", psk)
|
||||
print('psk', psk)
|
||||
|
||||
'''
|
||||
stdout = " [*] Pin is '11867722', key is '9a6f7997'"
|
||||
|
||||
@@ -9,7 +9,7 @@ class Dependency(object):
|
||||
for attr_name in cls.required_attr_names:
|
||||
if not attr_name in cls.__dict__:
|
||||
raise NotImplementedError(
|
||||
"Attribute '{}' has not been overridden in class '{}'" \
|
||||
'Attribute "{}" has not been overridden in class "{}"' \
|
||||
.format(attr_name, cls.__name__)
|
||||
)
|
||||
|
||||
@@ -65,10 +65,10 @@ class Dependency(object):
|
||||
|
||||
if cls.dependency_required:
|
||||
Color.pp('{!} {R}error: required app {O}%s{R} was not found' % cls.dependency_name)
|
||||
Color.pl(' {W}install @ {C}%s{W}' % cls.dependency_url)
|
||||
Color.pl('. {W}install @ {C}%s{W}' % cls.dependency_url)
|
||||
return True
|
||||
|
||||
else:
|
||||
Color.p('{!} {O}warning: recommended app {R}%s{O} was not found' % cls.dependency_name)
|
||||
Color.pl(' {W}install @ {C}%s{W}' % cls.dependency_url)
|
||||
Color.pl('. {W}install @ {C}%s{W}' % cls.dependency_url)
|
||||
return False
|
||||
|
||||
@@ -71,12 +71,12 @@ class HcxDumpTool(Dependency):
|
||||
os.remove(pcapng_file)
|
||||
|
||||
command = [
|
||||
"hcxdumptool",
|
||||
"-i", Configuration.interface,
|
||||
"--filterlist", filterlist,
|
||||
"--filtermode", "2",
|
||||
"-c", str(target.channel),
|
||||
"-o", pcapng_file
|
||||
'hcxdumptool',
|
||||
'-i', Configuration.interface,
|
||||
'--filterlist', filterlist,
|
||||
'--filtermode', '2',
|
||||
'-c', str(target.channel),
|
||||
'-o', pcapng_file
|
||||
]
|
||||
|
||||
self.proc = Process(command)
|
||||
|
||||
@@ -56,7 +56,7 @@ class Pyrit(Dependency):
|
||||
|
||||
elif current_bssid is not None and current_essid is not None:
|
||||
# We hit an AP that we care about.
|
||||
# Line does not contain AccessPoint, see if it's "good"
|
||||
# Line does not contain AccessPoint, see if it's 'good'
|
||||
if ', good' in line:
|
||||
bssid_essid_pairs.add( (current_bssid, current_essid) )
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ class Reaver(Attack, Dependency):
|
||||
output_file_prefix='pixie') as airodump:
|
||||
|
||||
# Wait for target
|
||||
self.pattack("Waiting for target to appear...")
|
||||
self.pattack('Waiting for target to appear...')
|
||||
self.target = self.wait_for_target(airodump)
|
||||
|
||||
# Start reaver
|
||||
@@ -125,13 +125,13 @@ class Reaver(Attack, Dependency):
|
||||
|
||||
meta_statuses = []
|
||||
if self.total_timeouts > 0:
|
||||
meta_statuses.append("{O}Timeouts:%d{W}" % self.total_timeouts)
|
||||
meta_statuses.append('{O}Timeouts:%d{W}' % self.total_timeouts)
|
||||
|
||||
if self.total_wpsfails > 0:
|
||||
meta_statuses.append("{O}WPSFail:%d{W}" % self.total_wpsfails)
|
||||
meta_statuses.append('{O}WPSFail:%d{W}' % self.total_wpsfails)
|
||||
|
||||
if self.locked:
|
||||
meta_statuses.append("{R}Locked{W}")
|
||||
meta_statuses.append('{R}Locked{W}')
|
||||
|
||||
if len(meta_statuses) > 0:
|
||||
main_status += ' (%s)' % ', '.join(meta_statuses)
|
||||
@@ -159,7 +159,7 @@ class Reaver(Attack, Dependency):
|
||||
self.pattack('{W}Retrieving PSK using {C}bully{W}...')
|
||||
psk = Bully.get_psk_from_pin(self.target, pin)
|
||||
if psk is None:
|
||||
Color.pl("")
|
||||
Color.pl('')
|
||||
self.pattack('{R}Failed {O}to get PSK using bully', newline=True)
|
||||
else:
|
||||
self.pattack('{G}Cracked WPS PSK: {C}%s' % psk, newline=True)
|
||||
@@ -231,12 +231,12 @@ class Reaver(Attack, Dependency):
|
||||
time_left = Configuration.wps_pixie_timeout - self.running_time()
|
||||
|
||||
Color.clear_entire_line()
|
||||
Color.pattack("WPS",
|
||||
Color.pattack('WPS',
|
||||
self.target,
|
||||
'Pixie-Dust',
|
||||
'{W}[{C}%s{W}] %s' % (Timer.secs_to_str(time_left), message))
|
||||
if newline:
|
||||
Color.pl("")
|
||||
Color.pl('')
|
||||
|
||||
|
||||
def running_time(self):
|
||||
@@ -256,19 +256,19 @@ class Reaver(Attack, Dependency):
|
||||
|
||||
# Check for PSK.
|
||||
# Note: Reaver 1.6.x does not appear to return PSK (?)
|
||||
regex = re.search("WPA PSK: *'(.+)'", stdout)
|
||||
regex = re.search(r"WPA PSK: *'(.+)'", stdout)
|
||||
if regex:
|
||||
psk = regex.group(1)
|
||||
|
||||
# Check for SSID
|
||||
"""1.x [Reaver Test] [+] AP SSID: 'Test Router' """
|
||||
'''1.x [Reaver Test] [+] AP SSID: 'Test Router' '''
|
||||
regex = re.search(r"AP SSID:\s*'(.*)'", stdout)
|
||||
if regex:
|
||||
ssid = regex.group(1)
|
||||
|
||||
# Check (again) for SSID
|
||||
if ssid is None:
|
||||
"""1.6.x [+] Associated with EC:1A:59:37:70:0E (ESSID: belkin.00e)"""
|
||||
'''1.6.x [+] Associated with EC:1A:59:37:70:0E (ESSID: belkin.00e)'''
|
||||
regex = re.search(r"Associated with [0-9A-F:]+ \(ESSID: (.*)\)", stdout)
|
||||
if regex:
|
||||
ssid = regex.group(1)
|
||||
@@ -349,15 +349,15 @@ executing pixiewps -e d0141b15656e96b85fcead2e8e76330d2b1ac1576bb026e7a328c0e1ba
|
||||
(pin, psk, ssid) = Reaver.get_pin_psk_ssid(old_stdout)
|
||||
assert pin == '12345678', 'pin was "%s", should have been "12345678"' % pin
|
||||
assert psk == 'Test PSK', 'psk was "%s", should have been "Test PSK"' % psk
|
||||
assert ssid == "Test Router", 'ssid was %s, should have been Test Router' % repr(ssid)
|
||||
assert ssid == 'Test Router', 'ssid was %s, should have been Test Router' % repr(ssid)
|
||||
result = CrackResultWPS('AA:BB:CC:DD:EE:FF', ssid, pin, psk)
|
||||
result.dump()
|
||||
|
||||
print("")
|
||||
print('')
|
||||
|
||||
(pin, psk, ssid) = Reaver.get_pin_psk_ssid(new_stdout)
|
||||
assert pin == '11867722', 'pin was "%s", should have been "11867722"' % pin
|
||||
assert psk == None, 'psk was "%s", should have been "None"' % psk
|
||||
assert ssid == "belkin.00e", 'ssid was "%s", should have been "belkin.00e"' % repr(ssid)
|
||||
assert ssid == 'belkin.00e', 'ssid was "%s", should have been "belkin.00e"' % repr(ssid)
|
||||
result = CrackResultWPS('AA:BB:CC:DD:EE:FF', ssid, pin, psk)
|
||||
result.dump()
|
||||
|
||||
@@ -20,7 +20,7 @@ class Tshark(Dependency):
|
||||
|
||||
@staticmethod
|
||||
def _extract_src_dst_index_total(line):
|
||||
# Extract BSSIDs, handshake # (1-4) and handshake "total" (4)
|
||||
# Extract BSSIDs, handshake # (1-4) and handshake 'total' (4)
|
||||
mac_regex = ('[a-zA-Z0-9]{2}:' * 6)[:-1]
|
||||
match = re.search('(%s)\s*.*\s*(%s).*Message.*(\d).*of.*(\d)' % (mac_regex, mac_regex), line)
|
||||
if match is None:
|
||||
@@ -135,7 +135,7 @@ class Tshark(Dependency):
|
||||
|
||||
(src, dst, essid) = match.groups()
|
||||
|
||||
if dst.lower() == "ff:ff:ff:ff:ff:ff":
|
||||
if dst.lower() == 'ff:ff:ff:ff:ff:ff':
|
||||
continue # Skip broadcast packets
|
||||
|
||||
if bssid is not None:
|
||||
|
||||
Reference in New Issue
Block a user