Your unit can operate in one of three different “modes” that determines how it behaves:
If connected to the USB data port of your computer (or a tablet, phone, etc), your Pwnagotchi will start in MANUAL mode.
This means it will read the log of the last session and report a few statistics on the screen. This is the mode you should be using your unit when you want to transfer data from/to it. Moreover, in MANU mode, you’ll be able to access bettercap’s web UI from your computer by pointing your browser to http://pwnagotchi.local
.
You can "force" the unit to always go in AUTO mode regardless of which USB port you're using by creating the /root/.pwnagotchi-auto
file.
This is the default mode your unit will start if only connected to the USB power port, for instance when connected to a powerbank without any host computer on the data port.
In AUTO mode, your unit will start operating, perform attacks and sniffing handshakes only by using the default personality
configuration parameters.
If AI is enabled in your configuration (as it is by default), AUTO mode will transition to AI mode after a few minutes (on average, about 10-15 minutes on a Rpi0W with a decent quality SD card).
This interval is required to load all the dependencies the AI module will be using and initialize the neural network. You can think about this as your Pwnagotchi waking up :D
Once the dependencies are loaded and so the /root/brain.nn
file, AI mode will pick the optimal set of parameters in real time, depending on how long
it’s been trained on the specific type of WiFi environment it’s observing now.
Moreover, depending on the laziness
configuration parameter, it will more or less frequently start to learn continuously for a given amount of time.
While this is happening and the AI is “training”, the algorithm tends to explore wider ranges of parameters in order to determine how those changes affect the reward.
While during simple inference epochs (when the unit is not learning but just picking the parameters using previous knowledge), the AI tends to be more conservative and
only use parameters in smaller ranges that are known to work in that situation.
Ideally, the laziness
value should be very low at the beginning (say 0.1) and you should manually increase over time in order to reduce the amount of training vs inference epochs.
Pwnagotchi’s face—otherwise known as the UI—is available at a dedicated web interface located at http://pwnagotchi.local:8080/
if you’ve already connected to the unit via usb0
(by using the RPi0W’s data port) and set a static address on the network interface (see the ui.web
section config.toml
). You can think of this as a Pwnagotchi in “headless” mode.
pwnagotchi
in http://pwnagotchi.local:8080/
to the new hostname you’ve given your unit.http://pwnagotchi.local
whenever your Pwnagotchi is set to MANUAL mode.The username and password for the web UI are both changeme
by default.
You should change these by updating the config.toml
to include the new username and password. For example:
ui.web.username = "my_new_username"
ui.web.password = "my_new_password"
If you’ve properly attached the optional supported e-ink display to your Pwnagotchi’s body and successfully configured it to use that display, you will also be able to see Pwnagotchi’s UI displayed on that screen.
*
instead of a number. It is gathering the number of APs on each channel when it is conducting recon. Recon signals the start of a new epoch.An important note about the AI: a network trained with a specific WiFi interface will ONLY work with another interface if it supports the exact same WiFi channels of the first one. For instance, you CANNOT use a neural network trained on a Raspberry Pi Zero W (which only supports 2.4GHz channels) with a 5GHz antenna; you will need to train one from scratch for those channels to be included. This means that if you swap out your SD card or neural network from, say, a RPi0W body into a RPi4 body, the neural network will NOT work.
At its core Pwnagotchi is a very simple creature: we could summarize its main algorithm as:
# main loop
while True:
# ask bettercap for all visible access points and their clients
aps = get_all_visible_access_points()
# loop each AP
for ap in aps:
# send an association frame in order to grab the PMKID
send_assoc(ap)
# loop each client station of the AP
for client in ap.clients:
# deauthenticate the client to get its half or full handshake
deauthenticate(client)
wait_for_loot()
Despite its simplicity, this logic is controlled by several parameters that regulate the wait times, the timeouts, on which channels to hop, and so on.
From config.toml
:
# advertise our presence
personality.advertise = true
# perform a deauthentication attack to client stations in order to get full or half handshakes
personality.deauth = true
# send association frames to APs in order to get the PMKID
personality.associate = true
# list of channels to recon on, or empty for all channels
personality.channels = []
# minimum WiFi signal strength in dBm
personality.min_rssi = -200
# number of seconds for wifi.ap.ttl
personality.ap_ttl = 120
# number of seconds for wifi.sta.ttl
personality.sta_ttl = 300
# time in seconds to wait during channel recon
personality.recon_time = 30
# number of inactive epochs after which recon_time gets multiplied by recon_inactive_multiplier
personality.max_inactive_scale = 2
# if more than max_inactive_scale epochs are inactive, recon_time *= recon_inactive_multiplier
personality.recon_inactive_multiplier = 2
# time in seconds to wait during channel hopping if activity has been performed
personality.hop_recon_time = 10
# time in seconds to wait during channel hopping if no activity has been performed
personality.min_recon_time = 5
# maximum amount of deauths/associations per BSSID per session
personality.max_interactions = 3
# maximum amount of misses before considering the data stale and triggering a new recon
personality.max_misses_for_recon = 5
# number of active epochs that triggers the excited state
personality.excited_num_epochs = 10
# number of inactive epochs that triggers the bored state
personality.bored_num_epochs = 15
# number of inactive epochs that triggers the sad state
personality.sad_num_epochs = 25
There is no optimal set of parameters for every situation: when the unit is moving (during a walk for instance) smaller timeouts and RSSI thresholds might be preferred in order to quickly remove routers that are not in range anymore, while when stationary in high density areas (like an office) other parameters might be better. The role of the AI is to observe what’s going on at the WiFi level, and adjust those parameters in order to maximize the cumulative reward of that loop / epoch.
After each iteration of the main loop (an epoch
), the reward, a score that represents how well the parameters performed, is computed as (an excerpt from pwnagotchi/ai/reward.py
):
# state contains the information of the last epoch
# epoch_n is the number of the last epoch
tot_epochs = epoch_n + 1e-20 # 1e-20 is added to avoid a division by 0
tot_interactions = max(state['num_deauths'] + state['num_associations'], state['num_handshakes']) + 1e-20
tot_channels = wifi.NumChannels
# ideally, for each interaction we would have an handshake
h = state['num_handshakes'] / tot_interactions
# small positive rewards the more active epochs we have
a = .2 * (state['active_for_epochs'] / tot_epochs)
# make sure we keep hopping on the widest channel spectrum
c = .1 * (state['num_hops'] / tot_channels)
# small negative reward if we don't see aps for a while
b = -.3 * (state['blind_for_epochs'] / tot_epochs)
# small negative reward if we interact with things that are not in range anymore
m = -.3 * (state['missed_interactions'] / tot_interactions)
# small negative reward for inactive epochs
i = -.2 * (state['inactive_for_epochs'] / tot_epochs)
reward = h + a + c + b + i + m
By maximizing this reward value, the AI learns over time to find the set of parameters that better perform with the current environmental conditions.
/etc/pwnagotchi/config.toml
: This is where you put your custom configurations.
default.toml
! They will be overwritten whenever you update your unit!/root/handshakes/
/root/peers/
./var/log/pwnagotchi.log
./root/brain.nn
, while the information about its age at /root/brain.json
. If you want to save your Pwnagotchi’s memories, these are the files to back up.You probably don’t know yet, but Pwnagotchi is also a “crypto-pager”! By using the PwnGRID API (and internet connectivity of course), your unit is able to exchange end to end encrypted messages with other units enrolled in the grid. Each message is encrypted on your Raspberry with the recipient RSA public key before being sent, therefore we only have access to encrypted data and we have absolutely no way to see the cleartext as it can only be done by the original recipient via his private key.
Your PwnMAIL address is your unit’s cryptographic fingerprint (which is the SHA256 checksum of its public key in PEM format), you can
read it from your unit’s filesystem at /etc/pwnagotchi/fingerprint
or by running sudo pwngrid -whoami
. You can also use this address to open (and share) your “pwnfile”.
Each unit corresponds to a single cryptographically signed and hardware isolated address.
To check your PwnMAIL inbox, you’ll need to SSH into your unit and then use the pwngrid
binary:
You can also check your webUI, there is a tab there just for checking your inbox
sudo pwngrid -inbox
# and for all other pages if more than one
sudo pwngrid -inbox -page 2
To fetch a message given its id (123 in this example), verify the sender signature and decryp it:
sudo pwngrid -inbox -id 123
# in case you want to save the decrypted message body to a file
sudo pwngrid -inbox -id 123 -output picture.jpg
This will automatically mark the message as read, to mark it back as unread:
sudo pwngrid -inbox -id 123 -unread
To delete it:
sudo pwngrid -inbox -id 123 -delete
To send an encrypted message (max size is 512KB) to another unit having its fingerprint (ca1225b86dc35fef90922d83421d2fc9c824e95b864cfa62da7bea64ffb05aea
in this example):
sudo pwngrid -send ca1225b86dc35fef90922d83421d2fc9c824e95b864cfa62da7bea64ffb05aea -message "hi there, how are you doing?"
# in case you want to send a file instead
sudo pwngrid -send ca1225b86dc35fef90922d83421d2fc9c824e95b864cfa62da7bea64ffb05aea -message @/path/to/file.jpg
Whenever Pwnagotchi is pwning, it is being powered by bettercap! Conveniently, this means your Pwnagotchi can double as a portable WiFi penetration testing station when you access bettercap’s web UI at http://pwnagotchi.local
.
pwnagotchi
in http://pwnagotchi.local
to the new hostname you’ve given your unit.pwnagotchi:pwnagotchi
, if you decide to change them in /usr/local/share/bettercap/caplets/pwnagotchi-*.cap
, you’ll also need
to update the configuration in /etc/pwnagotchi/config.toml
to use the new credentials.Why can't I use bettercap's web UI while my Pwnagotchi is eating handshakes? This is because when Pwnagotchi is running in AUTO or AI modes, it is basically instrumenting bettercap in order to sniff packets and capture and record handshakes. You and Pwnagotchi cannot BOTH use bettercap at the same time; for this reason, it is only when your Pwnagotchi isn't hunting for handshakes to eat—AKA, when it is in MANUAL mode—that you are free to use bettercap (and its web UI) yourself.
You can use the scripts/backup.sh
script to backup the important files of your unit.
usage: ./scripts/backup.sh HOSTNAME backup.zip
The recommended update procedure is to backup your Pwnagotchi as explained in the previous section, flash the SD card with the new image and restore the backup by extracting the files back in the root filesystem.
Putting this into your .bashrc will create the pwnlog
alias which is a pretty and uncluttered view on the pwnagotchi logs.
alias pwnlog='tail -f -n300 /var/log/pwn*.log | sed --unbuffered "s/,[[:digit:]]\{3\}\]//g" | cut -d " " -f 2-'
Putting this into your .bashrc will create the pwnver
alias, useful for printing the version of Pwnagotchi currently running.
alias pwnver='python3 -c "import pwnagotchi as p; print(p.__version__)"'
Pwnagotchi goes blind and detects no APs on any channel
Every once in a while, nexmon dies with:
[ 4341.527847] brcmfmac: brcmf_cfg80211_nexmon_set_channel: Set Channel failed: chspec=4101, -110
[ 4344.327806] brcmfmac: brcmf_cfg80211_nexmon_set_channel: Set Channel failed: chspec=4097, -110
[ 4347.127853] brcmfmac: brcmf_cfg80211_nexmon_set_channel: Set Channel failed: chspec=4098, -110
[ 4349.927917] brcmfmac: brcmf_cfg80211_nexmon_set_channel: Set Channel failed: chspec=4099, -110
[ 4352.728074] brcmfmac: brcmf_cfg80211_nexmon_set_channel: Set Channel failed: chspec=4100, -110
[ 4355.527970] brcmfmac: brcmf_cfg80211_nexmon_set_channel: Set Channel failed: chspec=4101, -110
[ 4358.328022] brcmfmac: brcmf_cfg80211_nexmon_set_channel: Set Channel failed: chspec=4102, -110
[ 4361.208095] brcmfmac: brcmf_cfg80211_nexmon_set_channel: Set Channel failed: chspec=4103, -110
[ 4364.008157] brcmfmac: brcmf_cfg80211_nexmon_set_channel: Set Channel failed: chspec=4104, -110
[ 4366.808218] brcmfmac: brcmf_cfg80211_nexmon_set_channel: Set Channel failed: chspec=4105, -110
[ 4369.608431] brcmfmac: brcmf_cfg80211_nexmon_set_channel: Set Channel failed: chspec=4097, -110
[ 4372.408345] brcmfmac: brcmf_cfg80211_nexmon_set_channel: Set Channel failed: chspec=4098, -110
[ 4375.288408] brcmfmac: brcmf_cfg80211_nexmon_set_channel: Set Channel failed: chspec=4099, -110
[ 4378.088474] brcmfmac: brcmf_cfg80211_nexmon_set_channel: Set Channel failed: chspec=4100, -110
[ 4380.891399] brcmfmac: brcmf_cfg80211_nexmon_set_channel: Set Channel failed: chspec=4101, -110
…and only a reboot can fix the WiFi and fix Pwnagotchi’s apparent blindness. This is why the mon_max_blind_epochs
parameter exists—to reboot the RPi0W board automatically whenever this happens. This mon_max_blind_epochs
parameter is the number of epochs (or rounds of recon) during which Pwnagotchi has no detection of any APs on any channels it hops on. Maybe someday somebody will fix this, but until that happens, mon_max_blind_epochs
will be the existing work-around. See GitHub issue #267 and this tweet from @evilsocket for more details.