Handshake detection, various bug fixes.

This commit is contained in:
derv82
2015-05-30 05:35:36 -07:00
parent 1c99b74cf2
commit aebc38c631
9 changed files with 578 additions and 48 deletions

View File

@@ -5,41 +5,108 @@ from Process import Process
class WEPAttackType(object): class WEPAttackType(object):
''' Enumeration of different WEP attack types ''' ''' Enumeration of different WEP attack types '''
replay = 0 fakeauth = 0
chopchop = 1 replay = 1
fragmentation = 2 chopchop = 2
caffelatte = 3 fragment = 3
p0841 = 4 caffelatte = 4
hirte = 5 p0841 = 5
hirte = 6
def __init__(self, var):
'''
Sets appropriate attack name/value given an input.
Args:
var - Can be a string, number, or WEPAttackType object
This object's name & value is set depending on var.
'''
self.value = None
self.name = None
if type(var) == int:
for (name,value) in WEPAttackType.__dict__.iteritems():
if type(value) == int:
if value == var:
self.name = name
self.value = value
return
raise Exception("Attack number %d not found" % var)
elif type(var) == str:
for (name,value) in WEPAttackType.__dict__.iteritems():
if type(value) == int:
if name == var:
self.name = name
self.value = value
return
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")
def __str__(self):
return self.name
class Aireplay(object): class Aireplay(object):
def __init__(self): def __init__(self, target, attack_type):
self.pid = None # Process instance for aireplay-ng '''
self.attack_type = None Starts aireplay process.
Args:
target - Instance of Target object, AP to attack.
attack_type - int, str, or WEPAttackType instance.
'''
cmd = Aireplay.get_aireplay_command(target, attack_type)
self.pid = Process(cmd, devnull=False)
def run_wep_attack(self, target, attack_type): def is_running(self):
''' Starts aireplay process ''' return self.pid.poll() == None
cmd = self.get_aireplay_command(target, attack_type)
if self.pid and self.pid.poll() != None:
# Aireplay is already running, kill it
self.pid.interrupt()
self.pid = Process(cmd, devnull=True)
def stop_wep_attack(self): def stop(self):
''' Stops aireplay process ''' ''' Stops aireplay process '''
if self.pid and self.pid.poll() != None: if self.pid and self.pid.poll() != None:
self.pid.interrupt() self.pid.interrupt()
def get_aireplay_command(self, target, attack_type): def get_output(self):
''' Generates aireplay command based on target and attack type ''' ''' Returns stdout from aireplay process '''
return self.pid.stdout()
@staticmethod
def get_aireplay_command(target, attack_type):
'''
Generates aireplay command based on target and attack type
Args:
target - Instance of Target object, AP to attack.
attack_type - int, str, or WEPAttackType instance.
'''
# Interface is required at this point
Configuration.initialize()
if Configuration.interface == None:
raise Exception("Wireless interface must be defined (-i)")
cmd = ['aireplay-ng'] cmd = ['aireplay-ng']
cmd.append('--ignore-negative-one') cmd.append('--ignore-negative-one')
client_mac = None client_mac = None
if len(target.clients) > 0: if len(target.clients) > 0:
client_mac = target.clients[0].station client_mac = target.clients[0].station
if attack_type == WEPAttackType.replay: # type(attack_type) might be str, int, or WEPAttackType.
# Find the appropriate attack enum.
attack_type = WEPAttackType(attack_type).value
if attack_type == WEPAttackType.fakeauth:
cmd.extend(['-1', '0']) # Fake auth, no delay
cmd.extend(['-a', target.bssid])
cmd.extend(['-T', '1']) # Make 1 attemp
if target.essid_known:
cmd.extend(['-e', target.essid])
# TODO Should we specify the source MAC as a client station?
#if client_mac:
# cmd.extend(['-h', client_mac])
elif attack_type == WEPAttackType.replay:
cmd.append('--arpreplay') cmd.append('--arpreplay')
cmd.extend(['-b', target.bssid]) cmd.extend(['-b', target.bssid])
cmd.extend(['-x', str(Configuration.wep_pps)]) cmd.extend(['-x', str(Configuration.wep_pps)])
@@ -56,7 +123,7 @@ class Aireplay(object):
if client_mac: if client_mac:
cmd.extend(['-h', client_mac]) cmd.extend(['-h', client_mac])
elif attack_type == WEPAttackType.fragmentation: elif attack_type == WEPAttackType.fragment:
cmd.append('--fragment') cmd.append('--fragment')
cmd.extend(['-b', target.bssid]) cmd.extend(['-b', target.bssid])
cmd.extend(['-x', str(Configuration.wep_pps)]) cmd.extend(['-x', str(Configuration.wep_pps)])
@@ -88,19 +155,27 @@ class Aireplay(object):
raise Exception("Client is required for hirte attack") raise Exception("Client is required for hirte attack")
cmd.append('--cfrag') cmd.append('--cfrag')
cmd.extend(['-h', client_mac]) cmd.extend(['-h', client_mac])
else:
raise Exception("Unexpected attack type: %s" % attack_type)
cmd.append(Configuration.interface) cmd.append(Configuration.interface)
return cmd return cmd
if __name__ == '__main__': if __name__ == '__main__':
Configuration.initialize() t = WEPAttackType(4)
print t.name, type(t.name), t.value
a = Aireplay() t = WEPAttackType('caffelatte')
print t.name, type(t.name), t.value
t = WEPAttackType(t)
print t.name, type(t.name), t.value
from Target import Target from Target import Target
fields = 'AA:BB:CC:DD:EE:FF, 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, HOME-ABCD, '.split(',') fields = 'AA:BB:CC:DD:EE:FF, 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, HOME-ABCD, '.split(',')
t = Target(fields) t = Target(fields)
print ' '.join(a.get_aireplay_command(t, WEPAttackType.replay)) cmd = Aireplay.get_aireplay_command(t, 'fakeauth')
print ' '.join(['"%s"' % a for a in cmd])

View File

@@ -19,7 +19,7 @@ class Airodump(object):
if interface == None: if interface == None:
interface = Configuration.interface interface = Configuration.interface
if interface == None: if interface == None:
raise Exception("Interface must be defined") raise Exception("Wireless interface must be defined (-i)")
self.interface = interface self.interface = interface
self.targets = [] self.targets = []
@@ -86,7 +86,7 @@ class Airodump(object):
# Find the .CSV file # Find the .CSV file
csv_filename = None csv_filename = None
for fil in os.listdir(Configuration.temp()): for fil in os.listdir(Configuration.temp()):
if fil.startswith(self.output_file_prefix) and fil.endswith('csv'): if fil.startswith(self.output_file_prefix) and fil.endswith('-01.csv'):
# Found the file # Found the file
csv_filename = Configuration.temp() + fil csv_filename = Configuration.temp() + fil
break break
@@ -100,6 +100,7 @@ class Airodump(object):
import csv import csv
with open(csv_filename, 'rb') as csvopen: with open(csv_filename, 'rb') as csvopen:
csv_reader = csv.reader(csvopen, delimiter=',') csv_reader = csv.reader(csvopen, delimiter=',')
hit_clients = False
for row in csv_reader: 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

View File

@@ -33,21 +33,25 @@ class Arguments(object):
action='store_true', action='store_true',
dest='wep_only', dest='wep_only',
help='Only target WEP-encrypted networks (ignores WPA)') help='Only target WEP-encrypted networks (ignores WPA)')
wep.add_argument('--require-fakeauth',
action='store_true',
dest='require_fakeauth',
help='Fails attacks if fake-authentication fails')
# WPA # WPA
wep = parser.add_argument_group('WPA-RELATED') wpa = parser.add_argument_group('WPA-RELATED')
wep.add_argument('--wpa', wpa.add_argument('--wpa',
action='store_true', action='store_true',
dest='wpa_only', dest='wpa_only',
help='Only target WPA-encrypted networks (ignores WEP)') help='Only target WPA-encrypted networks (ignores WEP)')
# WPS # WPS
wep = parser.add_argument_group('WPS-RELATED') wps = parser.add_argument_group('WPS-RELATED')
wep.add_argument('--wps', wps.add_argument('--wps',
action='store_true', action='store_true',
dest='wps_only', dest='wps_only',
help='Only target WPS-encrypted networks (ignores WEP/nonWPS)') help='Only target WPS-encrypted networks (ignores WEP/nonWPS)')
wep.add_argument('--pixie', wps.add_argument('--pixie',
action='store_true', action='store_true',
dest='pixie_only', dest='pixie_only',
help='Only use the WPS Pixie-Dust attack (do not crack PINs)') help='Only use the WPS Pixie-Dust attack (do not crack PINs)')

View File

@@ -1,9 +1,47 @@
#!/usr/bin/python #!/usr/bin/python
class Attack(object): class Attack(object):
'''
Contains functionality common to all attacks
'''
target_wait = 10
def __init__(self, target): def __init__(self, target):
self.target = target self.target = target
def run(self): def run(self):
raise Exception("Unimplemented method: run") raise Exception("Unimplemented method: run")
def wait_for_target(self, airodump):
'''
Waits for target to appear in airodump
'''
start_time = time.time()
targets = airodump.get_targets()
while len(targets) == 0:
# Wait for target to appear in airodump.
if int(time.time() - start_time) > Attack.target_wait:
raise Exception(
"Target did not appear after %d seconds, stopping"
% Attack.target_wait)
time.sleep(1)
targets = airodump.get_targets()
continue
# Ensure this target was seen by airodump
airodump_target = None
for t in targets:
if t.bssid == self.target.bssid:
airodump_target = t
break
if airodump_target == None:
raise Exception(
'Could not find target (%s) in airodump' % self.target.bssid)
return airodump_target
if __name__ == '__main__':
pass

View File

@@ -1,33 +1,94 @@
#!/usr/bin/python #!/usr/bin/python
from Attack import Attack from Attack import Attack
from Airodump import Airodump
from Aireplay import Aireplay, WEPAttackType
from Configuration import Configuration
from Color import Color
import time import time
class AttackWEP(Attack): class AttackWEP(Attack):
'''
Contains logic for attacking a WEP-encrypted access point.
'''
fakeauth_wait = 5
def __init__(self, target): def __init__(self, target):
super(AttackWEP, self).__init__(target) super(AttackWEP, self).__init__(target)
def run(self): def run(self):
start_time = time.time() '''
Initiates full WEP attack.
Including airodump-ng starting, cracking, etc.
'''
# First, start Airodump process # First, start Airodump process
with Airodump(channel=target.channel, with Airodump(channel=self.target.channel,
target_bssid=self.target.bssid, target_bssid=self.target.bssid,
output_file_prefix='wep') output_file_prefix='wep') as airodump:
as airodump:
while len(airodump.targets) == 0: airodump_target = self.wait_for_target(airodump)
# Target has not appeared in airodump yet
# Wait for it to appear for attack_num in xrange(1, 6):
if int(time.time() - start_time) > 10: pass
# Target didn't appear after 10 seconds, drop out #aireplay = Aireplay(self.target, attack_num)
print "Target did not show after 10 seconds, stopping" '''
return None TODO (pending until I get a router to test on)
* Wait for IVS to start flowing, e.g. sleep(1).
* if IVS > threshold, start cracking.
* Check aireplay.is_running() to see if it completed/failed.
* Continue attacks depending on aireplay status.
'''
def fake_auth(self):
'''
Attempts to fake-authenticate with target.
Returns: True if successful,
False is unsuccesful.
'''
start_time = time.time()
aireplay = Aireplay(self.target, 'fakeauth')
process_failed = False
while aireplay.is_running():
if int(time.time() - start_time) > AttackWEP.fakeauth_wait:
aireplay.stop()
process_failed = True
break
time.sleep(1) time.sleep(1)
continue
airodump_target = airodump.targets[0] # Check if fake-auth was successful
if process_failed:
fakeauth = False
else:
output = aireplay.get_output()
fakeauth = 'association successful' in output.lower()
if fakeauth:
Color.pl('{+} {G}fake-authentication successful{W}')
else:
if Configuration.require_fakeauth:
# Fakeauth is requried, fail
raise Exception(
'Fake-authenticate did not complete within' +
' %d seconds' % AttackWEP.fakeauth_wait)
else:
# Warn that fakeauth failed
Color.pl('{!} {O}' +
'unable to fake-authenticate with target' +
' (%s){W}' % self.target.bssid)
Color.pl('{!} continuing attacks because' +
' {G}--require-fakeauth{W} was not set')
return fakeauth
if __name__ == '__main__':
from Target import Target
fields = "30:85:A9:39:D2:18, 2015-05-27 19:28:44, 2015-05-27 19:28:46, 6, 54, WPA2, CCMP TKIP,PSK, -58, 2, 0, 0. 0. 0. 0, 9, Uncle Router's Gigabit LAN Party, ".split(',')
target = Target(fields)
wep = AttackWEP(target)
wep.fake_auth()
# Fake authenticate
#aireplay = Aireplay()

View File

@@ -7,5 +7,16 @@ class AttackWPA(Attack):
super(AttackWPA, self).__init__(target) super(AttackWPA, self).__init__(target)
def run(self): def run(self):
raise Exception("TODO: Crack WPA") '''
Initiates full WPA hanshake capture attack.
'''
# First, start Airodump process
with Airodump(channel=self.target.channel,
target_bssid=self.target.bssid,
output_file_prefix='wpa') as airodump:
airodump_target = self.wait_for_target(airodump)
for attack_num in xrange(1, 6):
attack_type = WEPAttackType(attack_num)

13
py/AttackWPS.py Normal file
View File

@@ -0,0 +1,13 @@
#!/usr/bin/python
from Attack import Attack
class AttackWPS(Attack):
def __init__(self, target):
super(AttackWPS, self).__init__(target)
def run(self):
raise Exception("TODO: Crack WPS")
if __name__ == '__main__':
pass

View File

@@ -32,6 +32,7 @@ class Configuration(object):
Configuration.wep_only = False # Only attack WEP networks Configuration.wep_only = False # Only attack WEP networks
Configuration.wep_pps = 6000 # Packets per second Configuration.wep_pps = 6000 # Packets per second
Configuration.wep_timeout = 600 # Seconds to wait before failing Configuration.wep_timeout = 600 # Seconds to wait before failing
Configuration.require_fakeauth = False
# WEP-specific attacks # WEP-specific attacks
Configuration.wep_fragment = True Configuration.wep_fragment = True
Configuration.wep_caffelatte = True Configuration.wep_caffelatte = True
@@ -79,6 +80,7 @@ class Configuration(object):
if args.wps_only: Configuration.wps_only = args.wps_only if args.wps_only: Configuration.wps_only = args.wps_only
if args.pixie_only: Configuration.pixie_only = args.pixie_only if args.pixie_only: Configuration.pixie_only = args.pixie_only
if args.wordlist: Configuration.wordlist = args.wordlist if args.wordlist: Configuration.wordlist = args.wordlist
if args.require_fakeauth: Configuration.require_fakeauth = False
@staticmethod @staticmethod

325
py/Handshake.py Normal file
View File

@@ -0,0 +1,325 @@
#!/usr/bin/python
from Process import Process
from Color import Color
import re
class Handshake(object):
def __init__(self, capfile, bssid=None, essid=None):
self.capfile = capfile
self.bssid = bssid
self.essid = essid
def divine_bssid_and_essid(self):
'''
Tries to find BSSID and ESSID from cap file.
Sets this instances 'bssid' and 'essid' instance fields.
'''
# Get list of bssid/essid pairs from cap file
pairs = self.tshark_bssid_essid_pairs()
if len(pairs) == 0:
# Find bssid/essid pairs that have handshakes in Pyrit
pairs = self.pyrit_handshakes()
if len(pairs) == 0:
# Tshark and Pyrit failed us, nothing else we can do.
raise Exception("Cannot find BSSID or ESSID in cap file")
if not self.essid and not self.bssid:
# We do not know the bssid nor the essid
Color.pl('{!} {O}Warning{W}:' +
' {R}bssid{O} and {R}essid{O} were not specified{W}')
# TODO: Display menu for user to select from list
# HACK: Just use the first one we see
self.bssid = pairs[0][0]
self.essid = pairs[0][1]
Color.pl('{!} {O}Warning{W}:' +
' {O}Arbitrarily selected' +
' {R}bssid{O} {C}%s{O} and {R}essid{O} "{C}%s{O}"{W}'
% (self.bssid, self.essid))
elif not self.bssid:
# We already know essid
for (bssid, essid) in pairs:
if self.essid == essid:
Color.pl('{+} Discovered bssid {C}%s{W}' % bssid)
self.bssid = bssid
break
elif not self.essid:
# We already know bssid
for (bssid, essid) in pairs:
if self.bssid.lower() == bssid.lower():
Color.pl('{+} Discovered essid "{C}%s{W}"' % essid)
self.essid = essid
break
def has_handshake(self):
if not self.bssid or not self.essid:
self.divine_essid_and_bssid()
if self.tshark_handshakes():
return True
if self.cowpatty_handshakes():
return True
if self.pyrit_handshakes():
return True
def tshark_bssid_essid_pairs(self):
'''
Scrapes capfile for beacon frames indicating the ESSID.
Returns list of tuples: (bssid,essid)
'''
if not Process.exists('tshark'):
raise Exception('tshark is required to find ESSID')
essids = set()
# Extract beacon frames from cap file
cmd = [
'tshark',
'-r', self.capfile,
'-R', 'wlan.fc.type_subtype == 0x08',
'-n'
]
proc = Process(cmd, devnull=False)
for line in proc.stdout().split('\n'):
# Extract src, dst, and essid
mac_regex = ('[a-zA-Z0-9]{2}:' * 6)[:-1]
match = re.search('(%s) -> (%s).*.*SSID=(.*)$'
% (mac_regex, mac_regex), line)
if match == None:
# Line doesn't contain src, dst, ssid
continue
(src, dst, essid) = match.groups()
if self.bssid:
# We know the BSSID, only return the ESSID for this BSSID.
if self.bssid.lower() == src.lower():
essids.add((src, essid))
else:
# We do not know BSSID, add it.
essids.add((src, essid))
# Return list of tuples
return [x for x in essids]
def tshark_command(self):
return [
'tshark',
'-r', self.capfile,
'-R', 'eapol',
'-n'
]
def tshark_handshakes(self):
''' Returns True if tshark identifies a handshake, False otherwise '''
if not Process.exists('tshark'):
return []
target_client_msg_nums = {}
# Dump EAPOL packets
proc = Process(self.tshark_command(), devnull=False)
for line in proc.stdout().split('\n'):
# Extract source mac, destination mac, and message numbers
mac_regex = ('[a-zA-Z0-9]{2}:' * 6)[:-1]
match = re.search('(%s) -> (%s).*Message.*(\d).*(\d)'
% (mac_regex, mac_regex), line)
if match == None:
# Line doesn't contain src, dst, Message numbers
continue
(src, dst, index, ttl) = match.groups()
# "Message (index) of (ttl)"
index = int(index)
ttl = int(ttl)
if ttl != 4:
# Must be a 4-way handshake
continue
# Identify the client and target MAC addresses
if index % 2 == 1:
# First and Third messages
target = src
client = dst
else:
# Second and Fourth messages
client = src
target = dst
if self.bssid and self.bssid.lower() != target.lower():
# We know the BSSID and this msg was not for the target
continue
target_client_key = '%s,%s' % (target, client)
# Ensure all 4 messages are:
# Between the same client and target
# In numeric & chronological order (1,2,3,4)
if index == 1:
# First message, add to dict
target_client_msg_nums[target_client_key] = 1
elif target_client_key not in target_client_msg_nums:
# Not first message, we haven't gotten the first message yet
continue
elif index - 1 != target_client_msg_nums[target_client_key]:
# Message is not in sequence
continue
else:
# Message is > 1 and is received in-order
target_client_msg_nums[target_client_key] = index
bssids = set()
# Check if we have all 4 messages for the handshake between the same MACs
for (client_target, num) in target_client_msg_nums.iteritems():
if num == 4:
# We got a handshake!
bssid = client_target.split(',')[0]
bssids.add(bssid)
return [(bssid, None) for bssid in bssids]
def cowpatty_command(self):
return [
'cowpatty',
'-r', self.capfile,
'-s', self.essid,
'-c' # Check for handshake
]
def cowpatty_handshakes(self):
''' Returns True if cowpatty identifies a handshake, False otherwise '''
if not Process.exists('cowpatty'):
return []
if not self.essid:
return [] # We need a essid for cowpatty :(
proc = Process(self.cowpatty_command(), devnull=False)
for line in proc.stdout().split('\n'):
if 'Collected all necessary data to mount crack against WPA' in line:
return [(None, self.essid)]
return []
def pyrit_command(self):
return [
'pyrit',
'-r', self.capfile,
'analyze'
]
def pyrit_handshakes(self):
''' Returns True if pyrit identifies a handshake, False otherwise '''
if not Process.exists('pyrit'):
return []
bssid_essid_pairs = set()
hit_target = False
current_bssid = self.bssid
current_essid = self.essid
proc = Process(self.pyrit_command(), devnull=False)
for line in proc.stdout().split('\n'):
mac_regex = ('[a-zA-Z0-9]{2}:' * 6)[:-1]
match = re.search("^#\d+: AccessPoint (%s) \('(.*)'\):$"
% (mac_regex), line)
if match:
# We found a BSSID and ESSID
(bssid, essid) = match.groups()
# Compare to what we're searching for
if self.bssid and self.bssid.lower() == bssid.lower():
current_essid = essid
hit_target = True
continue
elif self.essid and self.essid == essid:
current_bssid = bssid
hit_target = True
continue
elif not self.bssid and not self.essid:
# We don't know either
current_bssid = bssid
current_essid = essid
hit_target = True
else:
# This AccessPoint is not what we're looking for
hit_Target = False
else:
# Line does not contain AccessPoint
if hit_target and ', good,' in line:
bssid_essid_pairs.add( (current_bssid, current_essid) )
return [x for x in bssid_essid_pairs]
def aircrack_command(self):
return 'echo "" | aircrack-ng -a 2 -w - -b %s "%s"' % (self.bssid, self.capfile)
def aircrack_handshakes(self):
if not self.bssid:
return []
(stdout, stderr) = Process.call(self.aircrack_command())
if 'passphrase not in dictionary' in stdout.lower():
return [(self.bssid, None)]
else:
return []
def analyze(self):
hs.divine_bssid_and_essid()
pairs = hs.tshark_handshakes()
Handshake.print_pairs(pairs, 'tshark')
pairs = hs.pyrit_handshakes()
Handshake.print_pairs(pairs, 'pyrit')
pairs = hs.cowpatty_handshakes()
Handshake.print_pairs(pairs, 'cowpatty')
pairs = hs.aircrack_handshakes()
Handshake.print_pairs(pairs, 'aircrack')
@staticmethod
def print_pairs(pairs, tool=None):
'''
Prints out BSSID and/or ESSID given a list of tuples (bssid,essid)
'''
tool_str = ''
if tool:
tool_str = '{C}%s{W}: ' % tool.rjust(8)
if len(pairs) == 0:
Color.pl("{!} %s%s {R}does not{O} contain a handshake{W}"
% (tool_str,hs.capfile))
return
for (bssid, essid) in pairs:
if bssid and essid:
Color.pl("{+} %s%s {G}contains a handshake{W} for {G}%s{W} ({G}%s{W})"
% (tool_str, hs.capfile, bssid, essid))
elif bssid:
Color.pl("{+} %s%s {G}contains a handshake{W} for {G}%s{W}"
% (tool_str, hs.capfile, bssid))
elif essid:
Color.pl("{+} %s%s {G}contains a handshake{W} for ({G}%s{W})"
% (tool_str, hs.capfile, essid))
if __name__ == '__main__':
#hs = Handshake('/tmp/test.cap')
hs = Handshake('/tmp/test.cap', bssid='30:85:a9:39:d2:18')
#hs = Handshake('/tmp/test.cap', bssid='30:85:a9:39:d2:18', essid="Uncle Router's Gigabit LAN Party")
#hs = Handshake('/tmp/test.cap.stripped.tshark')
hs.analyze()
print "has_hanshake() =", hs.has_handshake()