112 Commits

Author SHA1 Message Date
Simone Margaritelli
9e92201d82 releasing v1.2.1 2019-11-05 19:11:37 +01:00
Simone Margaritelli
4e592df6d8 fix: fixed a bug which prevented rebooting 2019-11-05 19:07:16 +01:00
Simone Margaritelli
403ee242a6 releasing v1.2.0 2019-11-05 18:54:42 +01:00
Simone Margaritelli
106d72c4a2 reverted 5ddc2d7080 2019-11-05 18:51:01 +01:00
Simone Margaritelli
5ddc2d7080 fix: fixed issue with webui in landcape (fixes #523) 2019-11-05 18:30:09 +01:00
evilsocket
52f1111a5b Merge pull request #525 from dadav/fix/backup-loop
Add max_tries param
2019-11-05 18:25:44 +01:00
dadav
346773f790 Add max_tries param 2019-11-05 18:02:36 +01:00
Simone Margaritelli
9264b837c8 misc: small fix or general refactoring i did not bother commenting 2019-11-05 15:56:30 +01:00
Simone Margaritelli
81032fe5e3 misc: refactored webui into separate files instead of strings 2019-11-05 15:51:26 +01:00
Simone Margaritelli
2aa73d1a7e misc: small fix or general refactoring i did not bother commenting 2019-11-05 15:11:38 +01:00
Simone Margaritelli
b796384345 new: webui status pages reload /ui after a given interval 2019-11-05 15:04:58 +01:00
Simone Margaritelli
a6ca99c693 misc: small fix or general refactoring i did not bother commenting 2019-11-05 15:01:58 +01:00
Simone Margaritelli
60d9fd46ae misc: small fix or general refactoring i did not bother commenting 2019-11-05 14:57:22 +01:00
Simone Margaritelli
de71d18a72 misc: small fix or general refactoring i did not bother commenting 2019-11-05 14:52:54 +01:00
Simone Margaritelli
aba5b938bc misc: small fix or general refactoring i did not bother commenting 2019-11-05 14:48:26 +01:00
Simone Margaritelli
0830e0c74b misc: small fix or general refactoring i did not bother commenting 2019-11-05 14:37:42 +01:00
Simone Margaritelli
cf8a4da9e7 misc: small fix or general refactoring i did not bother commenting 2019-11-05 14:33:16 +01:00
Simone Margaritelli
80e2cdcd8d refact: using flask templating 2019-11-05 14:26:35 +01:00
Simone Margaritelli
a5cfb9aa8b misc: small fix or general refactoring i did not bother commenting 2019-11-05 14:03:56 +01:00
Simone Margaritelli
62a0cc6276 fix: suppress flask logging 2019-11-05 14:03:15 +01:00
Simone Margaritelli
f8523eb382 fix: added flask requirements 2019-11-05 13:55:35 +01:00
Simone Margaritelli
58b0b0fea0 fix: using try/finally to return the html and then call the action 2019-11-05 11:03:41 +01:00
evilsocket
bdf585afe5 Merge pull request #518 from dadav/fix/bt-tet-loaded-msg
More bt-tether fixes
2019-11-05 10:48:19 +01:00
evilsocket
b7d1c82788 Merge pull request #519 from dadav/feature/flask_migration
Migrate to flask
2019-11-05 10:47:51 +01:00
evilsocket
74838d6b96 Merge pull request #520 from gkrs/master
Updated polish language pack. New messages translated.
2019-11-05 10:47:08 +01:00
dadav
7fc46ddcf6 Add restart route 2019-11-04 18:43:33 +01:00
dadav
4503e71bfb Rebased 2019-11-04 18:38:12 +01:00
dadav
11fb95d299 Add CSRF support 2019-11-04 18:31:58 +01:00
dadav
0aaeeb8011 First step 2019-11-04 18:29:26 +01:00
dadav
3e1f3d5eec Add deps 2019-11-04 18:28:42 +01:00
dadav
86a3443b8d Handle exception if no Interface found 2019-11-04 17:55:16 +01:00
dadav
806efa1fc2 More consistency 2019-11-04 17:45:53 +01:00
dadav
537519dea6 Fix typo 2019-11-04 17:44:53 +01:00
Simone Margaritelli
0c1d98f2ab misc: small fix or general refactoring i did not bother commenting 2019-11-04 17:44:35 +01:00
dadav
4852b3f59e Make bt-tet lil bit more verbose 2019-11-04 17:41:27 +01:00
Simone Margaritelli
364af70ad5 new: button to restart in auto or manu mode 2019-11-04 17:35:06 +01:00
Simone Margaritelli
e336fca0de fix: fixed race condition (again) on override files 2019-11-04 17:33:35 +01:00
Simone Margaritelli
00e7c04980 misc: small fix or general refactoring i did not bother commenting 2019-11-04 14:30:22 +01:00
evilsocket
2549433e34 Merge pull request #517 from neutralinsomniac/fix_pixelated_css
fix: use a css class for the pixelated property, since apparently this still isn't standardized among different browsers
2019-11-04 14:24:55 +01:00
Jeremy O'Brien
9f9fca02e5 fix: use a css class for the pixelated property, since apparently this still isn't standardized among different browsers
Signed-off-by: Jeremy O'Brien <neutral@fastmail.com>
2019-11-04 08:19:13 -05:00
gkrs
6945e260bd Updated polish language pack. New messages translated.
Signed-off-by: gkrs <457603+gkrs@users.noreply.github.com>
2019-11-04 13:07:43 +01:00
evilsocket
c21986488d Merge pull request #514 from xenDE/master
add timestamp to net-pos plugin, https://github.com/evilsocket/pwnago…
2019-11-04 12:49:41 +01:00
Simone Margaritelli
b52ceae2ee misc: small fix or general refactoring i did not bother commenting 2019-11-04 11:44:24 +01:00
Simone Margaritelli
19fc25d508 new: reporting version on startup (closes #504) 2019-11-04 11:32:09 +01:00
Simone Margaritelli
ba22b7d5d7 misc: basic refactoring of #502 2019-11-04 11:13:51 +01:00
evilsocket
59019efad0 Merge pull request #512 from edmael/web_reboot
Added "Reboot into AUTO mode" button in web ui
2019-11-04 11:07:22 +01:00
evilsocket
20aa0d1909 Merge pull request #510 from dwi/patch-1
Unblurred image rendering in browsers
2019-11-04 11:07:10 +01:00
evilsocket
c56c6bb8f5 Merge pull request #513 from dadav/fix/bt-tet-silence
Make bt-tet less verbose
2019-11-04 11:05:18 +01:00
xenDE
1e426f7411 add timestamp to net-pos plugin, https://github.com/evilsocket/pwnagotchi/issues/308
added timestam as "ts" var in both .json files.
tested with old plugin format.
2019-11-04 00:16:50 +01:00
dadav
aeb6002e10 Make bt-tet less verbose 2019-11-03 23:25:55 +01:00
Edoardo Maria Elidoro
dc2362c371 Added "Reboot into AUTO mode" button in web ui
As requested in issue #502
2019-11-03 22:57:32 +01:00
dwi
d6c0ec0dfd Unblurred image rendering in browsers
Unblurred image rendering in browsers. Affects both desktop and mobile image rendering.

![Before and after](https://i.imgur.com/pffQREV.png)
2019-11-03 22:14:43 +01:00
evilsocket
04e551600d Merge pull request #471 from neutralinsomniac/ohc_whitelist
Add whitelist support to onlinehashcrack plugin
2019-11-03 16:54:44 +01:00
evilsocket
62983dfea5 Merge pull request #478 from opteeks/master
Adding 2.9inch Display support
2019-11-03 16:48:55 +01:00
evilsocket
b2c812d05d Merge pull request #501 from damoklov/master
Added Ukrainian language
2019-11-03 15:57:54 +01:00
evilsocket
7ba9b35d06 Merge pull request #507 from FrixosTh/patch-2
Bug Fix on AircrackOnly.py plugin
2019-11-03 15:57:38 +01:00
FrixosTh
b4b14ba9fd Bug Fix on AircrackOnly.py plugin
The code will always delete the pcap file unless there is a PMKID due to the structure of the if statements. Added a new variable handshakeFound that will allow the file not to be deleted if there is a handshake and also it will scan for PMKID only if no handshake is found... If no handshake is found and no PMKID, only then the file is marked as to be deleted
2019-11-03 02:31:13 +02:00
opteeks
1ba3a69651 Merge branch 'master' into master 2019-11-02 13:48:11 -07:00
damoklov
0aef199131 Added Ukrainian language
Signed-off-by: damoklov <mishanya@protonmail.com>
2019-11-02 16:47:13 +02:00
evilsocket
ace61836e5 Merge pull request #496 from dadav/fix/bt-tet-defaults
Fix bt-tether config
2019-11-02 12:13:08 +01:00
evilsocket
d04f124add Merge pull request #498 from LuckyFishGeek/ch
Add Chinese translation version 0.0.1
2019-11-02 12:12:50 +01:00
LuckyFish
bc1db7ceea Add files via upload 2019-11-02 09:42:54 +08:00
dadav
fd506b1533 Fix bt-tether config 2019-11-01 21:42:02 +01:00
evilsocket
6c44a687b1 Merge pull request #492 from dadav/feature/multi-device-tether
Add multi bt-tether support
2019-11-01 20:19:22 +01:00
dadav
a2bb66ad57 Fix case 2019-11-01 20:12:04 +01:00
Jeremy O'Brien
61af8b4762 Add whitelist support to onlinehashcrack plugin
Signed-off-by: Jeremy O'Brien <neutral@fastmail.com>
2019-11-01 15:08:05 -04:00
dadav
9b58fed862 Typo 2019-11-01 18:23:55 +01:00
dadav
53ae8ea1cf Add check if connected but no interface created 2019-11-01 18:13:38 +01:00
dadav
5b66d687c4 Add multi bt-tether support 2019-11-01 18:00:07 +01:00
Simone Margaritelli
4b74de48bf misc: small fix or general refactoring i did not bother commenting 2019-11-01 17:49:50 +01:00
Simone Margaritelli
22e76f956c fix: fixed memtemp for waveshare v2 2019-11-01 17:45:53 +01:00
Simone Margaritelli
4418492637 Merge branch 'master' of github.com:evilsocket/pwnagotchi 2019-11-01 15:53:39 +01:00
Simone Margaritelli
31a89cbe4b new: added new angry state (closes #486) 2019-11-01 15:53:31 +01:00
evilsocket
d91f49d596 Merge pull request #490 from dadav/fix/grid
Fix debug msg to fit new plugin class
2019-11-01 14:57:31 +01:00
dadav
66dc03ec05 Fix debug msg to fit new plugin class 2019-11-01 14:25:20 +01:00
Simone Margaritelli
2f948306eb misc: refactored plugin system to use classes 2019-11-01 13:51:45 +01:00
Simone Margaritelli
ae330dc0b5 fix: don't reset network interfaces configuration if not needed (closes #483) 2019-11-01 12:12:37 +01:00
evilsocket
e06f2a32e8 Merge pull request #487 from dadav/fix/fix-bt-disconnect-bug
Add reference to network object (bt-tether fix)
2019-11-01 10:45:33 +01:00
evilsocket
e184176ae4 Merge pull request #488 from dadav/feature/version-option
Add version option
2019-11-01 10:45:15 +01:00
dadav
1827ee564c Add version option 2019-11-01 10:04:36 +01:00
dadav
bfdaffa14b Add reference to network object 2019-11-01 09:20:06 +01:00
evilsocket
53f99f4c28 Merge pull request #485 from neutralinsomniac/backup_script_enhancements
enhancement: Improve the backup script
2019-10-31 19:06:32 +01:00
Jeremy O'Brien
3efa96b292 enhancement: Improve the backup script
- Significantly decrease time it takes to save a backup
- Remove host dependency on 'zip' binary
- Preserve file attributes on backed-up files
- Avoid copying files on the pi itself to /tmp

Signed-off-by: Jeremy O'Brien <neutral@fastmail.com>
2019-10-31 14:02:07 -04:00
Simone Margaritelli
8118a10a6a new: auto-update is now enabled by default 2019-10-31 18:24:43 +01:00
Simone Margaritelli
bd63f71a1d fix: +x to /usr/bin/* while creating the .img 2019-10-31 17:25:21 +01:00
Simone Margaritelli
31d401e03b fix: increased delay before shutting down to allow slower displays to update (closes #446) 2019-10-31 15:39:59 +01:00
Simone Margaritelli
3c32bbb582 releasing v1.1.1 2019-10-31 14:50:03 +01:00
Simone Margaritelli
03067da005 misc: small fix or general refactoring i did not bother commenting 2019-10-31 14:49:44 +01:00
Simone Margaritelli
64aad56fba releasing v[3~1.1.1 2019-10-31 14:47:51 +01:00
Simone Margaritelli
4240d05872 misc: small fix or general refactoring i did not bother commenting 2019-10-31 14:20:35 +01:00
evilsocket
f03e07f0cf Merge pull request #472 from neutralinsomniac/grid_handle_malformed_filenames
fix: don't attempt to parse/upload pcaps with malformed filenames
2019-10-31 14:17:00 +01:00
Simone Margaritelli
13064879e0 fix: fixed, refactored and centralized launchers logic (closes #473) 2019-10-31 14:14:13 +01:00
Simone Margaritelli
96c617e152 misc: small fix or general refactoring i did not bother commenting 2019-10-31 12:56:27 +01:00
Simone Margaritelli
783ac61594 fix: the auto-update plugin now also installs launchers and service files via setup.py (closes #470) 2019-10-31 12:48:15 +01:00
evilsocket
279d885ec6 Merge pull request #479 from deveth0/patch-2
Change memory splitting to have more memory available
2019-10-31 10:52:59 +01:00
evilsocket
a8705c1d3c Merge pull request #476 from dadav/fix/provisioning
Change the ordering of the provisioners of packer
2019-10-31 10:50:30 +01:00
Alex Muthmann
90386c7a64 Change memory splitting to have more memory available
As most users won't use a big GUI, it should be sufficient to have 16MB assigned to the GPU and have some more for the system.
2019-10-31 08:49:58 +01:00
opteeks
205480bc38 Merge pull request #4 from opteeks/waveshare29inch
Added 2.9inch display libraries
2019-10-30 22:57:10 -07:00
opteeks
d2726c1a14 Added 2.9inch display libraries 2019-10-30 22:56:02 -07:00
opteeks
1c9a25d22a Merge pull request #3 from opteeks/opteeks-waveshare29inch
Add support for Waveshare 2.9inch display
2019-10-30 22:50:12 -07:00
opteeks
e9494992fc Add support for Waveshare 2.9inch display 2019-10-30 22:49:06 -07:00
dadav
c7931450c3 change ordering 2019-10-30 22:28:12 +01:00
opteeks
cc7299153c Merge pull request #2 from opteeks/opteeks-waveshare29inch
Added waveshare29inch.py
2019-10-30 13:22:23 -07:00
opteeks
a27f09871f Added waveshare29inch.py
Added the Waveshare e-ink 2.9 inch display definition and layout.
2019-10-30 13:09:32 -07:00
Jeremy O'Brien
3714899e95 fix: don't attempt to parse/upload pcaps with malformed filenames
Signed-off-by: Jeremy O'Brien <neutral@fastmail.com>
2019-10-30 15:33:09 -04:00
Simone Margaritelli
be414e57b3 fix: builder now uses files provisioners and auto-update installs project data (ref #470) 2019-10-30 19:24:12 +01:00
Simone Margaritelli
1600d8cbd1 fix: skipping open access points (fixes #463) 2019-10-30 17:50:56 +01:00
Simone Margaritelli
965416483d fix: more robust version parsing in auto-update (fixes #469) 2019-10-30 17:37:17 +01:00
Simone Margaritelli
78a036ed1a fix: fixed Slack invite link (fixes #466) 2019-10-30 16:49:20 +01:00
opteeks
ddc264bbb9 Deleted epd2in9.py
Wrong format
2019-10-23 00:25:08 -07:00
opteeks
f0092ff154 Waveshare 2.9 inch e-ink display 2019-10-23 00:17:28 -07:00
opteeks
ed0df18f68 Added 2.9 inch Waveshare E-Ink display 2019-10-23 00:11:25 -07:00
70 changed files with 2728 additions and 1590 deletions

View File

@@ -6,3 +6,4 @@ include LICENSE
recursive-include bin * recursive-include bin *
recursive-include pwnagotchi *.py recursive-include pwnagotchi *.py
recursive-include pwnagotchi *.yml recursive-include pwnagotchi *.yml
recursive-include pwnagotchi *.*

View File

@@ -1,14 +1,13 @@
# Pwnagotchi # Pwnagotchi
<p align="center"> <p align="center">
<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/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/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://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://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://invite.pwnagotchi.ai/"><img alt="Slack" src="https://invite.pwnagotchi.ai/badge.svg"></a>
<a href="https://community.pwnagotchi.ai/"><img alt="Forum" src="https://img.shields.io/discourse/posts?server=https%3A%2F%2Fcommunity.pwnagotchi.ai%2F&style=flat-square"></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> <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> </p>
[Pwnagotchi](https://pwnagotchi.ai/) 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 to maximize the crackable WPA key material it captures (either passively, or by performing authentication 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/), [Pwnagotchi](https://pwnagotchi.ai/) 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 to maximize the crackable WPA key material it captures (either passively, or by performing authentication 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/),
@@ -32,10 +31,11 @@ https://www.pwnagotchi.ai
&nbsp; | Official Links &nbsp; | 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/) Website | [pwnagotchi.ai](https://pwnagotchi.ai/)
Forum | [community.pwnagotchi.ai](https://community.pwnagotchi.ai/)
Slack | [pwnagotchi.slack.com](https://invite.pwnagotchi.ai/)
Subreddit | [r/pwnagotchi](https://www.reddit.com/r/pwnagotchi/)
Twitter | [@pwnagotchi](https://twitter.com/pwnagotchi)
## License ## License

View File

@@ -31,7 +31,15 @@ if __name__ == '__main__':
parser.add_argument('--debug', dest="debug", action="store_true", default=False, parser.add_argument('--debug', dest="debug", action="store_true", default=False,
help="Enable debug logs.") help="Enable debug logs.")
parser.add_argument('--version', dest="version", action="store_true", default=False,
help="Prints the version.")
args = parser.parse_args() args = parser.parse_args()
if args.version:
print(pwnagotchi.version)
SystemExit(0)
config = utils.load_config(args) config = utils.load_config(args)
utils.setup_logging(args, config) utils.setup_logging(args, config)
@@ -48,7 +56,7 @@ if __name__ == '__main__':
logging.debug("effective configuration:\n\n%s\n\n" % yaml.dump(config, default_flow_style=False)) logging.debug("effective configuration:\n\n%s\n\n" % yaml.dump(config, default_flow_style=False))
for _, plugin in plugins.loaded.items(): for _, plugin in plugins.loaded.items():
logging.debug("plugin '%s' v%s loaded from %s" % (plugin.__name__, plugin.__version__, plugin.__file__)) logging.debug("plugin '%s' v%s" % (plugin.__class__.__name__, plugin.__version__))
if args.do_clear: if args.do_clear:
logging.info("clearing the display ...") logging.info("clearing the display ...")
@@ -57,6 +65,7 @@ if __name__ == '__main__':
elif args.do_manual: elif args.do_manual:
logging.info("entering manual mode ...") logging.info("entering manual mode ...")
agent.mode = 'manual'
agent.last_session.parse(agent.view(), args.skip_session) agent.last_session.parse(agent.view(), args.skip_session)
if not args.skip_session: if not args.skip_session:
logging.info( logging.info(
@@ -77,6 +86,7 @@ if __name__ == '__main__':
else: else:
logging.info("entering auto mode ...") logging.info("entering auto mode ...")
agent.mode = 'auto'
agent.start() agent.start()
while True: while True:

View File

@@ -0,0 +1,2 @@
allow-hotplug eth0
iface eth0 inet dhcp

View File

@@ -0,0 +1,2 @@
auto lo
iface lo inet loopback

View File

@@ -0,0 +1,7 @@
allow-hotplug usb0
iface usb0 inet static
address 10.0.0.2
netmask 255.255.255.0
network 10.0.0.0
broadcast 10.0.0.255
gateway 10.0.0.1

View File

@@ -0,0 +1,2 @@
allow-hotplug wlan0
iface wlan0 inet static

View File

@@ -0,0 +1,14 @@
[Unit]
Description=bettercap api.rest service.
Documentation=https://bettercap.org
Wants=network.target
[Service]
Type=simple
PermissionsStartOnly=true
ExecStart=/usr/bin/bettercap-launcher
Restart=always
RestartSec=30
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,15 @@
[Unit]
Description=pwnagotchi Deep Reinforcement Learning instrumenting bettercap for WiFI pwning.
Documentation=https://pwnagotchi.ai
Wants=network.target
After=pwngrid-peer.service
[Service]
Type=simple
PermissionsStartOnly=true
ExecStart=/usr/bin/pwnagotchi-launcher
Restart=always
RestartSec=30
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,15 @@
[Unit]
Description=pwngrid peer service.
Documentation=https://pwnagotchi.ai
Wants=network.target
After=bettercap.service
[Service]
Type=simple
PermissionsStartOnly=true
ExecStart=/usr/bin/pwngrid -keys /etc/pwnagotchi -address 127.0.0.1:8666 -client-token /root/.api-enrollment.json -wait -log /var/log/pwngrid-peer.log -iface mon0
Restart=always
RestartSec=30
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,11 @@
#!/usr/bin/env bash
source /usr/bin/pwnlib
# start mon0
start_monitor_interface
if is_auto_mode_no_delete; then
/usr/bin/bettercap -no-colors -caplet pwnagotchi-auto -iface mon0
else
/usr/bin/bettercap -no-colors -caplet pwnagotchi-manual -iface mon0
fi

2
builder/data/usr/bin/hdmioff Executable file
View File

@@ -0,0 +1,2 @@
#!/usr/bin/env bash
sudo /opt/vc/bin/tvservice -o

2
builder/data/usr/bin/hdmion Executable file
View File

@@ -0,0 +1,2 @@
#!/usr/bin/env bash
sudo /opt/vc/bin/tvservice -p

3
builder/data/usr/bin/monstart Executable file
View File

@@ -0,0 +1,3 @@
#!/usr/bin/env bash
source /usr/bin/pwnlib
start_monitor_interface

3
builder/data/usr/bin/monstop Executable file
View File

@@ -0,0 +1,3 @@
#!/usr/bin/env bash
source /usr/bin/pwnlib
stop_monitor_interface

View File

@@ -0,0 +1,11 @@
#!/usr/bin/env bash
source /usr/bin/pwnlib
# blink 10 times to signal ready state
blink_led 10 &
if is_auto_mode; then
/usr/local/bin/pwnagotchi
else
/usr/local/bin/pwnagotchi --manual
fi

87
builder/data/usr/bin/pwnlib Executable file
View File

@@ -0,0 +1,87 @@
#!/usr/bin/env bash
# well ... it blinks the led
blink_led() {
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
}
# starts mon0
start_monitor_interface() {
iw phy phy0 interface add mon0 type monitor && ifconfig mon0 up
}
# stops mon0
stop_monitor_interface() {
ifconfig mon0 down && iw dev mon0 del
}
# returns 0 if the specificed network interface is up
is_interface_up() {
if grep -qi 'up' /sys/class/net/$1/operstate; then
return 0
fi
return 1
}
# returns 0 if conditions for AUTO mode are met
is_auto_mode() {
# check override file first
if [ -f /root/.pwnagotchi-manual ]; then
# remove the override file if found
rm -rf /root/.pwnagotchi-manual
return 1
fi
# check override file first
if [ -f /root/.pwnagotchi-auto ]; then
# remove the override file if found
rm -rf /root/.pwnagotchi-auto
return 0
fi
# if usb0 is up, we're in MANU
if is_interface_up usb0; then
return 1
fi
# if eth0 is up (for other boards), we're in MANU
if is_interface_up eth0; then
return 1
fi
# no override, but none of the interfaces is up -> AUTO
return 0
}
# returns 0 if conditions for AUTO mode are met
is_auto_mode_no_delete() {
# check override file first
if [ -f /root/.pwnagotchi-manual ]; then
return 1
fi
# check override file first
if [ -f /root/.pwnagotchi-auto ]; then
return 0
fi
# if usb0 is up, we're in MANU
if is_interface_up usb0; then
return 1
fi
# if eth0 is up (for other boards), we're in MANU
if is_interface_up eth0; then
return 1
fi
# no override, but none of the interfaces is up -> AUTO
return 0
}

View File

@@ -1,12 +1,14 @@
{ {
"builders": [{ "builders": [
"name": "pwnagotchi", {
"type": "arm-image", "name": "pwnagotchi",
"iso_url" : "https://downloads.raspberrypi.org/raspbian_lite/images/raspbian_lite-2019-07-12/2019-07-10-raspbian-buster-lite.zip", "type": "arm-image",
"iso_checksum_type":"sha256", "iso_url": "https://downloads.raspberrypi.org/raspbian_lite/images/raspbian_lite-2019-07-12/2019-07-10-raspbian-buster-lite.zip",
"iso_checksum":"9e5cf24ce483bb96e7736ea75ca422e3560e7b455eee63dd28f66fa1825db70e", "iso_checksum_type": "sha256",
"last_partition_extra_size" : 3221225472 "iso_checksum": "9e5cf24ce483bb96e7736ea75ca422e3560e7b455eee63dd28f66fa1825db70e",
}], "last_partition_extra_size": 3221225472
}
],
"provisioners": [ "provisioners": [
{ {
"type": "shell", "type": "shell",
@@ -18,7 +20,83 @@
] ]
}, },
{ {
"type":"ansible-local", "type": "file",
"source": "data/usr/bin/pwnlib",
"destination": "/usr/bin/pwnlib"
},
{
"type": "file",
"source": "data/usr/bin/bettercap-launcher",
"destination": "/usr/bin/bettercap-launcher"
},
{
"type": "file",
"source": "data/usr/bin/pwnagotchi-launcher",
"destination": "/usr/bin/pwnagotchi-launcher"
},
{
"type": "file",
"source": "data/usr/bin/monstop",
"destination": "/usr/bin/monstop"
},
{
"type": "file",
"source": "data/usr/bin/monstart",
"destination": "/usr/bin/monstart"
},
{
"type": "file",
"source": "data/usr/bin/hdmion",
"destination": "/usr/bin/hdmion"
},
{
"type": "file",
"source": "data/usr/bin/hdmioff",
"destination": "/usr/bin/hdmioff"
},
{
"type": "file",
"source": "data/etc/network/interfaces.d/lo-cfg",
"destination": "/etc/network/interfaces.d/lo-cfg"
},
{
"type": "file",
"source": "data/etc/network/interfaces.d/wlan0-cfg",
"destination": "/etc/network/interfaces.d/wlan0-cfg"
},
{
"type": "file",
"source": "data/etc/network/interfaces.d/usb0-cfg",
"destination": "/etc/network/interfaces.d/usb0-cfg"
},
{
"type": "file",
"source": "data/etc/network/interfaces.d/eth0-cfg",
"destination": "/etc/network/interfaces.d/eth0-cfg"
},
{
"type": "file",
"source": "data/etc/systemd/system/pwngrid-peer.service",
"destination": "/etc/systemd/system/pwngrid-peer.service"
},
{
"type": "file",
"source": "data/etc/systemd/system/pwnagotchi.service",
"destination": "/etc/systemd/system/pwnagotchi.service"
},
{
"type": "file",
"source": "data/etc/systemd/system/bettercap.service",
"destination": "/etc/systemd/system/bettercap.service"
},
{
"type": "shell",
"inline": [
"chmod +x /usr/bin/*"
]
},
{
"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" "command": "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 PWN_VERSION={{user `pwn_version`}} PWN_HOSTNAME={{user `pwn_hostname`}} ansible-playbook"
}, },

View File

@@ -13,6 +13,7 @@
- "dtparam=spi=on" - "dtparam=spi=on"
- "dtparam=i2c_arm=on" - "dtparam=i2c_arm=on"
- "dtparam=i2c1=on" - "dtparam=i2c1=on"
- "gpu_mem=16"
modules: modules:
- "i2c-dev" - "i2c-dev"
services: services:
@@ -98,6 +99,9 @@
- bc - bc
- fonts-freefont-ttf - fonts-freefont-ttf
- fbi - fbi
- python3-flask
- python3-flask-cors
- python3-flaskext.wtf
tasks: tasks:
- name: change hostname - name: change hostname
@@ -279,93 +283,6 @@
remote_src: yes remote_src: yes
mode: 0755 mode: 0755
- 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) ]] || [[ !$(grep '1' /sys/class/net/eth0/carrier) ]]; 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
/usr/bin/monstart
if [[ $(ifconfig | grep usb0 | grep RUNNING) ]] || [[ !$(grep '1' /sys/class/net/eth0/carrier) ]]; then
# if override file exists, go into auto mode
if [ -f /root/.pwnagotchi-auto ]; then
/usr/bin/bettercap -no-colors -caplet pwnagotchi-auto -iface mon0
else
/usr/bin/bettercap -no-colors -caplet pwnagotchi-manual -iface mon0
fi
else
/usr/bin/bettercap -no-colors -caplet pwnagotchi-auto -iface mon0
fi
- name: create monstart script
copy:
dest: /usr/bin/monstart
mode: 0755
content: |
#!/usr/bin/env bash
iw phy phy0 interface add mon0 type monitor && ifconfig mon0 up
- name: create monstop script
copy:
dest: /usr/bin/monstop
mode: 0755
content: |
#!/usr/bin/env bash
ifconfig mon0 down && iw dev mon0 del
- 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 - name: add HDMI powersave to rc.local
blockinfile: blockinfile:
path: /etc/rc.local path: /etc/rc.local
@@ -399,39 +316,6 @@
# #
when: not user_config.stat.exists when: not user_config.stat.exists
- name: configure lo interface
copy:
dest: /etc/network/interfaces.d/lo-cfg
content: |
auto lo
iface lo inet loopback
- name: configure wlan interface
copy:
dest: /etc/network/interfaces.d/wlan0-cfg
content: |
allow-hotplug wlan0
iface wlan0 inet static
- name: configure usb interface
copy:
dest: /etc/network/interfaces.d/usb0-cfg
content: |
allow-hotplug usb0
iface usb0 inet static
address 10.0.0.2
netmask 255.255.255.0
network 10.0.0.0
broadcast 10.0.0.255
gateway 10.0.0.1
- name: configure eth0 interface (pi2/3/4)
copy:
dest: /etc/network/interfaces.d/eth0-cfg
content: |
allow-hotplug eth0
iface eth0 inet dhcp
- name: enable ssh on boot - name: enable ssh on boot
file: file:
path: /boot/ssh path: /boot/ssh
@@ -471,7 +355,7 @@
copy: copy:
dest: /etc/motd dest: /etc/motd
content: | content: |
(◕‿‿◕) {{pwnagotchi.hostname}} (pwnagotchi-{{pwnagotchi.version}}) (◕‿‿◕) {{pwnagotchi.hostname}}
Hi! I'm a pwnagotchi, please take good care of me! Hi! I'm a pwnagotchi, please take good care of me!
Here are some basic things you need to know to raise me properly! Here are some basic things you need to know to raise me properly!
@@ -507,71 +391,6 @@
apt: apt:
autoremove: yes autoremove: yes
- name: add pwngrid-peer service to systemd
copy:
dest: /etc/systemd/system/pwngrid-peer.service
content: |
[Unit]
Description=pwngrid peer service.
Documentation=https://pwnagotchi.ai
Wants=network.target
[Service]
Type=simple
PermissionsStartOnly=true
ExecStart=/usr/bin/pwngrid -keys /etc/pwnagotchi -address 127.0.0.1:8666 -client-token /root/.api-enrollment.json -wait -log /var/log/pwngrid-peer.log -iface mon0
Restart=always
RestartSec=30
[Install]
WantedBy=multi-user.target
notify:
- reload systemd services
- 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=pwngrid.service
[Service]
Type=simple
PermissionsStartOnly=true
ExecStart=/usr/bin/bettercap-launcher
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 - name: enable services
systemd: systemd:
name: "{{ item }}" name: "{{ item }}"

View File

@@ -6,7 +6,7 @@ import re
import pwnagotchi.ui.view as view import pwnagotchi.ui.view as view
import pwnagotchi import pwnagotchi
version = '1.1.0' version = '1.2.1'
_name = None _name = None
@@ -103,12 +103,39 @@ def shutdown():
if view.ROOT: if view.ROOT:
view.ROOT.on_shutdown() view.ROOT.on_shutdown()
# give it some time to refresh the ui # give it some time to refresh the ui
time.sleep(5) time.sleep(10)
os.system("sync") os.system("sync")
os.system("halt") os.system("halt")
def reboot(): def restart(mode):
logging.warning("rebooting ...") logging.warning("restarting in %s mode ..." % mode)
if mode == 'AUTO':
os.system("touch /root/.pwnagotchi-auto")
else:
os.system("touch /root/.pwnagotchi-manual")
os.system("service bettercap restart")
os.system("service pwnagotchi restart")
def reboot(mode=None):
if mode is not None:
mode = mode.upper()
logging.warning("rebooting in %s mode ..." % mode)
else:
logging.warning("rebooting ...")
if view.ROOT:
view.ROOT.on_rebooting()
# give it some time to refresh the ui
time.sleep(10)
if mode == 'AUTO':
os.system("touch /root/.pwnagotchi-auto")
elif mode == 'MANU':
os.system("touch /root/.pwnagotchi-manual")
os.system("sync") os.system("sync")
os.system("shutdown -r now") os.system("shutdown -r now")

View File

@@ -8,6 +8,7 @@ import _thread
import pwnagotchi import pwnagotchi
import pwnagotchi.utils as utils import pwnagotchi.utils as utils
import pwnagotchi.plugins as plugins import pwnagotchi.plugins as plugins
from pwnagotchi.ui.web.server import Server
from pwnagotchi.automata import Automata from pwnagotchi.automata import Automata
from pwnagotchi.log import LastSession from pwnagotchi.log import LastSession
from pwnagotchi.bettercap import Client from pwnagotchi.bettercap import Client
@@ -34,11 +35,14 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
self._supported_channels = utils.iface_channels(config['main']['iface']) self._supported_channels = utils.iface_channels(config['main']['iface'])
self._view = view self._view = view
self._view.set_agent(self) self._view.set_agent(self)
self._web_ui = Server(self, self._config['ui']['display'])
self._access_points = [] self._access_points = []
self._last_pwnd = None self._last_pwnd = None
self._history = {} self._history = {}
self._handshakes = {} self._handshakes = {}
self.last_session = LastSession(self._config) self.last_session = LastSession(self._config)
self.mode = 'auto'
if not os.path.exists(config['bettercap']['handshakes']): if not os.path.exists(config['bettercap']['handshakes']):
os.makedirs(config['bettercap']['handshakes']) os.makedirs(config['bettercap']['handshakes'])
@@ -170,7 +174,9 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
s = self.session() s = self.session()
plugins.on("unfiltered_ap_list", self, s['wifi']['aps']) plugins.on("unfiltered_ap_list", self, s['wifi']['aps'])
for ap in s['wifi']['aps']: for ap in s['wifi']['aps']:
if ap['hostname'] not in whitelist: if ap['encryption'] == '' or ap['encryption'] == 'OPEN':
continue
elif ap['hostname'] not in whitelist:
if self._filter_included(ap): if self._filter_included(ap):
aps.append(ap) aps.append(ap)
except Exception as e: except Exception as e:

View File

@@ -75,6 +75,15 @@ class Automata(object):
logging.info("unit is grateful instead of sad") logging.info("unit is grateful instead of sad")
self.set_grateful() self.set_grateful()
def set_angry(self, factor):
if not self._has_support_network_for(factor):
logging.warning("%d epochs with no activity -> angry" % self._epoch.inactive_for)
self._view.on_angry()
plugins.on('angry', self)
else:
logging.info("unit is grateful instead of angry")
self.set_grateful()
def set_excited(self): def set_excited(self):
logging.warning("%d epochs with activity -> excited" % self._epoch.active_for) logging.warning("%d epochs with activity -> excited" % self._epoch.active_for)
self._view.on_excited() self._view.on_excited()
@@ -103,13 +112,21 @@ class Automata(object):
self._epoch.next() self._epoch.next()
# after X misses during an epoch, set the status to lonely # after X misses during an epoch, set the status to lonely or angry
if was_stale: if was_stale:
logging.warning("agent missed %d interactions -> lonely" % did_miss) factor = did_miss / self._config['personality']['max_misses_for_recon']
self.set_lonely() if factor >= 2.0:
# after X times being bored, the status is set to sad self.set_angry(factor)
else:
logging.warning("agent missed %d interactions -> lonely" % did_miss)
self.set_lonely()
# after X times being bored, the status is set to sad or angry
elif self._epoch.inactive_for >= self._config['personality']['sad_num_epochs']: elif self._epoch.inactive_for >= self._config['personality']['sad_num_epochs']:
self.set_sad() factor = self._epoch.inactive_for / self._config['personality']['sad_num_epochs']
if factor >= 2.0:
self.set_angry(factor)
else:
self.set_sad()
# after X times being inactive, the status is set to bored # after X times being inactive, the status is set to bored
elif self._epoch.inactive_for >= self._config['personality']['bored_num_epochs']: elif self._epoch.inactive_for >= self._config['personality']['bored_num_epochs']:
self.set_bored() self.set_bored()

View File

@@ -21,13 +21,14 @@ main:
- YourHomeNetworkHere - YourHomeNetworkHere
auto-update: auto-update:
enabled: false enabled: true
interval: 12 # every 12 hours
install: true # if false, it will only warn that updates are available, if true it will install them install: true # if false, it will only warn that updates are available, if true it will install them
interval: 1 # every 1 hour
auto-backup: auto-backup:
enabled: false enabled: false
interval: 1 # every day interval: 1 # every day
max_tries: 0 # 0=infinity
files: files:
- /root/brain.nn - /root/brain.nn
- /root/brain.json - /root/brain.json
@@ -71,11 +72,29 @@ main:
enabled: false enabled: false
bt-tether: bt-tether:
enabled: false # if you want to use this, set ui.display.video.address to 0.0.0.0 enabled: false # if you want to use this, set ui.display.video.address to 0.0.0.0
mac: ~ # mac of your bluetooth device devices:
ip: '192.168.44.44' # ip from which your pwnagotchi should be reachable android-phone:
netmask: 24 enabled: false
interval: 1 # check every x minutes for device search_order: 1 # search for this first
share_internet: false mac: ~ # mac of your bluetooth device
ip: '192.168.44.44' # ip from which your pwnagotchi should be reachable
netmask: 24
interval: 1 # check every minute for device
scantime: 10 # search for 10 seconds
max_tries: 10 # after 10 tries of "not found"; don't try anymore
share_internet: false
priority: 1 # low routing priority; ios (prio: 999) would win here
ios-phone:
enabled: false
search_order: 2 # search for this second
mac: ~ # mac of your bluetooth device
ip: '172.20.10.6' # ip from which your pwnagotchi should be reachable
netmask: 24
interval: 5 # check every 5 minutes for device
scantime: 20
max_tries: 0 # infinity
share_internet: false
priority: 999 # routing priority
memtemp: # Display memory usage, cpu load and cpu temperature on screen memtemp: # Display memory usage, cpu load and cpu temperature on screen
enabled: false enabled: false
orientation: horizontal # horizontal/vertical orientation: horizontal # horizontal/vertical
@@ -199,6 +218,7 @@ ui:
smart: '(✜‿‿✜)' smart: '(✜‿‿✜)'
lonely: '(ب__ب)' lonely: '(ب__ب)'
sad: '(╥☁╥ )' sad: '(╥☁╥ )'
angry: "(-_-')"
friend: '(♥‿‿♥)' friend: '(♥‿‿♥)'
broken: '(☓‿‿☓)' broken: '(☓‿‿☓)'
debug: '(#__#)' debug: '(#__#)'

Binary file not shown.

View File

@@ -0,0 +1,225 @@
# 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 <511225068@qq.com>, 2019.
# 还有很多未翻译和翻译不准确,后期希望大家加入进来一起翻译!
# 翻译可以联系QQ群959559103 找 名字叫 初九 的 管理员
msgid ""
msgstr ""
"Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-10-23 20:56+0200\n"
"PO-Revision-Date: 2019-11-02 10:00+0008\n"
"Last-Translator: 极客之眼-初九 <511225068@qq.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: chinese\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "ZzzzZZzzzzZzzz"
msgstr ""
msgid "Hi, I'm Pwnagotchi! Starting ..."
msgstr "主人,你好.我是WiFi狩猎兽..."
msgid "New day, new hunt, new pwns!"
msgstr "美好的一天,狩猎开始!"
msgid "Hack the Planet!"
msgstr "我要入侵整个地球!"
msgid "AI ready."
msgstr "人工智能已启动."
msgid "The neural network is ready."
msgstr "神经元网络已启动."
msgid "Generating keys, do not turn off ..."
msgstr "创建密钥中, 请勿断电..."
#, python-brace-format
msgid "Hey, channel {channel} is free! Your AP will say thanks."
msgstr ""
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."
msgstr "你好{name}!很高兴认识你."
#, python-brace-format
msgid "Unit {name} is nearby!"
msgstr ""
#, 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 "Good friends are a blessing!"
msgstr "有个好朋友就是福气"
msgid "I love my friends!"
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}s ..."
msgid "Zzzzz"
msgstr ""
#, python-brace-format
msgid "ZzzZzzz ({secs}s)"
msgstr ""
msgid "Good night."
msgstr "晚安宝贝."
msgid "Zzz"
msgstr ""
#, python-brace-format
msgid "Waiting for {secs}s ..."
msgstr "等待{secs}s ..."
#, python-brace-format
msgid "Looking around ({secs}s)"
msgstr "追踪四周猎物({secs}s)"
#, 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 "追踪到你了{what}!"
#, python-brace-format
msgid "Just decided that {mac} needs no WiFi!"
msgstr "猎物{mac}不需要联网,我们给它断开!"
#, 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}新的猎物{plural}!"
#, python-brace-format
msgid "You have {count} new message{plural}!"
msgstr "主人,有{count}新消息{plural}!"
msgid "Ops, something went wrong ... Rebooting ..."
msgstr "行动,额等等有点小问题... 重启ing ..."
#, 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 ""
#, 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 ""
msgid "hours"
msgstr "时"
msgid "minutes"
msgstr "分"
msgid "seconds"
msgstr "秒"
msgid "hour"
msgstr "时"
msgid "minute"
msgstr "分"
msgid "second"
msgstr "秒"

View File

@@ -5,9 +5,9 @@
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 0.0.2\n" "Project-Id-Version: 0.1.0\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-10-21 08:39+0200\n" "POT-Creation-Date: 2019-11-04 06:37+0100\n"
"PO-Revision-Date: 2019-10-21 10:55+0200\n" "PO-Revision-Date: 2019-10-21 10:55+0200\n"
"Last-Translator: gkrs <457603+gkrs@users.noreply.github.com>\n" "Last-Translator: gkrs <457603+gkrs@users.noreply.github.com>\n"
"Language-Team: PL <457603+gkrs@users.noreply.github.com>\n" "Language-Team: PL <457603+gkrs@users.noreply.github.com>\n"
@@ -41,6 +41,13 @@ msgstr "Generuję klucze, nie wyłączaj ..."
msgid "Hey, channel {channel} is free! Your AP will say thanks." msgid "Hey, channel {channel} is free! Your AP will say thanks."
msgstr "Hej, kanał {channel} jest wolny! Twój AP będzie Ci wdzięczny." msgstr "Hej, kanał {channel} jest wolny! Twój AP będzie Ci wdzięczny."
msgid "Reading last session logs ..."
msgstr "Czytam logi z ostatniej sesji ..."
#, python-brace-format
msgid "Read {lines_so_far} log lines so far ..."
msgstr "Na razie przeczytałem {lines_so_far} linii logów ..."
msgid "I'm bored ..." msgid "I'm bored ..."
msgstr "Nudzi mi się ..." msgstr "Nudzi mi się ..."
@@ -62,6 +69,12 @@ msgstr "Jest mi bardzo smutno ..."
msgid "I'm sad" msgid "I'm sad"
msgstr "Jest mi smutno" msgstr "Jest mi smutno"
msgid "Leave me alone ..."
msgstr "Zostaw mnie w spokoju ..."
msgid "I'm mad at you!"
msgstr "Wkurzam się na ciebie"
msgid "I'm living the life!" msgid "I'm living the life!"
msgstr "Cieszę się życiem!" msgstr "Cieszę się życiem!"
@@ -81,6 +94,14 @@ msgstr "Moją zbrodnią jest ciekawość ..."
msgid "Hello {name}! Nice to meet you." msgid "Hello {name}! Nice to meet you."
msgstr "Cześć {name}! Miło Cię poznać." msgstr "Cześć {name}! Miło Cię poznać."
#, python-brace-format
msgid "Yo {name}! Sup?"
msgstr "Siema {name}! Co słychać?"
#, python-brace-format
msgid "Hey {name} how are you doing?"
msgstr "Hej {name} jak się masz?"
#, python-brace-format #, python-brace-format
msgid "Unit {name} is nearby!" msgid "Unit {name} is nearby!"
msgstr "Urządzenie {name} jest w pobliżu!" msgstr "Urządzenie {name} jest w pobliżu!"
@@ -104,6 +125,12 @@ msgstr "{name} pudło!"
msgid "Missed!" msgid "Missed!"
msgstr "Pudło!" msgstr "Pudło!"
msgid "Good friends are a blessing!"
msgstr "Dobrzy przyjaciele to błogosławieństwo!"
msgid "I love my friends!"
msgstr "Kocham moich przyjaciół!"
msgid "Nobody wants to play with me ..." msgid "Nobody wants to play with me ..."
msgstr "Nikt nie chce się ze mną bawić ..." msgstr "Nikt nie chce się ze mną bawić ..."
@@ -166,6 +193,10 @@ msgstr "Banuję {mac}!"
msgid "Cool, we got {num} new handshake{plural}!" msgid "Cool, we got {num} new handshake{plural}!"
msgstr "Super, zdobyliśmy {num} nowych handshake'ów!" msgstr "Super, zdobyliśmy {num} nowych handshake'ów!"
#, python-brace-format
msgid "You have {count} new message{plural}!"
msgstr "Masz {count} nowych wiadomości!"
msgid "Ops, something went wrong ... Rebooting ..." msgid "Ops, something went wrong ... Rebooting ..."
msgstr "Ups, coś poszło nie tak ... Restaruję ..." msgstr "Ups, coś poszło nie tak ... Restaruję ..."
@@ -195,8 +226,8 @@ msgid ""
"#pwnlog #pwnlife #hacktheplanet #skynet" "#pwnlog #pwnlife #hacktheplanet #skynet"
msgstr "" msgstr ""
"Pwnowałem {duration} i wyrzuciłem {deauthed} klientów! Spotkałem także " "Pwnowałem {duration} i wyrzuciłem {deauthed} klientów! Spotkałem także "
"{associated} nowych przyjaciół i zjadłem {handshakes} handshake'ow! #pwnagotchi " "{associated} nowych przyjaciół i zjadłem {handshakes} handshake'ow! "
"#pwnlog #pwnlife #hacktheplanet #skynet" "#pwnagotchi #pwnlog #pwnlife #hacktheplanet #skynet"
msgid "hours" msgid "hours"
msgstr "godzin" msgstr "godzin"

Binary file not shown.

View File

@@ -0,0 +1,228 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# damoklov <mishanya@protonmail.com>, 2019.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-10-23 20:56+0200\n"
"PO-Revision-Date: 2019-11-02 16:20+0200\n"
"Last-Translator: damoklov <mishanya@protonmail.com>\n"
"Language-Team: Ukrainian\n"
"Language: ua\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 "Привіт, я 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 "Нейронна мережа готова."
msgid "Generating keys, do not turn off ..."
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."
msgstr "Привіт, {name}! Приємно познайомитись."
#, python-brace-format
msgid "Unit {name} is nearby!"
msgstr "Ціль {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 "Good friends are a blessing!"
msgstr "Справжні друзі - це чудово!"
msgid "I love my friends!"
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}с)"
msgid "Good night."
msgstr "Спокійної нічки."
msgid "Zzz"
msgstr "Zzz"
#, python-brace-format
msgid "Waiting for {secs}s ..."
msgstr "Очікую {secs}с ..."
#, 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 "Гей, {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} нових рукостискань!"
#, python-brace-format
msgid "You have {count} new message{plural}!"
msgstr "Нових повідомлень: {count}"
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 "minutes"
msgstr "хвилин"
msgid "seconds"
msgstr "секунд"
msgid "hour"
msgstr "година"
msgid "minute"
msgstr "хвилина"
msgid "second"
msgstr "секунда"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-10-23 20:56+0200\n" "POT-Creation-Date: 2019-11-04 12:57+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -42,6 +42,13 @@ msgstr ""
msgid "Hey, channel {channel} is free! Your AP will say thanks." msgid "Hey, channel {channel} is free! Your AP will say thanks."
msgstr "" msgstr ""
msgid "Reading last session logs ..."
msgstr ""
#, python-brace-format
msgid "Read {lines_so_far} log lines so far ..."
msgstr ""
msgid "I'm bored ..." msgid "I'm bored ..."
msgstr "" msgstr ""
@@ -63,6 +70,12 @@ msgstr ""
msgid "I'm sad" msgid "I'm sad"
msgstr "" msgstr ""
msgid "Leave me alone ..."
msgstr ""
msgid "I'm mad at you!"
msgstr ""
msgid "I'm living the life!" msgid "I'm living the life!"
msgstr "" msgstr ""
@@ -82,6 +95,14 @@ msgstr ""
msgid "Hello {name}! Nice to meet you." msgid "Hello {name}! Nice to meet you."
msgstr "" msgstr ""
#, python-brace-format
msgid "Yo {name}! Sup?"
msgstr ""
#, python-brace-format
msgid "Hey {name} how are you doing?"
msgstr ""
#, python-brace-format #, python-brace-format
msgid "Unit {name} is nearby!" msgid "Unit {name} is nearby!"
msgstr "" msgstr ""

View File

@@ -7,20 +7,20 @@ default_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "defaul
loaded = {} loaded = {}
def dummy_callback(): class Plugin:
pass @classmethod
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
global loaded
plugin_name = cls.__module__.split('.')[0]
plugin_instance = cls()
logging.debug("loaded plugin %s as %s" % (plugin_name, plugin_instance))
loaded[plugin_name] = plugin_instance
def on(event_name, *args, **kwargs): def on(event_name, *args, **kwargs):
global loaded
cb_name = 'on_%s' % event_name
for plugin_name, plugin in loaded.items(): for plugin_name, plugin in loaded.items():
if cb_name in plugin.__dict__: one(plugin_name, event_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))
logging.error(e, exc_info=True)
def one(plugin_name, event_name, *args, **kwargs): def one(plugin_name, event_name, *args, **kwargs):
@@ -28,15 +28,17 @@ def one(plugin_name, event_name, *args, **kwargs):
if plugin_name in loaded: if plugin_name in loaded:
plugin = loaded[plugin_name] plugin = loaded[plugin_name]
cb_name = 'on_%s' % event_name cb_name = 'on_%s' % event_name
if cb_name in plugin.__dict__: callback = getattr(plugin, cb_name, None)
if callback is not None and callable(callback):
try: try:
plugin.__dict__[cb_name](*args, **kwargs) callback(*args, **kwargs)
except Exception as e: except Exception as e:
logging.error("error while running %s.%s : %s" % (plugin_name, cb_name, e)) logging.error("error while running %s.%s : %s" % (plugin_name, cb_name, e))
logging.error(e, exc_info=True) logging.error(e, exc_info=True)
def load_from_file(filename): def load_from_file(filename):
logging.debug("loading %s" % filename)
plugin_name = os.path.basename(filename.replace(".py", "")) plugin_name = os.path.basename(filename.replace(".py", ""))
spec = importlib.util.spec_from_file_location(plugin_name, filename) spec = importlib.util.spec_from_file_location(plugin_name, filename)
instance = importlib.util.module_from_spec(spec) instance = importlib.util.module_from_spec(spec)
@@ -46,19 +48,15 @@ def load_from_file(filename):
def load_from_path(path, enabled=()): def load_from_path(path, enabled=()):
global loaded global loaded
logging.debug("loading plugins from %s - enabled: %s" % (path, enabled))
for filename in glob.glob(os.path.join(path, "*.py")): for filename in glob.glob(os.path.join(path, "*.py")):
try: plugin_name = os.path.basename(filename.replace(".py", ""))
name, plugin = load_from_file(filename) if plugin_name in enabled:
if name in loaded: try:
raise Exception("plugin %s already loaded from %s" % (name, plugin.__file__)) load_from_file(filename)
elif name not in enabled: except Exception as e:
# print("plugin %s is not enabled" % name) logging.warning("error while loading %s: %s" % (filename, e))
pass logging.debug(e, exc_info=True)
else:
loaded[name] = plugin
except Exception as e:
logging.warning("error while loading %s: %s" % (filename, e))
logging.debug(e, exc_info=True)
return loaded return loaded
@@ -66,17 +64,17 @@ def load_from_path(path, enabled=()):
def load(config): def load(config):
enabled = [name for name, options in config['main']['plugins'].items() if enabled = [name for name, options in config['main']['plugins'].items() if
'enabled' in options and options['enabled']] 'enabled' in options and options['enabled']]
custom_path = config['main']['custom_plugins'] if 'custom_plugins' in config['main'] else None
# load default plugins # load default plugins
loaded = load_from_path(default_path, enabled=enabled) load_from_path(default_path, enabled=enabled)
# set the options
for name, plugin in loaded.items():
plugin.__dict__['OPTIONS'] = config['main']['plugins'][name]
# load custom ones # load custom ones
custom_path = config['main']['custom_plugins'] if 'custom_plugins' in config['main'] else None
if custom_path is not None: if custom_path is not None:
loaded = load_from_path(custom_path, enabled=enabled) load_from_path(custom_path, enabled=enabled)
# set the options
for name, plugin in loaded.items(): # propagate options
plugin.__dict__['OPTIONS'] = config['main']['plugins'][name] for name, plugin in loaded.items():
plugin.options = config['main']['plugins'][name]
on('loaded') on('loaded')

View File

@@ -1,56 +1,57 @@
__author__ = 'pwnagotchi [at] rossmarks [dot] uk' import pwnagotchi.plugins as plugins
__version__ = '1.0.1'
__name__ = 'AircrackOnly'
__license__ = 'GPL3'
__description__ = 'confirm pcap contains handshake/PMKID or delete it'
'''
Aircrack-ng needed, to install:
> apt-get install aircrack-ng
'''
import logging import logging
import subprocess import subprocess
import string import string
import os import os
OPTIONS = dict() '''
Aircrack-ng needed, to install:
> apt-get install aircrack-ng
'''
def on_loaded():
logging.info("aircrackonly plugin loaded")
def on_handshake(agent, filename, access_point, client_station): class AircrackOnly(plugins.Plugin):
display = agent._view __author__ = 'pwnagotchi [at] rossmarks [dot] uk'
todelete = 0 __version__ = '1.0.1'
__license__ = 'GPL3'
__description__ = 'confirm pcap contains handshake/PMKID or delete it'
result = subprocess.run(('/usr/bin/aircrack-ng '+ filename +' | grep "1 handshake" | awk \'{print $2}\''),shell=True, stdout=subprocess.PIPE) def __init__(self):
result = result.stdout.decode('utf-8').translate({ord(c) :None for c in string.whitespace}) super().__init__(self)
if result: self.text_to_set = ""
logging.info("[AircrackOnly] contains handshake")
else:
todelete = 1
if todelete == 0: def on_loaded(self):
result = subprocess.run(('/usr/bin/aircrack-ng '+ filename +' | grep "PMKID" | awk \'{print $2}\''),shell=True, stdout=subprocess.PIPE) logging.info("aircrackonly plugin loaded")
result = result.stdout.decode('utf-8').translate({ord(c) :None for c in string.whitespace})
def on_handshake(self, agent, filename, access_point, client_station):
display = agent._view
todelete = 0
handshakeFound = 0
result = subprocess.run(('/usr/bin/aircrack-ng ' + filename + ' | grep "1 handshake" | awk \'{print $2}\''),
shell=True, stdout=subprocess.PIPE)
result = result.stdout.decode('utf-8').translate({ord(c): None for c in string.whitespace})
if result: if result:
logging.info("[AircrackOnly] contains PMKID") handshakeFound = 1
else: logging.info("[AircrackOnly] contains handshake")
todelete = 1
if todelete == 1: if handshakeFound == 0:
os.remove(filename) result = subprocess.run(('/usr/bin/aircrack-ng ' + filename + ' | grep "PMKID" | awk \'{print $2}\''),
set_text("Removed an uncrackable pcap") shell=True, stdout=subprocess.PIPE)
display.update(force=True) result = result.stdout.decode('utf-8').translate({ord(c): None for c in string.whitespace})
if result:
logging.info("[AircrackOnly] contains PMKID")
else:
todelete = 1
text_to_set = ""; if todelete == 1:
def set_text(text): os.remove(filename)
global text_to_set self.text_to_set = "Removed an uncrackable pcap"
text_to_set = text display.update(force=True)
def on_ui_update(ui): def on_ui_update(self, ui):
global text_to_set if self.text_to_set:
if text_to_set: ui.set('face', "(>.<)")
ui.set('face', "(>.<)") ui.set('status', self.text_to_set)
ui.set('status', text_to_set) self.text_to_set = ""
text_to_set = ""

View File

@@ -1,47 +1,42 @@
__author__ = '33197631+dadav@users.noreply.github.com' import pwnagotchi.plugins as plugins
__version__ = '1.0.0'
__name__ = 'auto-backup'
__license__ = 'GPL3'
__description__ = 'This plugin backups files when internet is available.'
from pwnagotchi.utils import StatusFile from pwnagotchi.utils import StatusFile
import logging import logging
import os import os
import subprocess import subprocess
OPTIONS = dict()
READY = False
STATUS = StatusFile('/root/.auto-backup')
class AutoBackup(plugins.Plugin):
__author__ = '33197631+dadav@users.noreply.github.com'
__version__ = '1.0.0'
__license__ = 'GPL3'
__description__ = 'This plugin backups files when internet is available.'
def on_loaded(): def __init__(self):
global READY self.ready = False
self.tries = 0
self.status = StatusFile('/root/.auto-backup')
if 'files' not in OPTIONS or ('files' in OPTIONS and OPTIONS['files'] is None): def on_loaded(self):
logging.error("AUTO-BACKUP: No files to backup.") for opt in ['files', 'interval', 'commands', 'max_tries']:
return if opt not in self.options or (opt in self.options and self.options[opt] is None):
logging.error(f"AUTO-BACKUP: Option {opt} is not set.")
return
if 'interval' not in OPTIONS or ('interval' in OPTIONS and OPTIONS['interval'] is None): self.ready = True
logging.error("AUTO-BACKUP: Interval is not set.") logging.info("AUTO-BACKUP: Successfully loaded.")
return
if 'commands' not in OPTIONS or ('commands' in OPTIONS and OPTIONS['commands'] is None): def on_internet_available(self, agent):
logging.error("AUTO-BACKUP: No commands given.") if not self.ready:
return return
READY = True if self.options['max_tries'] and self.tries >= self.options['max_tries']:
logging.info("AUTO-BACKUP: Successfully loaded.") return
if self.status.newer_then_days(self.options['interval']):
def on_internet_available(agent):
global STATUS
if READY:
if STATUS.newer_then_days(OPTIONS['interval']):
return return
# Only backup existing files to prevent errors # Only backup existing files to prevent errors
existing_files = list(filter(lambda f: os.path.exists(f), OPTIONS['files'])) existing_files = list(filter(lambda f: os.path.exists(f), self.options['files']))
files_to_backup = " ".join(existing_files) files_to_backup = " ".join(existing_files)
try: try:
@@ -51,10 +46,10 @@ def on_internet_available(agent):
display.set('status', 'Backing up ...') display.set('status', 'Backing up ...')
display.update() display.update()
for cmd in OPTIONS['commands']: for cmd in self.options['commands']:
logging.info(f"AUTO-BACKUP: Running {cmd.format(files=files_to_backup)}") 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, process = subprocess.Popen(cmd.format(files=files_to_backup), shell=True, stdin=None,
stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash") stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
process.wait() process.wait()
if process.returncode > 0: if process.returncode > 0:
raise OSError(f"Command failed (rc: {process.returncode})") raise OSError(f"Command failed (rc: {process.returncode})")
@@ -62,8 +57,9 @@ def on_internet_available(agent):
logging.info("AUTO-BACKUP: backup done") logging.info("AUTO-BACKUP: backup done")
display.set('status', 'Backup done!') display.set('status', 'Backup done!')
display.update() display.update()
STATUS.update() self.status.update()
except OSError as os_e: except OSError as os_e:
self.tries += 1
logging.info(f"AUTO-BACKUP: Error: {os_e}") logging.info(f"AUTO-BACKUP: Error: {os_e}")
display.set('status', 'Backup failed!') display.set('status', 'Backup failed!')
display.update() display.update()

View File

@@ -1,10 +1,5 @@
__author__ = 'evilsocket@gmail.com'
__version__ = '1.1.0'
__name__ = 'auto-update'
__license__ = 'GPL3'
__description__ = 'This plugin checks when updates are available and applies them when internet is available.'
import os import os
import re
import logging import logging
import subprocess import subprocess
import requests import requests
@@ -14,21 +9,9 @@ import glob
import pkg_resources import pkg_resources
import pwnagotchi import pwnagotchi
import pwnagotchi.plugins as plugins
from pwnagotchi.utils import StatusFile from pwnagotchi.utils import StatusFile
OPTIONS = dict()
READY = False
STATUS = StatusFile('/root/.auto-update')
def on_loaded():
global READY
if 'interval' not in OPTIONS or ('interval' in OPTIONS and OPTIONS['interval'] is None):
logging.error("[update] main.plugins.auto-update.interval is not set")
return
READY = True
logging.info("[update] plugin loaded.")
def check(version, repo, native=True): def check(version, repo, native=True):
logging.debug("checking remote version for %s, local is %s" % (repo, version)) logging.debug("checking remote version for %s, local is %s" % (repo, version))
@@ -142,19 +125,47 @@ def install(display, update):
if not os.path.exists(source_path): if not os.path.exists(source_path):
source_path = "%s-%s" % (source_path, update['available']) source_path = "%s-%s" % (source_path, update['available'])
# setup.py is going to install data files for us
os.system("cd %s && pip3 install ." % source_path) os.system("cd %s && pip3 install ." % source_path)
return True return True
def on_internet_available(agent): def parse_version(cmd):
global STATUS out = subprocess.getoutput(cmd)
for part in out.split(' '):
part = part.replace('v', '').strip()
if re.search(r'^\d+\.\d+\.\d+.*$', part):
return part
raise Exception('could not parse version from "%s": output=\n%s' % (cmd, out))
logging.debug("[update] internet connectivity is available (ready %s)" % READY)
if READY: class AutoUpdate(plugins.Plugin):
if STATUS.newer_then_hours(OPTIONS['interval']): __author__ = 'evilsocket@gmail.com'
logging.debug("[update] last check happened less than %d hours ago" % OPTIONS['interval']) __version__ = '1.1.1'
__name__ = 'auto-update'
__license__ = 'GPL3'
__description__ = 'This plugin checks when updates are available and applies them when internet is available.'
def __init__(self):
self.ready = False
self.status = StatusFile('/root/.auto-update')
def on_loaded(self):
if 'interval' not in self.options or ('interval' in self.options and self.options['interval'] is None):
logging.error("[update] main.plugins.auto-update.interval is not set")
return
self.ready = True
logging.info("[update] plugin loaded.")
def on_internet_available(self, agent):
logging.debug("[update] internet connectivity is available (ready %s)" % self.ready)
if not self.ready:
return
if self.status.newer_then_hours(self.options['interval']):
logging.debug("[update] last check happened less than %d hours ago" % self.options['interval'])
return return
logging.info("[update] checking for updates ...") logging.info("[update] checking for updates ...")
@@ -167,9 +178,8 @@ def on_internet_available(agent):
to_install = [] to_install = []
to_check = [ to_check = [
('bettercap/bettercap', subprocess.getoutput('bettercap -version').split(' ')[1].replace('v', ''), ('bettercap/bettercap', parse_version('bettercap -version'), True, 'bettercap'),
True, 'bettercap'), ('evilsocket/pwngrid', parse_version('pwngrid -version'), True, 'pwngrid-peer'),
('evilsocket/pwngrid', subprocess.getoutput('pwngrid -version').replace('v', ''), True, 'pwngrid-peer'),
('evilsocket/pwnagotchi', pwnagotchi.version, False, 'pwnagotchi') ('evilsocket/pwnagotchi', pwnagotchi.version, False, 'pwnagotchi')
] ]
@@ -177,7 +187,8 @@ def on_internet_available(agent):
info = check(local_version, repo, is_native) info = check(local_version, repo, is_native)
if info['url'] is not None: if info['url'] is not None:
logging.warning( logging.warning(
"update for %s available (local version is '%s'): %s" % (repo, info['current'], info['url'])) "update for %s available (local version is '%s'): %s" % (
repo, info['current'], info['url']))
info['service'] = svc_name info['service'] = svc_name
to_install.append(info) to_install.append(info)
@@ -185,7 +196,7 @@ def on_internet_available(agent):
num_installed = 0 num_installed = 0
if num_updates > 0: if num_updates > 0:
if OPTIONS['install']: if self.options['install']:
for update in to_install: for update in to_install:
if install(display, update): if install(display, update):
num_installed += 1 num_installed += 1
@@ -194,7 +205,7 @@ def on_internet_available(agent):
logging.info("[update] done") logging.info("[update] done")
STATUS.update() self.status.update()
if num_installed > 0: if num_installed > 0:
display.update(force=True, new_data={'status': 'Rebooting ...'}) display.update(force=True, new_data={'status': 'Rebooting ...'})

View File

@@ -1,9 +1,3 @@
__author__ = '33197631+dadav@users.noreply.github.com'
__version__ = '1.0.0'
__name__ = 'bt-tether'
__license__ = 'GPL3'
__description__ = 'This makes the display reachable over bluetooth'
import os import os
import time import time
import re import re
@@ -14,10 +8,7 @@ from pwnagotchi.ui.components import LabeledValue
from pwnagotchi.ui.view import BLACK from pwnagotchi.ui.view import BLACK
import pwnagotchi.ui.fonts as fonts import pwnagotchi.ui.fonts as fonts
from pwnagotchi.utils import StatusFile from pwnagotchi.utils import StatusFile
import pwnagotchi.plugins as plugins
READY = False
INTERVAL = StatusFile('/root/.bt-tether')
OPTIONS = dict()
class BTError(Exception): class BTError(Exception):
@@ -26,6 +17,7 @@ class BTError(Exception):
""" """
pass pass
class BTNap: class BTNap:
""" """
This class creates a bluetooth connection to the specified bt-mac This class creates a bluetooth connection to the specified bt-mac
@@ -41,7 +33,6 @@ class BTNap:
def __init__(self, mac): def __init__(self, mac):
self._mac = mac self._mac = mac
@staticmethod @staticmethod
def get_bus(): def get_bus():
""" """
@@ -59,9 +50,9 @@ class BTNap:
""" """
manager = getattr(BTNap.get_manager, 'cached_obj', None) manager = getattr(BTNap.get_manager, 'cached_obj', None)
if not manager: if not manager:
manager = BTNap.get_manager.cached_obj = dbus.Interface( manager = BTNap.get_manager.cached_obj = dbus.Interface(
BTNap.get_bus().get_object(BTNap.IFACE_BASE, '/'), BTNap.get_bus().get_object(BTNap.IFACE_BASE, '/'),
'org.freedesktop.DBus.ObjectManager' ) 'org.freedesktop.DBus.ObjectManager')
return manager return manager
@staticmethod @staticmethod
@@ -82,7 +73,6 @@ class BTNap:
iface = obj.dbus_interface iface = obj.dbus_interface
return obj.Set(iface, k, v, dbus_interface=BTNap.IFACE_PROPS) return obj.Set(iface, k, v, dbus_interface=BTNap.IFACE_PROPS)
@staticmethod @staticmethod
def find_adapter(pattern=None): def find_adapter(pattern=None):
""" """
@@ -98,14 +88,14 @@ class BTNap:
""" """
bus, obj = BTNap.get_bus(), None bus, obj = BTNap.get_bus(), None
for path, ifaces in objects.items(): for path, ifaces in objects.items():
adapter = ifaces.get(BTNap.IFACE_ADAPTER) adapter = ifaces.get(BTNap.IFACE_ADAPTER)
if adapter is None: if adapter is None:
continue continue
if not pattern or pattern == adapter['Address'] or path.endswith(pattern): if not pattern or pattern == adapter['Address'] or path.endswith(pattern):
obj = bus.get_object(BTNap.IFACE_BASE, path) obj = bus.get_object(BTNap.IFACE_BASE, path)
yield dbus.Interface(obj, BTNap.IFACE_ADAPTER) yield dbus.Interface(obj, BTNap.IFACE_ADAPTER)
if obj is None: if obj is None:
raise BTError('Bluetooth adapter not found') raise BTError('Bluetooth adapter not found')
@staticmethod @staticmethod
def find_device(device_address, adapter_pattern=None): def find_device(device_address, adapter_pattern=None):
@@ -132,7 +122,7 @@ class BTNap:
device = ifaces.get(BTNap.IFACE_DEV) device = ifaces.get(BTNap.IFACE_DEV)
if device is None: if device is None:
continue continue
if str(device['Address']) == device_address and path.startswith(path_prefix): if str(device['Address']).lower() == device_address.lower() and path.startswith(path_prefix):
obj = bus.get_object(BTNap.IFACE_BASE, path) obj = bus.get_object(BTNap.IFACE_BASE, path)
return dbus.Interface(obj, BTNap.IFACE_DEV) return dbus.Interface(obj, BTNap.IFACE_DEV)
raise BTError('Bluetooth device not found') raise BTError('Bluetooth device not found')
@@ -159,25 +149,6 @@ class BTNap:
return None return None
def is_connected(self):
"""
Check if already connected
"""
logging.debug("BT-TETHER: Checking if device is connected.")
bt_dev = self.power(True)
if not bt_dev:
logging.debug("BT-TETHER: No bluetooth device found.")
return None, False
try:
dev_remote = BTNap.find_device(self._mac, bt_dev)
return dev_remote, bool(BTNap.prop_get(dev_remote, 'Connected'))
except BTError:
logging.debug("BT-TETHER: Device is not connected.")
return None, False
def is_paired(self): def is_paired(self):
""" """
@@ -198,7 +169,6 @@ class BTNap:
logging.debug("BT-TETHER: Device is not paired.") logging.debug("BT-TETHER: Device is not paired.")
return False return False
def wait_for_device(self, timeout=15): def wait_for_device(self, timeout=15):
""" """
Wait for device Wait for device
@@ -227,7 +197,7 @@ class BTNap:
try: try:
dev_remote = BTNap.find_device(self._mac, bt_dev) dev_remote = BTNap.find_device(self._mac, bt_dev)
logging.debug("BT-TETHER: Using remote device (addr: %s): %s", logging.debug("BT-TETHER: Using remote device (addr: %s): %s",
BTNap.prop_get(dev_remote, 'Address'), dev_remote.object_path ) BTNap.prop_get(dev_remote, 'Address'), dev_remote.object_path)
break break
except BTError: except BTError:
logging.debug("BT-TETHER: Not found yet ...") logging.debug("BT-TETHER: Not found yet ...")
@@ -249,7 +219,7 @@ class BTNap:
logging.debug('BT-TETHER: Trying to pair ...') logging.debug('BT-TETHER: Trying to pair ...')
try: try:
device.Pair() device.Pair()
logging.info('BT-TETHER: Successful paired with device ;)') logging.debug('BT-TETHER: Successful paired with device ;)')
return True return True
except dbus.exceptions.DBusException as err: except dbus.exceptions.DBusException as err:
if err.get_dbus_name() == 'org.bluez.Error.AlreadyExists': if err.get_dbus_name() == 'org.bluez.Error.AlreadyExists':
@@ -259,7 +229,6 @@ class BTNap:
pass pass
return False return False
@staticmethod @staticmethod
def nap(device): def nap(device):
logging.debug('BT-TETHER: Trying to nap ...') logging.debug('BT-TETHER: Trying to nap ...')
@@ -267,7 +236,7 @@ class BTNap:
try: try:
logging.debug('BT-TETHER: Connecting to profile ...') logging.debug('BT-TETHER: Connecting to profile ...')
device.ConnectProfile('nap') device.ConnectProfile('nap')
except Exception: # raises exception, but still works except Exception: # raises exception, but still works
pass pass
net = dbus.Interface(device, 'org.bluez.Network1') net = dbus.Interface(device, 'org.bluez.Network1')
@@ -275,15 +244,15 @@ class BTNap:
try: try:
logging.debug('BT-TETHER: Connecting to nap network ...') logging.debug('BT-TETHER: Connecting to nap network ...')
net.Connect('nap') net.Connect('nap')
return True return net, True
except dbus.exceptions.DBusException as err: except dbus.exceptions.DBusException as err:
if err.get_dbus_name() == 'org.bluez.Error.AlreadyConnected': if err.get_dbus_name() == 'org.bluez.Error.AlreadyConnected':
return True return net, True
connected = BTNap.prop_get(net, 'Connected') connected = BTNap.prop_get(net, 'Connected')
if not connected: if not connected:
return False return None, False
return True return net, True
class SystemdUnitWrapper: class SystemdUnitWrapper:
@@ -297,7 +266,7 @@ class SystemdUnitWrapper:
@staticmethod @staticmethod
def _action_on_unit(action, unit): def _action_on_unit(action, unit):
process = subprocess.Popen(f"systemctl {action} {unit}", shell=True, stdin=None, process = subprocess.Popen(f"systemctl {action} {unit}", shell=True, stdin=None,
stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash") stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
process.wait() process.wait()
if process.returncode > 0: if process.returncode > 0:
return False return False
@@ -309,7 +278,7 @@ class SystemdUnitWrapper:
Calls systemctl daemon-reload Calls systemctl daemon-reload
""" """
process = subprocess.Popen("systemctl daemon-reload", shell=True, stdin=None, process = subprocess.Popen("systemctl daemon-reload", shell=True, stdin=None,
stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash") stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
process.wait() process.wait()
if process.returncode > 0: if process.returncode > 0:
return False return False
@@ -387,24 +356,23 @@ class IfaceWrapper:
""" """
return open(f"{self.path}/operstate", 'r').read().rsplit('\n') == 'up' return open(f"{self.path}/operstate", 'r').read().rsplit('\n') == 'up'
def set_addr(self, addr): def set_addr(self, addr):
""" """
Set the netmask Set the netmask
""" """
process = subprocess.Popen(f"ip addr add {addr} dev {self.iface}", shell=True, stdin=None, process = subprocess.Popen(f"ip addr add {addr} dev {self.iface}", shell=True, stdin=None,
stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash") stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
process.wait() process.wait()
if process.returncode == 2 or process.returncode == 0: # 2 = already set if process.returncode == 2 or process.returncode == 0: # 2 = already set
return True return True
return False return False
@staticmethod @staticmethod
def set_route(addr): def set_route(gateway, device):
process = subprocess.Popen(f"ip route replace default via {addr}", shell=True, stdin=None, process = subprocess.Popen(f"ip route replace default via {gateway} dev {device}", shell=True, stdin=None,
stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash") stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
process.wait() process.wait()
if process.returncode > 0: if process.returncode > 0:
@@ -413,122 +381,190 @@ class IfaceWrapper:
return True return True
class Device:
def __init__(self, name, share_internet, mac, ip, netmask, interval, priority=10, scantime=15, search_order=0, max_tries=0, **kwargs):
self.name = name
self.status = StatusFile(f'/root/.bt-tether-{name}')
self.status.update()
self.tries = 0
self.network = None
def on_loaded(): self.max_tries = max_tries
""" self.search_order = search_order
Gets called when the plugin gets loaded self.share_internet = share_internet
""" self.ip = ip
global READY self.netmask = netmask
global INTERVAL self.interval = interval
self.mac = mac
self.scantime = scantime
self.priority = priority
for opt in ['share_internet', 'mac', 'ip', 'netmask', 'interval']: def connected(self):
if opt not in OPTIONS or (opt in OPTIONS and OPTIONS[opt] is None): """
logging.error("BT-TET: Please specify the %s in your config.yml.", opt) Checks if device is connected
"""
return self.network and BTNap.prop_get(self.network, 'Connected')
def interface(self):
"""
Returns the interface name or None
"""
if not self.connected():
return None
return BTNap.prop_get(self.network, 'Interface')
class BTTether(plugins.Plugin):
__author__ = '33197631+dadav@users.noreply.github.com'
__version__ = '1.0.0'
__license__ = 'GPL3'
__description__ = 'This makes the display reachable over bluetooth'
def __init__(self):
self.ready = False
self.options = dict()
self.devices = dict()
def on_loaded(self):
# new config
if 'devices' in self.options:
for device, options in self.options['devices'].items():
if 'enabled' in options and options['enabled']:
for device_opt in ['enabled', 'priority', 'scantime', 'search_order',
'max_tries', 'share_internet', 'mac', 'ip',
'netmask', 'interval']:
if device_opt not in options or (device_opt in options and options[device_opt] is None):
logging.error("BT-TETHER: Please specify the %s for device %s.",
device_opt, device)
break
else:
if options['enabled']:
self.devices[device] = Device(name=device, **options)
# legacy
if 'mac' in self.options:
for opt in ['share_internet', 'mac', 'ip', 'netmask', 'interval']:
if opt not in self.options or (opt in self.options and self.options[opt] is None):
logging.error("BT-TETHER: Please specify the %s in your config.yml.", opt)
return
self.devices['legacy'] = Device(name='legacy', **self.options)
if not self.devices:
logging.error("BT-TETHER: No valid devices found")
return return
# ensure bluetooth is running # ensure bluetooth is running
bt_unit = SystemdUnitWrapper('bluetooth.service') bt_unit = SystemdUnitWrapper('bluetooth.service')
if not bt_unit.is_active(): if not bt_unit.is_active():
if not bt_unit.start(): if not bt_unit.start():
logging.error("BT-TET: Can't start bluetooth.service") logging.error("BT-TETHER: Can't start bluetooth.service")
return
logging.info("BT-TETHER: Sussessfully loaded ...")
self.ready = True
def on_ui_setup(self, ui):
ui.add_element('bluetooth', LabeledValue(color=BLACK, label='BT', value='-', position=(ui.width() / 2 - 15, 0),
label_font=fonts.Bold, text_font=fonts.Medium))
def on_ui_update(self, ui):
if not self.ready:
return return
INTERVAL.update() devices_to_try = list()
READY = True connected_priorities = list()
any_device_connected = False # if this is true, last status on screen should be C
for _, device in self.devices.items():
if device.connected():
connected_priorities.append(device.priority)
any_device_connected = True
continue
def on_ui_update(ui): if not device.max_tries or (device.max_tries > device.tries):
""" if not device.status.newer_then_minutes(device.interval):
Try to connect to device devices_to_try.append(device)
""" device.status.update()
device.tries += 1
if READY: sorted_devices = sorted(devices_to_try, key=lambda x: x.search_order)
global INTERVAL
if INTERVAL.newer_then_minutes(OPTIONS['interval']):
return
INTERVAL.update() for device in sorted_devices:
bt = BTNap(device.mac)
bt = BTNap(OPTIONS['mac']) try:
logging.debug('BT-TETHER: Search %d secs for %s ...', device.scantime, device.name)
logging.debug('BT-TETHER: Check if already connected and paired') dev_remote = bt.wait_for_device(timeout=device.scantime)
dev_remote, connected = bt.is_connected() if dev_remote is None:
logging.debug('BT-TETHER: Could not find %s, try again in %d minutes.', device.name, device.interval)
if connected: ui.set('bluetooth', 'NF')
logging.debug('BT-TETHER: Already connected.') continue
ui.set('bluetooth', 'C') except Exception as bt_ex:
return logging.error(bt_ex)
try:
logging.info('BT-TETHER: Search device ...')
dev_remote = bt.wait_for_device()
if dev_remote is None:
logging.info('BT-TETHER: Could not find device.')
ui.set('bluetooth', 'NF') ui.set('bluetooth', 'NF')
return continue
except Exception as bt_ex:
logging.error(bt_ex)
ui.set('bluetooth', 'NF')
return
paired = bt.is_paired() paired = bt.is_paired()
if not paired: if not paired:
if BTNap.pair(dev_remote): if BTNap.pair(dev_remote):
logging.info('BT-TETHER: Paired with device.') logging.debug('BT-TETHER: Paired with %s.', device.name)
else:
logging.debug('BT-TETHER: Pairing with %s failed ...', device.name)
ui.set('bluetooth', 'PE')
continue
else: else:
logging.info('BT-TETHER: Pairing failed ...') logging.debug('BT-TETHER: Already paired.')
ui.set('bluetooth', 'PE')
return
else:
logging.debug('BT-TETHER: Already paired.')
btnap_iface = IfaceWrapper('bnep0') logging.debug('BT-TETHER: Try to create nap connection with %s ...', device.name)
logging.debug('BT-TETHER: Check interface') device.network, success = BTNap.nap(dev_remote)
if not btnap_iface.exists(): interface = None
# connected and paired but not napping
logging.debug('BT-TETHER: Try to connect to nap ...') if success:
if BTNap.nap(dev_remote): try:
logging.info('BT-TETHER: Napping!') interface = device.interface()
except Exception:
logging.debug('BT-TETHER: Could not establish nap connection with %s', device.name)
continue
if interface is None:
ui.set('bluetooth', 'BE')
logging.debug('BT-TETHER: Could not establish nap connection with %s', device.name)
continue
logging.debug('BT-TETHER: Created interface (%s)', interface)
ui.set('bluetooth', 'C') ui.set('bluetooth', 'C')
time.sleep(5) any_device_connected = True
device.tries = 0 # reset tries
else: else:
logging.info('BT-TETHER: Napping failed ...') logging.debug('BT-TETHER: Could not establish nap connection with %s', device.name)
ui.set('bluetooth', 'NF') ui.set('bluetooth', 'NF')
return continue
if btnap_iface.exists(): addr = f"{device.ip}/{device.netmask}"
logging.debug('BT-TETHER: Interface found') gateway = ".".join(device.ip.split('.')[:-1] + ['1'])
# check ip wrapped_interface = IfaceWrapper(interface)
addr = f"{OPTIONS['ip']}/{OPTIONS['netmask']}" logging.debug('BT-TETHER: Add ip to %s', interface)
if not wrapped_interface.set_addr(addr):
logging.debug('BT-TETHER: Try to set ADDR to interface')
if not btnap_iface.set_addr(addr):
ui.set('bluetooth', 'AE') ui.set('bluetooth', 'AE')
logging.error("BT-TETHER: Could not set ip of bnep0 to %s", addr) logging.debug("BT-TETHER: Could not add ip to %s", interface)
return continue
logging.debug('BT-TETHER: Set ADDR to interface') if device.share_internet:
if not connected_priorities or device.priority > max(connected_priorities):
logging.debug('BT-TETHER: Set default route to %s via %s', gateway, interface)
IfaceWrapper.set_route(gateway, interface)
connected_priorities.append(device.priority)
# change route if sharking logging.debug('BT-TETHER: Change resolv.conf if necessary ...')
if OPTIONS['share_internet']: with open('/etc/resolv.conf', 'r+') as resolv:
logging.debug('BT-TETHER: Set routing and change resolv.conf') nameserver = resolv.read()
IfaceWrapper.set_route(".".join(OPTIONS['ip'].split('.')[:-1] + ['1'])) # im not proud about that if 'nameserver 9.9.9.9' not in nameserver:
# fix resolv.conf; dns over https ftw! logging.debug('BT-TETHER: Added nameserver')
with open('/etc/resolv.conf', 'r+') as resolv: resolv.seek(0)
nameserver = resolv.read() resolv.write(nameserver + 'nameserver 9.9.9.9\n')
if 'nameserver 9.9.9.9' not in nameserver:
logging.info('BT-TETHER: Added nameserver')
resolv.seek(0)
resolv.write(nameserver + 'nameserver 9.9.9.9\n')
if any_device_connected:
ui.set('bluetooth', 'C') ui.set('bluetooth', 'C')
else:
logging.error('BT-TETHER: bnep0 not found')
ui.set('bluetooth', 'BE')
def on_ui_setup(ui):
ui.add_element('bluetooth', LabeledValue(color=BLACK, label='BT', value='-', position=(ui.width() / 2 - 15, 0),
label_font=fonts.Bold, text_font=fonts.Medium))

View File

@@ -1,182 +1,159 @@
__author__ = 'evilsocket@gmail.com'
__version__ = '1.0.0'
__name__ = 'hello_world'
__license__ = 'GPL3'
__description__ = 'An example plugin for pwnagotchi that implements all the available callbacks.'
import logging import logging
import pwnagotchi.plugins as plugins
from pwnagotchi.ui.components import LabeledValue from pwnagotchi.ui.components import LabeledValue
from pwnagotchi.ui.view import BLACK from pwnagotchi.ui.view import BLACK
import pwnagotchi.ui.fonts as fonts import pwnagotchi.ui.fonts as fonts
# Will be set with the options in config.yml config['main']['plugins'][__name__] class Example(plugins.Plugin):
OPTIONS = dict() __author__ = 'evilsocket@gmail.com'
__version__ = '1.0.0'
__license__ = 'GPL3'
__description__ = 'An example plugin for pwnagotchi that implements all the available callbacks.'
# called when <host>:<port>/plugins/<pluginname> is opened def __init__(self):
def on_webhook(response, path): logging.debug("example plugin created")
res = "<html><body><a>Hook triggered</a></body></html>"
response.send_response(200)
response.send_header('Content-type', 'text/html')
response.end_headers()
try: # called when http://<host>:<port>/plugins/<plugin>/ is called
response.wfile.write(bytes(res, "utf-8")) # must return a response
except Exception as ex: def on_webhook(self, path, args, req_method):
logging.error(ex) pass
# called when the plugin is loaded # called when the plugin is loaded
def on_loaded(): def on_loaded(self):
logging.warning("WARNING: plugin %s should be disabled!" % __name__) logging.warning("WARNING: this plugin should be disabled! options = " % self.options)
# called when <host>:<port>/plugins/<pluginname> is opened
def on_webhook(self, response, path):
res = "<html><body><a>Hook triggered</a></body></html>"
response.send_response(200)
response.send_header('Content-type', 'text/html')
response.end_headers()
# called in manual mode when there's internet connectivity try:
def on_internet_available(agent): response.wfile.write(bytes(res, "utf-8"))
pass except Exception as ex:
logging.error(ex)
# called in manual mode when there's internet connectivity
def on_internet_available(self, agent):
pass
# called to setup the ui elements # called to setup the ui elements
def on_ui_setup(ui): def on_ui_setup(self, ui):
# add custom UI elements # add custom UI elements
ui.add_element('ups', LabeledValue(color=BLACK, label='UPS', value='0%/0V', position=(ui.width() / 2 - 25, 0), ui.add_element('ups', LabeledValue(color=BLACK, label='UPS', value='0%/0V', position=(ui.width() / 2 - 25, 0),
label_font=fonts.Bold, text_font=fonts.Medium)) label_font=fonts.Bold, text_font=fonts.Medium))
# called when the ui is updated
def on_ui_update(self, ui):
# update those elements
some_voltage = 0.1
some_capacity = 100.0
ui.set('ups', "%4.2fV/%2i%%" % (some_voltage, some_capacity))
# called when the ui is updated # called when the hardware display setup is done, display is an hardware specific object
def on_ui_update(ui): def on_display_setup(self, display):
# update those elements pass
some_voltage = 0.1
some_capacity = 100.0
ui.set('ups', "%4.2fV/%2i%%" % (some_voltage, some_capacity)) # called when everything is ready and the main loop is about to start
def on_ready(self, agent):
logging.info("unit is ready")
# you can run custom bettercap commands if you want
# agent.run('ble.recon on')
# or set a custom state
# agent.set_bored()
# called when the AI finished loading
def on_ai_ready(self, agent):
pass
# called when the hardware display setup is done, display is an hardware specific object # called when the AI finds a new set of parameters
def on_display_setup(display): def on_ai_policy(self, agent, policy):
pass pass
# called when the AI starts training for a given number of epochs
def on_ai_training_start(self, agent, epochs):
pass
# called when everything is ready and the main loop is about to start # called after the AI completed a training epoch
def on_ready(agent): def on_ai_training_step(self, agent, _locals, _globals):
logging.info("unit is ready") pass
# you can run custom bettercap commands if you want
# agent.run('ble.recon on')
# or set a custom state
# agent.set_bored()
# called when the AI has done training
def on_ai_training_end(self, agent):
pass
# called when the AI finished loading # called when the AI got the best reward so far
def on_ai_ready(agent): def on_ai_best_reward(self, agent, reward):
pass pass
# called when the AI got the worst reward so far
def on_ai_worst_reward(self, agent, reward):
pass
# called when the AI finds a new set of parameters # called when a non overlapping wifi channel is found to be free
def on_ai_policy(agent, policy): def on_free_channel(self, agent, channel):
pass pass
# called when the status is set to bored
def on_bored(self, agent):
pass
# called when the AI starts training for a given number of epochs # called when the status is set to sad
def on_ai_training_start(agent, epochs): def on_sad(self, agent):
pass pass
# called when the status is set to excited
def on_excited(aself, gent):
pass
# called after the AI completed a training epoch # called when the status is set to lonely
def on_ai_training_step(agent, _locals, _globals): def on_lonely(self, agent):
pass pass
# called when the agent is rebooting the board
def on_rebooting(self, agent):
pass
# called when the AI has done training # called when the agent is waiting for t seconds
def on_ai_training_end(agent): def on_wait(self, agent, t):
pass pass
# called when the agent is sleeping for t seconds
def on_sleep(self, agent, t):
pass
# called when the AI got the best reward so far # called when the agent refreshed its access points list
def on_ai_best_reward(agent, reward): def on_wifi_update(self, agent, access_points):
pass pass
# called when the agent is sending an association frame
def on_association(self, agent, access_point):
pass
# called when the AI got the worst reward so far # called when the agent is deauthenticating a client station from an AP
def on_ai_worst_reward(agent, reward): def on_deauthentication(self, agent, access_point, client_station):
pass pass
# callend when the agent is tuning on a specific channel
def on_channel_hop(self, agent, channel):
pass
# called when a non overlapping wifi channel is found to be free # called when a new handshake is captured, access_point and client_station are json objects
def on_free_channel(agent, channel): # if the agent could match the BSSIDs to the current list, otherwise they are just the strings of the BSSIDs
pass def on_handshake(self, agent, filename, access_point, client_station):
pass
# called when an epoch is over (where an epoch is a single loop of the main algorithm)
def on_epoch(self, agent, epoch, epoch_data):
pass
# called when the status is set to bored # called when a new peer is detected
def on_bored(agent): def on_peer_detected(self, agent, peer):
pass pass
# called when a known peer is lost
# called when the status is set to sad def on_peer_lost(self, agent, peer):
def on_sad(agent): pass
pass
# called when the status is set to excited
def on_excited(agent):
pass
# called when the status is set to lonely
def on_lonely(agent):
pass
# called when the agent is rebooting the board
def on_rebooting(agent):
pass
# called when the agent is waiting for t seconds
def on_wait(agent, t):
pass
# called when the agent is sleeping for t seconds
def on_sleep(agent, t):
pass
# called when the agent refreshed its access points list
def on_wifi_update(agent, access_points):
pass
# called when the agent is sending an association frame
def on_association(agent, access_point):
pass
# callend when the agent is deauthenticating a client station from an AP
def on_deauthentication(agent, access_point, client_station):
pass
# callend when the agent is tuning on a specific channel
def on_channel_hop(agent, channel):
pass
# called when a new handshake is captured, access_point and client_station are json objects
# if the agent could match the BSSIDs to the current list, otherwise they are just the strings of the BSSIDs
def on_handshake(agent, filename, access_point, client_station):
pass
# called when an epoch is over (where an epoch is a single loop of the main algorithm)
def on_epoch(agent, epoch, epoch_data):
pass
# called when a new peer is detected
def on_peer_detected(agent, peer):
pass
# called when a known peer is lost
def on_peer_lost(agent, peer):
pass

View File

@@ -1,38 +1,40 @@
__author__ = 'ratmandu@gmail.com'
__version__ = '1.0.0'
__name__ = 'gpio_buttons'
__license__ = 'GPL3'
__description__ = 'GPIO Button support plugin'
import logging import logging
import RPi.GPIO as GPIO import RPi.GPIO as GPIO
import subprocess import subprocess
import pwnagotchi.plugins as plugins
running = False
OPTIONS = dict()
GPIOs = {}
COMMANDs = None
def runCommand(channel):
command = GPIOs[channel]
logging.info(f"Button Pressed! Running command: {command}")
process = subprocess.Popen(command, shell=True, stdin=None, stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
process.wait()
def on_loaded(): class GPIOButtons(plugins.Plugin):
logging.info("GPIO Button plugin loaded.") __author__ = 'ratmandu@gmail.com'
__version__ = '1.0.0'
__license__ = 'GPL3'
__description__ = 'GPIO Button support plugin'
#get list of GPIOs def __init__(self):
gpios = OPTIONS['gpios'] self.running = False
self.ports = {}
self.commands = None
#set gpio numbering def runCommand(self, channel):
GPIO.setmode(GPIO.BCM) command = self.ports[channel]
logging.info(f"Button Pressed! Running command: {command}")
process = subprocess.Popen(command, shell=True, stdin=None, stdout=open("/dev/null", "w"), stderr=None,
executable="/bin/bash")
process.wait()
for i in gpios: def on_loaded(self):
gpio = list(i)[0] logging.info("GPIO Button plugin loaded.")
command = i[gpio]
GPIOs[gpio] = command # get list of GPIOs
GPIO.setup(gpio, GPIO.IN, GPIO.PUD_UP) gpios = self.options['gpios']
GPIO.add_event_detect(gpio, GPIO.FALLING, callback=runCommand, bouncetime=250)
logging.info("Added command: %s to GPIO #%d", command, gpio) # set gpio numbering
GPIO.setmode(GPIO.BCM)
for i in gpios:
gpio = list(i)[0]
command = i[gpio]
self.ports[gpio] = command
GPIO.setup(gpio, GPIO.IN, GPIO.PUD_UP)
GPIO.add_event_detect(gpio, GPIO.FALLING, callback=self.runCommand, bouncetime=250)
logging.info("Added command: %s to GPIO #%d", command, gpio)

View File

@@ -1,45 +1,42 @@
__author__ = 'evilsocket@gmail.com'
__version__ = '1.0.0'
__name__ = 'gps'
__license__ = 'GPL3'
__description__ = 'Save GPS coordinates whenever an handshake is captured.'
import logging import logging
import json import json
import os import os
import pwnagotchi.plugins as plugins
running = False
OPTIONS = dict()
def on_loaded(): class GPS(plugins.Plugin):
logging.info("gps plugin loaded for %s" % OPTIONS['device']) __author__ = 'evilsocket@gmail.com'
__version__ = '1.0.0'
__license__ = 'GPL3'
__description__ = 'Save GPS coordinates whenever an handshake is captured.'
def __init__(self):
self.running = False
def on_ready(agent): def on_loaded(self):
global running logging.info("gps plugin loaded for %s" % self.options['device'])
if os.path.exists(OPTIONS['device']): def on_ready(self, agent):
logging.info("enabling gps bettercap's module for %s" % OPTIONS['device']) if os.path.exists(self.options['device']):
try: logging.info("enabling gps bettercap's module for %s" % self.options['device'])
agent.run('gps off') try:
except: agent.run('gps off')
pass except:
pass
agent.run('set gps.device %s' % OPTIONS['device']) agent.run('set gps.device %s' % self.options['device'])
agent.run('set gps.speed %d' % OPTIONS['speed']) agent.run('set gps.speed %d' % self.options['speed'])
agent.run('gps on') agent.run('gps on')
running = True running = True
else: else:
logging.warning("no GPS detected") logging.warning("no GPS detected")
def on_handshake(self, agent, filename, access_point, client_station):
if self.running:
info = agent.session()
gps = info['gps']
gps_filename = filename.replace('.pcap', '.gps.json')
def on_handshake(agent, filename, access_point, client_station): logging.info("saving GPS to %s (%s)" % (gps_filename, gps))
if running: with open(gps_filename, 'w+t') as fp:
info = agent.session() json.dump(gps, fp)
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)

View File

@@ -1,28 +1,13 @@
__author__ = 'evilsocket@gmail.com'
__version__ = '1.0.1'
__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 os
import logging import logging
import time import time
import glob import glob
import re
import pwnagotchi.grid as grid import pwnagotchi.grid as grid
import pwnagotchi.plugins as plugins
from pwnagotchi.utils import StatusFile, WifiInfo, extract_from_pcap from pwnagotchi.utils import StatusFile, WifiInfo, extract_from_pcap
OPTIONS = dict()
REPORT = StatusFile('/root/.api-report.json', data_format='json')
UNREAD_MESSAGES = 0
TOTAL_MESSAGES = 0
def on_loaded():
logging.info("grid plugin loaded.")
def parse_pcap(filename): def parse_pcap(filename):
logging.info("grid: parsing %s ..." % filename) logging.info("grid: parsing %s ..." % filename)
@@ -36,6 +21,10 @@ def parse_pcap(filename):
# /root/handshakes/BSSID.pcap # /root/handshakes/BSSID.pcap
essid, bssid = '', net_id essid, bssid = '', net_id
mac_re = re.compile('[0-9a-fA-F]{12}')
if not mac_re.match(bssid):
return '', ''
it = iter(bssid) it = iter(bssid)
bssid = ':'.join([a + b for a, b in zip(it, it)]) bssid = ':'.join([a + b for a, b in zip(it, it)])
@@ -52,93 +41,100 @@ def parse_pcap(filename):
return info[WifiInfo.ESSID], info[WifiInfo.BSSID] return info[WifiInfo.ESSID], info[WifiInfo.BSSID]
def is_excluded(what): class Grid(plugins.Plugin):
for skip in OPTIONS['exclude']: __author__ = 'evilsocket@gmail.com'
skip = skip.lower() __version__ = '1.0.1'
what = what.lower() __license__ = 'GPL3'
if skip in what or skip.replace(':', '') in what: __description__ = 'This plugin signals the unit cryptographic identity and list of pwned networks and list of pwned ' \
return True 'networks to api.pwnagotchi.ai '
return False
def __init__(self):
self.options = dict()
self.report = StatusFile('/root/.api-report.json', data_format='json')
def set_reported(reported, net_id): self.unread_messages = 0
global REPORT self.total_messages = 0
reported.append(net_id)
REPORT.update(data={'reported': reported})
def is_excluded(self, what):
for skip in self.options['exclude']:
skip = skip.lower()
what = what.lower()
if skip in what or skip.replace(':', '') in what:
return True
return False
def check_inbox(agent): def on_loaded(self):
global REPORT, UNREAD_MESSAGES, TOTAL_MESSAGES logging.info("grid plugin loaded.")
logging.debug("checking mailbox ...") def set_reported(self, reported, net_id):
reported.append(net_id)
self.report.update(data={'reported': reported})
messages = grid.inbox() def check_inbox(self, agent):
TOTAL_MESSAGES = len(messages) logging.debug("checking mailbox ...")
UNREAD_MESSAGES = len([m for m in messages if m['seen_at'] is None]) messages = grid.inbox()
self.total_messages = len(messages)
self.unread_messages = len([m for m in messages if m['seen_at'] is None])
if UNREAD_MESSAGES: if self.unread_messages:
logging.debug("[grid] unread:%d total:%d" % (UNREAD_MESSAGES, TOTAL_MESSAGES)) logging.debug("[grid] unread:%d total:%d" % (self.unread_messages, self.total_messages))
agent.view().on_unread_messages(UNREAD_MESSAGES, TOTAL_MESSAGES) agent.view().on_unread_messages(self.unread_messages, self.total_messages)
def check_handshakes(self, agent):
logging.debug("checking pcaps")
def check_handshakes(agent): pcap_files = glob.glob(os.path.join(agent.config()['bettercap']['handshakes'], "*.pcap"))
logging.debug("checking pcaps") num_networks = len(pcap_files)
reported = self.report.data_field_or('reported', default=[])
num_reported = len(reported)
num_new = num_networks - num_reported
pcap_files = glob.glob(os.path.join(agent.config()['bettercap']['handshakes'], "*.pcap")) if num_new > 0:
num_networks = len(pcap_files) if self.options['report']:
reported = REPORT.data_field_or('reported', default=[]) logging.info("grid: %d new networks to report" % num_new)
num_reported = len(reported) logging.debug("self.options: %s" % self.options)
num_new = num_networks - num_reported logging.debug(" exclude: %s" % self.options['exclude'])
if num_new > 0: for pcap_file in pcap_files:
if OPTIONS['report']: net_id = os.path.basename(pcap_file).replace('.pcap', '')
logging.info("grid: %d new networks to report" % num_new) if net_id not in reported:
logging.debug("OPTIONS: %s" % OPTIONS) if self.is_excluded(net_id):
logging.debug(" exclude: %s" % OPTIONS['exclude']) logging.debug("skipping %s due to exclusion filter" % pcap_file)
self.set_reported(reported, net_id)
continue
for pcap_file in pcap_files: essid, bssid = parse_pcap(pcap_file)
net_id = os.path.basename(pcap_file).replace('.pcap', '') if bssid:
if net_id not in reported: if self.is_excluded(essid) or self.is_excluded(bssid):
if is_excluded(net_id): logging.debug("not reporting %s due to exclusion filter" % pcap_file)
logging.debug("skipping %s due to exclusion filter" % pcap_file) self.set_reported(reported, net_id)
set_reported(reported, net_id) else:
continue if grid.report_ap(essid, bssid):
self.set_reported(reported, net_id)
essid, bssid = parse_pcap(pcap_file) time.sleep(1.5)
if bssid:
if is_excluded(essid) or is_excluded(bssid):
logging.debug("not reporting %s due to exclusion filter" % pcap_file)
set_reported(reported, net_id)
else: else:
if grid.report_ap(essid, bssid): logging.warning("no bssid found?!")
set_reported(reported, net_id) else:
time.sleep(1.5) logging.debug("grid: reporting disabled")
else:
logging.warning("no bssid found?!")
else:
logging.debug("grid: reporting disabled")
def on_internet_available(self, agent):
logging.debug("internet available")
def on_internet_available(agent): try:
global REPORT, UNREAD_MESSAGES, TOTAL_MESSAGES grid.update_data(agent.last_session)
except Exception as e:
logging.error("error connecting to the pwngrid-peer service: %s" % e)
logging.debug(e, exc_info=True)
return
logging.debug("internet available") try:
self.check_inbox(agent)
except Exception as e:
logging.error("[grid] error while checking inbox: %s" % e)
logging.debug(e, exc_info=True)
try: try:
grid.update_data(agent.last_session) self.check_handshakes(agent)
except Exception as e: except Exception as e:
logging.error("error connecting to the pwngrid-peer service: %s" % e) logging.error("[grid] error while checking pcaps: %s" % e)
logging.debug(e, exc_info=True) logging.debug(e, exc_info=True)
return
try:
check_inbox(agent)
except Exception as e:
logging.error("[grid] error while checking inbox: %s" % e)
logging.debug(e, exc_info=True)
try:
check_handshakes(agent)
except Exception as e:
logging.error("[grid] error while checking pcaps: %s" % e)
logging.debug(e, exc_info=True)

View File

@@ -17,48 +17,51 @@
# - Added horizontal and vertical orientation # - Added horizontal and vertical orientation
# #
############################################################### ###############################################################
__author__ = 'https://github.com/xenDE'
__version__ = '1.0.1'
__name__ = 'memtemp'
__license__ = 'GPL3'
__description__ = 'A plugin that will display memory/cpu usage and temperature'
from pwnagotchi.ui.components import LabeledValue from pwnagotchi.ui.components import LabeledValue
from pwnagotchi.ui.view import BLACK from pwnagotchi.ui.view import BLACK
import pwnagotchi.ui.fonts as fonts import pwnagotchi.ui.fonts as fonts
import pwnagotchi.plugins as plugins
import pwnagotchi import pwnagotchi
import logging import logging
OPTIONS = dict()
class MemTemp(plugins.Plugin):
__author__ = 'https://github.com/xenDE'
__version__ = '1.0.1'
__license__ = 'GPL3'
__description__ = 'A plugin that will display memory/cpu usage and temperature'
def on_loaded(): def on_loaded(self):
logging.info("memtemp plugin loaded.") logging.info("memtemp plugin loaded.")
def mem_usage(self):
return int(pwnagotchi.mem_usage() * 100)
def mem_usage(): def cpu_load(self):
return int(pwnagotchi.mem_usage() * 100) return int(pwnagotchi.cpu_load() * 100)
def on_ui_setup(self, ui):
if ui.is_waveshare_v2():
h_pos = (180, 80)
v_pos = (180, 61)
else:
h_pos = (155, 76)
v_pos = (180, 61)
def cpu_load(): if self.options['orientation'] == "horizontal":
return int(pwnagotchi.cpu_load() * 100) ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value='mem cpu temp\n - - -',
position=h_pos,
label_font=fonts.Small, text_font=fonts.Small))
elif self.options['orientation'] == "vertical":
ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value=' mem:-\n cpu:-\ntemp:-',
position=v_pos,
label_font=fonts.Small, text_font=fonts.Small))
def on_ui_update(self, ui):
if self.options['orientation'] == "horizontal":
ui.set('memtemp',
" mem cpu temp\n %s%% %s%% %sc" % (self.mem_usage(), self.cpu_load(), pwnagotchi.temperature()))
def on_ui_setup(ui): elif self.options['orientation'] == "vertical":
if OPTIONS['orientation'] == "horizontal": ui.set('memtemp',
ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value='mem cpu temp\n - - -', " mem:%s%%\n cpu:%s%%\ntemp:%sc" % (self.mem_usage(), self.cpu_load(), pwnagotchi.temperature()))
position=(ui.width() / 2 + 30, ui.height() / 2 + 15),
label_font=fonts.Small, text_font=fonts.Small))
elif OPTIONS['orientation'] == "vertical":
ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value=' mem:-\n cpu:-\ntemp:-',
position=(ui.width() / 2 + 55, ui.height() / 2),
label_font=fonts.Small, text_font=fonts.Small))
def on_ui_update(ui):
if OPTIONS['orientation'] == "horizontal":
ui.set('memtemp', " mem cpu temp\n %s%% %s%% %sc" % (mem_usage(), cpu_load(), pwnagotchi.temperature()))
elif OPTIONS['orientation'] == "vertical":
ui.set('memtemp', " mem:%s%%\n cpu:%s%%\ntemp:%sc" % (mem_usage(), cpu_load(), pwnagotchi.temperature()))

View File

@@ -1,140 +1,139 @@
__author__ = 'zenzen san'
__version__ = '2.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 logging
import json import json
import os import os
import requests import requests
import time
import pwnagotchi.plugins as plugins
from pwnagotchi.utils import StatusFile from pwnagotchi.utils import StatusFile
MOZILLA_API_URL = 'https://location.services.mozilla.com/v1/geolocate?key={api}' MOZILLA_API_URL = 'https://location.services.mozilla.com/v1/geolocate?key={api}'
REPORT = StatusFile('/root/.net_pos_saved', data_format='json')
SKIP = list()
READY = False
OPTIONS = dict()
def on_loaded(): class NetPos(plugins.Plugin):
global READY __author__ = 'zenzen san'
__version__ = '2.0.1'
__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 """
if 'api_key' not in OPTIONS or ('api_key' in OPTIONS and OPTIONS['api_key'] is None): def __init__(self):
logging.error("NET-POS: api_key isn't set. Can't use mozilla's api.") self.report = StatusFile('/root/.net_pos_saved', data_format='json')
return self.skip = list()
self.ready = False
READY = True def on_loaded(self):
if 'api_key' not in self.options or ('api_key' in self.options and self.options['api_key'] is None):
logging.error("NET-POS: api_key isn't set. Can't use mozilla's api.")
return
logging.info("net-pos plugin loaded.") self.ready = True
logging.info("net-pos plugin loaded.")
def _append_saved(path): def _append_saved(self, path):
to_save = list() to_save = list()
if isinstance(path, str): if isinstance(path, str):
to_save.append(path) to_save.append(path)
elif isinstance(path, list): elif isinstance(path, list):
to_save += path to_save += path
else: else:
raise TypeError("Expected list or str, got %s" % type(path)) raise TypeError("Expected list or str, got %s" % type(path))
with open('/root/.net_pos_saved', 'a') as saved_file: with open('/root/.net_pos_saved', 'a') as saved_file:
for x in to_save: for x in to_save:
saved_file.write(x + "\n") saved_file.write(x + "\n")
def on_internet_available(agent): def on_internet_available(self, agent):
global SKIP if self.ready:
global REPORT config = agent.config()
display = agent.view()
reported = self.report.data_field_or('reported', default=list())
handshake_dir = config['bettercap']['handshakes']
if READY: all_files = os.listdir(handshake_dir)
config = agent.config() all_np_files = [os.path.join(handshake_dir, filename)
display = agent.view() for filename in all_files
reported = REPORT.data_field_or('reported', default=list()) if filename.endswith('.net-pos.json')]
handshake_dir = config['bettercap']['handshakes'] new_np_files = set(all_np_files) - set(reported) - set(self.skip)
all_files = os.listdir(handshake_dir) if new_np_files:
all_np_files = [os.path.join(handshake_dir, filename) logging.info("NET-POS: Found %d new net-pos files. Fetching positions ...", len(new_np_files))
for filename in all_files display.set('status', f"Found {len(new_np_files)} new net-pos files. Fetching positions ...")
if filename.endswith('.net-pos.json')]
new_np_files = set(all_np_files) - set(reported) - set(SKIP)
if new_np_files:
logging.info("NET-POS: Found %d 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
reported.append(np_file)
REPORT.update(data={'reported': reported})
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
continue
except json.JSONDecodeError as js_e:
logging.error("NET-POS: %s", js_e)
SKIP += np_file
continue
except OSError as os_e:
logging.error("NET-POS: %s", os_e)
SKIP += np_file
continue
with open(geo_file, 'w+t') as sf:
json.dump(geo_data, sf)
reported.append(np_file)
REPORT.update(data={'reported': reported})
display.set('status', f"Fetching positions ({idx+1}/{len(new_np_files)})")
display.update(force=True) 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
reported.append(np_file)
self.report.update(data={'reported': reported})
continue
def on_handshake(agent, filename, access_point, client_station): try:
netpos = _get_netpos(agent) geo_data = self._get_geo_data(np_file) # returns json obj
netpos_filename = filename.replace('.pcap', '.net-pos.json') except requests.exceptions.RequestException as req_e:
logging.info("NET-POS: Saving net-location to %s", netpos_filename) logging.error("NET-POS: %s", req_e)
self.skip += np_file
continue
except json.JSONDecodeError as js_e:
logging.error("NET-POS: %s", js_e)
self.skip += np_file
continue
except OSError as os_e:
logging.error("NET-POS: %s", os_e)
self.skip += np_file
continue
try: with open(geo_file, 'w+t') as sf:
with open(netpos_filename, 'w+t') as net_pos_file: json.dump(geo_data, sf)
json.dump(netpos, net_pos_file)
except OSError as os_e:
logging.error("NET-POS: %s", os_e)
reported.append(np_file)
self.report.update(data={'reported': reported})
def _get_netpos(agent): display.set('status', f"Fetching positions ({idx + 1}/{len(new_np_files)})")
aps = agent.get_access_points() display.update(force=True)
netpos = dict()
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): def on_handshake(self, agent, filename, access_point, client_station):
geourl = MOZILLA_API_URL.format(api=OPTIONS['api_key']) netpos = self._get_netpos(agent)
netpos["ts"] = int("%.0f" % time.time())
netpos_filename = filename.replace('.pcap', '.net-pos.json')
logging.info("NET-POS: Saving net-location to %s", netpos_filename)
try: try:
with open(path, "r") as json_file: with open(netpos_filename, 'w+t') as net_pos_file:
data = json.load(json_file) json.dump(netpos, net_pos_file)
except json.JSONDecodeError as js_e: except OSError as os_e:
raise js_e logging.error("NET-POS: %s", os_e)
except OSError as os_e:
raise os_e
try: def _get_netpos(self, agent):
result = requests.post(geourl, aps = agent.get_access_points()
json=data, netpos = dict()
timeout=timeout) netpos['wifiAccessPoints'] = list()
return result.json() # 6 seems a good number to save a wifi networks location
except requests.exceptions.RequestException as req_e: for access_point in sorted(aps, key=lambda i: i['rssi'], reverse=True)[:6]:
raise req_e netpos['wifiAccessPoints'].append({'macAddress': access_point['mac'],
'signalStrength': access_point['rssi']})
return netpos
def _get_geo_data(self, path, timeout=30):
geourl = MOZILLA_API_URL.format(api=self.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_geo = result.json()
if data["ts"]:
return_geo["ts"] = data["ts"]
return return_geo
except requests.exceptions.RequestException as req_e:
raise req_e

View File

@@ -1,86 +1,105 @@
__author__ = '33197631+dadav@users.noreply.github.com'
__version__ = '2.0.0'
__name__ = 'onlinehashcrack'
__license__ = 'GPL3'
__description__ = 'This plugin automatically uploads handshakes to https://onlinehashcrack.com'
import os import os
import logging import logging
import re
import requests import requests
from pwnagotchi.utils import StatusFile from pwnagotchi.utils import StatusFile
import pwnagotchi.plugins as plugins
READY = False
REPORT = StatusFile('/root/.ohc_uploads', data_format='json')
SKIP = list()
OPTIONS = dict()
def on_loaded(): class OnlineHashCrack(plugins.Plugin):
""" __author__ = '33197631+dadav@users.noreply.github.com'
Gets called when the plugin gets loaded __version__ = '2.0.0'
""" __license__ = 'GPL3'
global READY __description__ = 'This plugin automatically uploads handshakes to https://onlinehashcrack.com'
if 'email' not in OPTIONS or ('email' in OPTIONS and OPTIONS['email'] is None): def __init__(self):
logging.error("OHC: Email isn't set. Can't upload to onlinehashcrack.com") self.ready = False
return self.report = StatusFile('/root/.ohc_uploads', data_format='json')
self.skip = list()
READY = True def on_loaded(self):
"""
Gets called when the plugin gets loaded
"""
if 'email' not in self.options or ('email' in self.options and self.options['email'] is None):
logging.error("OHC: Email isn't set. Can't upload to onlinehashcrack.com")
return
if 'whitelist' not in self.options:
self.options['whitelist'] = []
def _upload_to_ohc(path, timeout=30): # remove special characters from whitelist APs to match on-disk format
""" self.options['whitelist'] = set(map(lambda x: re.sub(r'[^a-zA-Z0-9]', '', x), self.options['whitelist']))
Uploads the file to onlinehashcrack.com
"""
with open(path, 'rb') as file_to_upload:
data = {'email': OPTIONS['email']}
payload = {'file': file_to_upload}
self.ready = True
def _filter_handshake_file(self, handshake_filename):
try: try:
result = requests.post('https://api.onlinehashcrack.com', basename = os.path.basename(handshake_filename)
data=data, ssid, bssid = basename.split('_')
files=payload, # remove the ".pcap" from the bssid (which is really just the end of the filename)
timeout=timeout) bssid = bssid[:-5]
if 'already been sent' in result.text: except:
logging.warning(f"{path} was already uploaded.") # something failed in our parsing of the filename. let the file through
except requests.exceptions.RequestException as e: return True
logging.error(f"OHC: Got an exception while uploading {path} -> {e}")
raise e
return ssid not in self.options['whitelist'] and bssid not in self.options['whitelist']
def on_internet_available(agent): def _upload_to_ohc(self, path, timeout=30):
""" """
Called in manual mode when there's internet connectivity Uploads the file to onlinehashcrack.com
""" """
global REPORT with open(path, 'rb') as file_to_upload:
global SKIP data = {'email': self.options['email']}
if READY: payload = {'file': file_to_upload}
display = agent.view()
config = agent.config()
reported = REPORT.data_field_or('reported', default=list())
handshake_dir = config['bettercap']['handshakes'] try:
handshake_filenames = os.listdir(handshake_dir) result = requests.post('https://api.onlinehashcrack.com',
handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if filename.endswith('.pcap')] data=data,
handshake_new = set(handshake_paths) - set(reported) - set(SKIP) files=payload,
timeout=timeout)
if 'already been sent' in result.text:
logging.warning(f"{path} was already uploaded.")
except requests.exceptions.RequestException as e:
logging.error(f"OHC: Got an exception while uploading {path} -> {e}")
raise e
if handshake_new: def on_internet_available(self, agent):
logging.info("OHC: Internet connectivity detected. Uploading new handshakes to onelinehashcrack.com") """
Called in manual mode when there's internet connectivity
"""
if self.ready:
display = agent.view()
config = agent.config()
reported = self.report.data_field_or('reported', default=list())
for idx, handshake in enumerate(handshake_new): handshake_dir = config['bettercap']['handshakes']
display.set('status', f"Uploading handshake to onlinehashcrack.com ({idx + 1}/{len(handshake_new)})") handshake_filenames = os.listdir(handshake_dir)
display.update(force=True) handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if
try: filename.endswith('.pcap')]
_upload_to_ohc(handshake)
reported.append(handshake)
REPORT.update(data={'reported': reported})
logging.info(f"OHC: Successfully uploaded {handshake}")
except requests.exceptions.RequestException as req_e:
SKIP.append(handshake)
logging.error("OHC: %s", req_e)
continue
except OSError as os_e:
SKIP.append(handshake)
logging.error("OHC: %s", os_e)
continue
# pull out whitelisted APs
handshake_paths = filter(lambda path: self._filter_handshake_file(path), handshake_paths)
handshake_new = set(handshake_paths) - set(reported) - set(self.skip)
if handshake_new:
logging.info("OHC: Internet connectivity detected. Uploading new handshakes to onelinehashcrack.com")
for idx, handshake in enumerate(handshake_new):
display.set('status',
f"Uploading handshake to onlinehashcrack.com ({idx + 1}/{len(handshake_new)})")
display.update(force=True)
try:
self._upload_to_ohc(handshake)
reported.append(handshake)
self.report.update(data={'reported': reported})
logging.info(f"OHC: Successfully uploaded {handshake}")
except requests.exceptions.RequestException as req_e:
self.skip.append(handshake)
logging.error("OHC: %s", req_e)
continue
except OSError as os_e:
self.skip.append(handshake)
logging.error("OHC: %s", os_e)
continue

View File

@@ -1,27 +1,27 @@
__author__ = 'leont' import logging
__version__ = '1.0.0' import requests
__name__ = 'pawgps' import pwnagotchi.plugins as plugins
__license__ = 'GPL3'
__description__ = 'Saves GPS coordinates whenever an handshake is captured. The GPS data is get from PAW on android '
''' '''
You need an bluetooth connection to your android phone which is running PAW server with the GPS "hack" from Systemic: You need an bluetooth connection to your android phone which is running PAW server with the GPS "hack" from Systemic:
https://raw.githubusercontent.com/systemik/pwnagotchi-bt-tether/master/GPS-via-PAW https://raw.githubusercontent.com/systemik/pwnagotchi-bt-tether/master/GPS-via-PAW
''' '''
import logging
import requests
OPTIONS = dict() class PawGPS(plugins.Plugin):
__author__ = 'leont'
__version__ = '1.0.0'
__name__ = 'pawgps'
__license__ = 'GPL3'
__description__ = 'Saves GPS coordinates whenever an handshake is captured. The GPS data is get from PAW on android '
def on_loaded(self):
logging.info("PAW-GPS loaded")
if 'ip' not in self.options or ('ip' in self.options and self.options['ip'] is None):
logging.info("PAW-GPS: No IP Address in the config file is defined, it uses the default (192.168.44.1)")
def on_loaded(): def on_handshake(self, agent, filename, access_point, client_station):
logging.info("PAW-GPS loaded") if 'ip' not in self.options or ('ip' in self.options and self.options['ip'] is None):
if 'ip' not in OPTIONS or ('ip' in OPTIONS and OPTIONS['ip'] is None):
logging.info("PAW-GPS: No IP Address in the config file is defined, it uses the default (192.168.44.1)")
def on_handshake(agent, filename, access_point, client_station):
if 'ip' not in OPTIONS or ('ip' in OPTIONS and OPTIONS['ip'] is None):
ip = "192.168.44.1" ip = "192.168.44.1"
gps = requests.get('http://' + ip + '/gps.xhtml') gps = requests.get('http://' + ip + '/gps.xhtml')

View File

@@ -1,8 +1,8 @@
__author__ = 'pwnagotchi [at] rossmarks [dot] uk' import logging
__version__ = '1.0.0' import subprocess
__name__ = 'quickdic' import string
__license__ = 'GPL3' import re
__description__ = 'Run a quick dictionary scan against captured handshakes' import pwnagotchi.plugins as plugins
''' '''
Aircrack-ng needed, to install: Aircrack-ng needed, to install:
@@ -11,42 +11,41 @@ Upload wordlist files in .txt format to folder in config file (Default: /opt/wor
Cracked handshakes stored in handshake folder as [essid].pcap.cracked Cracked handshakes stored in handshake folder as [essid].pcap.cracked
''' '''
import logging
import subprocess
import string
import re
OPTIONS = dict() class QuickDic(plugins.Plugin):
__author__ = 'pwnagotchi [at] rossmarks [dot] uk'
__version__ = '1.0.0'
__license__ = 'GPL3'
__description__ = 'Run a quick dictionary scan against captured handshakes'
def on_loaded(): def __init__(self):
logging.info("Quick dictionary check plugin loaded") self.text_to_set = ""
def on_handshake(agent, filename, access_point, client_station): def on_loaded(self):
display = agent._view logging.info("Quick dictionary check plugin loaded")
result = subprocess.run(('/usr/bin/aircrack-ng '+ filename +' | grep "1 handshake" | awk \'{print $2}\''),shell=True, stdout=subprocess.PIPE) def on_handshake(self, agent, filename, access_point, client_station):
result = result.stdout.decode('utf-8').translate({ord(c) :None for c in string.whitespace}) display = agent.view()
if not result: result = subprocess.run(('/usr/bin/aircrack-ng ' + filename + ' | grep "1 handshake" | awk \'{print $2}\''),
logging.info("[quickdic] No handshake") shell=True, stdout=subprocess.PIPE)
else: result = result.stdout.decode('utf-8').translate({ord(c): None for c in string.whitespace})
logging.info("[quickdic] Handshake confirmed") if not result:
result2 = subprocess.run(('aircrack-ng -w `echo '+OPTIONS['wordlist_folder']+'*.txt | sed \'s/\ /,/g\'` -l '+filename+'.cracked -q -b '+result+' '+filename+' | grep KEY'),shell=True,stdout=subprocess.PIPE) logging.info("[quickdic] No handshake")
result2 = result2.stdout.decode('utf-8').strip() else:
logging.info("[quickdic] "+result2) logging.info("[quickdic] Handshake confirmed")
if result2 != "KEY NOT FOUND": result2 = subprocess.run(('aircrack-ng -w `echo ' + self.options[
key = re.search('\[(.*)\]', result2) 'wordlist_folder'] + '*.txt | sed \'s/\ /,/g\'` -l ' + filename + '.cracked -q -b ' + result + ' ' + filename + ' | grep KEY'),
pwd = str(key.group(1)) shell=True, stdout=subprocess.PIPE)
set_text("Cracked password: "+pwd) result2 = result2.stdout.decode('utf-8').strip()
display.update(force=True) logging.info("[quickdic] " + result2)
if result2 != "KEY NOT FOUND":
key = re.search('\[(.*)\]', result2)
pwd = str(key.group(1))
self.text_to_set = "Cracked password: " + pwd
display.update(force=True)
text_to_set = ""; def on_ui_update(self, ui):
def set_text(text): if self.text_to_set:
global text_to_set ui.set('face', "(·ω·)")
text_to_set = text ui.set('status', self.text_to_set)
self.text_to_set = ""
def on_ui_update(ui):
global text_to_set
if text_to_set:
ui.set('face', "(·ω·)")
ui.set('status', text_to_set)
text_to_set = ""

View File

@@ -1,24 +1,23 @@
__author__ = 'pwnagotchi [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 import logging
import pwnagotchi.plugins as plugins
OPTIONS = dict()
update_count = 0;
def on_loaded(): class ScreenRefresh(plugins.Plugin):
logging.info("Screen refresh plugin loaded") __author__ = 'pwnagotchi [at] rossmarks [dot] uk'
__version__ = '1.0.0'
__license__ = 'GPL3'
__description__ = 'Refresh he e-ink display after X amount of updates'
def __init__(self):
self.update_count = 0;
def on_ui_update(ui): def on_loaded(self):
global update_count logging.info("Screen refresh plugin loaded")
update_count += 1
if update_count == OPTIONS['refresh_interval']: def on_ui_update(self, ui):
ui.init_display() self.update_count += 1
ui.set('status', "Screen cleaned") if self.update_count == self.options['refresh_interval']:
logging.info("Screen refreshing") ui.init_display()
update_count = 0 ui.set('status', "Screen cleaned")
logging.info("Screen refreshing")
self.update_count = 0

View File

@@ -1,50 +1,49 @@
__author__ = '33197631+dadav@users.noreply.github.com'
__version__ = '1.0.0'
__name__ = 'twitter'
__license__ = 'GPL3'
__description__ = 'This plugin creates tweets about the recent activity of pwnagotchi'
import logging import logging
from pwnagotchi.voice import Voice from pwnagotchi.voice import Voice
import pwnagotchi.plugins as plugins
OPTIONS = dict()
def on_loaded():
logging.info("twitter plugin loaded.")
# called in manual mode when there's internet connectivity class Twitter(plugins.Plugin):
def on_internet_available(agent): __author__ = 'evilsocket@gmail.com'
config = agent.config() __version__ = '1.0.0'
display = agent.view() __license__ = 'GPL3'
last_session = agent.last_session __description__ = 'This plugin creates tweets about the recent activity of pwnagotchi'
if last_session.is_new() and last_session.handshakes > 0: def on_loaded(self):
try: logging.info("twitter plugin loaded.")
import tweepy
except ImportError:
logging.error("Couldn't import tweepy")
return
logging.info("detected a new session and internet connectivity!") # called in manual mode when there's internet connectivity
def on_internet_available(self, agent):
config = agent.config()
display = agent.view()
last_session = agent.last_session
picture = '/dev/shm/pwnagotchi.png' if last_session.is_new() and last_session.handshakes > 0:
try:
import tweepy
except ImportError:
logging.error("Couldn't import tweepy")
return
display.on_manual_mode(last_session) logging.info("detected a new session and internet connectivity!")
display.update(force=True)
display.image().save(picture, 'png')
display.set('status', 'Tweeting...')
display.update(force=True)
try: picture = '/root/pwnagotchi.png'
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_last_session_tweet(last_session) display.on_manual_mode(last_session)
api.update_with_media(filename=picture, status=tweet) display.update(force=True)
last_session.save_session_id() display.image().save(picture, 'png')
display.set('status', 'Tweeting...')
display.update(force=True)
logging.info("tweeted: %s" % tweet) try:
except Exception as e: auth = tweepy.OAuthHandler(self.options['consumer_key'], self.options['consumer_secret'])
logging.exception("error while tweeting") auth.set_access_token(self.options['access_token_key'], self.options['access_token_secret'])
api = tweepy.API(auth)
tweet = Voice(lang=config['main']['lang']).on_last_session_tweet(last_session)
api.update_with_media(filename=picture, status=tweet)
last_session.save_session_id()
logging.info("tweeted: %s" % tweet)
except Exception as e:
logging.exception("error while tweeting")

View File

@@ -1,22 +0,0 @@
__author__ = 'diemelcw@gmail.com'
__version__ = '1.0.0'
__name__ = 'unfiltered_example'
__license__ = 'GPL3'
__description__ = 'An example plugin for pwnagotchi that implements on_unfiltered_ap_list(agent,aps)'
import logging
# Will be set with the options in config.yml config['main']['plugins'][__name__]
OPTIONS = dict()
# called when the plugin is loaded
def on_loaded():
logging.warning("%s plugin loaded" % __name__)
# called when AP list is ready, before whitelist filtering has occurred
def on_unfiltered_ap_list(agent,aps):
logging.info("Unfiltered AP list to follow")
for ap in aps:
logging.info(ap['hostname'])
## Additional logic here ##

View File

@@ -7,17 +7,12 @@
# For Raspberry Pi Zero Ups Power Expansion Board with Integrated Serial Port S3U4 # For Raspberry Pi Zero Ups Power Expansion Board with Integrated Serial Port S3U4
# https://www.ebay.de/itm/For-Raspberry-Pi-Zero-Ups-Power-Expansion-Board-with-Integrated-Serial-Port-S3U4/323873804310 # https://www.ebay.de/itm/For-Raspberry-Pi-Zero-Ups-Power-Expansion-Board-with-Integrated-Serial-Port-S3U4/323873804310
# https://www.aliexpress.com/item/32888533624.html # https://www.aliexpress.com/item/32888533624.html
__author__ = 'evilsocket@gmail.com'
__version__ = '1.0.0'
__name__ = 'ups_lite'
__license__ = 'GPL3'
__description__ = 'A plugin that will add a voltage indicator for the UPS Lite v1.1'
import struct import struct
from pwnagotchi.ui.components import LabeledValue from pwnagotchi.ui.components import LabeledValue
from pwnagotchi.ui.view import BLACK from pwnagotchi.ui.view import BLACK
import pwnagotchi.ui.fonts as fonts import pwnagotchi.ui.fonts as fonts
import pwnagotchi.plugins as plugins
# TODO: add enable switch in config.yml an cleanup all to the best place # TODO: add enable switch in config.yml an cleanup all to the best place
@@ -47,18 +42,21 @@ class UPS:
return 0.0 return 0.0
ups = None class UPSLite(plugins.Plugin):
__author__ = 'evilsocket@gmail.com'
__version__ = '1.0.0'
__license__ = 'GPL3'
__description__ = 'A plugin that will add a voltage indicator for the UPS Lite v1.1'
def __init__(self):
self.ups = None
def on_loaded(): def on_loaded(self):
global ups self.ups = UPS()
ups = UPS()
def on_ui_setup(self, ui):
ui.add_element('ups', LabeledValue(color=BLACK, label='UPS', value='0%/0V', position=(ui.width() / 2 - 25, 0),
label_font=fonts.Bold, text_font=fonts.Medium))
def on_ui_setup(ui): def on_ui_update(self, ui):
ui.add_element('ups', LabeledValue(color=BLACK, label='UPS', value='0%/0V', position=(ui.width() / 2 - 25, 0), ui.set('ups', "%4.2fV/%2i%%" % (self.ups.voltage(), self.ups.capacity()))
label_font=fonts.Bold, text_font=fonts.Medium))
def on_ui_update(ui):
ui.set('ups', "%4.2fV/%2i%%" % (ups.voltage(), ups.capacity()))

View File

@@ -1,9 +1,3 @@
__author__ = '33197631+dadav@users.noreply.github.com'
__version__ = '2.0.0'
__name__ = 'wigle'
__license__ = 'GPL3'
__description__ = 'This plugin automatically uploads collected wifis to wigle.net'
import os import os
import logging import logging
import json import json
@@ -12,24 +6,7 @@ import csv
from datetime import datetime from datetime import datetime
import requests import requests
from pwnagotchi.utils import WifiInfo, FieldNotFoundError, extract_from_pcap, StatusFile from pwnagotchi.utils import WifiInfo, FieldNotFoundError, extract_from_pcap, StatusFile
import pwnagotchi.plugins as plugins
READY = False
REPORT = StatusFile('/root/.wigle_uploads', data_format='json')
SKIP = list()
OPTIONS = dict()
def on_loaded():
"""
Gets called when the plugin gets loaded
"""
global READY
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
READY = True
def _extract_gps_data(path): def _extract_gps_data(path):
@@ -54,14 +31,17 @@ def _format_auth(data):
out = f"{out}[{auth}]" out = f"{out}[{auth}]"
return out return out
def _transform_wigle_entry(gps_data, pcap_data): def _transform_wigle_entry(gps_data, pcap_data):
""" """
Transform to wigle entry in file Transform to wigle entry in file
""" """
dummy = StringIO() dummy = StringIO()
# write kismet header # 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(
dummy.write("MAC,SSID,AuthMode,FirstSeen,Channel,RSSI,CurrentLatitude,CurrentLongitude,AltitudeMeters,AccuracyMeters,Type") "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, escapechar="\\") writer = csv.writer(dummy, delimiter=",", quoting=csv.QUOTE_NONE, escapechar="\\")
writer.writerow([ writer.writerow([
@@ -75,10 +55,11 @@ def _transform_wigle_entry(gps_data, pcap_data):
gps_data['Latitude'], gps_data['Latitude'],
gps_data['Longitude'], gps_data['Longitude'],
gps_data['Altitude'], gps_data['Altitude'],
0, # accuracy? 0, # accuracy?
'WIFI']) 'WIFI'])
return dummy.getvalue() return dummy.getvalue()
def _send_to_wigle(lines, api_key, timeout=30): def _send_to_wigle(lines, api_key, timeout=30):
""" """
Uploads the file to wigle-net Uploads the file to wigle-net
@@ -109,87 +90,100 @@ def _send_to_wigle(lines, api_key, timeout=30):
raise re_e raise re_e
def on_internet_available(agent): class Wigle(plugins.Plugin):
from scapy.all import Scapy_Exception __author__ = '33197631+dadav@users.noreply.github.com'
""" __version__ = '2.0.0'
Called in manual mode when there's internet connectivity __license__ = 'GPL3'
""" __description__ = 'This plugin automatically uploads collected wifis to wigle.net'
global REPORT
global SKIP
if READY: def __init__(self):
config = agent.config() self.ready = False
display = agent.view() self.report = StatusFile('/root/.wigle_uploads', data_format='json')
reported = REPORT.data_field_or('reported', default=list()) self.skip = list()
handshake_dir = config['bettercap']['handshakes'] def on_loaded(self):
all_files = os.listdir(handshake_dir) if 'api_key' not in self.options or ('api_key' in self.options and self.options['api_key'] is None):
all_gps_files = [os.path.join(handshake_dir, filename) logging.error("WIGLE: api_key isn't set. Can't upload to wigle.net")
for filename in all_files return
if filename.endswith('.gps.json')] self.ready = True
new_gps_files = set(all_gps_files) - set(reported) - set(SKIP)
if new_gps_files: def on_internet_available(self, agent):
logging.info("WIGLE: Internet connectivity detected. Uploading new handshakes to wigle.net") from scapy.all import Scapy_Exception
"""
Called in manual mode when there's internet connectivity
"""
if self.ready:
config = agent.config()
display = agent.view()
reported = self.report.data_field_or('reported', default=list())
csv_entries = list() handshake_dir = config['bettercap']['handshakes']
no_err_entries = list() 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(reported) - set(self.skip)
for gps_file in new_gps_files: if new_gps_files:
pcap_filename = gps_file.replace('.gps.json', '.pcap') logging.info("WIGLE: Internet connectivity detected. Uploading new handshakes to wigle.net")
if not os.path.exists(pcap_filename): csv_entries = list()
logging.error("WIGLE: Can't find pcap for %s", gps_file) no_err_entries = list()
SKIP.append(gps_file)
continue
try: for gps_file in new_gps_files:
gps_data = _extract_gps_data(gps_file) pcap_filename = gps_file.replace('.gps.json', '.pcap')
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: if not os.path.exists(pcap_filename):
logging.warning("WIGLE: Not enough gps-information for %s. Trying again next time.", gps_file) logging.error("WIGLE: Can't find pcap for %s", gps_file)
SKIP.append(gps_file) self.skip.append(gps_file)
continue continue
try:
gps_data = _extract_gps_data(gps_file)
except OSError as os_err:
logging.error("WIGLE: %s", os_err)
self.skip.append(gps_file)
continue
except json.JSONDecodeError as json_err:
logging.error("WIGLE: %s", json_err)
self.skip.append(gps_file)
continue
try: if gps_data['Latitude'] == 0 and gps_data['Longitude'] == 0:
pcap_data = extract_from_pcap(pcap_filename, [WifiInfo.BSSID, logging.warning("WIGLE: Not enough gps-information for %s. Trying again next time.", gps_file)
WifiInfo.ESSID, self.skip.append(gps_file)
WifiInfo.ENCRYPTION, continue
WifiInfo.CHANNEL,
WifiInfo.RSSI])
except FieldNotFoundError:
logging.error("WIGLE: Could not extract all information. 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) try:
csv_entries.append(new_entry) pcap_data = extract_from_pcap(pcap_filename, [WifiInfo.BSSID,
no_err_entries.append(gps_file) WifiInfo.ESSID,
WifiInfo.ENCRYPTION,
WifiInfo.CHANNEL,
WifiInfo.RSSI])
except FieldNotFoundError:
logging.error("WIGLE: Could not extract all information. Skip %s", gps_file)
self.skip.append(gps_file)
continue
except Scapy_Exception as sc_e:
logging.error("WIGLE: %s", sc_e)
self.skip.append(gps_file)
continue
if csv_entries: new_entry = _transform_wigle_entry(gps_data, pcap_data)
display.set('status', "Uploading gps-data to wigle.net ...") csv_entries.append(new_entry)
display.update(force=True) no_err_entries.append(gps_file)
try:
_send_to_wigle(csv_entries, OPTIONS['api_key']) if csv_entries:
reported += no_err_entries display.set('status', "Uploading gps-data to wigle.net ...")
REPORT.update(data={'reported': reported}) display.update(force=True)
logging.info("WIGLE: Successfully uploaded %d files", len(no_err_entries)) try:
except requests.exceptions.RequestException as re_e: _send_to_wigle(csv_entries, self.options['api_key'])
SKIP += no_err_entries reported += no_err_entries
logging.error("WIGLE: Got an exception while uploading %s", re_e) self.report.update(data={'reported': reported})
except OSError as os_e: logging.info("WIGLE: Successfully uploaded %d files", len(no_err_entries))
SKIP += no_err_entries except requests.exceptions.RequestException as re_e:
logging.error("WIGLE: Got the following error: %s", os_e) self.skip += no_err_entries
logging.error("WIGLE: Got an exception while uploading %s", re_e)
except OSError as os_e:
self.skip += no_err_entries
logging.error("WIGLE: Got the following error: %s", os_e)

View File

@@ -1,87 +1,84 @@
__author__ = '33197631+dadav@users.noreply.github.com'
__version__ = '2.0.1'
__name__ = 'wpa-sec'
__license__ = 'GPL3'
__description__ = 'This plugin automatically uploads handshakes to https://wpa-sec.stanev.org'
import os import os
import logging import logging
import requests import requests
from pwnagotchi.utils import StatusFile from pwnagotchi.utils import StatusFile
import pwnagotchi.plugins as plugins
READY = False
REPORT = StatusFile('/root/.wpa_sec_uploads', data_format='json')
OPTIONS = dict()
SKIP = list()
def on_loaded(): class WpaSec(plugins.Plugin):
""" __author__ = '33197631+dadav@users.noreply.github.com'
Gets called when the plugin gets loaded __version__ = '2.0.1'
""" __license__ = 'GPL3'
global READY __description__ = 'This plugin automatically uploads handshakes to https://wpa-sec.stanev.org'
if 'api_key' not in OPTIONS or ('api_key' in OPTIONS and OPTIONS['api_key'] is None): def __init__(self):
logging.error("WPA_SEC: API-KEY isn't set. Can't upload to wpa-sec.stanev.org") self.ready = False
return self.report = StatusFile('/root/.wpa_sec_uploads', data_format='json')
self.options = dict()
self.skip = list()
if 'api_url' not in OPTIONS or ('api_url' in OPTIONS and OPTIONS['api_url'] is None): def _upload_to_wpasec(self, path, timeout=30):
logging.error("WPA_SEC: API-URL isn't set. Can't upload, no endpoint configured.") """
return Uploads the file to https://wpa-sec.stanev.org, or another endpoint.
"""
with open(path, 'rb') as file_to_upload:
cookie = {'key': self.options['api_key']}
payload = {'file': file_to_upload}
READY = True try:
result = requests.post(self.options['api_url'],
cookies=cookie,
files=payload,
timeout=timeout)
if ' already submitted' in result.text:
logging.warning("%s was already submitted.", path)
except requests.exceptions.RequestException as req_e:
raise req_e
def on_loaded(self):
"""
Gets called when the plugin gets loaded
"""
if 'api_key' not in self.options or ('api_key' in self.options and self.options['api_key'] is None):
logging.error("WPA_SEC: API-KEY isn't set. Can't upload to wpa-sec.stanev.org")
return
def _upload_to_wpasec(path, timeout=30): if 'api_url' not in self.options or ('api_url' in self.options and self.options['api_url'] is None):
""" logging.error("WPA_SEC: API-URL isn't set. Can't upload, no endpoint configured.")
Uploads the file to https://wpa-sec.stanev.org, or another endpoint. return
"""
with open(path, 'rb') as file_to_upload:
cookie = {'key': OPTIONS['api_key']}
payload = {'file': file_to_upload}
try: self.ready = True
result = requests.post(OPTIONS['api_url'],
cookies=cookie,
files=payload,
timeout=timeout)
if ' already submitted' in result.text:
logging.warning("%s was already submitted.", path)
except requests.exceptions.RequestException as req_e:
raise req_e
def on_internet_available(self, agent):
"""
Called in manual mode when there's internet connectivity
"""
if self.ready:
config = agent.config()
display = agent.view()
reported = self.report.data_field_or('reported', default=list())
def on_internet_available(agent): handshake_dir = config['bettercap']['handshakes']
""" handshake_filenames = os.listdir(handshake_dir)
Called in manual mode when there's internet connectivity handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if
""" filename.endswith('.pcap')]
global REPORT handshake_new = set(handshake_paths) - set(reported) - set(self.skip)
global SKIP
if READY:
config = agent.config()
display = agent.view()
reported = REPORT.data_field_or('reported', default=list())
handshake_dir = config['bettercap']['handshakes'] if handshake_new:
handshake_filenames = os.listdir(handshake_dir) logging.info("WPA_SEC: Internet connectivity detected. Uploading new handshakes to wpa-sec.stanev.org")
handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if filename.endswith('.pcap')]
handshake_new = set(handshake_paths) - set(reported) - set(SKIP)
if handshake_new: for idx, handshake in enumerate(handshake_new):
logging.info("WPA_SEC: Internet connectivity detected. Uploading new handshakes to wpa-sec.stanev.org") display.set('status', f"Uploading handshake to wpa-sec.stanev.org ({idx + 1}/{len(handshake_new)})")
display.update(force=True)
for idx, handshake in enumerate(handshake_new): try:
display.set('status', f"Uploading handshake to wpa-sec.stanev.org ({idx + 1}/{len(handshake_new)})") self._upload_to_wpasec(handshake)
display.update(force=True) reported.append(handshake)
try: self.report.update(data={'reported': reported})
_upload_to_wpasec(handshake) logging.info("WPA_SEC: Successfully uploaded %s", handshake)
reported.append(handshake) except requests.exceptions.RequestException as req_e:
REPORT.update(data={'reported': reported}) self.skip.append(handshake)
logging.info("WPA_SEC: Successfully uploaded %s", handshake) logging.error("WPA_SEC: %s", req_e)
except requests.exceptions.RequestException as req_e: continue
SKIP.append(handshake) except OSError as os_e:
logging.error("WPA_SEC: %s", req_e) logging.error("WPA_SEC: %s", os_e)
continue continue
except OSError as os_e:
logging.error("WPA_SEC: %s", os_e)
continue

View File

@@ -4,7 +4,6 @@ import threading
import pwnagotchi.plugins as plugins import pwnagotchi.plugins as plugins
import pwnagotchi.ui.hw as hw import pwnagotchi.ui.hw as hw
import pwnagotchi.ui.web as web
from pwnagotchi.ui.view import View from pwnagotchi.ui.view import View
@@ -15,7 +14,6 @@ class Display(View):
self._enabled = config['enabled'] self._enabled = config['enabled']
self._rotation = config['rotation'] self._rotation = config['rotation']
self._webui = web.Server(config)
self.init_display() self.init_display()
@@ -27,6 +25,9 @@ class Display(View):
) )
self._render_thread_instance.start() self._render_thread_instance.start()
def set_ready(self):
self._webui.start()
def is_inky(self): def is_inky(self):
return self._implementation.name == 'inky' return self._implementation.name == 'inky'
@@ -42,6 +43,9 @@ class Display(View):
def is_waveshare27inch(self): def is_waveshare27inch(self):
return self._implementation.name == 'waveshare27inch' return self._implementation.name == 'waveshare27inch'
def is_waveshare29inch(self):
return self._implementation.name == 'waveshare29inch'
def is_oledhat(self): def is_oledhat(self):
return self._implementation.name == 'oledhat' return self._implementation.name == 'oledhat'
@@ -86,7 +90,6 @@ class Display(View):
self._implementation.render(self._canvas_next) self._implementation.render(self._canvas_next)
def _on_view_rendered(self, img): def _on_view_rendered(self, img):
web.update_frame(img)
try: try:
if self._config['ui']['display']['video']['on_frame'] != '': if self._config['ui']['display']['video']['on_frame'] != '':
os.system(self._config['ui']['display']['video']['on_frame']) os.system(self._config['ui']['display']['video']['on_frame'])

View File

@@ -16,6 +16,7 @@ DEMOTIVATED = '(≖__≖)'
SMART = '(✜‿‿✜)' SMART = '(✜‿‿✜)'
LONELY = '(ب__ب)' LONELY = '(ب__ب)'
SAD = '(╥☁╥ )' SAD = '(╥☁╥ )'
ANGRY = "(-_-')"
FRIEND = '(♥‿‿♥)' FRIEND = '(♥‿‿♥)'
BROKEN = '(☓‿‿☓)' BROKEN = '(☓‿‿☓)'
DEBUG = '(#__#)' DEBUG = '(#__#)'

View File

@@ -6,6 +6,7 @@ from pwnagotchi.ui.hw.dfrobot import DFRobot
from pwnagotchi.ui.hw.waveshare1 import WaveshareV1 from pwnagotchi.ui.hw.waveshare1 import WaveshareV1
from pwnagotchi.ui.hw.waveshare2 import WaveshareV2 from pwnagotchi.ui.hw.waveshare2 import WaveshareV2
from pwnagotchi.ui.hw.waveshare27inch import Waveshare27inch from pwnagotchi.ui.hw.waveshare27inch import Waveshare27inch
from pwnagotchi.ui.hw.waveshare29inch import Waveshare29inch
from pwnagotchi.ui.hw.waveshare154inch import Waveshare154inch from pwnagotchi.ui.hw.waveshare154inch import Waveshare154inch
from pwnagotchi.ui.hw.waveshare213d import Waveshare213d from pwnagotchi.ui.hw.waveshare213d import Waveshare213d
@@ -36,6 +37,9 @@ def display_for(config):
elif config['ui']['display']['type'] == 'waveshare27inch': elif config['ui']['display']['type'] == 'waveshare27inch':
return Waveshare27inch(config) return Waveshare27inch(config)
elif config['ui']['display']['type'] == 'waveshare29inch':
return Waveshare29inch(config)
elif config['ui']['display']['type'] == 'waveshare154inch': elif config['ui']['display']['type'] == 'waveshare154inch':
return Waveshare154inch(config) return Waveshare154inch(config)

View File

@@ -0,0 +1,201 @@
# *****************************************************************************
# * | File : epd2in9.py
# * | Author : Waveshare team
# * | Function : Electronic paper driver
# * | Info :
# *----------------
# * | This version: V4.0
# * | Date : 2019-06-20
# # | Info : python demo
# -----------------------------------------------------------------------------
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documnetation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
import logging
from . import epdconfig
# Display resolution
EPD_WIDTH = 128
EPD_HEIGHT = 296
class EPD:
def __init__(self):
self.reset_pin = epdconfig.RST_PIN
self.dc_pin = epdconfig.DC_PIN
self.busy_pin = epdconfig.BUSY_PIN
self.cs_pin = epdconfig.CS_PIN
self.width = EPD_WIDTH
self.height = EPD_HEIGHT
lut_full_update = [
0x50, 0xAA, 0x55, 0xAA, 0x11, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xFF, 0xFF, 0x1F, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00
]
lut_partial_update = [
0x10, 0x18, 0x18, 0x08, 0x18, 0x18,
0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x13, 0x14, 0x44, 0x12,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00
]
# Hardware reset
def reset(self):
epdconfig.digital_write(self.reset_pin, 1)
epdconfig.delay_ms(200)
epdconfig.digital_write(self.reset_pin, 0)
epdconfig.delay_ms(10)
epdconfig.digital_write(self.reset_pin, 1)
epdconfig.delay_ms(200)
def send_command(self, command):
epdconfig.digital_write(self.dc_pin, 0)
epdconfig.digital_write(self.cs_pin, 0)
epdconfig.spi_writebyte([command])
epdconfig.digital_write(self.cs_pin, 1)
def send_data(self, data):
epdconfig.digital_write(self.dc_pin, 1)
epdconfig.digital_write(self.cs_pin, 0)
epdconfig.spi_writebyte([data])
epdconfig.digital_write(self.cs_pin, 1)
def ReadBusy(self):
while(epdconfig.digital_read(self.busy_pin) == 1): # 0: idle, 1: busy
epdconfig.delay_ms(200)
def TurnOnDisplay(self):
self.send_command(0x22) # DISPLAY_UPDATE_CONTROL_2
self.send_data(0xC4)
self.send_command(0x20) # MASTER_ACTIVATION
self.send_command(0xFF) # TERMINATE_FRAME_READ_WRITE
logging.debug("e-Paper busy")
self.ReadBusy()
logging.debug("e-Paper busy release")
def SetWindow(self, x_start, y_start, x_end, y_end):
self.send_command(0x44) # SET_RAM_X_ADDRESS_START_END_POSITION
# x point must be the multiple of 8 or the last 3 bits will be ignored
self.send_data((x_start >> 3) & 0xFF)
self.send_data((x_end >> 3) & 0xFF)
self.send_command(0x45) # SET_RAM_Y_ADDRESS_START_END_POSITION
self.send_data(y_start & 0xFF)
self.send_data((y_start >> 8) & 0xFF)
self.send_data(y_end & 0xFF)
self.send_data((y_end >> 8) & 0xFF)
def SetCursor(self, x, y):
self.send_command(0x4E) # SET_RAM_X_ADDRESS_COUNTER
# x point must be the multiple of 8 or the last 3 bits will be ignored
self.send_data((x >> 3) & 0xFF)
self.send_command(0x4F) # SET_RAM_Y_ADDRESS_COUNTER
self.send_data(y & 0xFF)
self.send_data((y >> 8) & 0xFF)
self.ReadBusy()
def init(self, lut):
if (epdconfig.module_init() != 0):
return -1
# EPD hardware init start
self.reset()
self.send_command(0x01) # DRIVER_OUTPUT_CONTROL
self.send_data((EPD_HEIGHT - 1) & 0xFF)
self.send_data(((EPD_HEIGHT - 1) >> 8) & 0xFF)
self.send_data(0x00) # GD = 0 SM = 0 TB = 0
self.send_command(0x0C) # BOOSTER_SOFT_START_CONTROL
self.send_data(0xD7)
self.send_data(0xD6)
self.send_data(0x9D)
self.send_command(0x2C) # WRITE_VCOM_REGISTER
self.send_data(0xA8) # VCOM 7C
self.send_command(0x3A) # SET_DUMMY_LINE_PERIOD
self.send_data(0x1A) # 4 dummy lines per gate
self.send_command(0x3B) # SET_GATE_TIME
self.send_data(0x08) # 2us per line
self.send_command(0x11) # DATA_ENTRY_MODE_SETTING
self.send_data(0x03) # X increment Y increment
self.send_command(0x32) # WRITE_LUT_REGISTER
for i in range(0, len(lut)):
self.send_data(lut[i])
# EPD hardware init end
return 0
def getbuffer(self, image):
# logging.debug("bufsiz = ",int(self.width/8) * self.height)
buf = [0xFF] * (int(self.width/8) * self.height)
image_monocolor = image.convert('1')
imwidth, imheight = image_monocolor.size
pixels = image_monocolor.load()
# logging.debug("imwidth = %d, imheight = %d",imwidth,imheight)
if(imwidth == self.width and imheight == self.height):
logging.debug("Vertical")
for y in range(imheight):
for x in range(imwidth):
# Set the bits for the column of pixels at the current position.
if pixels[x, y] == 0:
buf[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8))
elif(imwidth == self.height and imheight == self.width):
logging.debug("Horizontal")
for y in range(imheight):
for x in range(imwidth):
newx = y
newy = self.height - x - 1
if pixels[x, y] == 0:
buf[int((newx + newy*self.width) / 8)] &= ~(0x80 >> (y % 8))
return buf
def display(self, image):
if (image == None):
return
self.SetWindow(0, 0, self.width - 1, self.height - 1)
for j in range(0, self.height):
self.SetCursor(0, j)
self.send_command(0x24) # WRITE_RAM
for i in range(0, int(self.width / 8)):
self.send_data(image[i + j * int(self.width / 8)])
self.TurnOnDisplay()
def Clear(self, color):
self.SetWindow(0, 0, self.width - 1, self.height - 1)
for j in range(0, self.height):
self.SetCursor(0, j)
self.send_command(0x24) # WRITE_RAM
for i in range(0, int(self.width / 8)):
self.send_data(color)
self.TurnOnDisplay()
def sleep(self):
self.send_command(0x10) # DEEP_SLEEP_MODE
self.send_data(0x01)
epdconfig.module_exit()
### END OF FILE ###

View File

@@ -0,0 +1,154 @@
# /*****************************************************************************
# * | File : epdconfig.py
# * | Author : Waveshare team
# * | Function : Hardware underlying interface
# * | Info :
# *----------------
# * | This version: V1.0
# * | Date : 2019-06-21
# * | Info :
# ******************************************************************************
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documnetation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
import os
import logging
import sys
import time
class RaspberryPi:
# Pin definition
RST_PIN = 17
DC_PIN = 25
CS_PIN = 8
BUSY_PIN = 24
def __init__(self):
import spidev
import RPi.GPIO
self.GPIO = RPi.GPIO
# SPI device, bus = 0, device = 0
self.SPI = spidev.SpiDev(0, 0)
def digital_write(self, pin, value):
self.GPIO.output(pin, value)
def digital_read(self, pin):
return self.GPIO.input(pin)
def delay_ms(self, delaytime):
time.sleep(delaytime / 1000.0)
def spi_writebyte(self, data):
self.SPI.writebytes(data)
def module_init(self):
self.GPIO.setmode(self.GPIO.BCM)
self.GPIO.setwarnings(False)
self.GPIO.setup(self.RST_PIN, self.GPIO.OUT)
self.GPIO.setup(self.DC_PIN, self.GPIO.OUT)
self.GPIO.setup(self.CS_PIN, self.GPIO.OUT)
self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN)
self.SPI.max_speed_hz = 4000000
self.SPI.mode = 0b00
return 0
def module_exit(self):
logging.debug("spi end")
self.SPI.close()
logging.debug("close 5V, Module enters 0 power consumption ...")
self.GPIO.output(self.RST_PIN, 0)
self.GPIO.output(self.DC_PIN, 0)
self.GPIO.cleanup()
class JetsonNano:
# Pin definition
RST_PIN = 17
DC_PIN = 25
CS_PIN = 8
BUSY_PIN = 24
def __init__(self):
import ctypes
find_dirs = [
os.path.dirname(os.path.realpath(__file__)),
'/usr/local/lib',
'/usr/lib',
]
self.SPI = None
for find_dir in find_dirs:
so_filename = os.path.join(find_dir, 'sysfs_software_spi.so')
if os.path.exists(so_filename):
self.SPI = ctypes.cdll.LoadLibrary(so_filename)
break
if self.SPI is None:
raise RuntimeError('Cannot find sysfs_software_spi.so')
import Jetson.GPIO
self.GPIO = Jetson.GPIO
def digital_write(self, pin, value):
self.GPIO.output(pin, value)
def digital_read(self, pin):
return self.GPIO.input(self.BUSY_PIN)
def delay_ms(self, delaytime):
time.sleep(delaytime / 1000.0)
def spi_writebyte(self, data):
self.SPI.SYSFS_software_spi_transfer(data[0])
def module_init(self):
self.GPIO.setmode(self.GPIO.BCM)
self.GPIO.setwarnings(False)
self.GPIO.setup(self.RST_PIN, self.GPIO.OUT)
self.GPIO.setup(self.DC_PIN, self.GPIO.OUT)
self.GPIO.setup(self.CS_PIN, self.GPIO.OUT)
self.GPIO.setup(self.BUSY_PIN, self.GPIO.IN)
self.SPI.SYSFS_software_spi_begin()
return 0
def module_exit(self):
logging.debug("spi end")
self.SPI.SYSFS_software_spi_end()
logging.debug("close 5V, Module enters 0 power consumption ...")
self.GPIO.output(self.RST_PIN, 0)
self.GPIO.output(self.DC_PIN, 0)
self.GPIO.cleanup()
if os.path.exists('/sys/bus/platform/drivers/gpiomem-bcm2835'):
implementation = RaspberryPi()
else:
implementation = JetsonNano()
for func in [x for x in dir(implementation) if not x.startswith('_')]:
setattr(sys.modules[__name__], func, getattr(implementation, func))
### END OF FILE ###

View File

@@ -0,0 +1,47 @@
import logging
import pwnagotchi.ui.fonts as fonts
from pwnagotchi.ui.hw.base import DisplayImpl
class Waveshare29inch(DisplayImpl):
def __init__(self, config):
super(Waveshare29inch, self).__init__(config, 'waveshare_29inch')
self._display = None
def layout(self):
fonts.setup(10, 9, 10, 35)
self._layout['width'] = 296
self._layout['height'] = 128
self._layout['face'] = (0, 40)
self._layout['name'] = (5, 25)
self._layout['channel'] = (0, 0)
self._layout['aps'] = (28, 0)
self._layout['uptime'] = (230, 0)
self._layout['line1'] = [0, 14, 296, 14]
self._layout['line2'] = [0, 112, 296, 112]
self._layout['friend_face'] = (0, 96)
self._layout['friend_name'] = (40, 96)
self._layout['shakes'] = (0, 114)
self._layout['mode'] = (268, 114)
self._layout['status'] = {
'pos': (130, 25),
'font': fonts.Medium,
'max': 28
}
return self._layout
def initialize(self):
logging.info("initializing waveshare v1 2.9 inch display")
from pwnagotchi.ui.hw.libs.waveshare.v29inch.epd2in9 import EPD
self._display = EPD()
self._display.init(self._display.lut_full_update)
self._display.Clear(0xFF)
self._display.init(self._display.lut_partial_update)
def render(self, canvas):
buf = self._display.getbuffer(canvas)
self._display.display(buf)
def clear(self):
self._display.Clear(0xFF)

View File

@@ -5,10 +5,12 @@ import logging
import random import random
from PIL import ImageDraw from PIL import ImageDraw
import pwnagotchi
import pwnagotchi.utils as utils import pwnagotchi.utils as utils
import pwnagotchi.plugins as plugins import pwnagotchi.plugins as plugins
from pwnagotchi.voice import Voice from pwnagotchi.voice import Voice
import pwnagotchi.ui.web as web
import pwnagotchi.ui.fonts as fonts import pwnagotchi.ui.fonts as fonts
import pwnagotchi.ui.faces as faces import pwnagotchi.ui.faces as faces
from pwnagotchi.ui.components import * from pwnagotchi.ui.components import *
@@ -132,7 +134,7 @@ class View(object):
return self._state.get(key) return self._state.get(key)
def on_starting(self): def on_starting(self):
self.set('status', self._voice.on_starting()) self.set('status', self._voice.on_starting() + ("\n(v%s)" % pwnagotchi.version))
self.set('face', faces.AWAKE) self.set('face', faces.AWAKE)
def on_ai_ready(self): def on_ai_ready(self):
@@ -284,6 +286,11 @@ class View(object):
self.set('status', self._voice.on_sad()) self.set('status', self._voice.on_sad())
self.update() self.update()
def on_angry(self):
self.set('face', faces.ANGRY)
self.set('status', self._voice.on_angry())
self.update()
def on_motivated(self, reward): def on_motivated(self, reward):
self.set('face', faces.MOTIVATED) self.set('face', faces.MOTIVATED)
self.set('status', self._voice.on_motivated(reward)) self.set('status', self._voice.on_motivated(reward))
@@ -363,6 +370,8 @@ class View(object):
for key, lv in self._state.items(): for key, lv in self._state.items():
lv.draw(self._canvas, drawer) lv.draw(self._canvas, drawer)
web.update_frame(self._canvas)
for cb in self._render_cbs: for cb in self._render_cbs:
cb(self._canvas) cb(self._canvas)

View File

@@ -1,205 +0,0 @@
import re
import _thread
from http.server import BaseHTTPRequestHandler, HTTPServer
from threading import Lock
import shutil
import logging
import pwnagotchi
from pwnagotchi import plugins
frame_path = '/root/pwnagotchi.png'
frame_format = 'PNG'
frame_ctype = 'image/png'
frame_lock = Lock()
def update_frame(img):
global frame_lock, frame_path, frame_format
with frame_lock:
img.save(frame_path, format=frame_format)
STYLE = """
.block {
-webkit-appearance: button;
-moz-appearance: button;
appearance: button;
display: block;
cursor: pointer;
text-align: center;
}
"""
SCRIPT = """
window.onload = function() {
var image = document.getElementById("ui");
function updateImage() {
image.src = image.src.split("?")[0] + "?" + new Date().getTime();
}
setInterval(updateImage, %d);
}
"""
INDEX = """<html>
<head>
<title>%s</title>
<style>""" + STYLE + """</style>
</head>
<body>
<div style="position: absolute; top:0; left:0; width:100%%;">
<img src="/ui" id="ui" style="width:100%%"/>
<br/>
<hr/>
<form method="POST" action="/shutdown" onsubmit="return confirm('This will halt the unit, continue?');">
<input type="submit" class="block" value="Shutdown"/>
</form>
</div>
<script type="text/javascript">""" + SCRIPT + """</script>
</body>
</html>"""
SHUTDOWN = """<html>
<head>
<title>%s</title>
<style>""" + STYLE + """</style>
</head>
<body>
<div style="position: absolute; top:0; left:0; width:100%%;">
Shutting down ...
</div>
</body>
</html>"""
class Handler(BaseHTTPRequestHandler):
AllowedOrigin = None # CORS headers are not sent
# suppress internal logging
def log_message(self, format, *args):
return
def _send_cors_headers(self):
# misc security
self.send_header("X-Frame-Options", "DENY")
self.send_header("X-Content-Type-Options", "nosniff")
self.send_header("X-XSS-Protection", "1; mode=block")
self.send_header("Referrer-Policy", "same-origin")
# cors
if Handler.AllowedOrigin:
self.send_header("Access-Control-Allow-Origin", Handler.AllowedOrigin)
self.send_header('Access-Control-Allow-Credentials', 'true')
self.send_header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
self.send_header("Access-Control-Allow-Headers",
"Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
self.send_header("Vary", "Origin")
# just render some html in a 200 response
def _html(self, html):
self.send_response(200)
self._send_cors_headers()
self.send_header('Content-type', 'text/html')
self.end_headers()
try:
self.wfile.write(bytes(html, "utf8"))
except:
pass
# serve the main html page
def _index(self):
self._html(INDEX % (pwnagotchi.name(), 1000))
# serve a message and shuts down the unit
def _shutdown(self):
self._html(SHUTDOWN % pwnagotchi.name())
pwnagotchi.shutdown()
# serve the PNG file with the display image
def _image(self):
global frame_lock, frame_path, frame_ctype
with frame_lock:
self.send_response(200)
self._send_cors_headers()
self.send_header('Content-type', frame_ctype)
self.end_headers()
try:
with open(frame_path, 'rb') as fp:
shutil.copyfileobj(fp, self.wfile)
except:
pass
# check the Origin header vs CORS
def _is_allowed(self):
if not Handler.AllowedOrigin or Handler.AllowedOrigin == '*':
return True
# TODO: FIX doesn't work with GET requests same-origin
origin = self.headers.get('origin')
if not origin:
logging.warning("request with no Origin header from %s" % self.address_string())
return False
if origin != Handler.AllowedOrigin:
logging.warning("request with blocked Origin from %s: %s" % (self.address_string(), origin))
return False
return True
def do_OPTIONS(self):
self.send_response(200)
self._send_cors_headers()
self.end_headers()
def do_POST(self):
if not self._is_allowed():
return
if self.path.startswith('/shutdown'):
self._shutdown()
else:
self.send_response(404)
def do_GET(self):
if not self._is_allowed():
return
if self.path == '/':
self._index()
elif self.path.startswith('/ui'):
self._image()
elif self.path.startswith('/plugins'):
matches = re.match(r'\/plugins\/([^\/]+)(\/.*)?', self.path)
if matches:
groups = matches.groups()
plugin_name = groups[0]
right_path = groups[1] if len(groups) == 2 else None
plugins.one(plugin_name, 'webhook', self, right_path)
else:
self.send_response(404)
class Server(object):
def __init__(self, config):
self._enabled = config['video']['enabled']
self._port = config['video']['port']
self._address = config['video']['address']
self._httpd = None
if 'origin' in config['video']:
Handler.AllowedOrigin = config['video']['origin']
if self._enabled:
_thread.start_new_thread(self._http_serve, ())
def _http_serve(self):
if self._address is not None:
self._httpd = HTTPServer((self._address, self._port), Handler)
logging.info("web ui available at http://%s:%d/" % (self._address, self._port))
self._httpd.serve_forever()
else:
logging.info("could not get ip of usb0, video server not starting")

View File

@@ -0,0 +1,12 @@
from threading import Lock
frame_path = '/root/pwnagotchi.png'
frame_format = 'PNG'
frame_ctype = 'image/png'
frame_lock = Lock()
def update_frame(img):
global frame_lock, frame_path, frame_format
with frame_lock:
img.save(frame_path, format=frame_format)

View File

@@ -0,0 +1,78 @@
import logging
import os
import _thread
# https://stackoverflow.com/questions/14888799/disable-console-messages-in-flask-server
logging.getLogger('werkzeug').setLevel(logging.ERROR)
os.environ['WERKZEUG_RUN_MAIN'] = 'true'
import pwnagotchi
import pwnagotchi.ui.web as web
from pwnagotchi import plugins
from flask import send_file
from flask import request
from flask import abort
from flask import render_template, render_template_string
class Handler:
def __init__(self, agent, app):
self._agent = agent
self._app = app
self._app.add_url_rule('/', 'index', self.index)
self._app.add_url_rule('/ui', 'ui', self.ui)
self._app.add_url_rule('/shutdown', 'shutdown', self.shutdown, methods=['POST'])
self._app.add_url_rule('/restart', 'restart', self.restart, methods=['POST'])
# plugins
self._app.add_url_rule('/plugins', 'plugins', self.plugins, strict_slashes=False,
defaults={'name': None, 'subpath': None})
self._app.add_url_rule('/plugins/<name>', 'plugins', self.plugins, strict_slashes=False,
methods=['GET', 'POST'], defaults={'subpath': None})
self._app.add_url_rule('/plugins/<name>/<path:subpath>', 'plugins', self.plugins, methods=['GET', 'POST'])
def index(self):
return render_template('index.html', title=pwnagotchi.name(),
other_mode='AUTO' if self._agent.mode == 'manual' else 'MANU')
def plugins(self, name, subpath):
if name is None:
# show plugins overview
abort(404)
else:
# call plugin on_webhook
arguments = request.args
req_method = request.method
# need to return something here
if name in plugins.loaded and hasattr(plugins.loaded[name], 'on_webhook'):
return render_template_string(
plugins.loaded[name].on_webhook(subpath, args=arguments, req_method=req_method))
abort(500)
# serve a message and shuts down the unit
def shutdown(self):
try:
return render_template('status.html', title=pwnagotchi.name(), go_back_after=60,
message='Shutting down ...')
finally:
_thread.start_new_thread(pwnagotchi.shutdown, ())
# serve a message and restart the unit in the other mode
def restart(self):
mode = request.form['mode']
if mode not in ('AUTO', 'MANU'):
mode = 'MANU'
try:
return render_template('status.html', title=pwnagotchi.name(), go_back_after=30,
message='Restarting in %s mode ...' % mode)
finally:
_thread.start_new_thread(pwnagotchi.restart, (mode,))
# serve the PNG file with the display image
def ui(self):
with web.frame_lock:
return send_file(web.frame_path, mimetype='image/png')

View File

@@ -0,0 +1,50 @@
import _thread
import secrets
import logging
import os
# https://stackoverflow.com/questions/14888799/disable-console-messages-in-flask-server
logging.getLogger('werkzeug').setLevel(logging.ERROR)
os.environ['WERKZEUG_RUN_MAIN'] = 'true'
from flask import Flask
from flask_cors import CORS
from flask_wtf.csrf import CSRFProtect
from pwnagotchi.ui.web.handler import Handler
class Server:
def __init__(self, agent, config):
self._enabled = config['video']['enabled']
self._port = config['video']['port']
self._address = config['video']['address']
self._origin = None
self._agent = agent
if 'origin' in config['video']:
self._origin = config['video']['origin']
if self._enabled:
_thread.start_new_thread(self._http_serve, ())
def _http_serve(self):
if self._address is not None:
web_path = os.path.dirname(os.path.realpath(__file__))
app = Flask(__name__,
static_url_path='',
static_folder=os.path.join(web_path, 'static'),
template_folder=os.path.join(web_path, 'templates'))
app.secret_key = secrets.token_urlsafe(256)
if self._origin:
CORS(app, resources={r"*": {"origins": self._origin}})
CSRFProtect(app)
Handler(self._agent, app)
logging.info("web ui available at http://%s:%d/" % (self._address, self._port))
app.run(host=self._address, port=self._port, debug=False)
else:
logging.info("could not get ip of usb0, video server not starting")

View File

@@ -0,0 +1,42 @@
.block {
-webkit-appearance: button;
-moz-appearance: button;
appearance: button;
display: block;
cursor: pointer;
text-align: center;
}
img#ui {
width:100%;
}
.full {
position: absolute;
top:0;
left:0;
width:100%;
}
.pixelated {
image-rendering:optimizeSpeed; /* Legal fallback */
image-rendering:-moz-crisp-edges; /* Firefox */
image-rendering:-o-crisp-edges; /* Opera */
image-rendering:-webkit-optimize-contrast; /* Safari */
image-rendering:optimize-contrast; /* CSS3 Proposed */
image-rendering:crisp-edges; /* CSS4 Proposed */
image-rendering:pixelated; /* CSS4 Proposed */
-ms-interpolation-mode:nearest-neighbor; /* IE8+ */
}
form.action {
display:inline;
}
div.status {
position: absolute;
top:0;
left:0;
width:100%;
}

View File

@@ -0,0 +1,7 @@
window.onload = function() {
var image = document.getElementById("ui");
function updateImage() {
image.src = image.src.split("?")[0] + "?" + new Date().getTime();
}
setInterval(updateImage, 1000);
}

View File

@@ -0,0 +1,29 @@
<html>
<head>
<title>{{ title }}</title>
<link rel="stylesheet" type="text/css" href="/css/style.css"/>
</head>
<body>
<div class="full pixelated">
<img src="/ui" id="ui"/>
<br/>
<hr/>
<form class="action" method="POST" action="/shutdown"
onsubmit="return confirm('This will halt the unit, continue?');">
<input style="display:inline;" type="submit" class="block" value="Shutdown"/>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
</form>
<form class="action" method="POST" action="/restart"
onsubmit="return confirm('This will restart the service in {{ other_mode }} mode, continue?');">
<input style="display:inline;" type="submit" class="block" value="Restart in {{ other_mode }} mode"/>
<input type="hidden" name="mode" value="{{ other_mode }}"/>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
</form>
</div>
<script type="text/javascript" src="/js/refresh.js"></script>
</body>
</html>

View File

@@ -0,0 +1,12 @@
<html>
<head>
<title>{{ title }}</title>
<meta http-equiv="refresh" content="{{ go_back_after }};URL=/">
<link rel="stylesheet" type="text/css" href="/css/style.css"/>
</head>
<body>
<div class="status">
{{ message }}
</div>
</body>
</html>

View File

@@ -67,6 +67,13 @@ class Voice:
self._('I\'m sad'), self._('I\'m sad'),
'...']) '...'])
def on_angry(self):
# passive aggressive or not? :D
return random.choice([
'...',
self._('Leave me alone ...'),
self._('I\'m mad at you!')])
def on_excited(self): def on_excited(self):
return random.choice([ return random.choice([
self._('I\'m living the life!'), self._('I\'m living the life!'),

View File

@@ -14,3 +14,6 @@ smbus2==0.3.0
Pillow==5.4.1 Pillow==5.4.1
spidev==3.4 spidev==3.4
gast==0.2.2 gast==0.2.2
flask==1.0.2
flask-cors==3.0.7
flask-wtf==0.14.2

View File

@@ -3,7 +3,7 @@
# name of the ethernet gadget interface on the host # name of the ethernet gadget interface on the host
UNIT_HOSTNAME=${1:-10.0.0.2} UNIT_HOSTNAME=${1:-10.0.0.2}
# output backup zip file # output backup zip file
OUTPUT=${2:-pwnagotchi-backup.zip} OUTPUT=${2:-pwnagotchi-backup.tgz}
# username to use for ssh # username to use for ssh
USERNAME=${3:-pi} USERNAME=${3:-pi}
# what to backup # what to backup
@@ -19,38 +19,10 @@ FILES_TO_BACKUP=(
/home/pi/.bashrc /home/pi/.bashrc
) )
if ! type "zip" >/dev/null 2>&1; then
echo "This script requires zip, please resolve and try again"
exit 1
fi
ping -c 1 "${UNIT_HOSTNAME}" >/dev/null || { ping -c 1 "${UNIT_HOSTNAME}" >/dev/null || {
echo "@ unit ${UNIT_HOSTNAME} can't be reached, make sure it's connected and a static IP assigned to the USB interface." echo "@ unit ${UNIT_HOSTNAME} can't be reached, make sure it's connected and a static IP assigned to the USB interface."
exit 1 exit 1
} }
echo "@ backing up $UNIT_HOSTNAME to $OUTPUT ..." echo "@ backing up $UNIT_HOSTNAME to $OUTPUT ..."
ssh "${USERNAME}@${UNIT_HOSTNAME}" "sudo tar cv ${FILES_TO_BACKUP[@]}" | gzip -9 > "$OUTPUT"
ssh "${USERNAME}@${UNIT_HOSTNAME}" "sudo rm -rf /tmp/backup && sudo rm -rf /tmp/backup.zip" > /dev/null
for file in "${FILES_TO_BACKUP[@]}"; do
dir=$(dirname "$file")
echo "@ copying $file to /tmp/backup$dir"
ssh "${USERNAME}@${UNIT_HOSTNAME}" "mkdir -p /tmp/backup${dir}" > /dev/null
ssh "${USERNAME}@${UNIT_HOSTNAME}" "sudo cp -r ${file} /tmp/backup${dir}" > /dev/null
done
ssh "${USERNAME}@${UNIT_HOSTNAME}" "sudo chown ${USERNAME}:${USERNAME} -R /tmp/backup" > /dev/null
echo "@ pulling from $UNIT_HOSTNAME ..."
rm -rf /tmp/backup
scp -rC "${USERNAME}@${UNIT_HOSTNAME}":/tmp/backup /tmp/
echo "@ compressing ..."
zip -r -9 -q "$OUTPUT" /tmp/backup
rm -rf /tmp/backup

16
scripts/restore.sh Executable file
View File

@@ -0,0 +1,16 @@
#!/usr/bin/env bash
# name of the ethernet gadget interface on the host
UNIT_HOSTNAME=${1:-10.0.0.2}
# output backup zip file
BACKUP=${2:-pwnagotchi-backup.tgz}
# username to use for ssh
USERNAME=${3:-pi}
ping -c 1 "${UNIT_HOSTNAME}" >/dev/null || {
echo "@ unit ${UNIT_HOSTNAME} can't be reached, make sure it's connected and a static IP assigned to the USB interface."
exit 1
}
echo "@ restoring $BACKUP to $UNIT_HOSTNAME ..."
cat ${BACKUP} | ssh "${USERNAME}@${UNIT_HOSTNAME}" "sudo tar xzv -C /"

View File

@@ -1,7 +1,43 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from setuptools import setup, find_packages from setuptools import setup, find_packages
import pwnagotchi import os
import glob
import shutil
def install_file(source_filename, dest_filename):
# do not overwrite network configuration if it exists already
# https://github.com/evilsocket/pwnagotchi/issues/483
if dest_filename.startswith('/etc/network/interfaces.d/') and os.path.exists(dest_filename):
print("%s exists, skipping ..." % dest_filename)
return
print("installing %s to %s ..." % (source_filename, dest_filename))
try:
dest_folder = os.path.dirname(dest_filename)
if not os.path.isdir(dest_folder):
os.makedirs(dest_folder)
shutil.copyfile(source_filename, dest_filename)
except Exception as e:
print("error installing %s: %s" % (source_filename, e))
def install_system_files():
setup_path = os.path.dirname(__file__)
data_path = os.path.join(setup_path, "builder/data")
for source_filename in glob.glob("%s/**" % data_path, recursive=True):
if os.path.isfile(source_filename):
dest_filename = source_filename.replace(data_path, '')
install_file(source_filename, dest_filename)
# reload systemd units
os.system("systemctl daemon-reload")
install_system_files()
required = [] required = []
with open('requirements.txt') as fp: with open('requirements.txt') as fp:
@@ -10,6 +46,8 @@ with open('requirements.txt') as fp:
if line != "": if line != "":
required.append(line) required.append(line)
import pwnagotchi
setup(name='pwnagotchi', setup(name='pwnagotchi',
version=pwnagotchi.version, version=pwnagotchi.version,
description='(⌐■_■) - Deep Reinforcement Learning instrumenting bettercap for WiFI pwning.', description='(⌐■_■) - Deep Reinforcement Learning instrumenting bettercap for WiFI pwning.',