Updating with master
This commit is contained in:
commit
3480b99a45
.github
.gitignore.travis.ymlMANIFEST.inMakefileREADME.mdbin
builder
docs
pwnagotchi
__init__.pyagent.pylog.py
requirements.txtai
bettercap.pydefaults.ymlidentity.pylocale
de/LC_MESSAGES
el/LC_MESSAGES
es/LC_MESSAGES
fr/LC_MESSAGES
it/LC_MESSAGES
mk/LC_MESSAGES
nl/LC_MESSAGES
pt-BR/LC_MESSAGES
ru/LC_MESSAGES
se/LC_MESSAGES
voice.potmesh
plugins
__init__.py
default
ui
utils.pyvoice.pyscripts
backup.shcreate_sibling.shlanguage.shpreview.pypypi_upload.shrelease.shupdate_pwnagotchi.shwin_connection_share.ps1
sdcard/boot
12
.github/FUNDING.yml
vendored
Normal file
12
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHubSponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: evilsocket
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -2,6 +2,7 @@
|
||||
*.img.bmap
|
||||
*.pcap
|
||||
*.po~
|
||||
preview.png
|
||||
__pycache__
|
||||
_backups
|
||||
_emulation
|
||||
@ -10,3 +11,7 @@ config.laptop.yml
|
||||
.idea
|
||||
packer_cache
|
||||
output-pwnagotchi
|
||||
.DS_Store
|
||||
build
|
||||
dist
|
||||
pwnagotchi.egg-info
|
||||
|
15
.travis.yml
15
.travis.yml
@ -12,7 +12,9 @@ deploy:
|
||||
secure: vBUokTv94n8s65STUgTiD6I0Iy8KXbBRvQUrAof8XG+U4ZMsH5PmDTpS+wz+SaxI6o0PRkfyOiPVdARhiKAFnfatG3q9EHllMQwqRR2YIju51A3aCxgEJ5uWDoybwQdipERUMMYwUO/8XZaRRpwFD2bdQBFWkBtQyMcAkrEL8BXckwQQ531oDN2hK5gAiTllqsOswV2idwUlBRU9jOtStzff+UgUYsp/ZebsRodyOYkEB2Ev15yARo2HTXbyZ2icwHPtMbx5zmNUSRtxs9a4hfzaK3m6ctK8qLYYUdQvXub/ruuACapdw4Ez88LY1agTecbZhFYmJzv8oANH1e4VUI4owuHnZCpU6LRutS4wOhglrkOrGo6lSUlJeA+RtQjyjBugjej9DDtDyyIlRU1ZaBF3qWR9N5EXKuquf0olOfmUR67ap1NykE9VUpzkYjkoVRTiPs/e2onM/nRNOvAQcIt75FD13u+Y/DcYQ8r7KpMIu1HNdtbVx8gMeq76bRhP1YdDg2jm+DdJ21KWjf5QHsbyoXDfJzdKlCloLIlAU3EPJhMoXsnNzre0/FXeUl6dfteR1axNS6U7e/vKsQ9rlUFZWIQaeVPjfXmFKblNNVQ5uFrrsB/EGHcJl7IUx5fvcRT5hMMNwC660YxVkBXDbRb5fxMW5/+K0BOi9cP6en8=
|
||||
skip_cleanup: true
|
||||
file_glob: true
|
||||
file: pwnagotchi-*.zip
|
||||
file:
|
||||
- pwnagotchi-*.zip
|
||||
- pwnagotchi-*.sha256
|
||||
on:
|
||||
tags: true
|
||||
repo: evilsocket/pwnagotchi
|
||||
@ -22,20 +24,13 @@ branches:
|
||||
cache:
|
||||
apt: true
|
||||
before_script:
|
||||
- wget https://download.qemu.org/qemu-4.1.0.tar.xz
|
||||
- tar xvJf qemu-4.1.0.tar.xz
|
||||
- cd qemu-4.1.0
|
||||
- "./configure --target-list=arm-softmmu"
|
||||
- make -j$(nproc)
|
||||
- sudo make install
|
||||
- cd $TRAVIS_BUILD_DIR
|
||||
- sudo apt-get -y update || true
|
||||
- sudo apt-get -y install qemu-user-static binfmt-support bmap-tools kpartx
|
||||
- sudo apt-get -y install qemu-system-arm qemu-user-static binfmt-support bmap-tools kpartx
|
||||
- sudo update-binfmts --display
|
||||
script:
|
||||
- sudo make clean
|
||||
- sudo -E env "PATH=$PATH" make install
|
||||
- sudo make image -e PWN_HOSTNAME=pwnagotchi VERSION=$TRAVIS_TAG
|
||||
- sudo make image -e PWN_HOSTNAME=pwnagotchi PWN_VERSION=$TRAVIS_TAG
|
||||
notifications:
|
||||
slack:
|
||||
secure: aovN87lswg+TTLobxJpevC0p2F4omTAlsOzeKqLysRW55o5rRhRC1SgwRkWUl19yr49nsyffwmv/b7OcyQiWIVnz1bxxE9XOKP8zgRMA/bKKcyAcPktPqHXsALIQDseXyl0kz7fwdkRWg0UC2HpKqi5koAhmBYTX/fbzieyeHCbcQ7lbFfVFIepE1401y9m1IqUHcHuGfFhMvTaSDIpXrDXnWdA8+gDAl0HKJv41MIsgmffbh/QhD2jLBWzItjxFC3llmNfy88pnzCk0+HBMY/4272LXb0czX7et5HJeM74oxPqkb3aKXFxZgNaDl7cYdV+kzj9dfKUk47hAqwbxlirit5WvHI1Br1VyA90+PFvcC/p41J8gCv0IlcB5vjWN8NKWA1J+Y1F+KvrujMvGtgd0foHZvaSutuRODhI1cBh5rYAiLCroRSlvKMw3IJRyCRstYgUlMIJ3cI2Ova/kU44KtDVmjT9VE/pPkhkHBPvcYThL6skZTdl19E/RlormLu3XObG1aHLZ+Znxe/aL7tWHi0KMOlpy+TMDdps4go7URnJ8yitHtIvU/zMtBrztIwN0Oy2JLKXrS5qIijmRAkBLxe0NxuG01DYFzEO3KtnRirP4uSe3QcrjyP4sqPrVhrjl3TR6gwg8V1juvDXB4e2h8yCpaUW5AdSBOlx9riY=
|
||||
|
8
MANIFEST.in
Normal file
8
MANIFEST.in
Normal file
@ -0,0 +1,8 @@
|
||||
exclude *.pyc .DS_Store .gitignore MANIFEST.in
|
||||
include setup.py
|
||||
include distribute_setup.py
|
||||
include README.md
|
||||
include LICENSE
|
||||
recursive-include bin *
|
||||
recursive-include pwnagotchi *.py
|
||||
recursive-include pwnagotchi *.yml
|
5
Makefile
5
Makefile
@ -12,9 +12,10 @@ install:
|
||||
cp /tmp/packer-builder-arm-image/packer-builder-arm-image /usr/bin
|
||||
|
||||
image:
|
||||
cd builder && sudo /usr/bin/packer build pwnagotchi.json
|
||||
cd builder && sudo /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" pwnagotchi.json
|
||||
mv builder/output-pwnagotchi/image pwnagotchi-raspbian-lite-$(PWN_VERSION).img
|
||||
zip pwnagotchi-raspbian-lite-$(PWN_VERSION).zip pwnagotchi-raspbian-lite-$(PWN_VERSION).img
|
||||
sha256sum pwnagotchi-raspbian-lite-$(PWN_VERSION).img > pwnagotchi-raspbian-lite-$(PWN_VERSION).sha256
|
||||
zip pwnagotchi-raspbian-lite-$(PWN_VERSION).zip pwnagotchi-raspbian-lite-$(PWN_VERSION).sha256 pwnagotchi-raspbian-lite-$(PWN_VERSION).img
|
||||
|
||||
clean:
|
||||
rm -rf /tmp/packer-builder-arm-image
|
||||
|
44
README.md
44
README.md
@ -4,41 +4,49 @@
|
||||
<p align="center">
|
||||
<a href="https://github.com/evilsocket/pwnagotchi/releases/latest"><img alt="Release" src="https://img.shields.io/github/release/evilsocket/pwnagotchi.svg?style=flat-square"></a>
|
||||
<a href="https://github.com/evilsocket/pwnagotchi/blob/master/LICENSE.md"><img alt="Software License" src="https://img.shields.io/badge/license-GPL3-brightgreen.svg?style=flat-square"></a>
|
||||
<a href="https://github.com/evilsocket/pwnagotchi/graphs/contributors"><img alt="Contributors" src="https://img.shields.io/github/contributors/evilsocket/pwnagotchi"/></a>
|
||||
<a href="https://travis-ci.org/evilsocket/pwnagotchi"><img alt="Travis" src="https://img.shields.io/travis/evilsocket/pwnagotchi/master.svg?style=flat-square"></a>
|
||||
<a href="https://pwnagotchi.herokuapp.com/"><img alt="Slack" src="https://pwnagotchi.herokuapp.com/badge.svg"></a>
|
||||
<a href="https://twitter.com/intent/follow?screen_name=pwnagotchi"><img src="https://img.shields.io/twitter/follow/pwnagotchi?style=social&logo=twitter" alt="follow on Twitter"></a>
|
||||
</p>
|
||||
</p>
|
||||
|
||||
[Pwnagotchi](https://twitter.com/pwnagotchi) is an "AI" that learns from the WiFi environment and instruments bettercap in order to maximize the WPA key material (any form of handshake that is crackable, including [PMKIDs](https://www.evilsocket.net/2019/02/13/Pwning-WiFi-networks-with-bettercap-and-the-PMKID-client-less-attack/), full and half WPA handshakes) captured.
|
||||
[Pwnagotchi](https://twitter.com/pwnagotchi) is an [A2C](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752)-based "AI" leveraging [bettercap](https://www.bettercap.org/) that learns from its surrounding WiFi environment in order to maximize the crackable WPA key material it captures (either passively, or by performing deauthentication and association attacks). This material is collected as PCAP files containing any form of handshake supported by [hashcat](https://hashcat.net/hashcat/), including [PMKIDs](https://www.evilsocket.net/2019/02/13/Pwning-WiFi-networks-with-bettercap-and-the-PMKID-client-less-attack/),
|
||||
full and half WPA handshakes.
|
||||
|
||||

|
||||

|
||||
|
||||
Specifically, it's using an [LSTM with MLP feature extractor](https://stable-baselines.readthedocs.io/en/master/modules/policies.html#stable_baselines.common.policies.MlpLstmPolicy) as its policy network for the [A2C agent](https://stable-baselines.readthedocs.io/en/master/modules/a2c.html), here is [a very good intro](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752) on the subject.
|
||||
Instead of merely playing [Super Mario or Atari games](https://becominghuman.ai/getting-mario-back-into-the-gym-setting-up-super-mario-bros-in-openais-gym-8e39a96c1e41?gi=c4b66c3d5ced) like most reinforcement learning based "AI" *(yawn)*, Pwnagotchi tunes [its own parameters](https://github.com/evilsocket/pwnagotchi/blob/master/pwnagotchi/defaults.yml#L73) over time to **get better at pwning WiFi things** in the environments you expose it to.
|
||||
|
||||
Instead of playing [Super Mario or Atari games](https://becominghuman.ai/getting-mario-back-into-the-gym-setting-up-super-mario-bros-in-openais-gym-8e39a96c1e41?gi=c4b66c3d5ced), pwnagotchi will tune over time [its own parameters](https://github.com/evilsocket/pwnagotchi/blob/master/sdcard/rootfs/root/pwnagotchi/config.yml#L54), effectively learning to get better at pwning WiFi things. **Keep in mind:** unlike the usual RL simulations, pwnagotchi learns over time (where a single epoch can last from a few seconds to minutes, depending on how many access points and client stations are visible), do not expect it to perform amazingly well at the beginning, as it'll be exploring several combinations of parameters ... but listen to it when it's bored, bring it with you and have it observe new networks and capture new handshakes and you'll see :)
|
||||
More specifically, Pwnagotchi is using an [LSTM with MLP feature extractor](https://stable-baselines.readthedocs.io/en/master/modules/policies.html#stable_baselines.common.policies.MlpLstmPolicy) as its policy network for the [A2C agent](https://stable-baselines.readthedocs.io/en/master/modules/a2c.html). If you're unfamiliar with A2C, here is [a very good introductory explanation](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752) (in comic form!) of the basic principles behind how Pwnagotchi learns. (You can read more about how Pwnagotchi learns in the [Usage](https://www.pwnagotchi.ai/usage/#training-the-ai) doc.)
|
||||
|
||||
Multiple units can talk to each other, advertising their own presence using a parasite protocol I've built on top of the existing dot11 standard, by broadcasting custom information elements. Over time, two or more units learn to cooperate if they detect each other's presence, by dividing the available channels among them.
|
||||
**Keep in mind:** Unlike the usual RL simulations, Pwnagotchi actually learns over time. Time for a Pwnagotchi is measured in epochs; a single epoch can last from a few seconds to minutes, depending on how many access points and client stations are visible. Do not expect your Pwnagotchi to perform amazingly well at the very beginning, as it will be [exploring](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752) several combinations of [key parameters](https://www.pwnagotchi.ai/usage/#training-the-ai) to determine ideal adjustments for pwning the particular environment you are exposing it to during its beginning epochs ... but **definitely listen to your Pwnagotchi when it tells you it's bored!** Bring it into novel WiFi environments with you and have it observe new networks and capture new handshakes—and you'll see. :)
|
||||
|
||||
## Why
|
||||
Multiple units within close physical proximity can "talk" to each other, advertising their own presence to each other by broadcasting custom information elements using a parasite protocol I've built on top of the existing dot11 standard. Over time, two or more units trained together will learn to cooperate upon detecting each other's presence by dividing the available channels among them for optimal pwnage.
|
||||
|
||||
For hackers to learn reinforcement learning, WiFi networking and have an excuse to take a walk more often. And **it's cute as f---**.
|
||||
## Why does Pwnagotchi exist?
|
||||
|
||||
For hackers to learn reinforcement learning, WiFi networking, and have an excuse to get out for more walks. Also? **It's cute as f---**.
|
||||
|
||||
**In case you're curious about the name:** *Pwnagotchi* is a portmanteau of *pwn* (which we shouldn't have to explain if you are interested in this project :kissing_heart:) and *-gotchi*. It is a nostalgic reference made in homage to a very popular children's toy from the 1990s called the [Tamagotchi](https://en.wikipedia.org/wiki/Tamagotchi). The Tamagotchi (たまごっち, derived from *tamago* (たまご) "egg" + *uotchi* (ウオッチ) "watch") is a cultural touchstone for many Millennial hackers as a formative electronic toy from our collective childhoods. Were you lucky enough to possess a Tamagotchi as a kid? Well, with your Pwnagotchi, you too can enjoy the nostalgic delight of being strangely emotionally attached to a handheld automata *yet again!* Except, this time around...you get to #HackThePlanet. >:D
|
||||
|
||||
## Documentation
|
||||
---
|
||||
:warning: **THE FOLLOWING DOCUMENTATION IS BEING PREPARED FOR THE v1.0 RELEASE OF PWNAGOTCHI. Since this effort is an active (and unstable) work-in-progress, the docs displayed here are in various stages of [in]completion. There will be dead links and placeholders throughout as we are still building things out in preparation for the v1.0 release.** :warning:
|
||||
|
||||
- [About the Project](https://github.com/evilsocket/pwnagotchi/blob/master/docs/about.md)
|
||||
- [How to Install](https://github.com/evilsocket/pwnagotchi/blob/master/docs/install.md)
|
||||
- [Configuration](https://github.com/evilsocket/pwnagotchi/blob/master/docs/configure.md)
|
||||
- [Usage](https://github.com/evilsocket/pwnagotchi/blob/master/docs/usage.md)
|
||||
- [Plugins](https://github.com/evilsocket/pwnagotchi/blob/master/docs/plugins.md)
|
||||
- [Developement](https://github.com/evilsocket/pwnagotchi/blob/master/docs/dev.md)
|
||||
- [FAQ](https://github.com/evilsocket/pwnagotchi/blob/master/docs/faq.md)
|
||||
**IMPORTANT NOTE:** If you'd like to alphatest Pwnagotchi and are trying to get yours up and running while the project is still very unstable, please understand that the documentation here may not reflect what is currently implemented. If you have questions, ask the community of alphatesters in the [official Pwnagotchi Slack](https://pwnagotchi.herokuapp.com). The Pwnagotchi dev team is entirely focused on the v1.0 release and will NOT be providing support for alphatesters trying to get their Pwnagotchis working in the meantime. All technical support during this period of development is being provided by your fellow alphatesters in the Slack (thanks, everybody! :heart:).
|
||||
|
||||
https://www.pwnagotchi.ai
|
||||
|
||||
## Links
|
||||
|
||||
- [Project Slack](https://join.slack.com/t/pwnagotchi/shared_invite/enQtNzc4NzY3MDE2OTAzLTg5NmNmNDJiMDM3ZWFkMWUwN2Y5NDk0Y2JlZWZjODlhMmRhNDZiOGMwYjJhM2UzNzA3YjA5NjJmZGY5NGI5NmI)
|
||||
- [Project Twitter](https://twitter.com/pwnagotchi)
|
||||
- [Project Website](https://pwnagotchi.ai/)
|
||||
| Official Links
|
||||
---------|-------
|
||||
Slack | [pwnagotchi.slack.com](https://pwnagotchi.herokuapp.com)
|
||||
Twitter | [@pwnagotchi](https://twitter.com/pwnagotchi)
|
||||
Subreddit | [r/pwnagotchi](https://www.reddit.com/r/pwnagotchi/)
|
||||
Website | [pwnagotchi.ai](https://pwnagotchi.ai/)
|
||||
|
||||
## License
|
||||
|
||||
`pwnagotchi` is made with ♥ by [@evilsocket](https://twitter.com/evilsocket) and the [amazing dev team](https://github.com/evilsocket/pwnagotchi/graphs/contributors). It's released under the GPL3 license.
|
||||
`pwnagotchi` is made with ♥ by [@evilsocket](https://twitter.com/evilsocket) and the [amazing dev team](https://github.com/evilsocket/pwnagotchi/graphs/contributors). It is released under the GPL3 license.
|
||||
|
111
bin/pwnagotchi
Executable file
111
bin/pwnagotchi
Executable file
@ -0,0 +1,111 @@
|
||||
#!/usr/bin/python3
|
||||
if __name__ == '__main__':
|
||||
import argparse
|
||||
import time
|
||||
import os
|
||||
import logging
|
||||
|
||||
import pwnagotchi
|
||||
import pwnagotchi.utils as utils
|
||||
import pwnagotchi.plugins as plugins
|
||||
|
||||
from pwnagotchi.identity import KeyPair
|
||||
from pwnagotchi.agent import Agent
|
||||
from pwnagotchi.ui.display import Display
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument('-C', '--config', action='store', dest='config', default='/etc/pwnagotchi/default.yml',
|
||||
help='Main configuration file.')
|
||||
parser.add_argument('-U', '--user-config', action='store', dest='user_config', default='/etc/pwnagotchi/config.yml',
|
||||
help='If this file exists, configuration will be merged and this will override default values.')
|
||||
|
||||
parser.add_argument('--manual', dest="do_manual", action="store_true", default=False, help="Manual mode.")
|
||||
parser.add_argument('--clear', dest="do_clear", action="store_true", default=False,
|
||||
help="Clear the ePaper display and exit.")
|
||||
|
||||
parser.add_argument('--debug', dest="debug", action="store_true", default=False,
|
||||
help="Enable debug logs.")
|
||||
|
||||
args = parser.parse_args()
|
||||
config = utils.load_config(args)
|
||||
utils.setup_logging(args, config)
|
||||
|
||||
plugins.load(config)
|
||||
|
||||
keypair = KeyPair()
|
||||
display = Display(config=config, state={'name': '%s>' % pwnagotchi.name()})
|
||||
agent = Agent(view=display, config=config, keypair=keypair)
|
||||
|
||||
logging.info("%s@%s (v%s)" % (pwnagotchi.name(), agent._keypair.fingerprint, pwnagotchi.version))
|
||||
|
||||
for _, plugin in plugins.loaded.items():
|
||||
logging.debug("plugin '%s' v%s loaded from %s" % (plugin.__name__, plugin.__version__, plugin.__file__))
|
||||
|
||||
if args.do_clear:
|
||||
logging.info("clearing the display ...")
|
||||
display.clear()
|
||||
|
||||
elif args.do_manual:
|
||||
logging.info("entering manual mode ...")
|
||||
|
||||
agent.last_session.parse()
|
||||
|
||||
logging.info(
|
||||
"the last session lasted %s (%d completed epochs, trained for %d), average reward:%s (min:%s max:%s)" % (
|
||||
agent.last_session.duration_human,
|
||||
agent.last_session.epochs,
|
||||
agent.last_session.train_epochs,
|
||||
agent.last_session.avg_reward,
|
||||
agent.last_session.min_reward,
|
||||
agent.last_session.max_reward))
|
||||
|
||||
while True:
|
||||
display.on_manual_mode(agent.last_session)
|
||||
time.sleep(1)
|
||||
|
||||
if Agent.is_connected():
|
||||
plugins.on('internet_available', agent)
|
||||
|
||||
else:
|
||||
logging.info("entering auto mode ...")
|
||||
|
||||
agent.start()
|
||||
|
||||
while True:
|
||||
try:
|
||||
# recon on all channels
|
||||
agent.recon()
|
||||
# get nearby access points grouped by channel
|
||||
channels = agent.get_access_points_by_channel()
|
||||
# check for free channels to use
|
||||
agent.check_channels(channels)
|
||||
# for each channel
|
||||
for ch, aps in channels:
|
||||
agent.set_channel(ch)
|
||||
|
||||
if not agent.is_stale() and agent.any_activity():
|
||||
logging.info("%d access points on channel %d" % (len(aps), ch))
|
||||
|
||||
# for each ap on this channel
|
||||
for ap in aps:
|
||||
# send an association frame in order to get for a PMKID
|
||||
agent.associate(ap)
|
||||
# deauth all client stations in order to get a full handshake
|
||||
for sta in ap['clients']:
|
||||
agent.deauth(ap, sta)
|
||||
|
||||
# An interesting effect of this:
|
||||
#
|
||||
# From Pwnagotchi's perspective, the more new access points
|
||||
# and / or client stations nearby, the longer one epoch of
|
||||
# its relative time will take ... basically, in Pwnagotchi's universe,
|
||||
# WiFi electromagnetic fields affect time like gravitational fields
|
||||
# affect ours ... neat ^_^
|
||||
agent.next_epoch()
|
||||
|
||||
if Agent.is_connected():
|
||||
plugins.on('internet_available', agent)
|
||||
|
||||
except Exception as e:
|
||||
logging.exception("main loop exception")
|
@ -1,7 +1,4 @@
|
||||
{
|
||||
"variables": {
|
||||
"home": "{{env `HOME`}}"
|
||||
},
|
||||
"builders": [{
|
||||
"name": "pwnagotchi",
|
||||
"type": "arm-image",
|
||||
@ -15,13 +12,15 @@
|
||||
"type": "shell",
|
||||
"inline": [
|
||||
"sed -i 's/^\\([^#]\\)/#\\1/g' /etc/ld.so.preload",
|
||||
"dpkg-architecture",
|
||||
"apt-get -y update",
|
||||
"apt-get install -y ansible"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type":"ansible-local",
|
||||
"playbook_file": "pwnagotchi.yml"
|
||||
"playbook_file": "pwnagotchi.yml",
|
||||
"command": "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 PWN_VERSION={{user `pwn_version`}} PWN_HOSTNAME={{user `pwn_hostname`}} ansible-playbook"
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
|
@ -3,28 +3,109 @@
|
||||
- 127.0.0.1
|
||||
become: yes
|
||||
vars:
|
||||
pwn_hostname: "pwnagotchi"
|
||||
pwn_version: "master"
|
||||
pwnagotchi:
|
||||
hostname: "{{ lookup('env', 'PWN_HOSTNAME') | default('pwnagotchi', true) }}"
|
||||
version: "{{ lookup('env', 'PWN_VERSION') | default('master', true) }}"
|
||||
system:
|
||||
boot_options:
|
||||
- "dtoverlay=dwc2"
|
||||
- "dtparam=spi=on"
|
||||
- "dtoverlay=spi1-3cs"
|
||||
- "dtoverlay=i2c_arm=on"
|
||||
- "dtoverlay=i2c1=on"
|
||||
services:
|
||||
enable:
|
||||
- dphys-swapfile.service
|
||||
- pwnagotchi.service
|
||||
- bettercap.service
|
||||
- epd-fuse.service
|
||||
disable:
|
||||
- apt-daily.timer
|
||||
- apt-daily.service
|
||||
- apt-daily-upgrade.timer
|
||||
- apt-daily-upgrade.service
|
||||
- wpa_supplicant.service
|
||||
- bluetooth.service
|
||||
- triggerhappy.service
|
||||
- ifup@wlan0.service
|
||||
packages:
|
||||
bettercap:
|
||||
url: "https://github.com/bettercap/bettercap/releases/download/v2.25/bettercap_linux_armv6l_2.25.zip"
|
||||
ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip"
|
||||
apt:
|
||||
remove:
|
||||
- rasberrypi-net-mods
|
||||
- dhcpcd5
|
||||
- triggerhappy
|
||||
- wpa_supplicant
|
||||
- nfs-common
|
||||
install:
|
||||
- vim
|
||||
- screen
|
||||
- golang
|
||||
- git
|
||||
- build-essential
|
||||
- python3-pip
|
||||
- python3-mpi4py
|
||||
- python3-smbus
|
||||
- unzip
|
||||
- gawk
|
||||
- libopenmpi-dev
|
||||
- libatlas-base-dev
|
||||
- libjasper-dev
|
||||
- libqtgui4
|
||||
- libqt4-test
|
||||
- libopenjp2-7
|
||||
- libtiff5
|
||||
- tcpdump
|
||||
- lsof
|
||||
- libilmbase23
|
||||
- libopenexr23
|
||||
- libgstreamer1.0-0
|
||||
- libavcodec58
|
||||
- libavformat58
|
||||
- libswscale5
|
||||
- libpcap-dev
|
||||
- libusb-1.0-0-dev
|
||||
- libnetfilter-queue-dev
|
||||
- libopenmpi3
|
||||
- dphys-swapfile
|
||||
- kalipi-kernel
|
||||
- kalipi-bootloader
|
||||
- kalipi-re4son-firmware
|
||||
- kalipi-kernel-headers
|
||||
- libraspberrypi0
|
||||
- libraspberrypi-dev
|
||||
- libraspberrypi-doc
|
||||
- libraspberrypi-bin
|
||||
- fonts-dejavu
|
||||
- fonts-dejavu-core
|
||||
- fonts-dejavu-extra
|
||||
- python3-pil
|
||||
- python3-smbus
|
||||
- libfuse-dev
|
||||
- bc
|
||||
- fonts-freefont-ttf
|
||||
|
||||
tasks:
|
||||
|
||||
- name: selected hostname
|
||||
debug:
|
||||
msg: "{{ pwn_hostname }}"
|
||||
msg: "{{ pwnagotchi.hostname }}"
|
||||
|
||||
- name: build version
|
||||
debug:
|
||||
msg: "{{ pwn_version }}"
|
||||
msg: "{{ pwnagotchi.version }}"
|
||||
|
||||
- name: change hostname
|
||||
hostname:
|
||||
name: "{{pwn_hostname}}"
|
||||
name: "{{pwnagotchi.hostname}}"
|
||||
|
||||
- name: add hostname to /etc/hosts
|
||||
lineinfile:
|
||||
dest: /etc/hosts
|
||||
regexp: '^127\.0\.0\.1[ \t]+localhost'
|
||||
line: '127.0.0.1 localhost {{pwn_hostname}} {{pwn_hostname}}.local'
|
||||
line: '127.0.0.1 localhost {{pwnagotchi.hostname}} {{pwnagotchi.hostname}}.local'
|
||||
state: present
|
||||
|
||||
- name: Add re4son-kernel repo key
|
||||
@ -41,88 +122,90 @@
|
||||
apt:
|
||||
update_cache: yes
|
||||
|
||||
- name: remove unecessary apt packages
|
||||
apt:
|
||||
name: "{{ packages.apt.remove }}"
|
||||
state: absent
|
||||
purge: yes
|
||||
|
||||
- name: upgrade apt distro
|
||||
apt:
|
||||
upgrade: dist
|
||||
|
||||
- name: install packages
|
||||
apt:
|
||||
name: "{{ packages }}"
|
||||
name: "{{ packages.apt.install }}"
|
||||
state: present
|
||||
vars:
|
||||
packages:
|
||||
- vim
|
||||
- screen
|
||||
- golang
|
||||
- git
|
||||
- build-essential
|
||||
- python3-pip
|
||||
- gawk
|
||||
- libopenmpi-dev
|
||||
- libatlas-base-dev
|
||||
- libjasper-dev
|
||||
- libqtgui4
|
||||
- libqt4-test
|
||||
- libopenjp2-7
|
||||
- tcpdump
|
||||
- lsof
|
||||
- libilmbase23
|
||||
- libopenexr23
|
||||
- libgstreamer1.0-0
|
||||
- libavcodec58
|
||||
- libavformat58
|
||||
- libswscale5
|
||||
- libpcap-dev
|
||||
- libusb-1.0-0-dev
|
||||
- libnetfilter-queue-dev
|
||||
- dphys-swapfile
|
||||
- kalipi-kernel
|
||||
- kalipi-bootloader
|
||||
- kalipi-re4son-firmware
|
||||
- kalipi-kernel-headers
|
||||
- libraspberrypi0
|
||||
- libraspberrypi-dev
|
||||
- libraspberrypi-doc
|
||||
- libraspberrypi-bin
|
||||
- fonts-dejavu
|
||||
- fonts-dejavu-core
|
||||
- fonts-dejavu-extra
|
||||
|
||||
- name: configure dphys-swapfile
|
||||
file:
|
||||
path: /etc/dphys-swapfile
|
||||
content: "CONF_SWAPSIZE=1024"
|
||||
|
||||
- name: disable unecessary services
|
||||
systemd:
|
||||
name: "{{services}}"
|
||||
state: stopped
|
||||
enabled: no
|
||||
vars:
|
||||
services:
|
||||
- apt-daily.timer
|
||||
- apt-daily.service
|
||||
- apt-daily-upgrade.timer
|
||||
- apt-daily-upgrade.service
|
||||
- bluetooth.service
|
||||
- triggerhappy.service
|
||||
- name: clone papirus repository
|
||||
git:
|
||||
repo: https://github.com/repaper/gratis.git
|
||||
dest: /usr/local/src/gratis
|
||||
|
||||
- name: enable dphys-swapfile service
|
||||
systemd:
|
||||
name: dphys-swapfile.service
|
||||
state: started
|
||||
enabled: yes
|
||||
- name: build papirus service
|
||||
make:
|
||||
chdir: /usr/local/src/gratis
|
||||
target: rpi
|
||||
params:
|
||||
EPD_IO: epd_io.h
|
||||
PANEL_VERSION: 'V231_G2'
|
||||
|
||||
- name: build bettercap
|
||||
command: go get -u github.com/bettercap/bettercap
|
||||
environment:
|
||||
GOPATH: /root/go
|
||||
GOROOT: /usr/lib/go
|
||||
- name: install papirus service
|
||||
make:
|
||||
chdir: /usr/local/src/gratis
|
||||
target: rpi-install
|
||||
params:
|
||||
EPD_IO: epd_io.h
|
||||
PANEL_VERSION: 'V231_G2'
|
||||
|
||||
- name: install bettercap
|
||||
copy:
|
||||
src: /root/go/bin/bettercap
|
||||
dest: /usr/bin/bettercap
|
||||
- name: configure papirus display size
|
||||
lineinfile:
|
||||
dest: /etc/default/epd-fuse
|
||||
regexp: "#EPD_SIZE=2.0"
|
||||
line: "EPD_SIZE=2.0"
|
||||
|
||||
- name: acquire python3 pip target
|
||||
command: "python3 -c 'import sys;print(sys.path.pop())'"
|
||||
register: pip_target
|
||||
|
||||
- name: clone pwnagotchi repository
|
||||
git:
|
||||
repo: https://github.com/evilsocket/pwnagotchi.git
|
||||
dest: /usr/local/src/pwnagotchi
|
||||
|
||||
- name: build pwnagotchi wheel
|
||||
command: "python3 setup.py sdist bdist_wheel"
|
||||
args:
|
||||
chdir: /usr/local/src/pwnagotchi
|
||||
|
||||
- name: install opencv-python
|
||||
pip:
|
||||
name: "https://www.piwheels.hostedpi.com/simple/opencv-python/opencv_python-3.4.3.18-cp37-cp37m-linux_armv6l.whl"
|
||||
extra_args: "--no-deps --no-cache-dir --platform=linux_armv6l --only-binary=:all: --target={{ pip_target.stdout }}"
|
||||
|
||||
- name: install tensorflow
|
||||
pip:
|
||||
name: "https://www.piwheels.hostedpi.com/simple/tensorflow/tensorflow-1.13.1-cp37-none-linux_armv6l.whl"
|
||||
extra_args: "--no-deps --no-cache-dir --platform=linux_armv6l --only-binary=:all: --target={{ pip_target.stdout }}"
|
||||
|
||||
- name: install pwnagotchi wheel and dependencies
|
||||
pip:
|
||||
name: "{{ lookup('fileglob', '/usr/local/src/pwnagotchi/dist/pwnagotchi*.whl') }}"
|
||||
extra_args: "--no-cache-dir"
|
||||
|
||||
- name: download and install bettercap
|
||||
unarchive:
|
||||
src: "{{ packages.bettercap.url }}"
|
||||
dest: /usr/bin
|
||||
remote_src: yes
|
||||
exclude:
|
||||
- README.md
|
||||
- LICENSE.md
|
||||
mode: 0755
|
||||
|
||||
- name: clone bettercap caplets
|
||||
@ -135,25 +218,12 @@
|
||||
chdir: /tmp/caplets
|
||||
target: install
|
||||
|
||||
- name: clone pwnagotchi repository
|
||||
git:
|
||||
repo: https://github.com/evilsocket/pwnagotchi.git
|
||||
dest: /tmp/pwnagotchi
|
||||
|
||||
- name: copy pwnagotchi files to final destination
|
||||
copy:
|
||||
src: /tmp/pwnagotchi/sdcard/rootfs/root/pwnagotchi/
|
||||
dest: /root/pwnagotchi/
|
||||
mode: preserve
|
||||
|
||||
- name: remove pwnagotchi files from temporary repository
|
||||
file:
|
||||
path: /tmp/pwnagotchi
|
||||
state: absent
|
||||
|
||||
- name: install python modules
|
||||
pip:
|
||||
requirements: /root/pwnagotchi/scripts/requirements.txt
|
||||
- name: download and install bettercap ui
|
||||
unarchive:
|
||||
src: "{{ packages.bettercap.ui }}"
|
||||
dest: /usr/local/share/bettercap/
|
||||
remote_src: yes
|
||||
mode: 0755
|
||||
|
||||
- name: create cpuusage script
|
||||
copy:
|
||||
@ -175,6 +245,63 @@
|
||||
#!/usr/bin/env bash
|
||||
free -m | awk '/Mem/ { printf( "%d %", $3 / $2 * 100 + 0.5 ) }'
|
||||
|
||||
- name: create bootblink script
|
||||
copy:
|
||||
dest: /usr/bin/bootblink
|
||||
mode: 0755
|
||||
content: |
|
||||
#!/usr/bin/env bash
|
||||
for i in $(seq 1 "$1");
|
||||
do
|
||||
echo 0 >/sys/class/leds/led0/brightness
|
||||
sleep 0.3
|
||||
echo 1 >/sys/class/leds/led0/brightness
|
||||
sleep 0.3
|
||||
done
|
||||
echo 0 >/sys/class/leds/led0/brightness
|
||||
sleep 0.3
|
||||
|
||||
- name: create pwnagotchi-launcher script
|
||||
copy:
|
||||
dest: /usr/bin/pwnagotchi-launcher
|
||||
mode: 0755
|
||||
content: |
|
||||
#!/usr/bin/env bash
|
||||
# blink 10 times to signal ready state
|
||||
/usr/bin/bootblink 10 &
|
||||
# start a detached screen session with bettercap
|
||||
if ifconfig | grep usb0 | grep RUNNING; then
|
||||
# if override file exists, go into auto mode
|
||||
if [ -f /root/.pwnagotchi-auto ]; then
|
||||
rm /root/.pwnagotchi-auto
|
||||
/usr/local/bin/pwnagotchi
|
||||
else
|
||||
/usr/local/bin/pwnagotchi --manual
|
||||
fi
|
||||
else
|
||||
/usr/local/bin/pwnagotchi
|
||||
fi
|
||||
|
||||
- name: create bettercap-launcher script
|
||||
copy:
|
||||
dest: /usr/bin/bettercap-launcher
|
||||
mode: 0755
|
||||
content: |
|
||||
#!/usr/bin/env bash
|
||||
# blink 10 times to signal ready state
|
||||
/usr/bin/bootblink 10 &
|
||||
if ifconfig | grep usb0 | grep RUNNING; then
|
||||
# if override file exists, go into auto mode
|
||||
if [ -f /root/.pwnagotchi-auto ]; then
|
||||
rm /root/.pwnagotchi-auto
|
||||
/usr/bin/bettercap -no-colors -caplet pwnagotchi-auto
|
||||
else
|
||||
/usr/bin/bettercap -no-colors -caplet pwnagotchi-manual
|
||||
fi
|
||||
else
|
||||
/usr/bin/bettercap -no-colors -caplet pwnagotchi-auto
|
||||
fi
|
||||
|
||||
- name: create monstart script
|
||||
copy:
|
||||
dest: /usr/bin/monstart
|
||||
@ -191,7 +318,23 @@
|
||||
#!/usr/bin/env bash
|
||||
ifconfig mon0 down && iw dev mon0 del
|
||||
|
||||
- name: configure rc.local
|
||||
- name: create hdmion script
|
||||
copy:
|
||||
dest: /usr/bin/hdmion
|
||||
mode: 0755
|
||||
content: |
|
||||
#!/usr/bin/env bash
|
||||
sudo /opt/vc/bin/tvservice -p
|
||||
|
||||
- name: create hdmioff script
|
||||
copy:
|
||||
dest: /usr/bin/hdmioff
|
||||
mode: 0755
|
||||
content: |
|
||||
#!/usr/bin/env bash
|
||||
sudo /opt/vc/bin/tvservice -o
|
||||
|
||||
- name: add HDMI powersave to rc.local
|
||||
blockinfile:
|
||||
path: /etc/rc.local
|
||||
insertbefore: "exit 0"
|
||||
@ -199,7 +342,13 @@
|
||||
if ! /opt/vc/bin/tvservice -s | grep HDMI; then
|
||||
/opt/vc/bin/tvservice -o
|
||||
fi
|
||||
/root/pwnagotchi/scripts/startup.sh &
|
||||
|
||||
- name: create /etc/pwnagotchi/config.yml
|
||||
blockinfile:
|
||||
path: /etc/pwnagotchi/config.yml
|
||||
create: yes
|
||||
block: |
|
||||
# put here your custom configuration overrides
|
||||
|
||||
- name: configure lo interface
|
||||
blockinfile:
|
||||
@ -249,11 +398,7 @@
|
||||
insertafter: EOF
|
||||
line: '{{ item }}'
|
||||
with_items:
|
||||
- "dtoverlay=dwc2"
|
||||
- "dtparam=spi=on"
|
||||
- "dtoverlay=spi1-3cs"
|
||||
- "dtoverlay=pi3-disable-bt"
|
||||
- "dtparam=audio=off"
|
||||
- "{{system.boot_options}}"
|
||||
|
||||
- name: change root partition
|
||||
replace:
|
||||
@ -271,17 +416,10 @@
|
||||
regexp: '(.*)$'
|
||||
line: '\1 modules-load=dwc2,g_ether'
|
||||
|
||||
- name: configure ssh
|
||||
lineinfile:
|
||||
dest: /etc/ssh/sshd_config
|
||||
backup: no
|
||||
regexp: '#?PermitRootLogin (.*)$'
|
||||
line: 'PermitRootLogin yes'
|
||||
|
||||
- name: configure motd
|
||||
copy:
|
||||
dest: /etc/motd
|
||||
content: "(◕‿‿◕) {{pwn_hostname}} (pwnagotchi-{{pwn_version}})"
|
||||
content: "(◕‿‿◕) {{pwnagotchi.hostname}} (pwnagotchi-{{pwnagotchi.version}})"
|
||||
|
||||
- name: clean apt cache
|
||||
apt:
|
||||
@ -291,16 +429,76 @@
|
||||
apt:
|
||||
autoremove: yes
|
||||
|
||||
- name: add bettercap service to systemd
|
||||
copy:
|
||||
dest: /etc/systemd/system/bettercap.service
|
||||
content: |
|
||||
[Unit]
|
||||
Description=bettercap api.rest service.
|
||||
Documentation=https://bettercap.org
|
||||
Wants=network.target
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
PermissionsStartOnly=true
|
||||
ExecStartPre=/usr/bin/monstart
|
||||
ExecStart=/usr/bin/bettercap-launcher
|
||||
ExecStopPost=/usr/bin/monstop
|
||||
Restart=always
|
||||
RestartSec=30
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
notify:
|
||||
- reload systemd services
|
||||
|
||||
- name: add pwnagotchi service to systemd
|
||||
copy:
|
||||
dest: /etc/systemd/system/pwnagotchi.service
|
||||
content: |
|
||||
[Unit]
|
||||
Description=pwnagotchi Deep Reinforcement Learning instrumenting bettercap for WiFI pwning.
|
||||
Documentation=https://pwnagotchi.ai
|
||||
Wants=network.target
|
||||
After=bettercap.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
PermissionsStartOnly=true
|
||||
ExecStart=/usr/bin/pwnagotchi-launcher
|
||||
Restart=always
|
||||
RestartSec=30
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
notify:
|
||||
- reload systemd services
|
||||
|
||||
- name: enable services
|
||||
systemd:
|
||||
name: "{{ item }}"
|
||||
state: started
|
||||
enabled: yes
|
||||
with_items: "{{ services.enable }}"
|
||||
|
||||
- name: disable unecessary services
|
||||
systemd:
|
||||
name: "{{ item }}"
|
||||
state: stopped
|
||||
enabled: no
|
||||
with_items: "{{ services.disable }}"
|
||||
|
||||
- name: remove ssh keys
|
||||
file:
|
||||
state: absent
|
||||
path: "{{item}}"
|
||||
with_items:
|
||||
- /etc/ssh/ssh_host_rsa_key
|
||||
- /etc/ssh/ssh_host_rsa_key.pub
|
||||
- /etc/ssh/ssh_host_dsa_key
|
||||
- /etc/ssh/ssh_host_dsa_key.pub
|
||||
- /etc/ssh/ssh_host/ecdsa_key
|
||||
- /etc/ssh/ssh_host/ecdsa_key.pub
|
||||
- /etc/ssh/ssh_host_ed25519_key
|
||||
- /etc/ssh/ssh_host_ed25519_key.pub
|
||||
with_fileglob:
|
||||
- "/etc/ssh/ssh_host*_key*"
|
||||
|
||||
handlers:
|
||||
- name: reload systemd services
|
||||
systemd:
|
||||
daemon_reload: yes
|
||||
|
||||
|
||||
|
@ -1,23 +0,0 @@
|
||||
# About the Project
|
||||
|
||||
[Pwnagotchi](https://twitter.com/pwnagotchi) is an [A2C](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752)-based "AI" leveraging [bettercap](https://www.bettercap.org/) that learns from its surrounding WiFi environment in order to maximize the WPA key material it captures (either passively, or by performing deauthentication and association attacks). This material is collected as PCAP files containing any form of handshake supported by [hashcat](https://hashcat.net/hashcat/), including [PMKIDs](https://www.evilsocket.net/2019/02/13/Pwning-WiFi-networks-with-bettercap-and-the-PMKID-client-less-attack/),
|
||||
full and half WPA handshakes.
|
||||
|
||||

|
||||
|
||||
Instead of merely playing [Super Mario or Atari games](https://becominghuman.ai/getting-mario-back-into-the-gym-setting-up-super-mario-bros-in-openais-gym-8e39a96c1e41?gi=c4b66c3d5ced) like most reinforcement learning based "AI" *(yawn)*, Pwnagotchi tunes [its own parameters](https://github.com/evilsocket/pwnagotchi/blob/master/sdcard/rootfs/root/pwnagotchi/config.yml#L54) over time to **get better at pwning WiFi things** in the environments you expose it to.
|
||||
|
||||
**Keep in mind:** Unlike the usual RL simulations, Pwnagotchi actually learns over time. Time for a Pwnagotchi is measured in epochs; a single epoch can last from a few seconds to minutes, depending on how many access points and client stations are visible. Do not expect your Pwnagotchi to perform amazingly well at the very beginning, as it will be [exploring](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752) several combinations of [key parameters](https://github.com/evilsocket/pwnagotchi/blob/master/docs/usage.md#training-the-ai) to determine ideal adjustments for pwning the particular environment you are exposing it to during its beginning epochs ... but **definitely listen to your pwnagotchi when it tells you it's bored!** Bring it into novel WiFi environments with you and have it observe new networks and capture new handshakes—and you'll see. :)
|
||||
|
||||
Multiple units within close physical proximity can "talk" to each other, advertising their own presence to each other by broadcasting custom information elements using a parasite protocol I've built on top of the existing dot11 standard. Over time, two or more units trained together will learn to cooperate upon detecting each other's presence by dividing the available channels among them for optimal pwnage.
|
||||
|
||||

|
||||
|
||||
[Depending on the status of the unit](), several states and states transitions are configurable and represented on the display as different moods, expressions and sentences. Pwnagotchi speaks [many languages](https://github.com/evilsocket/pwnagotchi/blob/master/docs/configure.md#configuration), too!
|
||||
|
||||
Of course, it is possible to run your Pwnagotchi with the AI disabled (configurable in `config.yml`). Why might you want to do this? Perhaps you simply want to use your own fixed parameters (instead of letting the AI decide for you), or maybe you want to save battery and CPU cycles, or maybe it's just you have strong concerns about aiding and abetting baby Skynet. Whatever your particular reasons may be: an AI-disabled Pwnagotchi is still a simple and very effective automated deauther, WPA handshake sniffer, and portable [bettercap](https://www.bettercap.org/) + [webui](https://github.com/evilsocket/pwnagotchi/blob/master/docs/usage.md#bettercaps-web-ui) dedicated hardware.
|
||||
|
||||
|
||||
## License
|
||||
|
||||
`pwnagotchi` is made with ♥ by [@evilsocket](https://twitter.com/evilsocket) and the [amazing dev team](https://github.com/evilsocket/pwnagotchi/graphs/contributors). It's released under the GPL3 license.
|
@ -1,56 +0,0 @@
|
||||
# Connecting to your Pwnagotchi
|
||||
|
||||
Once you wrote the image file on the SD card, there're a few steps you'll have to follow in order to configure your unit properly, first, start with connecting the USB cable to the data port of the Raspberry Pi and the RPi to your computer. After a few seconds the board will boot and you will see a new Ethernet interface on your host computer.
|
||||
|
||||
You'll need to configure it with a static IP address:
|
||||
|
||||
- IP: `10.0.0.1`
|
||||
- Netmask: `255.255.255.0`
|
||||
- Gateway: `10.0.0.1`
|
||||
- DNS (if required): `8.8.8.8` (or whatever)
|
||||
|
||||
If everything's been configured properly, you will now be able to `ping` both `10.0.0.2` or `pwnagotchi.local` (if you haven't customized the hostname yet).
|
||||
|
||||
You can now connect to your unit using SSH:
|
||||
|
||||
```bash
|
||||
ssh pi@10.0.0.2
|
||||
```
|
||||
|
||||
The default password is `raspberry`, you should change it as soon as you log in for the first time by issuing the `passwd`command and selecting a new and more complex passphrase.
|
||||
|
||||
Moreover, it is recommended that you copy your SSH public key among the unit's authorized ones, so you can directly log in without entering a password:
|
||||
|
||||
```bash
|
||||
ssh-copy-id -i ~/.ssh/id_rsa.pub pi@10.0.0.2
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
You can now set a new name for your unit by [changing the hostname](https://geek-university.com/raspberry-pi/change-raspberry-pis-hostname/). Create the `/root/custom.yml` file (either via SSH or by direclty editing the SD card contents from a computer) that will override the [default configuration](https://github.com/evilsocket/pwnagotchi/blob/master/sdcard/rootfs/root/pwnagotchi/config.yml) with your custom values.
|
||||
|
||||
## Language Selection
|
||||
|
||||
For instance, you can change `main.lang` to one of the supported languages:
|
||||
|
||||
- **english** (default)
|
||||
- german
|
||||
- dutch
|
||||
- greek
|
||||
- macedonian
|
||||
- italian
|
||||
- french
|
||||
|
||||
## Display Selection
|
||||
|
||||
Set the type of display you want to use via `ui.display.type` (if your display does not work after changing this setting, you might need to completely remove power from the Raspberry and make a clean boot).
|
||||
|
||||
You can configure the refresh interval of the display via `ui.fps`, we advise to use a slow refresh to not shorten the lifetime of your display. The default value is 0, which will only refresh when changes are made to the screen.
|
||||
|
||||
## Host Connection Share
|
||||
|
||||
If you connect to the unit via `usb0` (thus using the data port), you might want to use the `scripts/linux_connection_share.sh`, `scripts/macos_connection_share.sh` or `scripts/win_connection_share.ps1` script to bring the interface up on your end and share internet connectivity from another interface, so you can update the unit and generally download things from the internet on it.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If your network connection keeps flapping on your device connecting to your pwnagotchi, check if `usb0` (or equivalent) device is being controlled by NetworkManager. You can check this via `nmcli dev status`.
|
61
docs/dev.md
61
docs/dev.md
@ -1,61 +0,0 @@
|
||||
# Software
|
||||
|
||||
- Raspbian + [nexmon patches](https://re4son-kernel.com/re4son-pi-kernel/) for monitor mode, or any Linux with a monitor mode enabled interface (if you tune config.yml).
|
||||
|
||||
**Do not try with Kali on the Raspberry Pi 0 W, it is compiled without hardware floating point support and TensorFlow is simply not available for it, use Raspbian.**
|
||||
|
||||
## Creating an Image
|
||||
|
||||
You can use the `scripts/create_sibling.sh` script to create an - ready to flash - rasbian image with pwnagotchi.
|
||||
|
||||
```shell
|
||||
usage: ./scripts/create_sibling.sh [OPTIONS]
|
||||
|
||||
Options:
|
||||
-n <name> # Name of the pwnagotchi (default: pwnagotchi)
|
||||
-i <file> # Provide the path of an already downloaded raspbian image
|
||||
-o <file> # Name of the img-file (default: pwnagotchi.img)
|
||||
-s <size> # Size which should be added to second partition (in Gigabyte) (default: 4)
|
||||
-v <version> # Version of raspbian (Supported: latest; default: latest)
|
||||
-p # Only run provisioning (assumes the image is already mounted)
|
||||
-d # Only run dependencies checks
|
||||
-h # Show this help
|
||||
```
|
||||
|
||||
#### Known Issues
|
||||
|
||||
`GLib-ERROR **: 20:50:46.361: getauxval () failed: No such file or directory`
|
||||
|
||||
- Affected DEB & Versions: QEMU <= 2.11
|
||||
- Fix: Upgrade QEMU to >= 3.1
|
||||
- Bug Link: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=923289
|
||||
|
||||
## Adding a Language
|
||||
|
||||
If you want to add a language use the `language.sh` script. If you want to add for example the language **italian** you would type:
|
||||
|
||||
```shell
|
||||
./scripts/language.sh add it
|
||||
# Now make your changes to the file
|
||||
# sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/locale/it/LC_MESSAGES/voice.po
|
||||
./scripts/language.sh compile it
|
||||
# DONE
|
||||
```
|
||||
|
||||
If you changed the `voice.py`- File, the translations need an update. Do it like this:
|
||||
|
||||
```shell
|
||||
./scripts/language.sh update it
|
||||
# Now make your changes to the file (changed lines are marked with "fuzzy")
|
||||
# sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/locale/it/LC_MESSAGES/voice.po
|
||||
./scripts/language.sh compile it
|
||||
# DONE
|
||||
```
|
||||
|
||||
Now you can use the `preview.py`-script to preview the changes:
|
||||
|
||||
```shell
|
||||
./scripts/preview.py --lang it --display ws2 --port 8080 &
|
||||
./scripts/preview.py --lang it --display inky --port 8081 &
|
||||
# Now open http://localhost:8080 and http://localhost:8081
|
||||
```
|
13
docs/faq.md
13
docs/faq.md
@ -1,13 +0,0 @@
|
||||
# FAQ
|
||||
|
||||
## Why eINK?
|
||||
|
||||
Because!
|
||||
|
||||
## Why the AI takes 30 minutes to load?
|
||||
|
||||
Because Python sucks and TF is huge.
|
||||
|
||||
## Why ...?
|
||||
|
||||
Because!
|
@ -1,20 +0,0 @@
|
||||
# Documentation
|
||||
|
||||
- [About the Project](https://github.com/evilsocket/pwnagotchi/blob/master/docs/about.md)
|
||||
- [How to Install](https://github.com/evilsocket/pwnagotchi/blob/master/docs/install.md)
|
||||
- [Configuration](https://github.com/evilsocket/pwnagotchi/blob/master/docs/configure.md)
|
||||
- [Usage](https://github.com/evilsocket/pwnagotchi/blob/master/docs/usage.md)
|
||||
- [Plugins](https://github.com/evilsocket/pwnagotchi/blob/master/docs/plugins.md)
|
||||
- [Developement](https://github.com/evilsocket/pwnagotchi/blob/master/docs/dev.md)
|
||||
- [FAQ](https://github.com/evilsocket/pwnagotchi/blob/master/docs/faq.md)
|
||||
|
||||
## Links
|
||||
|
||||
- [Project Slack](https://join.slack.com/t/pwnagotchi/shared_invite/enQtNzc4NzY3MDE2OTAzLTg5NmNmNDJiMDM3ZWFkMWUwN2Y5NDk0Y2JlZWZjODlhMmRhNDZiOGMwYjJhM2UzNzA3YjA5NjJmZGY5NGI5NmI)
|
||||
- [Project Twitter](https://twitter.com/pwnagotchi)
|
||||
- [Project Subreddit](https://www.reddit.com/r/pwnagotchi/)
|
||||
- [Project Website](https://pwnagotchi.ai/)
|
||||
|
||||
## License
|
||||
|
||||
`pwnagotchi` is made with ♥ by [@evilsocket](https://twitter.com/evilsocket) and the [amazing dev team](https://github.com/evilsocket/pwnagotchi/graphs/contributors). It's released under the GPL3 license.
|
@ -1,45 +0,0 @@
|
||||
# Installation
|
||||
|
||||
The project has been developed to run on a Raspberry Pi 0 W configured as an [USB Ethernet gadget](https://learn.adafruit.com/turning-your-raspberry-pi-zero-into-a-usb-gadget/ethernet-gadget) device in order to connect to it via USB. However, given the proper configuration tweaks, any GNU/Linux computer with a WiFi interface that supports monitor mode could be used.
|
||||
|
||||
## Required Hardware
|
||||
|
||||
- [Raspberry Pi Zero W](https://www.raspberrypi.org/products/raspberry-pi-zero-w/).
|
||||
- A micro SD card, 8GB recomended, **preferably of good quality and speed**.
|
||||
- A decent power bank (with 1500 mAh you get ~2 hours with AI on).
|
||||
- One of the supported displays (optional).
|
||||
|
||||
### Display
|
||||
|
||||
The display is an optional component as the UI is also rendered via a web interface available via the USB cable. If you connect to `usb0` (by using the data port on the unit) and point your browser to the web ui (see config.yml), your unit can work in "headless mode".
|
||||
|
||||
If instead you want to fully enjoy walking around and literally looking at your unit's face, the supported display models are:
|
||||
|
||||
- [Waveshare eInk Display (both V1 and V2)](https://www.waveshare.com/2.13inch-e-paper-hat.htm)
|
||||
- [Pimoroni Inky pHAT](https://shop.pimoroni.com/products/inky-phat)
|
||||
- [PaPiRus eInk Screen](https://uk.pi-supply.com/products/papirus-zero-epaper-screen-phat-pi-zero)
|
||||
|
||||
Needless to say, we are always happy to receive pull requests adding support for new models.
|
||||
|
||||
One thing to note, not all displays are created equaly, TFT displays for example work similar to an HDMI display, and they are not supported, currently all the displays supported are I2C displays.
|
||||
|
||||
#### Color and Black & White displays
|
||||
|
||||
Some of the supported displays support Black & White and Coloured versions, one common question is regarding refresh speed of said displays.
|
||||
|
||||
Color displays have a much slower refresh rate, in some cases it can take up to 15 seconds, if slow refresh rates is something that you want to avoid we advise you to use Black & White displays
|
||||
|
||||
## Flashing an Image
|
||||
|
||||
The easiest way to create a new Pwnagotchi is downloading the latest stable image from [our release page](https://github.com/evilsocket/pwnagotchi/releases) and write it to your SD card. You will need to use an image writing tool to install the image you have downloaded on your SD card.
|
||||
|
||||
[balenaEtcher](https://www.balena.io/etcher/) is a graphical SD card writing tool that works on Mac OS, Linux and Windows, and is the easiest option for most users. balenaEtcher also supports writing images directly from the zip file, without any unzipping required. To write your image with balenaEtcher:
|
||||
|
||||
- Download the latest [Pwnagotchi .img file](https://github.com/evilsocket/pwnagotchi/releases).
|
||||
- Download [balenaEtcher](https://www.balena.io/etcher/) and install it.
|
||||
- Connect an SD card reader with the SD card inside.
|
||||
- Open balenaEtcher and select from your hard drive the Raspberry Pi .img or .zip file you wish to write to the SD card.
|
||||
- Select the SD card you wish to write your image to.
|
||||
- Review your selections and click 'Flash!' to begin writing data to the SD card.
|
||||
|
||||
Your SD card is now ready for the first boot!
|
@ -1,56 +0,0 @@
|
||||
# Plugins
|
||||
|
||||
Pwnagotchi has a simple plugins system that you can use to customize your unit and its behaviour. You can place your plugins anywhere
|
||||
as python files and then edit the `config.yml` file (`main.plugins` value) to point to their containing folder. Check the [plugins folder](https://github.com/evilsocket/pwnagotchi/tree/master/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/plugins/default/) for a list of default plugins and all the callbacks that you can define for your own customizations.
|
||||
|
||||
Here's as an example the GPS plugin:
|
||||
|
||||
```python
|
||||
__author__ = 'evilsocket@gmail.com'
|
||||
__version__ = '1.0.0'
|
||||
__name__ = 'gps'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'Save GPS coordinates whenever an handshake is captured.'
|
||||
__enabled__ = True # set to false if you just don't use GPS
|
||||
|
||||
import core
|
||||
import json
|
||||
import os
|
||||
|
||||
device = '/dev/ttyUSB0'
|
||||
speed = 19200
|
||||
running = False
|
||||
|
||||
|
||||
def on_loaded():
|
||||
logging.info("GPS plugin loaded for %s" % device)
|
||||
|
||||
|
||||
def on_ready(agent):
|
||||
global running
|
||||
|
||||
if os.path.exists(device):
|
||||
logging.info("enabling GPS bettercap's module for %s" % device)
|
||||
try:
|
||||
agent.run('gps off')
|
||||
except:
|
||||
pass
|
||||
|
||||
agent.run('set gps.device %s' % device)
|
||||
agent.run('set gps.speed %d' % speed)
|
||||
agent.run('gps on')
|
||||
running = True
|
||||
else:
|
||||
logging.info("no GPS detected")
|
||||
|
||||
|
||||
def on_handshake(agent, filename, access_point, client_station):
|
||||
if running:
|
||||
info = agent.session()
|
||||
gps = info['gps']
|
||||
gps_filename = filename.replace('.pcap', '.gps.json')
|
||||
|
||||
logging.info("saving GPS to %s (%s)" % (gps_filename, gps))
|
||||
with open(gps_filename, 'w+t') as fp:
|
||||
json.dump(gps, fp)
|
||||
```
|
146
docs/usage.md
146
docs/usage.md
@ -1,146 +0,0 @@
|
||||
# Usage
|
||||
|
||||
## User Interface
|
||||
|
||||
The UI is available either via display if installed, or via http://pwnagotchi.local:8080/ if you connect to the unit via `usb0` and set a static address on the network interface (change `pwnagotchi` with the hostname of your unit).
|
||||
|
||||

|
||||
|
||||
* **CH**: Current channel the unit is operating on or `*` when hopping on all channels.
|
||||
* **APS**: Number of access points on the current channel and total visible access points.
|
||||
* **UP**: Time since the unit has been activated.
|
||||
* **PWND**: Number of handshakes captured in this session and number of unique networks we own at least one handshake of, from the beginning.
|
||||
* **AUTO**: This indicates that the algorithm is running with AI disabled (or still loading), it disappears once the AI dependencies have been bootrapped and the neural network loaded.
|
||||
|
||||
## Training the AI
|
||||
|
||||
At its core Pwnagotchi is a very simple creature: we could summarize its main algorithm as:
|
||||
|
||||
```python
|
||||
# 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.yml`:
|
||||
|
||||
```yaml
|
||||
personality:
|
||||
# advertise our presence
|
||||
advertise: true
|
||||
# perform a deauthentication attack to client stations in order to get full or half handshakes
|
||||
deauth: true
|
||||
# send association frames to APs in order to get the PMKID
|
||||
associate: true
|
||||
# list of channels to recon on, or empty for all channels
|
||||
channels: []
|
||||
# minimum WiFi signal strength in dBm
|
||||
min_rssi: -200
|
||||
# number of seconds for wifi.ap.ttl
|
||||
ap_ttl: 120
|
||||
# number of seconds for wifi.sta.ttl
|
||||
sta_ttl: 300
|
||||
# time in seconds to wait during channel recon
|
||||
recon_time: 30
|
||||
# number of inactive epochs after which recon_time gets multiplied by recon_inactive_multiplier
|
||||
max_inactive_scale: 2
|
||||
# if more than max_inactive_scale epochs are inactive, recon_time *= recon_inactive_multiplier
|
||||
recon_inactive_multiplier: 2
|
||||
# time in seconds to wait during channel hopping if activity has been performed
|
||||
hop_recon_time: 10
|
||||
# time in seconds to wait during channel hopping if no activity has been performed
|
||||
min_recon_time: 5
|
||||
# maximum amount of deauths/associations per BSSID per session
|
||||
max_interactions: 3
|
||||
# maximum amount of misses before considering the data stale and triggering a new recon
|
||||
max_misses_for_recon: 5
|
||||
# number of active epochs that triggers the excited state
|
||||
excited_num_epochs: 10
|
||||
# number of inactive epochs that triggers the bored state
|
||||
bored_num_epochs: 15
|
||||
# number of inactive epochs that triggers the sad state
|
||||
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.
|
||||
|
||||
## Reward Function
|
||||
|
||||
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`):
|
||||
|
||||
```python
|
||||
# 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.
|
||||
|
||||
## BetterCAP's Web UI
|
||||
|
||||
Moreover, given that the unit is running bettercap with API and Web UI, you'll be able to use the unit as a WiFi penetration testing portable station by accessing `http://pwnagotchi.local/`.
|
||||
|
||||

|
||||
|
||||
## Update your Pwnagotchi
|
||||
|
||||
You can use the `scripts/update_pwnagotchi.sh` script to update to the most recent version of pwnagotchi.
|
||||
|
||||
```shell
|
||||
usage: ./update_pwnagitchi.sh [OPTIONS]
|
||||
|
||||
Options:
|
||||
-v # Version to update to, can be a branch or commit. (default: master)
|
||||
-u # Url to clone from. (default: https://github.com/evilsocket/pwnagotchi)
|
||||
-m # Mode to restart to. (Supported: auto manual; default: auto)
|
||||
-b # Backup the current pwnagotchi config.
|
||||
-r # Restore the current pwnagotchi config. -b will be enabled.
|
||||
-h # Shows this help. Shows this help.
|
||||
|
||||
```
|
||||
|
||||
## Backup your Pwnagotchi
|
||||
|
||||
You can use the `scripts/backup.sh` script to backup the important files of your unit.
|
||||
|
||||
```shell
|
||||
usage: ./scripts/backup.sh HOSTNAME backup.zip
|
||||
```
|
||||
|
||||
## Random Info
|
||||
|
||||
* **On a rpi0w, it'll take approximately 30 minutes to load the AI**.
|
||||
* `/var/log/pwnagotchi.log` is your friend.
|
||||
* if connected to a laptop via usb data port, with internet connectivity shared, magic things will happen.
|
||||
* checkout the `ui.video` section of the `config.yml` - if you don't want to use a display, you can connect to it with the browser and a cable.
|
||||
* If you get `[FAILED] Failed to start Remount Root and Kernel File Systems.` while booting pwnagotchi, make sure the `PARTUUID`s for `rootfs` and `boot` partitions are the same in `/etc/fstab`. Use `sudo blkid` to find those values when you are using `create_sibling.sh`.
|
@ -1,6 +1,10 @@
|
||||
import subprocess
|
||||
import os
|
||||
import logging
|
||||
import time
|
||||
import pwnagotchi.ui.view as view
|
||||
|
||||
version = '1.0.0plz2'
|
||||
version = '1.0.0RC2'
|
||||
|
||||
_name = None
|
||||
|
||||
@ -46,3 +50,13 @@ def temperature(celsius=True):
|
||||
temp = int(fp.read().strip())
|
||||
c = int(temp / 1000)
|
||||
return c if celsius else ((c * (9 / 5)) + 32)
|
||||
|
||||
|
||||
def shutdown():
|
||||
logging.warning("shutting down ...")
|
||||
if view.ROOT:
|
||||
view.ROOT.on_shutdown()
|
||||
# give it some time to refresh the ui
|
||||
time.sleep(5)
|
||||
os.system("sync")
|
||||
os.system("halt")
|
@ -9,6 +9,7 @@ import _thread
|
||||
|
||||
import pwnagotchi.utils as utils
|
||||
import pwnagotchi.plugins as plugins
|
||||
from pwnagotchi.log import LastSession
|
||||
from pwnagotchi.bettercap import Client
|
||||
from pwnagotchi.mesh.utils import AsyncAdvertiser
|
||||
from pwnagotchi.ai.train import AsyncTrainer
|
||||
@ -17,13 +18,13 @@ RECOVERY_DATA_FILE = '/root/.pwnagotchi-recovery'
|
||||
|
||||
|
||||
class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
def __init__(self, view, config):
|
||||
def __init__(self, view, config, keypair):
|
||||
Client.__init__(self, config['bettercap']['hostname'],
|
||||
config['bettercap']['scheme'],
|
||||
config['bettercap']['port'],
|
||||
config['bettercap']['username'],
|
||||
config['bettercap']['password'])
|
||||
AsyncAdvertiser.__init__(self, config, view)
|
||||
AsyncAdvertiser.__init__(self, config, view, keypair)
|
||||
AsyncTrainer.__init__(self, config)
|
||||
|
||||
self._started_at = time.time()
|
||||
@ -35,6 +36,10 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
self._last_pwnd = None
|
||||
self._history = {}
|
||||
self._handshakes = {}
|
||||
self.last_session = LastSession(self._config)
|
||||
|
||||
if not os.path.exists(config['bettercap']['handshakes']):
|
||||
os.makedirs(config['bettercap']['handshakes'])
|
||||
|
||||
@staticmethod
|
||||
def is_connected():
|
||||
@ -48,6 +53,9 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
def config(self):
|
||||
return self._config
|
||||
|
||||
def view(self):
|
||||
return self._view
|
||||
|
||||
def supported_channels(self):
|
||||
return self._supported_channels
|
||||
|
||||
@ -296,7 +304,8 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
|
||||
def _update_peers(self):
|
||||
peer = self._advertiser.closest_peer()
|
||||
self._view.set_closest_peer(peer)
|
||||
tot = self._advertiser.num_peers()
|
||||
self._view.set_closest_peer(peer, tot)
|
||||
|
||||
def _save_recovery_data(self):
|
||||
logging.warning("writing recovery data to %s ..." % RECOVERY_DATA_FILE)
|
@ -39,4 +39,4 @@ def load(config, agent, epoch, from_disk=True):
|
||||
for key, value in config['params'].items():
|
||||
logging.info(" %s: %s" % (key, value))
|
||||
|
||||
return a2c
|
||||
return a2c
|
@ -1,13 +1,19 @@
|
||||
# main algorithm configuration
|
||||
main:
|
||||
# currently implemented: en (default), de, el, fr, it, mk, nl, se
|
||||
# currently implemented: en (default), de, el, fr, it, mk, nl, ru, se, pt-BR, es
|
||||
lang: en
|
||||
# custom plugins path, if null only default plugins with be loaded
|
||||
custom_plugins:
|
||||
# which plugins to load and enable
|
||||
plugins:
|
||||
grid:
|
||||
enabled: true
|
||||
report: false # don't report pwned networks by default!
|
||||
exclude: # do not report the following networks (accepts both ESSIDs and BSSIDs)
|
||||
- YourHomeNetworkHere
|
||||
auto-update:
|
||||
enabled: false
|
||||
system: false # set to true to also enable system updates via apt
|
||||
interval: 1 # every day
|
||||
auto-backup:
|
||||
enabled: false
|
||||
@ -15,18 +21,22 @@ main:
|
||||
files:
|
||||
- /root/brain.nn
|
||||
- /root/brain.json
|
||||
- /root/custom.yaml
|
||||
- /root/handshakes
|
||||
- /etc/ssh
|
||||
- /root/handshakes/
|
||||
- /etc/pwnagotchi/
|
||||
- /etc/hostname
|
||||
- /etc/hosts
|
||||
- /etc/motd
|
||||
- /var/log/pwnagotchi.log
|
||||
commands:
|
||||
- 'tar czf /tmp/backup.tar.gz {files}'
|
||||
- 'scp /tmp/backup.tar.gz pwnagotchi@10.0.0.1:/home/pwnagotchi/backups/backup-$(date).tar.gz'
|
||||
- 'scp /tmp/backup.tar.gz pwnagotchi@10.0.0.1:/home/pwnagotchi/backups/backup-$(date +%s).tar.gz'
|
||||
net-pos:
|
||||
enabled: false
|
||||
api_key: 'test'
|
||||
gps:
|
||||
enabled: false
|
||||
speed: 19200
|
||||
device: /dev/ttyUSB0
|
||||
twitter:
|
||||
enabled: false
|
||||
consumer_key: aaa
|
||||
@ -39,6 +49,12 @@ main:
|
||||
wpa-sec:
|
||||
enabled: false
|
||||
api_key: ~
|
||||
wigle:
|
||||
enabled: false
|
||||
api_key: ~
|
||||
screen_refresh:
|
||||
enabled: false
|
||||
refresh_interval: 50
|
||||
|
||||
# monitor interface to use
|
||||
iface: mon0
|
||||
@ -153,8 +169,8 @@ bettercap:
|
||||
scheme: http
|
||||
hostname: localhost
|
||||
port: 8081
|
||||
username: user
|
||||
password: pass
|
||||
username: pwnagotchi
|
||||
password: pwnagotchi
|
||||
# folder where bettercap stores the WPA handshakes, given that
|
||||
# wifi.handshakes.aggregate will be set to false and individual
|
||||
# pcap files will be created in order to minimize the chances
|
47
pwnagotchi/identity.py
Normal file
47
pwnagotchi/identity.py
Normal file
@ -0,0 +1,47 @@
|
||||
from Crypto.Signature import PKCS1_PSS
|
||||
from Crypto.PublicKey import RSA
|
||||
import Crypto.Hash.SHA256 as SHA256
|
||||
import base64
|
||||
import hashlib
|
||||
import os
|
||||
import logging
|
||||
|
||||
DefaultPath = "/etc/pwnagotchi/"
|
||||
|
||||
|
||||
class KeyPair(object):
|
||||
def __init__(self, path=DefaultPath):
|
||||
self.path = path
|
||||
self.priv_path = os.path.join(path, "id_rsa")
|
||||
self.priv_key = None
|
||||
self.pub_path = "%s.pub" % self.priv_path
|
||||
self.pub_key = None
|
||||
|
||||
if not os.path.exists(self.path):
|
||||
os.makedirs(self.path)
|
||||
|
||||
if not os.path.exists(self.priv_path) or not os.path.exists(self.pub_path):
|
||||
logging.info("generating %s ..." % self.priv_path)
|
||||
os.system("/usr/bin/ssh-keygen -t rsa -m PEM -b 4096 -N '' -f '%s'" % self.priv_path)
|
||||
|
||||
with open(self.priv_path) as fp:
|
||||
self.priv_key = RSA.importKey(fp.read())
|
||||
|
||||
with open(self.pub_path) as fp:
|
||||
self.pub_key = RSA.importKey(fp.read())
|
||||
self.pub_key_pem = self.pub_key.exportKey('PEM').decode("ascii")
|
||||
# python is special
|
||||
if 'RSA PUBLIC KEY' not in self.pub_key_pem:
|
||||
self.pub_key_pem = self.pub_key_pem.replace('PUBLIC KEY', 'RSA PUBLIC KEY')
|
||||
|
||||
pem = self.pub_key_pem.encode("ascii")
|
||||
|
||||
self.pub_key_pem_b64 = base64.b64encode(pem).decode("ascii")
|
||||
self.fingerprint = hashlib.sha256(pem).hexdigest()
|
||||
|
||||
def sign(self, message):
|
||||
hasher = SHA256.new(message.encode("ascii"))
|
||||
signer = PKCS1_PSS.new(self.priv_key, saltLen=16)
|
||||
signature = signer.sign(hasher)
|
||||
signature_b64 = base64.b64encode(signature).decode("ascii")
|
||||
return signature, signature_b64
|
Binary file not shown.
@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 0.0.1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-10-05 14:10+0200\n"
|
||||
"POT-Creation-Date: 2019-10-09 17:42+0200\n"
|
||||
"PO-Revision-Date: 2019-09-29 14:00+0200\n"
|
||||
"Last-Translator: dadav <33197631+dadav@users.noreply.github.com>\n"
|
||||
"Language-Team: DE <33197631+dadav@users.noreply.github.com>\n"
|
||||
@ -121,6 +121,12 @@ msgstr ""
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Good night."
|
||||
msgstr "Gute Nacht."
|
||||
|
||||
msgid "Zzz"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr "Warte für {secs}s ..."
|
||||
@ -139,7 +145,7 @@ msgstr "Verbinde mit {what}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr ""
|
||||
msgstr "Jo {what}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
@ -33,11 +33,11 @@ msgid "AI ready."
|
||||
msgstr "ΤΝ έτοιμη."
|
||||
|
||||
msgid "The neural network is ready."
|
||||
msgstr "Το νευρωνικό δίκτυοείναι έτοιμο."
|
||||
msgstr "Το νευρωνικό δίκτυο είναι έτοιμο."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr "Ε, το κανάλι {channel} είναιελεύθερο! Το AP σου θαείναι ευγνώμων."
|
||||
msgstr "Ε, το κανάλι {channel} είναιελεύθερο! Το AP σου θα είναι ευγνώμων."
|
||||
|
||||
msgid "I'm bored ..."
|
||||
msgstr "Βαριέμαι ..."
|
BIN
pwnagotchi/locale/es/LC_MESSAGES/voice.mo
Normal file
BIN
pwnagotchi/locale/es/LC_MESSAGES/voice.mo
Normal file
Binary file not shown.
214
pwnagotchi/locale/es/LC_MESSAGES/voice.po
Normal file
214
pwnagotchi/locale/es/LC_MESSAGES/voice.po
Normal file
@ -0,0 +1,214 @@
|
||||
# pwnagotchi voice data
|
||||
# Copyright (C) 2019
|
||||
# This file is distributed under the same license as the pwnagotchi package.
|
||||
# FIRST AUTHOR diegopastor <dpastor29@alumnos.uaq.mx>, 2019.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 0.0.1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-10-09 17:42+0200\n"
|
||||
"PO-Revision-Date: 2019-10-09 21:07+0000\n"
|
||||
"Last-Translator: diegopastor <dpastor29@alumnos.uaq.mx>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: spanish\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr "ZzzzZZzzzzZzzz"
|
||||
|
||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||
msgstr "Hola, soy Pwnagotchi! Empezando ..."
|
||||
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr "Nuevo día, nueva cazería, nuevos pwns!"
|
||||
|
||||
msgid "Hack the Planet!"
|
||||
msgstr "Hackea el planeta!"
|
||||
|
||||
msgid "AI ready."
|
||||
msgstr "IA lista."
|
||||
|
||||
msgid "The neural network is ready."
|
||||
msgstr "La red neuronal está lista."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr "Oye, el canal {channel} está libre! Tú AP lo agradecerá."
|
||||
|
||||
msgid "I'm bored ..."
|
||||
msgstr "Estoy aburrido ..."
|
||||
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr "Vamos por un paseo!"
|
||||
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr "Este es el mejor día de mi vida!"
|
||||
|
||||
msgid "Shitty day :/"
|
||||
msgstr "Día de mierda :/"
|
||||
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr "Estoy extremadamente aburrido ..."
|
||||
|
||||
msgid "I'm very sad ..."
|
||||
msgstr "Estoy muy triste ..."
|
||||
|
||||
msgid "I'm sad"
|
||||
msgstr "Estoy triste."
|
||||
|
||||
msgid "I'm living the life!"
|
||||
msgstr "Estoy viviendo la vida!"
|
||||
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr "Pwneo, por lo tanto, existo"
|
||||
|
||||
msgid "So many networks!!!"
|
||||
msgstr "Cuantas redes!!!"
|
||||
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr "Me estoy divirtiendo mucho!"
|
||||
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr "Mi único crimen es la curiosidad ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you. {name}"
|
||||
msgstr "Hola {name}! encantado de conocerte."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby! {name}"
|
||||
msgstr "La unidad {name} está cerca!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
msgstr "Uhm ... adiós {name}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr "{name} se fue ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr "Uy ... {name} se fue"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr "{name} perdido!"
|
||||
|
||||
msgid "Missed!"
|
||||
msgstr "Perdido!"
|
||||
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr "Nadie quiere jugar conmigo ..."
|
||||
|
||||
msgid "I feel so alone ..."
|
||||
msgstr "Me siento tan solo ..."
|
||||
|
||||
msgid "Where's everybody?!"
|
||||
msgstr "Dónde está todo el mundo?"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr "Tomándo una siesta por {secs}s ..."
|
||||
|
||||
msgid "Zzzzz"
|
||||
msgstr "Zzzzz"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr "ZzzZzzz ({secs}s)"
|
||||
|
||||
msgid "Good night."
|
||||
msgstr "Buenas noches."
|
||||
|
||||
msgid "Zzz"
|
||||
msgstr "Zzz"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr "Esperando {secs}s .."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr "Mirando al rededor ({secs}s)"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr "Oye {what} seamos amigos!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
msgstr "Asociando a {what}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr "Ey {what}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr "Acabo de decidir que {mac} no necesita WiFi!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
msgstr "Desautenticando a {mac}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr "Expulsando y banneando a {mac}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr "Genial, obtuvimos {num} nuevo{plural} handshake{plural}!"
|
||||
|
||||
msgid "Ops, something went wrong ... Rebooting ..."
|
||||
msgstr "Oops, algo salió mal ... Reiniciándo ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr "Expulsamos {num} estaciones\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr "Hicimos {num} nuevos amigos\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr "Obtuvimos {num} handshakes\n"
|
||||
|
||||
msgid "Met 1 peer"
|
||||
msgstr "Conocí 1 igual"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr "Conocí {num} iguales"
|
||||
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
|
||||
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
msgstr ""
|
||||
"He estado pwneando por {duration} y expulsé {deauthed} clientes! También conocí"
|
||||
"{associated} nuevos amigos y me comí {handshakes} handshakes! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
|
||||
msgid "hours"
|
||||
msgstr "horas"
|
||||
|
||||
msgid "minutes"
|
||||
msgstr "minutos"
|
||||
|
||||
msgid "seconds"
|
||||
msgstr "segundos"
|
||||
|
||||
msgid "hour"
|
||||
msgstr "hora"
|
||||
|
||||
msgid "minute"
|
||||
msgstr "minuto"
|
||||
|
||||
msgid "second"
|
||||
msgstr "segundo"
|
@ -25,20 +25,20 @@ msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||
msgstr "Bonjour, je suis Pwnagotchi! Démarrage ..."
|
||||
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr "Nouvelle journée, nouvelle chasse, nouveau pwns!"
|
||||
msgstr "Nouveau jour, nouvelle chasse, nouveaux pwns !"
|
||||
|
||||
msgid "Hack the Planet!"
|
||||
msgstr "Hack la planète!"
|
||||
|
||||
msgid "AI ready."
|
||||
msgstr "IA prête."
|
||||
msgstr "L'IA est prête."
|
||||
|
||||
msgid "The neural network is ready."
|
||||
msgstr "Le réseau neuronal est prêt."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr "Hey, le channel {channel} est libre! Ton AP va dis merci."
|
||||
msgstr "Hey, le channel {channel} est libre! Ton point d'accès va te remercier."
|
||||
|
||||
msgid "I'm bored ..."
|
||||
msgstr "Je m'ennuie ..."
|
||||
@ -68,17 +68,17 @@ msgid "I pwn therefore I am."
|
||||
msgstr "Je pwn donc je suis."
|
||||
|
||||
msgid "So many networks!!!"
|
||||
msgstr "Autant de réseaux!!!"
|
||||
msgstr "Tellement de réseaux!!!"
|
||||
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr "Je m'amuse tellement!"
|
||||
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr "Mon crime est celui de la curiosité ..."
|
||||
msgstr "Mon crime, c'est la curiosité ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you. {name}"
|
||||
msgstr "Bonjour {name}! Ravis de te rencontrer. {name}"
|
||||
msgstr "Bonjour {name}! Ravi de te rencontrer. {name}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby! {name}"
|
||||
@ -145,7 +145,7 @@ msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr "Décidé à l'instant que {mac} n'a pas besoin de WiFi!"
|
||||
msgstr "Je viens de décider que {mac} n'a pas besoin de WiFi!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
@ -153,11 +153,11 @@ msgstr "Désauthentification de {mac}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr ""
|
||||
msgstr "Je kick et je bannis {mac}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr "Cool, nous avons {num} nouveaux handshake{plural}!"
|
||||
msgstr "Cool, on a {num} nouveaux handshake{plural}!"
|
||||
|
||||
msgid "Ops, something went wrong ... Rebooting ..."
|
||||
msgstr "Oups, quelque chose s'est mal passé ... Redémarrage ..."
|
||||
@ -188,7 +188,7 @@ msgid ""
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
msgstr ""
|
||||
"J'ai pwn durant {duration} et kick {deauthed} clients! J'ai aussi rencontré "
|
||||
"{associated} nouveaux amis and mangé {handshakes} handshakes! #pwnagotchi "
|
||||
"{associated} nouveaux amis et dévoré {handshakes} handshakes! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
|
||||
msgid "hours"
|
BIN
pwnagotchi/locale/pt-BR/LC_MESSAGES/voice.mo
Normal file
BIN
pwnagotchi/locale/pt-BR/LC_MESSAGES/voice.mo
Normal file
Binary file not shown.
209
pwnagotchi/locale/pt-BR/LC_MESSAGES/voice.po
Normal file
209
pwnagotchi/locale/pt-BR/LC_MESSAGES/voice.po
Normal file
@ -0,0 +1,209 @@
|
||||
# pwnagotchi Brazilian Portuguese translation file.
|
||||
# Copyright (C) 2019 Cassiano Aquino
|
||||
# This file is distributed under the same license as the pwnagotchi package.
|
||||
# Cassiano Aquino <cassianoaquino@me.com>, 2019.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-10-05 14:10+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: Cassiano Aquino <cassianoaquino@me.com>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: Brazilian Portuguese\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr "ZzzzZZzzzzZzzz"
|
||||
|
||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||
msgstr "Oi! Eu sou o Pwnagotchi! Iniciando ..."
|
||||
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr "Novo dia, Nova caça, Novos pwns!"
|
||||
|
||||
msgid "Hack the Planet!"
|
||||
msgstr "Hackeie o Planeta!"
|
||||
|
||||
msgid "AI ready."
|
||||
msgstr "AI pronta."
|
||||
|
||||
msgid "The neural network is ready."
|
||||
msgstr "A rede neural está pronta."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr "Ei, o canal {channel} está livre! Seu AP ira agradecer."
|
||||
|
||||
msgid "I'm bored ..."
|
||||
msgstr "Estou entediado ..."
|
||||
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr "Vamos dar uma caminhada!"
|
||||
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr "Este e o melhor dia da minha vida!"
|
||||
|
||||
msgid "Shitty day :/"
|
||||
msgstr "Dia de merda :/"
|
||||
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr "Estou extremamente entediado ..."
|
||||
|
||||
msgid "I'm very sad ..."
|
||||
msgstr "Estou muito triste ..."
|
||||
|
||||
msgid "I'm sad"
|
||||
msgstr "Estou triste"
|
||||
|
||||
msgid "I'm living the life!"
|
||||
msgstr "Estou aproveitando a vida!"
|
||||
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr "pwn, logo existo."
|
||||
|
||||
msgid "So many networks!!!"
|
||||
msgstr "Quantas redes!!!"
|
||||
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr "Estou me divertindo muito!"
|
||||
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr "Meu crime é ser curioso ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you. {name}"
|
||||
msgstr "Olá {name}! Prazer em conhecê-lo. {name}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby! {name}"
|
||||
msgstr "Unidade {name} está próxima! {name}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
msgstr "Uhm ... até logo {name}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr "{name} desapareceu ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr "Oops ... {name} desapareceu."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr "{name} perdido!"
|
||||
|
||||
msgid "Missed!"
|
||||
msgstr "Perdido!"
|
||||
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr "Ninguém quer brincar comigo ..."
|
||||
|
||||
msgid "I feel so alone ..."
|
||||
msgstr "Estou tão sozinho ..."
|
||||
|
||||
msgid "Where's everybody?!"
|
||||
msgstr "Aonde está todo mundo?!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr "Cochilando por {secs}s ..."
|
||||
|
||||
msgid "Zzzzz"
|
||||
msgstr "Zzzzz"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr "ZzzZzzz ({secs}s)"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr "Aguardando por {secs}s ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr "Olhando ao redor ({secs}s)"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr "Ei {what} vamos ser amigos!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
msgstr "Associando com {what}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr "Oi {what}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr "Acabei de decidir que {mac} não precisa de WiFi!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
msgstr "De-autenticando {mac}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr "Kickbanning {mac}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr "Legal, nos capturamos {num} handshake{plural} novo{plural}!"
|
||||
|
||||
msgid "Ops, something went wrong ... Rebooting ..."
|
||||
msgstr "Ops, algo falhou ... Reiniciando ..."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr "Kickei {num} estações\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr "Fiz {num} novos amigos\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr "Peguei {num} handshakes\n"
|
||||
|
||||
msgid "Met 1 peer"
|
||||
msgstr "Conheci 1 peer"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr "Conheci {num} peers"
|
||||
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
|
||||
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
msgstr ""
|
||||
"Eu estou pwning fazem {duration} e kickei {deauthed} clientes! Eu também conheci "
|
||||
"{associated} novos amigos e comi {handshakes} handshakes! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
|
||||
msgid "hours"
|
||||
msgstr "horas"
|
||||
|
||||
msgid "minutes"
|
||||
msgstr "minutos"
|
||||
|
||||
msgid "seconds"
|
||||
msgstr "segundos"
|
||||
|
||||
msgid "hour"
|
||||
msgstr "hora"
|
||||
|
||||
msgid "minute"
|
||||
msgstr "minuto"
|
||||
|
||||
msgid "second"
|
||||
msgstr "segundo"
|
BIN
pwnagotchi/locale/ru/LC_MESSAGES/voice.mo
Normal file
BIN
pwnagotchi/locale/ru/LC_MESSAGES/voice.mo
Normal file
Binary file not shown.
205
pwnagotchi/locale/ru/LC_MESSAGES/voice.po
Normal file
205
pwnagotchi/locale/ru/LC_MESSAGES/voice.po
Normal file
@ -0,0 +1,205 @@
|
||||
# SOME DESCRIPTIVE TITLE.
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <25989971+adolfaka@users.noreply.github.com>, 2019.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-10-03 16:47+0200\n"
|
||||
"PO-Revision-Date: 2019-10-05 18:50+0300\n"
|
||||
"Language-Team: \n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 2.2.4\n"
|
||||
"Last-Translator: Elliot Manson\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
|
||||
"%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n"
|
||||
"Language: ru_RU\n"
|
||||
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr "ZzzzZZzzzzZzzz"
|
||||
|
||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||
msgstr "Привет, я Pwnagotchi! Поехали …"
|
||||
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr "Новый день, новая охота, новые взломы!"
|
||||
|
||||
msgid "Hack the Planet!"
|
||||
msgstr "Взломаем всю планету!"
|
||||
|
||||
msgid "AI ready."
|
||||
msgstr "Искусственный интеллект готов."
|
||||
|
||||
msgid "The neural network is ready."
|
||||
msgstr "Нейронная сеть готова."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr "Эй, канал {channel} свободен! Ваша точка доступа скажет спасибо."
|
||||
|
||||
msgid "I'm bored ..."
|
||||
msgstr "Мне скучно …"
|
||||
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr "Пойдем прогуляемся!"
|
||||
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr "Это лучший день в моей жизни!"
|
||||
|
||||
msgid "Shitty day :/"
|
||||
msgstr "Дерьмовый день :/"
|
||||
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr "Мне очень скучно …"
|
||||
|
||||
msgid "I'm very sad ..."
|
||||
msgstr "Мне очень грустно …"
|
||||
|
||||
msgid "I'm sad"
|
||||
msgstr "Мне грустно"
|
||||
|
||||
msgid "I'm living the life!"
|
||||
msgstr "Я живу своей жизнью!"
|
||||
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr "Я взламываю, поэтому я существую."
|
||||
|
||||
msgid "So many networks!!!"
|
||||
msgstr "Так, много сетей!!!"
|
||||
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr "Мне так весело!"
|
||||
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr "Моё преступление - это любопытство …"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you. {name}"
|
||||
msgstr "Привет, {name}! Приятно познакомиться. {name}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby! {name}"
|
||||
msgstr "Цель {name} близко! {name}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
msgstr "Хм … до свидания {name}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr "{name} исчезла …"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr "Упс … {name} исчезла."
|
||||
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr "{name} упустил!"
|
||||
|
||||
msgid "Missed!"
|
||||
msgstr "Промахнулся!"
|
||||
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr "Никто не хочет играть со мной …"
|
||||
|
||||
msgid "I feel so alone ..."
|
||||
msgstr "Мне так одиноко …"
|
||||
|
||||
msgid "Where's everybody?!"
|
||||
msgstr "Где все?!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr "Дремлет {secs}с …"
|
||||
|
||||
msgid "Zzzzz"
|
||||
msgstr "Zzzzz"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr "ZzzZzzz ({secs}c)"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr "Ждем {secs}c …"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr "Оглядываюсь вокруг ({secs}с)"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr "Эй, {what} давай дружить!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
msgstr "Связываюсь с {what}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr "Йоy {what}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr "Просто решил, что {mac} не нужен WiFi! Кхе-кхе)"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
msgstr "Деаутентификация {mac}"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr "Кикаю {mac}!"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr "Круто, мы получили {num} новое рукопожатие!"
|
||||
|
||||
msgid "Ops, something went wrong ... Rebooting ..."
|
||||
msgstr "Ой, что-то пошло не так … Перезагружаюсь …"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr "Кикнул {num} станцию\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr "Заимел {num} новых друзей\n"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr "Получил {num} рукопожатие\n"
|
||||
|
||||
msgid "Met 1 peer"
|
||||
msgstr "Встретился один знакомый"
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr "Встретились {num} приятелей"
|
||||
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
|
||||
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
msgstr ""
|
||||
"Я взламывал {duration} и кикнул {deauthed} клиентов! Я также встретил "
|
||||
"{associated} новых друзей и съел {handshakes} рукопожатий! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
|
||||
msgid "hours"
|
||||
msgstr "часов"
|
||||
|
||||
msgid "hour"
|
||||
msgstr "час"
|
||||
|
||||
msgid "minutes"
|
||||
msgstr "минут"
|
||||
|
||||
msgid "minute"
|
||||
msgstr "минуту"
|
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-10-05 14:10+0200\n"
|
||||
"POT-Creation-Date: 2019-10-09 17:42+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@ -122,6 +122,12 @@ msgstr ""
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Good night."
|
||||
msgstr ""
|
||||
|
||||
msgid "Zzz"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr ""
|
@ -11,9 +11,9 @@ from file_read_backwards import FileReadBackwards
|
||||
LAST_SESSION_FILE = '/root/.pwnagotchi-last-session'
|
||||
|
||||
|
||||
class SessionParser(object):
|
||||
class LastSession(object):
|
||||
EPOCH_TOKEN = '[epoch '
|
||||
EPOCH_PARSER = re.compile(r'^\s*\[epoch (\d+)\] (.+)')
|
||||
EPOCH_PARSER = re.compile(r'^.+\[epoch (\d+)\] (.+)')
|
||||
EPOCH_DATA_PARSER = re.compile(r'([a-z_]+)=([^\s]+)')
|
||||
TRAINING_TOKEN = ' training epoch '
|
||||
START_TOKEN = 'connecting to http'
|
||||
@ -22,6 +22,29 @@ class SessionParser(object):
|
||||
HANDSHAKE_TOKEN = '!!! captured new handshake '
|
||||
PEER_TOKEN = 'detected unit '
|
||||
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
self.voice = Voice(lang=config['main']['lang'])
|
||||
self.path = config['main']['log']
|
||||
self.last_session = []
|
||||
self.last_session_id = ''
|
||||
self.last_saved_session_id = ''
|
||||
self.duration = ''
|
||||
self.duration_human = ''
|
||||
self.deauthed = 0
|
||||
self.associated = 0
|
||||
self.handshakes = 0
|
||||
self.peers = 0
|
||||
self.last_peer = None
|
||||
self.epochs = 0
|
||||
self.train_epochs = 0
|
||||
self.min_reward = 1000
|
||||
self.max_reward = -1000
|
||||
self.avg_reward = 0
|
||||
self._peer_parser = re.compile(
|
||||
'detected unit (.+)@(.+) \(v.+\) on channel \d+ \(([\d\-]+) dBm\) \[sid:(.+) pwnd_tot:(\d+) uptime:(\d+)\]')
|
||||
self.parsed = False
|
||||
|
||||
def _get_last_saved_session_id(self):
|
||||
saved = ''
|
||||
try:
|
||||
@ -70,27 +93,27 @@ class SessionParser(object):
|
||||
if started_at is None:
|
||||
started_at = stopped_at
|
||||
|
||||
if SessionParser.DEAUTH_TOKEN in line and line not in cache:
|
||||
if LastSession.DEAUTH_TOKEN in line and line not in cache:
|
||||
self.deauthed += 1
|
||||
cache[line] = 1
|
||||
|
||||
elif SessionParser.ASSOC_TOKEN in line and line not in cache:
|
||||
elif LastSession.ASSOC_TOKEN in line and line not in cache:
|
||||
self.associated += 1
|
||||
cache[line] = 1
|
||||
|
||||
elif SessionParser.HANDSHAKE_TOKEN in line and line not in cache:
|
||||
elif LastSession.HANDSHAKE_TOKEN in line and line not in cache:
|
||||
self.handshakes += 1
|
||||
cache[line] = 1
|
||||
|
||||
elif SessionParser.TRAINING_TOKEN in line:
|
||||
elif LastSession.TRAINING_TOKEN in line:
|
||||
self.train_epochs += 1
|
||||
|
||||
elif SessionParser.EPOCH_TOKEN in line:
|
||||
elif LastSession.EPOCH_TOKEN in line:
|
||||
self.epochs += 1
|
||||
m = SessionParser.EPOCH_PARSER.findall(line)
|
||||
m = LastSession.EPOCH_PARSER.findall(line)
|
||||
if m:
|
||||
epoch_num, epoch_data = m[0]
|
||||
m = SessionParser.EPOCH_DATA_PARSER.findall(epoch_data)
|
||||
m = LastSession.EPOCH_DATA_PARSER.findall(epoch_data)
|
||||
for key, value in m:
|
||||
if key == 'reward':
|
||||
reward = float(value)
|
||||
@ -101,7 +124,7 @@ class SessionParser(object):
|
||||
elif reward > self.max_reward:
|
||||
self.max_reward = reward
|
||||
|
||||
elif SessionParser.PEER_TOKEN in line:
|
||||
elif LastSession.PEER_TOKEN in line:
|
||||
m = self._peer_parser.findall(line)
|
||||
if m:
|
||||
name, pubkey, rssi, sid, pwnd_tot, uptime = m[0]
|
||||
@ -134,23 +157,7 @@ class SessionParser(object):
|
||||
self.duration_human = ', '.join(self.duration_human)
|
||||
self.avg_reward /= (self.epochs if self.epochs else 1)
|
||||
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
self.voice = Voice(lang=config['main']['lang'])
|
||||
self.path = config['main']['log']
|
||||
self.last_session = None
|
||||
self.last_session_id = ''
|
||||
self.last_saved_session_id = ''
|
||||
self.duration = ''
|
||||
self.duration_human = ''
|
||||
self.deauthed = 0
|
||||
self.associated = 0
|
||||
self.handshakes = 0
|
||||
self.peers = 0
|
||||
self.last_peer = None
|
||||
self._peer_parser = re.compile(
|
||||
'detected unit (.+)@(.+) \(v.+\) on channel \d+ \(([\d\-]+) dBm\) \[sid:(.+) pwnd_tot:(\d+) uptime:(\d+)\]')
|
||||
|
||||
def parse(self):
|
||||
lines = []
|
||||
|
||||
if os.path.exists(self.path):
|
||||
@ -160,7 +167,7 @@ class SessionParser(object):
|
||||
if line != "" and line[0] != '[':
|
||||
continue
|
||||
lines.append(line)
|
||||
if SessionParser.START_TOKEN in line:
|
||||
if LastSession.START_TOKEN in line:
|
||||
break
|
||||
lines.reverse()
|
||||
|
||||
@ -172,6 +179,7 @@ class SessionParser(object):
|
||||
self.last_saved_session_id = self._get_last_saved_session_id()
|
||||
|
||||
self._parse_stats()
|
||||
self.parsed = True
|
||||
|
||||
def is_new(self):
|
||||
return self.last_session_id != self.last_saved_session_id
|
4
pwnagotchi/mesh/__init__.py
Normal file
4
pwnagotchi/mesh/__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
import os
|
||||
|
||||
def new_session_id():
|
||||
return ':'.join(['%02x' % b for b in os.urandom(6)])
|
@ -152,7 +152,7 @@ class Advertiser(object):
|
||||
if self._is_broadcasted_advertisement(dot11):
|
||||
try:
|
||||
dot11elt = p.getlayer(Dot11Elt)
|
||||
if dot11elt.ID == wifi.Dot11ElemID_Identity:
|
||||
if dot11elt.ID == wifi.Dot11ElemID_Whisper:
|
||||
self._parse_identity(p[RadioTap], dot11, dot11elt)
|
||||
|
||||
else:
|
@ -3,16 +3,18 @@ import logging
|
||||
|
||||
import pwnagotchi
|
||||
import pwnagotchi.plugins as plugins
|
||||
from pwnagotchi.mesh import get_identity
|
||||
|
||||
|
||||
class AsyncAdvertiser(object):
|
||||
def __init__(self, config, view):
|
||||
def __init__(self, config, view, keypair):
|
||||
self._config = config
|
||||
self._view = view
|
||||
self._public_key, self._identity = get_identity(config)
|
||||
self._keypair = keypair
|
||||
self._advertiser = None
|
||||
|
||||
def keypair(self):
|
||||
return self._keypair
|
||||
|
||||
def start_advertising(self):
|
||||
_thread.start_new_thread(self._adv_worker, ())
|
||||
|
||||
@ -24,7 +26,7 @@ class AsyncAdvertiser(object):
|
||||
self._config['main']['iface'],
|
||||
pwnagotchi.name(),
|
||||
pwnagotchi.version,
|
||||
self._identity,
|
||||
self._keypair.fingerprint,
|
||||
period=0.3,
|
||||
data=self._config['personality'])
|
||||
|
@ -1,6 +1,6 @@
|
||||
SignatureAddress = 'de:ad:be:ef:de:ad'
|
||||
BroadcastAddress = 'ff:ff:ff:ff:ff:ff'
|
||||
Dot11ElemID_Identity = 222
|
||||
Dot11ElemID_Whisper = 222
|
||||
NumChannels = 140
|
||||
|
||||
def freq_to_channel(freq):
|
||||
@ -30,7 +30,7 @@ def encapsulate(payload, addr_from, addr_to=BroadcastAddress):
|
||||
while data_left > 0:
|
||||
sz = min(chunk_size, data_left)
|
||||
chunk = payload[data_off: data_off + sz]
|
||||
frame /= Dot11Elt(ID=Dot11ElemID_Identity, info=chunk, len=sz)
|
||||
frame /= Dot11Elt(ID=Dot11ElemID_Whisper, info=chunk, len=sz)
|
||||
data_off += sz
|
||||
data_left -= sz
|
||||
|
@ -1,6 +1,7 @@
|
||||
import os
|
||||
import glob
|
||||
import importlib, importlib.util
|
||||
import logging
|
||||
|
||||
default_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "default")
|
||||
loaded = {}
|
||||
@ -13,10 +14,13 @@ def dummy_callback():
|
||||
def on(event_name, *args, **kwargs):
|
||||
global loaded
|
||||
cb_name = 'on_%s' % event_name
|
||||
for _, plugin in loaded.items():
|
||||
for plugin_name, plugin in loaded.items():
|
||||
if cb_name in plugin.__dict__:
|
||||
# print("calling %s %s(%s)" %(cb_name, args, kwargs))
|
||||
plugin.__dict__[cb_name](*args, **kwargs)
|
||||
try:
|
||||
plugin.__dict__[cb_name](*args, **kwargs)
|
||||
except Exception as e:
|
||||
logging.error("error while running %s.%s : %s" % (plugin_name, cb_name, e))
|
||||
|
||||
|
||||
def load_from_file(filename):
|
@ -30,9 +30,10 @@ def on_loaded():
|
||||
return
|
||||
|
||||
READY = True
|
||||
logging.info("AUTO-BACKUP: Successfuly loaded.")
|
||||
|
||||
|
||||
def on_internet_available(display, config, log):
|
||||
def on_internet_available(agent):
|
||||
global STATUS
|
||||
|
||||
if READY:
|
||||
@ -41,11 +42,19 @@ def on_internet_available(display, config, log):
|
||||
|
||||
files_to_backup = " ".join(OPTIONS['files'])
|
||||
try:
|
||||
display = agent.view()
|
||||
|
||||
logging.info("AUTO-BACKUP: Backing up ...")
|
||||
display.set('status', 'Backing up ...')
|
||||
display.update()
|
||||
|
||||
for cmd in OPTIONS['commands']:
|
||||
subprocess.call(cmd.format(files=files_to_backup).split(), stdout=open(os.devnull, 'wb'))
|
||||
logging.info(f"AUTO-BACKUP: Running {cmd.format(files=files_to_backup)}")
|
||||
process = subprocess.Popen(cmd.format(files=files_to_backup), shell=True, stdin=None,
|
||||
stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
|
||||
process.wait()
|
||||
if process.returncode > 0:
|
||||
raise OSError(f"Command failed (rc: {process.returncode})")
|
||||
|
||||
logging.info("AUTO-BACKUP: backup done")
|
||||
STATUS.update()
|
@ -17,40 +17,44 @@ def on_loaded():
|
||||
global READY
|
||||
|
||||
if 'interval' not in OPTIONS or ('interval' in OPTIONS and OPTIONS['interval'] is None):
|
||||
logging.error("AUTO-UPDATE: Interval is not set.")
|
||||
logging.error("auto-update: Interval is not set.")
|
||||
return
|
||||
|
||||
READY = True
|
||||
|
||||
|
||||
def on_internet_available(display, config, log):
|
||||
def run(cmd):
|
||||
return subprocess.Popen(cmd, shell=True, stdin=None, stdout=open("/dev/null", "w"), stderr=None,
|
||||
executable="/bin/bash")
|
||||
|
||||
|
||||
def on_internet_available(agent):
|
||||
global STATUS
|
||||
|
||||
if READY:
|
||||
if STATUS.newer_then_days(OPTIONS['interval']):
|
||||
return
|
||||
|
||||
display = agent.view()
|
||||
|
||||
try:
|
||||
display.set('status', 'Updating ...')
|
||||
display.update()
|
||||
|
||||
logging.info("AUTO-UPDATE: updating packages index ...")
|
||||
logging.info("auto-update: updating pwnagotchi ...")
|
||||
run('pip3 install --upgrade --upgrade-strategy only-if-needed pwnagotchi').wait()
|
||||
|
||||
update = subprocess.Popen('apt update -y', shell=True, stdin=None,
|
||||
stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
|
||||
update.wait()
|
||||
if OPTIONS['system']:
|
||||
logging.info("auto-update: updating packages index ...")
|
||||
run('apt update -y').wait()
|
||||
|
||||
logging.info("AUTO-UPDATE: updating packages ...")
|
||||
|
||||
upgrade = subprocess.Popen('apt upgrade -y', shell=True, stdin=None,
|
||||
stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
|
||||
upgrade.wait()
|
||||
|
||||
logging.info("AUTO-UPDATE: complete.")
|
||||
logging.info("auto-update: updating packages ...")
|
||||
run('apt upgrade -y').wait()
|
||||
|
||||
logging.info("auto-update: complete.")
|
||||
STATUS.update()
|
||||
except Exception as e:
|
||||
logging.exception("AUTO-UPDATE ERROR")
|
||||
logging.exception("auto-update ERROR")
|
||||
|
||||
display.set('status', 'Updated!')
|
||||
display.update()
|
@ -20,7 +20,7 @@ def on_loaded():
|
||||
|
||||
|
||||
# called in manual mode when there's internet connectivity
|
||||
def on_internet_available(ui, config, log):
|
||||
def on_internet_available(agent):
|
||||
pass
|
||||
|
||||
|
@ -8,27 +8,26 @@ import logging
|
||||
import json
|
||||
import os
|
||||
|
||||
device = '/dev/ttyUSB0'
|
||||
speed = 19200
|
||||
running = False
|
||||
OPTIONS = dict()
|
||||
|
||||
|
||||
def on_loaded():
|
||||
logging.info("gps plugin loaded for %s" % device)
|
||||
logging.info("gps plugin loaded for %s" % OPTIONS['device'])
|
||||
|
||||
|
||||
def on_ready(agent):
|
||||
global running
|
||||
|
||||
if os.path.exists(device):
|
||||
logging.info("enabling gps bettercap's module for %s" % device)
|
||||
if os.path.exists(OPTIONS['device']):
|
||||
logging.info("enabling gps bettercap's module for %s" % OPTIONS['device'])
|
||||
try:
|
||||
agent.run('gps off')
|
||||
except:
|
||||
pass
|
||||
|
||||
agent.run('set gps.device %s' % device)
|
||||
agent.run('set gps.speed %d' % speed)
|
||||
agent.run('set gps.device %s' % OPTIONS['device'])
|
||||
agent.run('set gps.speed %d' % OPTIONS['speed'])
|
||||
agent.run('gps on')
|
||||
running = True
|
||||
else:
|
172
pwnagotchi/plugins/default/grid.py
Normal file
172
pwnagotchi/plugins/default/grid.py
Normal file
@ -0,0 +1,172 @@
|
||||
__author__ = 'evilsocket@gmail.com'
|
||||
__version__ = '1.0.0'
|
||||
__name__ = 'grid'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'This plugin signals the unit cryptographic identity and list of pwned networks and list of pwned networks to api.pwnagotchi.ai'
|
||||
|
||||
import os
|
||||
import logging
|
||||
import requests
|
||||
import glob
|
||||
import json
|
||||
import subprocess
|
||||
import pwnagotchi
|
||||
import pwnagotchi.utils as utils
|
||||
from pwnagotchi.utils import WifiInfo, extract_from_pcap
|
||||
|
||||
OPTIONS = dict()
|
||||
AUTH = utils.StatusFile('/root/.api-enrollment.json', data_format='json')
|
||||
REPORT = utils.StatusFile('/root/.api-report.json', data_format='json')
|
||||
|
||||
|
||||
def on_loaded():
|
||||
logging.info("grid plugin loaded.")
|
||||
|
||||
|
||||
def get_api_token(last_session, keys):
|
||||
global AUTH
|
||||
|
||||
if AUTH.newer_then_minutes(25) and AUTH.data is not None and 'token' in AUTH.data:
|
||||
return AUTH.data['token']
|
||||
|
||||
if AUTH.data is None:
|
||||
logging.info("grid: enrolling unit ...")
|
||||
else:
|
||||
logging.info("grid: refreshing token ...")
|
||||
|
||||
identity = "%s@%s" % (pwnagotchi.name(), keys.fingerprint)
|
||||
# sign the identity string to prove we own both keys
|
||||
_, signature_b64 = keys.sign(identity)
|
||||
|
||||
brain = {}
|
||||
try:
|
||||
with open('/root/brain.json') as fp:
|
||||
brain = json.load(fp)
|
||||
except:
|
||||
pass
|
||||
|
||||
api_address = 'https://api.pwnagotchi.ai/api/v1/unit/enroll'
|
||||
enrollment = {
|
||||
'identity': identity,
|
||||
'public_key': keys.pub_key_pem_b64,
|
||||
'signature': signature_b64,
|
||||
'data': {
|
||||
'duration': last_session.duration,
|
||||
'epochs': last_session.epochs,
|
||||
'train_epochs': last_session.train_epochs,
|
||||
'avg_reward': last_session.avg_reward,
|
||||
'min_reward': last_session.min_reward,
|
||||
'max_reward': last_session.max_reward,
|
||||
'deauthed': last_session.deauthed,
|
||||
'associated': last_session.associated,
|
||||
'handshakes': last_session.handshakes,
|
||||
'peers': last_session.peers,
|
||||
'uname': subprocess.getoutput("uname -a"),
|
||||
'brain': brain
|
||||
}
|
||||
}
|
||||
|
||||
r = requests.post(api_address, json=enrollment)
|
||||
if r.status_code != 200:
|
||||
raise Exception("(status %d) %s" % (r.status_code, r.json()))
|
||||
|
||||
AUTH.update(data=r.json())
|
||||
|
||||
logging.info("grid: done")
|
||||
|
||||
return AUTH.data["token"]
|
||||
|
||||
|
||||
def parse_pcap(filename):
|
||||
logging.info("grid: parsing %s ..." % filename)
|
||||
|
||||
net_id = os.path.basename(filename).replace('.pcap', '')
|
||||
|
||||
if '_' in net_id:
|
||||
# /root/handshakes/ESSID_BSSID.pcap
|
||||
essid, bssid = net_id.split('_')
|
||||
else:
|
||||
# /root/handshakes/BSSID.pcap
|
||||
essid, bssid = '', net_id
|
||||
|
||||
it = iter(bssid)
|
||||
bssid = ':'.join([a + b for a, b in zip(it, it)])
|
||||
|
||||
info = {
|
||||
WifiInfo.ESSID: essid,
|
||||
WifiInfo.BSSID: bssid,
|
||||
}
|
||||
|
||||
try:
|
||||
info = extract_from_pcap(filename, [WifiInfo.BSSID, WifiInfo.ESSID])
|
||||
except Exception as e:
|
||||
logging.error("grid: %s" % e)
|
||||
|
||||
return info[WifiInfo.ESSID], info[WifiInfo.BSSID]
|
||||
|
||||
|
||||
def api_report_ap(last_session, keys, token, essid, bssid):
|
||||
while True:
|
||||
token = AUTH.data['token']
|
||||
logging.info("grid: reporting %s (%s)" % (essid, bssid))
|
||||
try:
|
||||
api_address = 'https://api.pwnagotchi.ai/api/v1/unit/report/ap'
|
||||
headers = {'Authorization': 'access_token %s' % token}
|
||||
report = {
|
||||
'essid': essid,
|
||||
'bssid': bssid,
|
||||
}
|
||||
r = requests.post(api_address, headers=headers, json=report)
|
||||
if r.status_code != 200:
|
||||
if r.status_code == 401:
|
||||
logging.warning("token expired")
|
||||
token = get_api_token(last_session, keys)
|
||||
continue
|
||||
else:
|
||||
raise Exception("(status %d) %s" % (r.status_code, r.text))
|
||||
else:
|
||||
return True
|
||||
except Exception as e:
|
||||
logging.error("grid: %s" % e)
|
||||
return False
|
||||
|
||||
|
||||
def on_internet_available(agent):
|
||||
global REPORT
|
||||
|
||||
try:
|
||||
config = agent.config()
|
||||
keys = agent.keypair()
|
||||
|
||||
pcap_files = glob.glob(os.path.join(config['bettercap']['handshakes'], "*.pcap"))
|
||||
num_networks = len(pcap_files)
|
||||
reported = REPORT.data_field_or('reported', default=[])
|
||||
num_reported = len(reported)
|
||||
num_new = num_networks - num_reported
|
||||
|
||||
token = get_api_token(agent.last_session, agent.keypair())
|
||||
if num_new > 0:
|
||||
if OPTIONS['report']:
|
||||
logging.info("grid: %d new networks to report" % num_new)
|
||||
|
||||
for pcap_file in pcap_files:
|
||||
net_id = os.path.basename(pcap_file).replace('.pcap', '')
|
||||
do_skip = False
|
||||
for skip in OPTIONS['exclude']:
|
||||
skip = skip.lower()
|
||||
net = net_id.lower()
|
||||
if skip in net or skip.replace(':', '') in net:
|
||||
do_skip = True
|
||||
break
|
||||
|
||||
if net_id not in reported and not do_skip:
|
||||
essid, bssid = parse_pcap(pcap_file)
|
||||
if bssid:
|
||||
if api_report_ap(agent.last_session, keys, token, essid, bssid):
|
||||
reported.append(net_id)
|
||||
REPORT.update(data={'reported': reported})
|
||||
else:
|
||||
logging.debug("grid: reporting disabled")
|
||||
|
||||
except Exception as e:
|
||||
logging.exception("error while enrolling the unit")
|
144
pwnagotchi/plugins/default/net-pos.py
Normal file
144
pwnagotchi/plugins/default/net-pos.py
Normal file
@ -0,0 +1,144 @@
|
||||
__author__ = 'zenzen san'
|
||||
__version__ = '1.0.0'
|
||||
__name__ = 'net-pos'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = """Saves a json file with the access points with more signal
|
||||
whenever a handshake is captured.
|
||||
When internet is available the files are converted in geo locations
|
||||
using Mozilla LocationService """
|
||||
|
||||
import logging
|
||||
import json
|
||||
import os
|
||||
import requests
|
||||
|
||||
MOZILLA_API_URL = 'https://location.services.mozilla.com/v1/geolocate?key={api}'
|
||||
ALREADY_SAVED = None
|
||||
SKIP = None
|
||||
READY = False
|
||||
OPTIONS = {}
|
||||
|
||||
|
||||
def on_loaded():
|
||||
global ALREADY_SAVED
|
||||
global SKIP
|
||||
global READY
|
||||
|
||||
SKIP = list()
|
||||
|
||||
if 'api_key' not in OPTIONS or ('api_key' in OPTIONS and OPTIONS['api_key'] is None):
|
||||
logging.error("NET-POS: api_key isn't set. Can't use mozilla's api.")
|
||||
return
|
||||
|
||||
try:
|
||||
with open('/root/.net_pos_saved', 'r') as f:
|
||||
ALREADY_SAVED = f.read().splitlines()
|
||||
except OSError:
|
||||
logging.warning('NET-POS: No net-pos-file found.')
|
||||
ALREADY_SAVED = []
|
||||
|
||||
READY = True
|
||||
logging.info("net-pos plugin loaded.")
|
||||
|
||||
def _append_saved(path):
|
||||
to_save = list()
|
||||
if isinstance(path, str):
|
||||
to_save.append(path)
|
||||
elif isinstance(path, list):
|
||||
to_save += path
|
||||
else:
|
||||
raise TypeError("Expected list or str, got %s" % type(path))
|
||||
|
||||
with open('/root/.net_pos_saved', 'a') as saved_file:
|
||||
for x in to_save:
|
||||
saved_file.write(x + "\n")
|
||||
|
||||
def on_internet_available(agent):
|
||||
global SKIP
|
||||
|
||||
if READY:
|
||||
config = agent.config()
|
||||
display = agent.view()
|
||||
handshake_dir = config['bettercap']['handshakes']
|
||||
|
||||
all_files = os.listdir(handshake_dir)
|
||||
all_np_files = [os.path.join(handshake_dir, filename)
|
||||
for filename in all_files
|
||||
if filename.endswith('.net-pos.json')]
|
||||
new_np_files = set(all_np_files) - set(ALREADY_SAVED) - set(SKIP)
|
||||
|
||||
if new_np_files:
|
||||
logging.info("NET-POS: Found {num} new net-pos files. Fetching positions ...", len(new_np_files))
|
||||
display.set('status', f"Found {len(new_np_files)} new net-pos files. Fetching positions ...")
|
||||
display.update(force=True)
|
||||
for idx, np_file in enumerate(new_np_files):
|
||||
|
||||
geo_file = np_file.replace('.net-pos.json', '.geo.json')
|
||||
if os.path.exists(geo_file):
|
||||
# got already the position
|
||||
ALREADY_SAVED.append(np_file)
|
||||
_append_saved(np_file)
|
||||
continue
|
||||
|
||||
try:
|
||||
geo_data = _get_geo_data(np_file) # returns json obj
|
||||
except requests.exceptions.RequestException as req_e:
|
||||
logging.error("NET-POS: %s", req_e)
|
||||
SKIP += np_file
|
||||
except json.JSONDecodeError as js_e:
|
||||
logging.error("NET-POS: %s", js_e)
|
||||
SKIP += np_file
|
||||
except OSError as os_e:
|
||||
logging.error("NET-POS: %s", os_e)
|
||||
SKIP += np_file
|
||||
|
||||
with open(geo_file, 'w+t') as sf:
|
||||
json.dump(geo_data, sf)
|
||||
|
||||
ALREADY_SAVED.append(np_file)
|
||||
_append_saved(np_file)
|
||||
|
||||
display.set('status', f"Fetching positions ({idx+1}/{len(new_np_files)})")
|
||||
display.update(force=True)
|
||||
|
||||
|
||||
def on_handshake(agent, filename, access_point, client_station):
|
||||
netpos = _get_netpos(agent)
|
||||
netpos_filename = filename.replace('.pcap', '.net-pos.json')
|
||||
logging.info("NET-POS: Saving net-location to %s", netpos_filename)
|
||||
|
||||
try:
|
||||
with open(netpos_filename, 'w+t') as fp:
|
||||
json.dump(netpos, fp)
|
||||
except OSError as os_e:
|
||||
logging.error("NET-POS: %s", os_e)
|
||||
|
||||
|
||||
def _get_netpos(agent):
|
||||
aps = agent.get_access_points()
|
||||
netpos = {}
|
||||
netpos['wifiAccessPoints'] = list()
|
||||
# 6 seems a good number to save a wifi networks location
|
||||
for access_point in sorted(aps, key=lambda i: i['rssi'], reverse=True)[:6]:
|
||||
netpos['wifiAccessPoints'].append({'macAddress': access_point['mac'],
|
||||
'signalStrength': access_point['rssi']})
|
||||
return netpos
|
||||
|
||||
def _get_geo_data(path, timeout=30):
|
||||
geourl = MOZILLA_API_URL.format(api=OPTIONS['api_key'])
|
||||
|
||||
try:
|
||||
with open(path, "r") as json_file:
|
||||
data = json.load(json_file)
|
||||
except json.JSONDecodeError as js_e:
|
||||
raise js_e
|
||||
except OSError as os_e:
|
||||
raise os_e
|
||||
|
||||
try:
|
||||
result = requests.post(geourl,
|
||||
json=data,
|
||||
timeout=timeout)
|
||||
return result.json()
|
||||
except requests.exceptions.RequestException as req_e:
|
||||
raise req_e
|
@ -55,14 +55,17 @@ def _upload_to_ohc(path, timeout=30):
|
||||
raise e
|
||||
|
||||
|
||||
def on_internet_available(display, config, log):
|
||||
def on_internet_available(agent):
|
||||
"""
|
||||
Called in manual mode when there's internet connectivity
|
||||
"""
|
||||
if READY:
|
||||
display = agent.view()
|
||||
config = agent.config()
|
||||
|
||||
handshake_dir = config['bettercap']['handshakes']
|
||||
handshake_filenames = os.listdir(handshake_dir)
|
||||
handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames]
|
||||
handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if filename.endswith('.pcap')]
|
||||
handshake_new = set(handshake_paths) - set(ALREADY_UPLOADED)
|
||||
|
||||
if handshake_new:
|
24
pwnagotchi/plugins/default/screen_refresh.py
Normal file
24
pwnagotchi/plugins/default/screen_refresh.py
Normal file
@ -0,0 +1,24 @@
|
||||
__author__ = 'pwnagotcchi [at] rossmarks [dot] uk'
|
||||
__version__ = '1.0.0'
|
||||
__name__ = 'screen_refresh'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'Refresh he e-ink display after X amount of updates'
|
||||
|
||||
import logging
|
||||
|
||||
OPTIONS = dict()
|
||||
update_count = 0;
|
||||
|
||||
|
||||
def on_loaded():
|
||||
logging.info("Screen refresh plugin loaded")
|
||||
|
||||
|
||||
def on_ui_update(ui):
|
||||
global update_count
|
||||
update_count += 1
|
||||
if update_count == OPTIONS['refresh_interval']:
|
||||
ui._init_display()
|
||||
ui.set('status', "Screen cleaned")
|
||||
logging.info("Screen refreshing")
|
||||
update_count = 0
|
@ -14,8 +14,12 @@ def on_loaded():
|
||||
|
||||
|
||||
# called in manual mode when there's internet connectivity
|
||||
def on_internet_available(ui, config, log):
|
||||
if log.is_new() and log.handshakes > 0:
|
||||
def on_internet_available(agent):
|
||||
config = agent.config()
|
||||
display = agent.view()
|
||||
last_session = agent.last_session
|
||||
|
||||
if last_session.is_new() and last_session.handshakes > 0:
|
||||
try:
|
||||
import tweepy
|
||||
except ImportError:
|
||||
@ -26,20 +30,20 @@ def on_internet_available(ui, config, log):
|
||||
|
||||
picture = '/dev/shm/pwnagotchi.png'
|
||||
|
||||
ui.on_manual_mode(log)
|
||||
ui.update(force=True)
|
||||
ui.image().save(picture, 'png')
|
||||
ui.set('status', 'Tweeting...')
|
||||
ui.update(force=True)
|
||||
display.on_manual_mode(last_session)
|
||||
display.update(force=True)
|
||||
display.image().save(picture, 'png')
|
||||
display.set('status', 'Tweeting...')
|
||||
display.update(force=True)
|
||||
|
||||
try:
|
||||
auth = tweepy.OAuthHandler(OPTIONS['consumer_key'], OPTIONS['consumer_secret'])
|
||||
auth.set_access_token(OPTIONS['access_token_key'], OPTIONS['access_token_secret'])
|
||||
api = tweepy.API(auth)
|
||||
|
||||
tweet = Voice(lang=config['main']['lang']).on_log_tweet(log)
|
||||
tweet = Voice(lang=config['main']['lang']).on_last_session_tweet(last_session)
|
||||
api.update_with_media(filename=picture, status=tweet)
|
||||
log.save_session_id()
|
||||
last_session.save_session_id()
|
||||
|
||||
logging.info("tweeted: %s" % tweet)
|
||||
except Exception as e:
|
284
pwnagotchi/plugins/default/wigle.py
Normal file
284
pwnagotchi/plugins/default/wigle.py
Normal file
@ -0,0 +1,284 @@
|
||||
__author__ = '33197631+dadav@users.noreply.github.com'
|
||||
__version__ = '1.0.0'
|
||||
__name__ = 'wigle'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'This plugin automatically uploades collected wifis to wigle.net'
|
||||
|
||||
import os
|
||||
import logging
|
||||
import json
|
||||
from io import StringIO
|
||||
import csv
|
||||
from datetime import datetime
|
||||
import requests
|
||||
from pwnagotchi.mesh.wifi import freq_to_channel
|
||||
from pwnagotchi.utils import WifiInfo, FieldNotFoundError, extract_from_pcap
|
||||
|
||||
READY = False
|
||||
ALREADY_UPLOADED = None
|
||||
SKIP = None
|
||||
OPTIONS = dict()
|
||||
|
||||
AKMSUITE_TYPES = {
|
||||
0x00: "Reserved",
|
||||
0x01: "802.1X",
|
||||
0x02: "PSK",
|
||||
}
|
||||
|
||||
def _handle_packet(packet, result):
|
||||
from scapy.all import RadioTap, Dot11Elt, Dot11Beacon, rdpcap, Scapy_Exception, Dot11, Dot11ProbeResp, Dot11AssoReq, \
|
||||
Dot11ReassoReq, Dot11EltRSN, Dot11EltVendorSpecific, Dot11EltMicrosoftWPA
|
||||
"""
|
||||
Analyze each packet and extract the data from Dot11 layers
|
||||
"""
|
||||
|
||||
if hasattr(packet, 'cap') and 'privacy' in packet.cap:
|
||||
# packet is encrypted
|
||||
if 'encryption' not in result:
|
||||
result['encryption'] = set()
|
||||
|
||||
if packet.haslayer(Dot11Beacon):
|
||||
if packet.haslayer(Dot11Beacon)\
|
||||
or packet.haslayer(Dot11ProbeResp)\
|
||||
or packet.haslayer(Dot11AssoReq)\
|
||||
or packet.haslayer(Dot11ReassoReq):
|
||||
if 'bssid' not in result and hasattr(packet[Dot11], 'addr3'):
|
||||
result['bssid'] = packet[Dot11].addr3
|
||||
if 'essid' not in result and hasattr(packet[Dot11Elt], 'info'):
|
||||
result['essid'] = packet[Dot11Elt].info
|
||||
if 'channel' not in result and hasattr(packet[Dot11Elt:3], 'info'):
|
||||
result['channel'] = int(ord(packet[Dot11Elt:3].info))
|
||||
|
||||
if packet.haslayer(RadioTap):
|
||||
if 'rssi' not in result and hasattr(packet[RadioTap], 'dBm_AntSignal'):
|
||||
result['rssi'] = packet[RadioTap].dBm_AntSignal
|
||||
if 'channel' not in result and hasattr(packet[RadioTap], 'ChannelFrequency'):
|
||||
result['channel'] = freq_to_channel(packet[RadioTap].ChannelFrequency)
|
||||
|
||||
# see: https://fossies.org/linux/scapy/scapy/layers/dot11.py
|
||||
if packet.haslayer(Dot11EltRSN):
|
||||
if hasattr(packet[Dot11EltRSN], 'akm_suites'):
|
||||
auth = AKMSUITE_TYPES.get(packet[Dot11EltRSN].akm_suites[0].suite)
|
||||
result['encryption'].add(f"WPA2/{auth}")
|
||||
else:
|
||||
result['encryption'].add("WPA2")
|
||||
|
||||
if packet.haslayer(Dot11EltVendorSpecific)\
|
||||
and (packet.haslayer(Dot11EltMicrosoftWPA)
|
||||
or packet.info.startswith(b'\x00P\xf2\x01\x01\x00')):
|
||||
|
||||
if hasattr(packet, 'akm_suites'):
|
||||
auth = AKMSUITE_TYPES.get(packet.akm_suites[0].suite)
|
||||
result['encryption'].add(f"WPA2/{auth}")
|
||||
else:
|
||||
result['encryption'].add("WPA2")
|
||||
# end see
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _analyze_pcap(pcap):
|
||||
from scapy.all import RadioTap, Dot11Elt, Dot11Beacon, rdpcap, Scapy_Exception, Dot11, Dot11ProbeResp, Dot11AssoReq, \
|
||||
Dot11ReassoReq, Dot11EltRSN, Dot11EltVendorSpecific, Dot11EltMicrosoftWPA
|
||||
"""
|
||||
Iterate over the packets and extract data
|
||||
"""
|
||||
result = dict()
|
||||
|
||||
try:
|
||||
packets = rdpcap(pcap)
|
||||
for packet in packets:
|
||||
result = _handle_packet(packet, result)
|
||||
except Scapy_Exception as sc_e:
|
||||
raise sc_e
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def on_loaded():
|
||||
"""
|
||||
Gets called when the plugin gets loaded
|
||||
"""
|
||||
global READY
|
||||
global ALREADY_UPLOADED
|
||||
global SKIP
|
||||
|
||||
SKIP = list()
|
||||
|
||||
if 'api_key' not in OPTIONS or ('api_key' in OPTIONS and OPTIONS['api_key'] is None):
|
||||
logging.error("WIGLE: api_key isn't set. Can't upload to wigle.net")
|
||||
return
|
||||
|
||||
try:
|
||||
with open('/root/.wigle_uploads', 'r') as f:
|
||||
ALREADY_UPLOADED = f.read().splitlines()
|
||||
except OSError:
|
||||
logging.warning('WIGLE: No upload-file found.')
|
||||
ALREADY_UPLOADED = []
|
||||
|
||||
READY = True
|
||||
|
||||
|
||||
def _extract_gps_data(path):
|
||||
"""
|
||||
Extract data from gps-file
|
||||
|
||||
return json-obj
|
||||
"""
|
||||
|
||||
try:
|
||||
with open(path, 'r') as json_file:
|
||||
return json.load(json_file)
|
||||
except OSError as os_err:
|
||||
raise os_err
|
||||
except json.JSONDecodeError as json_err:
|
||||
raise json_err
|
||||
|
||||
|
||||
def _format_auth(data):
|
||||
out = ""
|
||||
for auth in data:
|
||||
out = f"{out}[{auth}]"
|
||||
return out
|
||||
|
||||
def _transform_wigle_entry(gps_data, pcap_data):
|
||||
"""
|
||||
Transform to wigle entry in file
|
||||
"""
|
||||
dummy = StringIO()
|
||||
# write kismet header
|
||||
dummy.write("WigleWifi-1.4,appRelease=20190201,model=Kismet,release=2019.02.01.{},device=kismet,display=kismet,board=kismet,brand=kismet\n")
|
||||
dummy.write("MAC,SSID,AuthMode,FirstSeen,Channel,RSSI,CurrentLatitude,CurrentLongitude,AltitudeMeters,AccuracyMeters,Type")
|
||||
|
||||
writer = csv.writer(dummy, delimiter=",", quoting=csv.QUOTE_NONE)
|
||||
writer.writerow([
|
||||
pcap_data[WifiInfo.BSSID],
|
||||
pcap_data[WifiInfo.ESSID],
|
||||
_format_auth(pcap_data[WifiInfo.ENCRYPTION]),
|
||||
datetime.strptime(gps_data['Updated'].rsplit('.')[0],
|
||||
"%Y-%m-%dT%H:%M:%S").strftime('%Y-%m-%d %H:%M:%S'),
|
||||
pcap_data[WifiInfo.CHANNEL],
|
||||
pcap_data[WifiInfo.RSSI],
|
||||
gps_data['Latitude'],
|
||||
gps_data['Longitude'],
|
||||
gps_data['Altitude'],
|
||||
0, # accuracy?
|
||||
'WIFI'])
|
||||
return dummy.getvalue()
|
||||
|
||||
def _send_to_wigle(lines, api_key, timeout=30):
|
||||
"""
|
||||
Uploads the file to wigle-net
|
||||
"""
|
||||
|
||||
dummy = StringIO()
|
||||
|
||||
for line in lines:
|
||||
dummy.write(f"{line}")
|
||||
|
||||
dummy.seek(0)
|
||||
|
||||
headers = {'Authorization': f"Basic {api_key}",
|
||||
'Accept': 'application/json'}
|
||||
data = {'donate': 'false'}
|
||||
payload = {'file': dummy, 'type': 'text/csv'}
|
||||
|
||||
try:
|
||||
res = requests.post('https://api.wigle.net/api/v2/file/upload',
|
||||
data=data,
|
||||
headers=headers,
|
||||
files=payload,
|
||||
timeout=timeout)
|
||||
json_res = res.json()
|
||||
if not json_res['success']:
|
||||
raise requests.exceptions.RequestException(json_res['message'])
|
||||
except requests.exceptions.RequestException as re_e:
|
||||
raise re_e
|
||||
|
||||
|
||||
def on_internet_available(agent):
|
||||
from scapy.all import RadioTap, Dot11Elt, Dot11Beacon, rdpcap, Scapy_Exception, Dot11, Dot11ProbeResp, Dot11AssoReq, \
|
||||
Dot11ReassoReq, Dot11EltRSN, Dot11EltVendorSpecific, Dot11EltMicrosoftWPA
|
||||
"""
|
||||
Called in manual mode when there's internet connectivity
|
||||
"""
|
||||
global ALREADY_UPLOADED
|
||||
global SKIP
|
||||
|
||||
if READY:
|
||||
config = agent.config()
|
||||
display = agent.view()
|
||||
|
||||
handshake_dir = config['bettercap']['handshakes']
|
||||
all_files = os.listdir(handshake_dir)
|
||||
all_gps_files = [os.path.join(handshake_dir, filename)
|
||||
for filename in all_files
|
||||
if filename.endswith('.gps.json')]
|
||||
new_gps_files = set(all_gps_files) - set(ALREADY_UPLOADED) - set(SKIP)
|
||||
|
||||
if new_gps_files:
|
||||
logging.info("WIGLE: Internet connectivity detected. Uploading new handshakes to wigle.net")
|
||||
|
||||
csv_entries = list()
|
||||
no_err_entries = list()
|
||||
|
||||
for gps_file in new_gps_files:
|
||||
pcap_filename = gps_file.replace('.gps.json', '.pcap')
|
||||
|
||||
if not os.path.exists(pcap_filename):
|
||||
logging.error("WIGLE: Can't find pcap for %s", gps_file)
|
||||
SKIP.append(gps_file)
|
||||
continue
|
||||
|
||||
try:
|
||||
gps_data = _extract_gps_data(gps_file)
|
||||
except OSError as os_err:
|
||||
logging.error("WIGLE: %s", os_err)
|
||||
SKIP.append(gps_file)
|
||||
continue
|
||||
except json.JSONDecodeError as json_err:
|
||||
logging.error("WIGLE: %s", json_err)
|
||||
SKIP.append(gps_file)
|
||||
continue
|
||||
|
||||
if gps_data['Latitude'] == 0 and gps_data['Longitude'] == 0:
|
||||
logging.warning("WIGLE: Not enough gps-informations for %s. Trying again next time.", gps_file)
|
||||
SKIP.append(gps_file)
|
||||
continue
|
||||
|
||||
|
||||
try:
|
||||
pcap_data = extract_from_pcap(pcap_filename, [WifiInfo.BSSID,
|
||||
WifiInfo.ESSID,
|
||||
WifiInfo.ENCRYPTION,
|
||||
WifiInfo.CHANNEL,
|
||||
WifiInfo.RSSI])
|
||||
except FieldNotFoundError:
|
||||
logging.error("WIGLE: Could not extract all informations. Skip %s", gps_file)
|
||||
SKIP.append(gps_file)
|
||||
continue
|
||||
except Scapy_Exception as sc_e:
|
||||
logging.error("WIGLE: %s", sc_e)
|
||||
SKIP.append(gps_file)
|
||||
continue
|
||||
|
||||
new_entry = _transform_wigle_entry(gps_data, pcap_data)
|
||||
csv_entries.append(new_entry)
|
||||
no_err_entries.append(gps_file)
|
||||
|
||||
if csv_entries:
|
||||
display.set('status', "Uploading gps-data to wigle.net ...")
|
||||
display.update(force=True)
|
||||
try:
|
||||
_send_to_wigle(csv_entries, OPTIONS['api_key'])
|
||||
ALREADY_UPLOADED += no_err_entries
|
||||
with open('/root/.wigle_uploads', 'a') as up_file:
|
||||
for gps in no_err_entries:
|
||||
up_file.write(gps + "\n")
|
||||
logging.info("WIGLE: Successfuly uploaded %d files", len(no_err_entries))
|
||||
except requests.exceptions.RequestException as re_e:
|
||||
SKIP += no_err_entries
|
||||
logging.error("WIGLE: Got an exception while uploading %s", re_e)
|
||||
except OSError as os_e:
|
||||
SKIP += no_err_entries
|
||||
logging.error("WIGLE: Got the following error: %s", os_e)
|
@ -54,14 +54,17 @@ def _upload_to_wpasec(path, timeout=30):
|
||||
raise e
|
||||
|
||||
|
||||
def on_internet_available(display, config, log):
|
||||
def on_internet_available(agent):
|
||||
"""
|
||||
Called in manual mode when there's internet connectivity
|
||||
"""
|
||||
if READY:
|
||||
config = agent.config()
|
||||
display = agent.view()
|
||||
|
||||
handshake_dir = config['bettercap']['handshakes']
|
||||
handshake_filenames = os.listdir(handshake_dir)
|
||||
handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames]
|
||||
handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if filename.endswith('.pcap')]
|
||||
handshake_new = set(handshake_paths) - set(ALREADY_UPLOADED)
|
||||
|
||||
if handshake_new:
|
@ -16,11 +16,29 @@ class VideoHandler(BaseHTTPRequestHandler):
|
||||
_lock = Lock()
|
||||
_index = """<html>
|
||||
<head>
|
||||
<title>%s</title>
|
||||
<title>%s</title>
|
||||
<style>
|
||||
.block {
|
||||
-webkit-appearance: button;
|
||||
-moz-appearance: button;
|
||||
appearance: button;
|
||||
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<img src="/ui" id="ui"/>
|
||||
|
||||
<div style="position: absolute; top:0; left:0; width:100%%;">
|
||||
<img src="/ui" id="ui" style="width:100%%"/>
|
||||
<br/>
|
||||
<hr/>
|
||||
<form action="/shutdown" onsubmit="return confirm('This will halt the unit, continue?');">
|
||||
<input type="submit" class="block" value="Shutdown"/>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
window.onload = function() {
|
||||
var image = document.getElementById("ui");
|
||||
@ -51,6 +69,9 @@ class VideoHandler(BaseHTTPRequestHandler):
|
||||
except:
|
||||
pass
|
||||
|
||||
elif self.path.startswith('/shutdown'):
|
||||
pwnagotchi.shutdown()
|
||||
|
||||
elif self.path.startswith('/ui'):
|
||||
with self._lock:
|
||||
self.send_response(200)
|
@ -15,7 +15,7 @@ from pwnagotchi.ui.state import State
|
||||
|
||||
WHITE = 0xff
|
||||
BLACK = 0x00
|
||||
|
||||
ROOT = None
|
||||
|
||||
def setup_display_specifics(config):
|
||||
width = 0
|
||||
@ -30,8 +30,8 @@ def setup_display_specifics(config):
|
||||
width = 212
|
||||
height = 104
|
||||
face_pos = (0, int(height / 4))
|
||||
name_pos = (int(width / 2) - 15, int(height * .15))
|
||||
status_pos = (int(width / 2) - 15, int(height * .30))
|
||||
name_pos = (5, int(height * .15))
|
||||
status_pos = (int(width / 2) - 15, int(height * .15))
|
||||
|
||||
elif config['ui']['display']['type'] in ('papirus', 'papi'):
|
||||
fonts.setup(10, 8, 10, 23)
|
||||
@ -39,8 +39,8 @@ def setup_display_specifics(config):
|
||||
width = 200
|
||||
height = 96
|
||||
face_pos = (0, int(height / 4))
|
||||
name_pos = (int(width / 2) - 15, int(height * .15))
|
||||
status_pos = (int(width / 2) - 15, int(height * .30))
|
||||
name_pos = (5, int(height * .15))
|
||||
status_pos = (int(width / 2) - 15, int(height * .15))
|
||||
|
||||
elif config['ui']['display']['type'] in ('ws_1', 'ws1', 'waveshare_1', 'waveshare1',
|
||||
'ws_2', 'ws2', 'waveshare_2', 'waveshare2'):
|
||||
@ -50,25 +50,28 @@ def setup_display_specifics(config):
|
||||
width = 250
|
||||
height = 122
|
||||
face_pos = (0, 40)
|
||||
name_pos = (125, 20)
|
||||
status_pos = (125, 35)
|
||||
name_pos = (5, 20)
|
||||
status_pos = (125, 20)
|
||||
else:
|
||||
fonts.setup(10, 8, 10, 25)
|
||||
|
||||
width = 212
|
||||
height = 104
|
||||
face_pos = (0, int(height / 4))
|
||||
name_pos = (int(width / 2) - 15, int(height * .15))
|
||||
status_pos = (int(width / 2) - 15, int(height * .30))
|
||||
name_pos = (5, int(height * .15))
|
||||
status_pos = (int(width / 2) - 15, int(height * .15))
|
||||
|
||||
return width, height, face_pos, name_pos, status_pos
|
||||
|
||||
|
||||
class View(object):
|
||||
def __init__(self, config, state={}):
|
||||
global ROOT
|
||||
|
||||
self._render_cbs = []
|
||||
self._config = config
|
||||
self._canvas = None
|
||||
self._frozen = False
|
||||
self._lock = Lock()
|
||||
self._voice = Voice(lang=config['main']['lang'])
|
||||
|
||||
@ -128,6 +131,8 @@ class View(object):
|
||||
logging.warning("ui.fps is 0, the display will only update for major changes")
|
||||
self._ignore_changes = ('uptime', 'name')
|
||||
|
||||
ROOT = self
|
||||
|
||||
def add_element(self, key, elem):
|
||||
self._state.add_element(key, elem)
|
||||
|
||||
@ -162,22 +167,22 @@ class View(object):
|
||||
self.set('face', faces.AWAKE)
|
||||
|
||||
def on_ai_ready(self):
|
||||
self.set('mode', '')
|
||||
self.set('mode', ' AI')
|
||||
self.set('face', faces.HAPPY)
|
||||
self.set('status', self._voice.on_ai_ready())
|
||||
self.update()
|
||||
|
||||
def on_manual_mode(self, log):
|
||||
def on_manual_mode(self, last_session):
|
||||
self.set('mode', 'MANU')
|
||||
self.set('face', faces.SAD if log.handshakes == 0 else faces.HAPPY)
|
||||
self.set('status', self._voice.on_log(log))
|
||||
self.set('epoch', "%04d" % log.epochs)
|
||||
self.set('uptime', log.duration)
|
||||
self.set('face', faces.SAD if last_session.handshakes == 0 else faces.HAPPY)
|
||||
self.set('status', self._voice.on_last_session_data(last_session))
|
||||
self.set('epoch', "%04d" % last_session.epochs)
|
||||
self.set('uptime', last_session.duration)
|
||||
self.set('channel', '-')
|
||||
self.set('aps', "%d" % log.associated)
|
||||
self.set('shakes', '%d (%s)' % (log.handshakes, \
|
||||
self.set('aps', "%d" % last_session.associated)
|
||||
self.set('shakes', '%d (%s)' % (last_session.handshakes, \
|
||||
utils.total_unique_handshakes(self._config['bettercap']['handshakes'])))
|
||||
self.set_closest_peer(log.last_peer)
|
||||
self.set_closest_peer(last_session.last_peer, last_session.peers)
|
||||
|
||||
def is_normal(self):
|
||||
return self._state.get('face') not in (
|
||||
@ -197,7 +202,7 @@ class View(object):
|
||||
self.set('status', self._voice.on_normal())
|
||||
self.update()
|
||||
|
||||
def set_closest_peer(self, peer):
|
||||
def set_closest_peer(self, peer, num_total):
|
||||
if peer is None:
|
||||
self.set('friend_face', None)
|
||||
self.set('friend_name', None)
|
||||
@ -216,6 +221,12 @@ class View(object):
|
||||
name += '│' * (4 - num_bars)
|
||||
name += ' %s %d (%d)' % (peer.name(), peer.pwnd_run(), peer.pwnd_total())
|
||||
|
||||
if num_total > 1:
|
||||
if num_total > 9000:
|
||||
name += ' of over 9000'
|
||||
else:
|
||||
name += ' of %d' % num_total
|
||||
|
||||
self.set('friend_face', peer.face())
|
||||
self.set('friend_name', name)
|
||||
self.update()
|
||||
@ -264,6 +275,12 @@ class View(object):
|
||||
|
||||
self.on_normal()
|
||||
|
||||
def on_shutdown(self):
|
||||
self.set('face', faces.SLEEP)
|
||||
self.set('status', self._voice.on_shutdown())
|
||||
self.update(force=True)
|
||||
self._frozen = True
|
||||
|
||||
def on_bored(self):
|
||||
self.set('face', faces.BORED)
|
||||
self.set('status', self._voice.on_bored())
|
||||
@ -326,6 +343,9 @@ class View(object):
|
||||
|
||||
def update(self, force=False):
|
||||
with self._lock:
|
||||
if self._frozen:
|
||||
return
|
||||
|
||||
changes = self._state.changes(ignore=self._ignore_changes)
|
||||
if force or len(changes):
|
||||
self._canvas = Image.new('1', (self._width, self._height), WHITE)
|
246
pwnagotchi/utils.py
Normal file
246
pwnagotchi/utils.py
Normal file
@ -0,0 +1,246 @@
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
import logging
|
||||
import glob
|
||||
import os
|
||||
import time
|
||||
import subprocess
|
||||
import yaml
|
||||
import json
|
||||
import shutil
|
||||
|
||||
import pwnagotchi
|
||||
|
||||
|
||||
# https://stackoverflow.com/questions/823196/yaml-merge-in-python
|
||||
def merge_config(user, default):
|
||||
if isinstance(user, dict) and isinstance(default, dict):
|
||||
for k, v in default.items():
|
||||
if k not in user:
|
||||
user[k] = v
|
||||
else:
|
||||
user[k] = merge_config(user[k], v)
|
||||
return user
|
||||
|
||||
|
||||
def load_config(args):
|
||||
default_config_path = os.path.dirname(args.config)
|
||||
if not os.path.exists(default_config_path):
|
||||
os.makedirs(default_config_path)
|
||||
|
||||
ref_defaults_file = os.path.join(os.path.dirname(pwnagotchi.__file__), 'defaults.yml')
|
||||
ref_defaults_data = None
|
||||
|
||||
if not os.path.exists(args.config):
|
||||
# logging not configured here yet
|
||||
print("copying %s to %s ..." % (ref_defaults_file, args.config))
|
||||
shutil.copy(ref_defaults_file, args.config)
|
||||
else:
|
||||
with open(ref_defaults_file) as fp:
|
||||
ref_defaults_data = fp.read()
|
||||
|
||||
with open(args.config) as fp:
|
||||
defaults_data = fp.read()
|
||||
|
||||
if ref_defaults_data != defaults_data:
|
||||
print("!!! file in %s is different than release defaults, overwriting !!!" % args.config)
|
||||
shutil.copy(ref_defaults_file, args.config)
|
||||
|
||||
with open(args.config) as fp:
|
||||
config = yaml.safe_load(fp)
|
||||
|
||||
if os.path.exists(args.user_config):
|
||||
with open(args.user_config) as fp:
|
||||
user_config = yaml.safe_load(fp)
|
||||
# if the file is empty, safe_load will return None and merge_config will boom.
|
||||
if user_config:
|
||||
config = merge_config(user_config, config)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def setup_logging(args, config):
|
||||
formatter = logging.Formatter("[%(asctime)s] [%(levelname)s] %(message)s")
|
||||
root = logging.getLogger()
|
||||
|
||||
root.setLevel(logging.DEBUG if args.debug else logging.INFO)
|
||||
|
||||
if config['main']['log']:
|
||||
file_handler = logging.FileHandler(config['main']['log'])
|
||||
file_handler.setFormatter(formatter)
|
||||
root.addHandler(file_handler)
|
||||
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setFormatter(formatter)
|
||||
root.addHandler(console_handler)
|
||||
|
||||
|
||||
def secs_to_hhmmss(secs):
|
||||
mins, secs = divmod(secs, 60)
|
||||
hours, mins = divmod(mins, 60)
|
||||
return '%02d:%02d:%02d' % (hours, mins, secs)
|
||||
|
||||
|
||||
def total_unique_handshakes(path):
|
||||
expr = os.path.join(path, "*.pcap")
|
||||
return len(glob.glob(expr))
|
||||
|
||||
|
||||
def iface_channels(ifname):
|
||||
channels = []
|
||||
output = subprocess.getoutput("/sbin/iwlist %s freq" % ifname)
|
||||
for line in output.split("\n"):
|
||||
line = line.strip()
|
||||
if line.startswith("Channel "):
|
||||
channels.append(int(line.split()[1]))
|
||||
return channels
|
||||
|
||||
|
||||
def led(on=True):
|
||||
with open('/sys/class/leds/led0/brightness', 'w+t') as fp:
|
||||
fp.write("%d" % (0 if on is True else 1))
|
||||
|
||||
|
||||
def blink(times=1, delay=0.3):
|
||||
for t in range(0, times):
|
||||
led(True)
|
||||
time.sleep(delay)
|
||||
led(False)
|
||||
time.sleep(delay)
|
||||
led(True)
|
||||
|
||||
class WifiInfo(Enum):
|
||||
"""
|
||||
Fields you can extract from a pcap file
|
||||
"""
|
||||
BSSID = 0
|
||||
ESSID = 1
|
||||
ENCRYPTION = 2
|
||||
CHANNEL = 3
|
||||
RSSI = 4
|
||||
|
||||
class FieldNotFoundError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def extract_from_pcap(path, fields):
|
||||
"""
|
||||
Search in pcap-file for specified information
|
||||
|
||||
path: Path to pcap file
|
||||
fields: Array of fields that should be extracted
|
||||
|
||||
If a field is not found, FieldNotFoundError is raised
|
||||
"""
|
||||
results = dict()
|
||||
for field in fields:
|
||||
if not isinstance(field, WifiInfo):
|
||||
raise TypeError("Invalid field")
|
||||
|
||||
subtypes = set()
|
||||
|
||||
if field == WifiInfo.BSSID:
|
||||
from scapy.all import Dot11Beacon, Dot11ProbeResp, Dot11AssoReq, Dot11ReassoReq, Dot11, sniff
|
||||
subtypes.add('beacon')
|
||||
bpf_filter = " or ".join([f"wlan type mgt subtype {subtype}" for subtype in subtypes])
|
||||
packets = sniff(offline=path, filter=bpf_filter)
|
||||
try:
|
||||
for packet in packets:
|
||||
if packet.haslayer(Dot11Beacon):
|
||||
if hasattr(packet[Dot11], 'addr3'):
|
||||
results[field] = packet[Dot11].addr3
|
||||
break
|
||||
else: # magic
|
||||
raise FieldNotFoundError("Could not find field [BSSID]")
|
||||
except Exception:
|
||||
raise FieldNotFoundError("Could not find field [BSSID]")
|
||||
elif field == WifiInfo.ESSID:
|
||||
from scapy.all import Dot11Beacon, Dot11ReassoReq, Dot11AssoReq, Dot11, sniff, Dot11Elt
|
||||
subtypes.add('beacon')
|
||||
subtypes.add('assoc-req')
|
||||
subtypes.add('reassoc-req')
|
||||
bpf_filter = " or ".join([f"wlan type mgt subtype {subtype}" for subtype in subtypes])
|
||||
packets = sniff(offline=path, filter=bpf_filter)
|
||||
try:
|
||||
for packet in packets:
|
||||
if packet.haslayer(Dot11Elt) and hasattr(packet[Dot11Elt], 'info'):
|
||||
results[field] = packet[Dot11Elt].info.decode('utf-8')
|
||||
break
|
||||
else: # magic
|
||||
raise FieldNotFoundError("Could not find field [ESSID]")
|
||||
except Exception:
|
||||
raise FieldNotFoundError("Could not find field [ESSID]")
|
||||
elif field == WifiInfo.ENCRYPTION:
|
||||
from scapy.all import Dot11Beacon, sniff
|
||||
subtypes.add('beacon')
|
||||
bpf_filter = " or ".join([f"wlan type mgt subtype {subtype}" for subtype in subtypes])
|
||||
packets = sniff(offline=path, filter=bpf_filter)
|
||||
try:
|
||||
for packet in packets:
|
||||
if packet.haslayer(Dot11Beacon) and hasattr(packet[Dot11Beacon], 'network_stats'):
|
||||
stats = packet[Dot11Beacon].network_stats()
|
||||
if 'crypto' in stats:
|
||||
results[field] = stats['crypto'] # set with encryption types
|
||||
break
|
||||
else: # magic
|
||||
raise FieldNotFoundError("Could not find field [ENCRYPTION]")
|
||||
except Exception:
|
||||
raise FieldNotFoundError("Could not find field [ENCRYPTION]")
|
||||
elif field == WifiInfo.CHANNEL:
|
||||
from scapy.all import sniff, RadioTap
|
||||
from pwnagotchi.mesh.wifi import freq_to_channel
|
||||
packets = sniff(offline=path, count=1)
|
||||
try:
|
||||
results[field] = freq_to_channel(packets[0][RadioTap].ChannelFrequency)
|
||||
except Exception:
|
||||
raise FieldNotFoundError("Could not find field [CHANNEL]")
|
||||
elif field == WifiInfo.RSSI:
|
||||
from scapy.all import sniff, RadioTap
|
||||
from pwnagotchi.mesh.wifi import freq_to_channel
|
||||
packets = sniff(offline=path, count=1)
|
||||
try:
|
||||
results[field] = packets[0][RadioTap].dBm_AntSignal
|
||||
except Exception:
|
||||
raise FieldNotFoundError("Could not find field [RSSI]")
|
||||
|
||||
return results
|
||||
|
||||
|
||||
class StatusFile(object):
|
||||
def __init__(self, path, data_format='raw'):
|
||||
self._path = path
|
||||
self._updated = None
|
||||
self._format = data_format
|
||||
self.data = None
|
||||
|
||||
if os.path.exists(path):
|
||||
self._updated = datetime.fromtimestamp(os.path.getmtime(path))
|
||||
with open(path) as fp:
|
||||
if data_format == 'json':
|
||||
self.data = json.load(fp)
|
||||
else:
|
||||
self.data = fp.read()
|
||||
|
||||
def data_field_or(self, name, default=""):
|
||||
if self.data is not None and name in self.data:
|
||||
return self.data[name]
|
||||
return default
|
||||
|
||||
def newer_then_minutes(self, minutes):
|
||||
return self._updated is not None and ((datetime.now() - self._updated).seconds / 60) < minutes
|
||||
|
||||
def newer_then_days(self, days):
|
||||
return self._updated is not None and (datetime.now() - self._updated).days < days
|
||||
|
||||
def update(self, data=None):
|
||||
self._updated = datetime.now()
|
||||
self.data = data
|
||||
with open(self._path, 'w') as fp:
|
||||
if data is None:
|
||||
fp.write(str(self._updated))
|
||||
|
||||
elif self._format == 'json':
|
||||
json.dump(self.data, fp)
|
||||
|
||||
else:
|
||||
fp.write(data)
|
@ -90,6 +90,11 @@ class Voice:
|
||||
self._('Zzzzz'),
|
||||
self._('ZzzZzzz ({secs}s)').format(secs=secs)])
|
||||
|
||||
def on_shutdown(self):
|
||||
return random.choice([
|
||||
self._('Good night.'),
|
||||
self._('Zzz')])
|
||||
|
||||
def on_awakening(self):
|
||||
return random.choice(['...', '!'])
|
||||
|
||||
@ -120,23 +125,23 @@ class Voice:
|
||||
def on_rebooting(self):
|
||||
return self._("Ops, something went wrong ... Rebooting ...")
|
||||
|
||||
def on_log(self, log):
|
||||
status = self._('Kicked {num} stations\n').format(num=log.deauthed)
|
||||
status += self._('Made {num} new friends\n').format(num=log.associated)
|
||||
status += self._('Got {num} handshakes\n').format(num=log.handshakes)
|
||||
if log.peers == 1:
|
||||
def on_last_session_data(self, last_session):
|
||||
status = self._('Kicked {num} stations\n').format(num=last_session.deauthed)
|
||||
status += self._('Made {num} new friends\n').format(num=last_session.associated)
|
||||
status += self._('Got {num} handshakes\n').format(num=last_session.handshakes)
|
||||
if last_session.peers == 1:
|
||||
status += self._('Met 1 peer')
|
||||
elif log.peers > 0:
|
||||
status += self._('Met {num} peers').format(num=log.peers)
|
||||
elif last_session.peers > 0:
|
||||
status += self._('Met {num} peers').format(num=last_session.peers)
|
||||
return status
|
||||
|
||||
def on_log_tweet(self, log):
|
||||
def on_last_session_tweet(self, last_session):
|
||||
return self._(
|
||||
'I\'ve been pwning for {duration} and kicked {deauthed} clients! I\'ve also met {associated} new friends and ate {handshakes} handshakes! #pwnagotchi #pwnlog #pwnlife #hacktheplanet #skynet').format(
|
||||
duration=log.duration_human,
|
||||
deauthed=log.deauthed,
|
||||
associated=log.associated,
|
||||
handshakes=log.handshakes)
|
||||
duration=last_session.duration_human,
|
||||
deauthed=last_session.deauthed,
|
||||
associated=last_session.associated,
|
||||
handshakes=last_session.handshakes)
|
||||
|
||||
def hhmmss(self, count, fmt):
|
||||
if count > 1:
|
15
requirements.txt
Normal file
15
requirements.txt
Normal file
@ -0,0 +1,15 @@
|
||||
crypto==1.4.1
|
||||
requests==2.21.0
|
||||
PyYAML==5.1
|
||||
scapy==2.4.3
|
||||
gym==0.14.0
|
||||
stable-baselines==2.7.0
|
||||
tensorflow==1.13.1
|
||||
tensorflow-estimator==1.14.0
|
||||
tweepy==3.6.0
|
||||
file-read-backwards==2.0.0
|
||||
numpy==1.17.2
|
||||
inky==0.0.5
|
||||
smbus2==0.3.0
|
||||
Pillow==5.4.1
|
||||
spidev==3.4
|
@ -10,9 +10,8 @@ TEMP_BACKUP_FOLDER=/tmp/pwnagotchi_backup
|
||||
FILES_TO_BACKUP=(
|
||||
/root/brain.nn
|
||||
/root/brain.json
|
||||
/root/custom.yaml
|
||||
/root/handshakes
|
||||
/etc/ssh
|
||||
/etc/pwnagotchi/
|
||||
/etc/hostname
|
||||
/etc/hosts
|
||||
/etc/motd
|
||||
|
@ -94,9 +94,8 @@ function provide_raspbian() {
|
||||
function setup_raspbian(){
|
||||
# Detect the ability to create sparse files
|
||||
if [ "${OPT_SPARSE}" -eq 0 ]; then
|
||||
if [ which bmaptool -eq 0 ]; then
|
||||
if ! type "bmaptool" >/dev/null 2>&1; then
|
||||
echo "[!] bmaptool not available, not creating a sparse image"
|
||||
|
||||
else
|
||||
echo "[+] Defaulting to sparse image generation as bmaptool is available"
|
||||
OPT_SPARSE=1
|
||||
|
@ -6,8 +6,8 @@ DEPENDENCIES=( 'xgettext' 'msgfmt' 'msgmerge' )
|
||||
COMMANDS=( 'add' 'update' 'delete' 'compile' )
|
||||
|
||||
REPO_DIR="$(dirname "$(dirname "$(realpath "$0")")")"
|
||||
LOCALE_DIR="${REPO_DIR}/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/locale"
|
||||
VOICE_FILE="${REPO_DIR}/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/voice.py"
|
||||
LOCALE_DIR="${REPO_DIR}/pwnagotchi/locale"
|
||||
VOICE_FILE="${REPO_DIR}/pwnagotchi/voice.py"
|
||||
|
||||
function usage() {
|
||||
cat <<EOF
|
||||
|
@ -1,99 +1,99 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import argparse
|
||||
from http.server import HTTPServer
|
||||
import shutil
|
||||
import logging
|
||||
import yaml
|
||||
|
||||
sys.path.insert(0,
|
||||
os.path.join(os.path.dirname(os.path.realpath(__file__)),
|
||||
'../sdcard/rootfs/root/pwnagotchi/scripts/'))
|
||||
'../'))
|
||||
|
||||
import pwnagotchi.ui.faces as faces
|
||||
from pwnagotchi.ui.display import Display, VideoHandler
|
||||
from PIL import Image
|
||||
|
||||
|
||||
class CustomDisplay(Display):
|
||||
|
||||
def __init__(self, config, state):
|
||||
self.last_image = None
|
||||
super(CustomDisplay, self).__init__(config, state)
|
||||
|
||||
def _http_serve(self):
|
||||
if self._video_address is not None:
|
||||
self._httpd = HTTPServer((self._video_address, self._video_port),
|
||||
CustomVideoHandler)
|
||||
logging.info("ui available at http://%s:%d/" % (self._video_address,
|
||||
self._video_port))
|
||||
self._httpd.serve_forever()
|
||||
else:
|
||||
logging.info("could not get ip of usb0, video server not starting")
|
||||
# do nothing
|
||||
pass
|
||||
|
||||
def _on_view_rendered(self, img):
|
||||
CustomVideoHandler.render(img)
|
||||
self.last_image = img
|
||||
|
||||
if self._enabled:
|
||||
self.canvas = (img if self._rotation == 0 else img.rotate(self._rotation))
|
||||
if self._render_cb is not None:
|
||||
self._render_cb()
|
||||
|
||||
|
||||
class CustomVideoHandler(VideoHandler):
|
||||
|
||||
@staticmethod
|
||||
def render(img):
|
||||
with CustomVideoHandler._lock:
|
||||
try:
|
||||
img.save("/tmp/pwnagotchi-{rand}.png".format(rand=id(CustomVideoHandler)), format='PNG')
|
||||
except BaseException:
|
||||
logging.exception("could not write preview")
|
||||
|
||||
def do_GET(self):
|
||||
if self.path == '/':
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'text/html')
|
||||
self.end_headers()
|
||||
try:
|
||||
self.wfile.write(
|
||||
bytes(
|
||||
self._index %
|
||||
('localhost', 1000), "utf8"))
|
||||
except BaseException:
|
||||
pass
|
||||
|
||||
elif self.path.startswith('/ui'):
|
||||
with self._lock:
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'image/png')
|
||||
self.end_headers()
|
||||
try:
|
||||
with open("/tmp/pwnagotchi-{rand}.png".format(rand=id(CustomVideoHandler)), 'rb') as fp:
|
||||
shutil.copyfileobj(fp, self.wfile)
|
||||
except BaseException:
|
||||
logging.exception("could not open preview")
|
||||
else:
|
||||
self.send_response(404)
|
||||
def get_image(self):
|
||||
"""
|
||||
Return the saved image
|
||||
"""
|
||||
return self.last_image
|
||||
|
||||
|
||||
class DummyPeer:
|
||||
|
||||
def __init__(self):
|
||||
self.rssi = -50
|
||||
|
||||
@staticmethod
|
||||
def name():
|
||||
return "beta"
|
||||
|
||||
@staticmethod
|
||||
def pwnd_run():
|
||||
return 50
|
||||
|
||||
@staticmethod
|
||||
def pwnd_total():
|
||||
return 100
|
||||
|
||||
@staticmethod
|
||||
def face():
|
||||
return faces.FRIEND
|
||||
|
||||
|
||||
def append_images(images, horizontal=True, xmargin=0, ymargin=0):
|
||||
w, h = zip(*(i.size for i in images))
|
||||
|
||||
if horizontal:
|
||||
t_w = sum(w)
|
||||
t_h = max(h)
|
||||
else:
|
||||
t_w = max(w)
|
||||
t_h = sum(h)
|
||||
|
||||
result = Image.new('RGB', (t_w, t_h))
|
||||
|
||||
x_offset = 0
|
||||
y_offset = 0
|
||||
|
||||
for im in images:
|
||||
result.paste(im, (x_offset, y_offset))
|
||||
if horizontal:
|
||||
x_offset += im.size[0] + xmargin
|
||||
else:
|
||||
y_offset += im.size[1] + ymargin
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="This program emulates\
|
||||
the pwnagotchi display")
|
||||
parser.add_argument('--display', help="Which display to use.",
|
||||
parser.add_argument('--displays', help="Which displays to use.", nargs="+",
|
||||
default="waveshare_2")
|
||||
parser.add_argument('--port', help="Which port to use",
|
||||
default=8080)
|
||||
parser.add_argument('--sleep', type=int, help="Time between emotions",
|
||||
default=2)
|
||||
parser.add_argument('--lang', help="Language to use",
|
||||
default="en")
|
||||
parser.add_argument('--output', help="Path to output image (PNG)", default="preview.png")
|
||||
parser.add_argument('--show-peer', dest="showpeer", help="This options will show a dummy peer", action="store_true")
|
||||
parser.add_argument('--xmargin', help="Add X-Margin", type=int, default=5)
|
||||
parser.add_argument('--ymargin', help="Add Y-Margin", type=int, default=5)
|
||||
args = parser.parse_args()
|
||||
|
||||
CONFIG = yaml.load('''
|
||||
config_template = '''
|
||||
main:
|
||||
lang: {lang}
|
||||
ui:
|
||||
@ -107,64 +107,80 @@ def main():
|
||||
video:
|
||||
enabled: true
|
||||
address: "0.0.0.0"
|
||||
port: {port}
|
||||
'''.format(display=args.display,
|
||||
port=args.port,
|
||||
lang=args.lang))
|
||||
port: 8080
|
||||
'''
|
||||
|
||||
DISPLAY = CustomDisplay(config=CONFIG, state={'name': '%s>' % 'preview'})
|
||||
list_of_displays = list()
|
||||
for display_type in args.displays:
|
||||
config = yaml.safe_load(config_template.format(display=display_type,
|
||||
lang=args.lang))
|
||||
display = CustomDisplay(config=config, state={'name': f"{display_type}>"})
|
||||
list_of_displays.append(display)
|
||||
|
||||
while True:
|
||||
DISPLAY.on_starting()
|
||||
DISPLAY.update()
|
||||
time.sleep(args.sleep)
|
||||
DISPLAY.on_ai_ready()
|
||||
DISPLAY.update()
|
||||
time.sleep(args.sleep)
|
||||
DISPLAY.on_normal()
|
||||
DISPLAY.update()
|
||||
time.sleep(args.sleep)
|
||||
DISPLAY.on_new_peer(DummyPeer())
|
||||
DISPLAY.update()
|
||||
time.sleep(args.sleep)
|
||||
DISPLAY.on_lost_peer(DummyPeer())
|
||||
DISPLAY.update()
|
||||
time.sleep(args.sleep)
|
||||
DISPLAY.on_free_channel('6')
|
||||
DISPLAY.update()
|
||||
time.sleep(args.sleep)
|
||||
DISPLAY.wait(args.sleep)
|
||||
DISPLAY.update()
|
||||
DISPLAY.on_bored()
|
||||
DISPLAY.update()
|
||||
time.sleep(args.sleep)
|
||||
DISPLAY.on_sad()
|
||||
DISPLAY.update()
|
||||
time.sleep(args.sleep)
|
||||
DISPLAY.on_motivated(1)
|
||||
DISPLAY.update()
|
||||
time.sleep(args.sleep)
|
||||
DISPLAY.on_demotivated(-1)
|
||||
DISPLAY.update()
|
||||
time.sleep(args.sleep)
|
||||
DISPLAY.on_excited()
|
||||
DISPLAY.update()
|
||||
time.sleep(args.sleep)
|
||||
DISPLAY.on_deauth({'mac': 'DE:AD:BE:EF:CA:FE'})
|
||||
DISPLAY.update()
|
||||
time.sleep(args.sleep)
|
||||
DISPLAY.on_miss('test')
|
||||
DISPLAY.update()
|
||||
time.sleep(args.sleep)
|
||||
DISPLAY.on_lonely()
|
||||
DISPLAY.update()
|
||||
time.sleep(args.sleep)
|
||||
DISPLAY.on_handshakes(1)
|
||||
DISPLAY.update()
|
||||
time.sleep(args.sleep)
|
||||
DISPLAY.on_rebooting()
|
||||
DISPLAY.update()
|
||||
time.sleep(args.sleep)
|
||||
columns = list()
|
||||
|
||||
for display in list_of_displays:
|
||||
emotions = list()
|
||||
if args.showpeer:
|
||||
display.set_closest_peer(DummyPeer(), 10)
|
||||
display.on_starting()
|
||||
display.update()
|
||||
emotions.append(display.get_image())
|
||||
display.on_ai_ready()
|
||||
display.update()
|
||||
emotions.append(display.get_image())
|
||||
display.on_normal()
|
||||
display.update()
|
||||
emotions.append(display.get_image())
|
||||
display.on_new_peer(DummyPeer())
|
||||
display.update()
|
||||
emotions.append(display.get_image())
|
||||
display.on_lost_peer(DummyPeer())
|
||||
display.update()
|
||||
emotions.append(display.get_image())
|
||||
display.on_free_channel('6')
|
||||
display.update()
|
||||
emotions.append(display.get_image())
|
||||
display.wait(2)
|
||||
display.update()
|
||||
emotions.append(display.get_image())
|
||||
display.on_bored()
|
||||
display.update()
|
||||
emotions.append(display.get_image())
|
||||
display.on_sad()
|
||||
display.update()
|
||||
emotions.append(display.get_image())
|
||||
display.on_motivated(1)
|
||||
display.update()
|
||||
emotions.append(display.get_image())
|
||||
display.on_demotivated(-1)
|
||||
display.update()
|
||||
emotions.append(display.get_image())
|
||||
display.on_excited()
|
||||
display.update()
|
||||
emotions.append(display.get_image())
|
||||
display.on_deauth({'mac': 'DE:AD:BE:EF:CA:FE'})
|
||||
display.update()
|
||||
emotions.append(display.get_image())
|
||||
display.on_miss('test')
|
||||
display.update()
|
||||
emotions.append(display.get_image())
|
||||
display.on_lonely()
|
||||
display.update()
|
||||
emotions.append(display.get_image())
|
||||
display.on_handshakes(1)
|
||||
display.update()
|
||||
emotions.append(display.get_image())
|
||||
display.on_rebooting()
|
||||
display.update()
|
||||
emotions.append(display.get_image())
|
||||
|
||||
# append them all together (vertical)
|
||||
columns.append(append_images(emotions, horizontal=False, xmargin=args.xmargin, ymargin=args.ymargin))
|
||||
|
||||
# append columns side by side
|
||||
final_image = append_images(columns, horizontal=True, xmargin=args.xmargin, ymargin=args.ymargin)
|
||||
final_image.save(args.output, 'PNG')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
6
scripts/pypi_upload.sh
Executable file
6
scripts/pypi_upload.sh
Executable file
@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
rm -rf build dist pwnagotchi.egg-info &&
|
||||
python3 setup.py sdist bdist_wheel &&
|
||||
clear &&
|
||||
twine upload dist/*
|
@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
# nothing to see here, just a utility i use to create new releases ^_^
|
||||
|
||||
VERSION_FILE=$(dirname "${BASH_SOURCE[0]}")/../sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/__init__.py
|
||||
VERSION_FILE=$(dirname "${BASH_SOURCE[0]}")/../pwnagotchi/__init__.py
|
||||
echo "version file is $VERSION_FILE"
|
||||
CURRENT_VERSION=$(cat $VERSION_FILE | grep version | cut -d"'" -f2)
|
||||
TO_UPDATE=(
|
||||
|
@ -1,121 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Default variables
|
||||
GIT_FOLDER="/tmp/pwnagotchi"
|
||||
GIT_URL="https://github.com/evilsocket/pwnagotchi/"
|
||||
VERSION="master"
|
||||
SUPPORTED_RESTART_MODES=( 'auto' 'manual' )
|
||||
MODE="auto"
|
||||
BACKUPCONFIG=0
|
||||
RESTORECONFIG=0
|
||||
|
||||
# Functions
|
||||
function usage() {
|
||||
cat <<EOF
|
||||
|
||||
usage: $0 [OPTIONS]
|
||||
Note: This should be run from the pwnagotchi itself!
|
||||
|
||||
Options:
|
||||
-v # Version to update to, can be a branch or commit. (default: master)
|
||||
-u # Url to clone from. (default: https://github.com/evilsocket/pwnagotchi)
|
||||
-m # Mode to restart to. (Supported: ${SUPPORTED_RESTART_MODES[*]}; default: auto)
|
||||
-b # Backup the current pwnagotchi config and hostname references, then overwrite with defaults.
|
||||
-r # Restore the current pwnagotchi config and hostname references after upgrade. (-b will be enabled.)
|
||||
-h # Shows this help.
|
||||
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
function test_root() {
|
||||
if ! [ $(id -u) = 0 ]; then
|
||||
echo "[!] This script must be run as root."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
function test_github() {
|
||||
wget -q --spider $GIT_URL
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "[!] Cannot reach github. This script requires internet access, ensure connection sharing is working."
|
||||
exit 2
|
||||
fi
|
||||
}
|
||||
|
||||
while getopts "v:u:m:brh" o; do
|
||||
case "${o}" in
|
||||
v)
|
||||
VERSION="${OPTARG}"
|
||||
;;
|
||||
u)
|
||||
GIT_URL="${OPTARG}"
|
||||
;;
|
||||
m)
|
||||
if [[ "${SUPPORTED_RESTART_MODES[*]}" =~ ${OPTARG} ]]; then
|
||||
MODE="${OPTARG}"
|
||||
else
|
||||
usage
|
||||
fi
|
||||
;;
|
||||
b)
|
||||
BACKUPCONFIG=1
|
||||
;;
|
||||
r)
|
||||
BACKUPCONFIG=1
|
||||
RESTORECONFIG=1
|
||||
;;
|
||||
h)
|
||||
usage
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
done
|
||||
shift $((OPTIND-1))
|
||||
|
||||
echo "[+] Checking prerequisites."
|
||||
test_root
|
||||
test_github
|
||||
|
||||
# clean up old files, clone master, set checkout to commit if needed.
|
||||
echo "[+] Cloning to $GIT_FOLDER..."
|
||||
rm $GIT_FOLDER -rf
|
||||
git clone $GIT_URL $GIT_FOLDER -q
|
||||
cd $GIT_FOLDER
|
||||
if [ $VERSION != "master" ]; then
|
||||
git checkout $VERSION -q
|
||||
fi
|
||||
|
||||
if [ $BACKUPCONFIG -eq 1 ]; then
|
||||
echo "[+] Creating backup of config.yml and hostname references"
|
||||
mv /root/pwnagotchi/config.yml /root/config.yml.bak -f
|
||||
mv /etc/hosts /root/hosts.bak -f
|
||||
mv /etc/hostname /root/hostname.bak -f
|
||||
mv /etc/motd /etc/motd.bak -f
|
||||
mv /etc/network/interfaces /root/interfaces.bak -f
|
||||
fi
|
||||
|
||||
echo "[+] Installing $(git log -1 --format="%h")"
|
||||
rm /root/pwnagotchi -rf # ensures old files are removed
|
||||
rsync -aPq $GIT_FOLDER/sdcard/boot/* /boot/
|
||||
rsync -aPq $GIT_FOLDER/sdcard/rootfs/* /
|
||||
cd /tmp
|
||||
rm $GIT_FOLDER -rf
|
||||
|
||||
if [ $RESTORECONFIG -eq 1 ]; then
|
||||
echo "[+] Restoring backup of config.yml and hostname references"
|
||||
mv /root/config.yml.bak /root/pwnagotchi/config.yml -f
|
||||
mv /root/hosts.bak /etc/hosts -f
|
||||
mv /root/hostname.bak /etc/hostname -f
|
||||
mv /root/interfaces.bak /etc/network/interfaces -f
|
||||
mv /etc/motd.bak /etc/motd -f
|
||||
fi
|
||||
|
||||
echo "[+] Restarting pwnagotchi in $MODE mode. $( screen -X -S pwnagotchi quit)"
|
||||
if [ $MODE == "auto" ]; then
|
||||
sudo -H -u root /usr/bin/screen -dmS pwnagotchi -c /root/pwnagotchi/data/screenrc.auto
|
||||
elif [ $MODE == "manual" ]; then
|
||||
sudo -H -u root /usr/bin/screen -dmS pwnagotchi -c /root/pwnagotchi/data/screenrc.manual
|
||||
fi
|
||||
echo "[+] Finished"
|
@ -5,7 +5,7 @@
|
||||
.DESCRIPTION
|
||||
A script that setups Internet Connection Sharing for Pwnagotchi.
|
||||
|
||||
Note: Internet Connection Sharing on Windows can be a bit unstable on windows between reboots.
|
||||
Note: Internet Connection Sharing on Windows can be a bit unstable on between reboots.
|
||||
You might need to run this script occasionally to disable and re-enable Internet Connection Sharing.
|
||||
|
||||
.PARAMETER EnableInternetConnectionSharing
|
||||
@ -15,7 +15,7 @@
|
||||
Disable Internet Connection Sharing
|
||||
|
||||
.PARAMETER SetPwnagotchiSubnet
|
||||
Change the Internet Connection Sharing subnet to the Pwnagotchi. Defaults to 10.0.0.1.
|
||||
Change the Internet Connection Sharing subnet to the Pwnagotchi subnet. The USB Gadget Interface IP will default to 10.0.0.1.
|
||||
|
||||
.PARAMETER ScopeAddress
|
||||
Custom ScopeAddress (The IP Address of the USB Gadget Interface.)
|
||||
@ -55,14 +55,12 @@ Function Create-HNetObjects {
|
||||
|
||||
.DESCRIPTION
|
||||
A helper function that does the heavy lifiting with NetCfg.HNetShare. This returns a PSObject containing the `INetSharingConfigurationForINetConnection` info of 2 Adapters.
|
||||
By default it tries to get the correct interfaces. This method might not be foolproof for every setup, but should work in most default senarios, if this causes issues these
|
||||
could be passed as a param, they would need to be implemented in Setup-PwnagotchiNetwork and the Param block of this file.
|
||||
|
||||
.PARAMETER InternetAdaptor
|
||||
The output of Get-NetAdaptor filtered down to the 'main' uplink interface. Should default to the correct value.
|
||||
The output of Get-NetAdaptor filtered down to the 'main' uplink interface.
|
||||
|
||||
.PARAMETER RNDISGadget
|
||||
The output of Get-NetAdaptor filtered down to the 'USB Ethernet/RNDIS Gadget' interface. Should default to the correct value.
|
||||
The output of Get-NetAdaptor filtered down to the 'USB Ethernet/RNDIS Gadget' interface.
|
||||
|
||||
.EXAMPLE
|
||||
PS> $HNetObject = Create-HNetObjects
|
||||
@ -73,8 +71,8 @@ Function Create-HNetObjects {
|
||||
#>
|
||||
[Cmdletbinding()]
|
||||
Param (
|
||||
$InternetAdaptor = $(Get-NetAdapter | Where-Object {$_.MediaConnectionState -eq 'Connected' -and $_.PhysicalMediaType -ne 'Unspecified'} | Sort-Object LinkSpeed -Descending),
|
||||
$RNDISGadget = $(Get-NetAdapter | Where-Object {$_.MediaConnectionState -eq 'Connected' -and $_.InterfaceDescription -eq "USB Ethernet/RNDIS Gadget"})
|
||||
$InternetAdaptor = $(Select-NetAdaptor -Message "Please select your main a ethernet adaptor with internet access that will be used for internet sharing."),
|
||||
$RNDISGadget = $(Select-NetAdaptor -Message "Please select your 'USB Ethernet/RNDIS Gadget' adaptor")
|
||||
)
|
||||
Begin {
|
||||
regsvr32.exe /s hnetcfg.dll
|
||||
@ -190,25 +188,25 @@ Function Set-PwnagotchiSubnet {
|
||||
Function Setup-PwnagotchiNetwork {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Function to setup networking.
|
||||
Function to setup networking.
|
||||
|
||||
.DESCRIPTION
|
||||
Function to setup networking. Main function calls helpers functions.
|
||||
Function to setup networking. Main function calls helpers functions.
|
||||
|
||||
.PARAMETER EnableInternetConnectionSharing
|
||||
Enable Internet Connection Sharing
|
||||
Enable Internet Connection Sharing
|
||||
|
||||
.PARAMETER DisableInternetConnectionSharing
|
||||
Disable Internet Connection Sharing
|
||||
Disable Internet Connection Sharing
|
||||
|
||||
.PARAMETER SetPwnagotchiSubnet
|
||||
Change the Internet Connection Sharing subnet to the Pwnagotchi. Defaults to 10.0.0.1.
|
||||
Change the Internet Connection Sharing subnet to the Pwnagotchi. Defaults to 10.0.0.1.
|
||||
|
||||
.PARAMETER ScopeAddress
|
||||
Custom ScopeAddress (the ICS ip address)
|
||||
Custom ScopeAddress (the ICS ip address)
|
||||
|
||||
.EXAMPLE
|
||||
PS> Setup-PwnagotchiNetwork -EnableInternetConnectionSharing
|
||||
PS> Setup-PwnagotchiNetwork -EnableInternetConnectionSharing
|
||||
|
||||
#>
|
||||
|
||||
@ -260,6 +258,33 @@ Function Setup-PwnagotchiNetwork {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Function Select-NetAdaptor {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
A menu function to select the correct network adaptors.
|
||||
|
||||
.DESCRIPTION
|
||||
A menu function to select the correct network adaptors.
|
||||
|
||||
.PARAMETER Message
|
||||
Message that will be displayed during the question.
|
||||
|
||||
#>
|
||||
|
||||
Param (
|
||||
$Message
|
||||
)
|
||||
$Adaptors = Get-NetAdapter | Where-Object {$_.MediaConnectionState -eq 'Connected'} | Sort-Object LinkSpeed -Descending
|
||||
do {
|
||||
Write-Host $Message
|
||||
$index = 1
|
||||
foreach ($Adaptor in $Adaptors) {
|
||||
Write-Host "[$index] $($Adaptor.Name), $($Adaptor.InterfaceDescription)"
|
||||
$index++
|
||||
}
|
||||
$Selection = Read-Host "Number"
|
||||
} until ($Adaptors[$selection-1])
|
||||
Return $Adaptors[$selection-1]
|
||||
}
|
||||
# Dynamically create params for Setup-PwnagotchiNetwork function based of param input of script.
|
||||
Setup-PwnagotchiNetwork @psBoundParameters
|
||||
Setup-PwnagotchiNetwork @psBoundParameters
|
||||
|
@ -1 +0,0 @@
|
||||
dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait modules-load=dwc2,g_ether
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user