diff --git a/Changelog b/Changelog index 0bb55a7..86fd78a 100644 --- a/Changelog +++ b/Changelog @@ -150,3 +150,6 @@ Added ssid and mac address level ACLs. 1.11.0 - Gabriel Ryan Added beacon forger for executing known SSID bursts + +1.12.0 - Gabriel Ryan +Added --known-ssids flag that can be used instead of --known-ssids-file flag. diff --git a/README.md b/README.md index 5a6323d..7e603f0 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ by Gabriel Ryan ([s0lst1c3](https://twitter.com/s0lst1c3))(gryan[at]specterops.i [![Foo](https://rawcdn.githack.com/toolswatch/badges/8bd9be6dac2a1d445367001f2371176cc50a5707/arsenal/usa/2017.svg)](https://www.blackhat.com/us-17/arsenal.html#eaphammer) -Current release: [v1.11.0](https://github.com/s0lst1c3/eaphammer/releases/tag/v1.11.0) +Current release: [v1.12.0](https://github.com/s0lst1c3/eaphammer/releases/tag/v1.12.0) Supports _Python 3.5+_. diff --git a/__version__.py b/__version__.py index 65343a7..e8979a0 100644 --- a/__version__.py +++ b/__version__.py @@ -1,4 +1,4 @@ -__version__ = '1.11.0' +__version__ = '1.12.0' __codename__ = 'Power Overwhelming' __author__ = '@s0lst1c3' __contact__ = 'gryan@specterops.io' diff --git a/core/cli.py b/core/cli.py index 6259529..309e9ae 100644 --- a/core/cli.py +++ b/core/cli.py @@ -50,6 +50,7 @@ 'user_list', 'bootstrap', 'known_ssids_file', + 'known_ssids', 'negotiate', ] @@ -467,6 +468,13 @@ def set_options(): help='Specify the wordlist to use with ' 'the --known-beacons features.') + karma_group.add_argument('--known-ssids', + dest='known_ssids', + nargs='+', + default=None, + type=str, + help='Specify known ssids via the CLI') + ap_advanced_subgroup = parser.add_argument_group('AP Advanced Options') ap_advanced_subgroup.add_argument('--wmm', @@ -908,18 +916,32 @@ def set_options(): print(msg, end='') sys.exit() - if options['known_ssids_file'] is None: + if options['known_ssids_file'] is None and \ + options['known_ssids'] is None: parser.print_usage() print() - msg = ('[!] Cannot use --known-beacons without known SSIDS file. ' - 'Please specify path to known SSIDS file with --known-ssids flag.') + msg = ('[!] Cannot use --known-beacons ' + 'without list of known SSIDS. ' + 'Please specify path to known SSIDS ' + 'file with the --known-ssids-file flag, ' + 'or provide a list of known SSIDS ' + 'using the --known-ssids flag.') print(msg, end='') sys.exit() - + if options['known_ssids_file'] is not None and \ + options['known_ssids'] is not None: + + parser.print_usage() + print() + msg = ('[!] Cannot use --known-ssids-file ' + 'and --known-ssids flags simultaneously.') + print(msg, end='') + sys.exit() - if not Path(options['known_ssids_file']).is_file(): + if options['known_ssids_file'] is not None and \ + not Path(options['known_ssids_file']).is_file(): parser.print_usage() print() diff --git a/core/hostapd_config.py b/core/hostapd_config.py index 9c72101..d7ddde3 100644 --- a/core/hostapd_config.py +++ b/core/hostapd_config.py @@ -526,7 +526,7 @@ def populate_general(self, settings, options): if options['known_beacons']: general_configs['known_beacons'] = '1' - general_configs['known_ssids_file'] = options['known_ssids_file'] + general_configs['known_ssids_file'] = settings.dict['paths']['hostapd']['known_ssids'] else: general_configs['known_beacons'] = settings.dict['core']['hostapd']['general']['known_beacons'] diff --git a/core/known_ssids_file.py b/core/known_ssids_file.py new file mode 100644 index 0000000..33389b7 --- /dev/null +++ b/core/known_ssids_file.py @@ -0,0 +1,73 @@ +import os +import shutil +import sys + +class KnownSSIDSFile(object): + + def __init__(self, + settings, + options): + + self.debug = options['debug'] + self.known_ssids = [] + self.output_path = settings.dict['paths']['hostapd']['known_ssids'] + + # having to do this makes me wish that python had a + # logic XOR operator... or even a NAND operator for + # that matter... + assert not (options['known_ssids_file'] is None and options['known_ssids'] is None) + assert not (options['known_ssids_file'] is not None and options['known_ssids'] is not None) + + if options['known_ssids_file'] is not None: + + with open(options['known_ssids_file']) as fd: + + for line in fd: + + # remove '\n' from end of line + ssid = line.rstrip('\n') + if ssid.strip() != ssid: + print('[eaphammer] WARNING: whitespace detected ' + 'at beginning or end of known-ssids-file. ' + 'Assuming this is intentional.') + self.known_ssids.append(ssid) + + elif options['known_ssids'] is not None: + + self.known_ssids = options['known_ssids'] + + else: + raise Exception('[KnownSSIDSFile] this should never happen') + + + if self.debug: + print('[KnownSSIDSFile] self.known_ssids: ', self.known_ssids) + print('[KnownSSIDSFile] self.output_path: ', self.output_path) + + def remove(self): + + if not self.debug: + + try: + + os.remove(self.output_path) + + except FileNotFoundError: + + print('[KnownSSIDSFile] Cannot remove file that does not exist') + + def path(self, path=None): + + if path is not None: + self.output_path = path + + return self.output_path + + def generate(self): + + + with open(self.output_path, 'w') as fd: + for ssid in self.known_ssids: + fd.write('{}\n'.format(ssid)) + + return self.path() diff --git a/eaphammer b/eaphammer index 95577a1..a457064 100755 --- a/eaphammer +++ b/eaphammer @@ -21,6 +21,7 @@ from core.hostapd_config import HostapdConfig from core.eap_user_file import EAPUserFile from core.hostapd_mac_acl import HostapdMACACL from core.hostapd_ssid_acl import HostapdSSIDACL +from core.known_ssids_file import KnownSSIDSFile from core.responder_config import ResponderConfig from core.lazy_file_reader import LazyFileReader from core.redirect_server import RedirectServer @@ -74,6 +75,10 @@ def hostile_portal(): hostapd_ssid_acl = HostapdSSIDACL(settings, options) hostapd_ssid_acl.generate() + if options['known_beacons']: + known_ssids_file = KnownSSIDSFile(settings, options) + known_ssids_file.generate() + # write hostapd config file to tmp directory hostapd_conf = HostapdConfig(settings, options) hostapd_conf.write() @@ -247,6 +252,10 @@ def captive_portal(): hostapd_ssid_acl = HostapdSSIDACL(settings, options) hostapd_ssid_acl.generate() + if options['known_beacons']: + known_ssids_file = KnownSSIDSFile(settings, options) + known_ssids_file.generate() + # write hostapd config file to tmp directory hostapd_conf = HostapdConfig(settings, options) hostapd_conf.write() @@ -303,6 +312,9 @@ def captive_portal(): # remove acl file from tmp directory hostapd_ssid_acl.remove() + + if options['known_beacons']: + known_ssids_file.remove() # restore iptables to a clean state (policy, flush tables) utils.Iptables.accept_all() @@ -341,6 +353,9 @@ def captive_portal(): # remove acl file from tmp directory hostapd_ssid_acl.remove() + + if options['known_beacons']: + known_ssids_file.remove() # restore iptables to a clean state (policy, flush tables) utils.Iptables.accept_all() @@ -378,6 +393,10 @@ def troll_defender(): hostapd_ssid_acl = HostapdSSIDACL(settings, options) hostapd_ssid_acl.generate() + if options['known_beacons']: + known_ssids_file = KnownSSIDSFile(settings, options) + known_ssids_file.generate() + # write hostapd config file to tmp directory hostapd_conf = HostapdConfig(settings, options) hostapd_conf.write() @@ -421,6 +440,9 @@ def troll_defender(): # remove acl file from tmp directory hostapd_ssid_acl.remove() + if options['known_beacons']: + known_ssids_file.remove() + # cleanly allow network manager to regain control of interface interface.nm_on() @@ -462,6 +484,10 @@ def reap_creds(): hostapd_ssid_acl = HostapdSSIDACL(settings, options) hostapd_ssid_acl.generate() + if options['known_beacons']: + known_ssids_file = KnownSSIDSFile(settings, options) + known_ssids_file.generate() + # write hostapd config file to tmp directory hostapd_conf = HostapdConfig(settings, options) hostapd_conf.write() @@ -497,6 +523,9 @@ def reap_creds(): # remove acl file from tmp directory hostapd_ssid_acl.remove() + if options['known_beacons']: + known_ssids_file.remove() + # cleanly allow network manager to regain control of interface interface.nm_on() @@ -526,6 +555,9 @@ def reap_creds(): # remove acl file from tmp directory hostapd_ssid_acl.remove() + if options['known_beacons']: + known_ssids_file.remove() + # cleanly allow network manager to regain control of interface interface.nm_on() diff --git a/settings/paths.py b/settings/paths.py index 308817b..9eb91a7 100644 --- a/settings/paths.py +++ b/settings/paths.py @@ -98,6 +98,10 @@ def __str__(self): PHASE1_ACCOUNTS = os.path.join(DB_DIR, 'phase1.accounts') PHASE2_ACCOUNTS = os.path.join(DB_DIR, 'phase2.accounts') +# known ssids file +output_file = OutputFile(ext='known_ssids').string() +KNOWN_SSIDS_FILE = os.path.join(TMP_DIR, output_file) + # ACL Files output_file = OutputFile(ext='accept').string() HOSTAPD_MAC_WHITELIST = os.path.join(TMP_DIR, output_file) @@ -193,6 +197,7 @@ def __str__(self): 'mac_blacklist' : HOSTAPD_MAC_BLACKLIST, 'ssid_whitelist' : HOSTAPD_SSID_WHITELIST, 'ssid_blacklist' : HOSTAPD_SSID_BLACKLIST, + 'known_ssids' : KNOWN_SSIDS_FILE, }, 'certs' : {