Compare commits
112 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
9e92201d82 | ||
|
4e592df6d8 | ||
|
403ee242a6 | ||
|
106d72c4a2 | ||
|
5ddc2d7080 | ||
|
52f1111a5b | ||
|
346773f790 | ||
|
9264b837c8 | ||
|
81032fe5e3 | ||
|
2aa73d1a7e | ||
|
b796384345 | ||
|
a6ca99c693 | ||
|
60d9fd46ae | ||
|
de71d18a72 | ||
|
aba5b938bc | ||
|
0830e0c74b | ||
|
cf8a4da9e7 | ||
|
80e2cdcd8d | ||
|
a5cfb9aa8b | ||
|
62a0cc6276 | ||
|
f8523eb382 | ||
|
58b0b0fea0 | ||
|
bdf585afe5 | ||
|
b7d1c82788 | ||
|
74838d6b96 | ||
|
7fc46ddcf6 | ||
|
4503e71bfb | ||
|
11fb95d299 | ||
|
0aaeeb8011 | ||
|
3e1f3d5eec | ||
|
86a3443b8d | ||
|
806efa1fc2 | ||
|
537519dea6 | ||
|
0c1d98f2ab | ||
|
4852b3f59e | ||
|
364af70ad5 | ||
|
e336fca0de | ||
|
00e7c04980 | ||
|
2549433e34 | ||
|
9f9fca02e5 | ||
|
6945e260bd | ||
|
c21986488d | ||
|
b52ceae2ee | ||
|
19fc25d508 | ||
|
ba22b7d5d7 | ||
|
59019efad0 | ||
|
20aa0d1909 | ||
|
c56c6bb8f5 | ||
|
1e426f7411 | ||
|
aeb6002e10 | ||
|
dc2362c371 | ||
|
d6c0ec0dfd | ||
|
04e551600d | ||
|
62983dfea5 | ||
|
b2c812d05d | ||
|
7ba9b35d06 | ||
|
b4b14ba9fd | ||
|
1ba3a69651 | ||
|
0aef199131 | ||
|
ace61836e5 | ||
|
d04f124add | ||
|
bc1db7ceea | ||
|
fd506b1533 | ||
|
6c44a687b1 | ||
|
a2bb66ad57 | ||
|
61af8b4762 | ||
|
9b58fed862 | ||
|
53ae8ea1cf | ||
|
5b66d687c4 | ||
|
4b74de48bf | ||
|
22e76f956c | ||
|
4418492637 | ||
|
31a89cbe4b | ||
|
d91f49d596 | ||
|
66dc03ec05 | ||
|
2f948306eb | ||
|
ae330dc0b5 | ||
|
e06f2a32e8 | ||
|
e184176ae4 | ||
|
1827ee564c | ||
|
bfdaffa14b | ||
|
53f99f4c28 | ||
|
3efa96b292 | ||
|
8118a10a6a | ||
|
bd63f71a1d | ||
|
31d401e03b | ||
|
3c32bbb582 | ||
|
03067da005 | ||
|
64aad56fba | ||
|
4240d05872 | ||
|
f03e07f0cf | ||
|
13064879e0 | ||
|
96c617e152 | ||
|
783ac61594 | ||
|
279d885ec6 | ||
|
a8705c1d3c | ||
|
90386c7a64 | ||
|
205480bc38 | ||
|
d2726c1a14 | ||
|
1c9a25d22a | ||
|
e9494992fc | ||
|
c7931450c3 | ||
|
cc7299153c | ||
|
a27f09871f | ||
|
3714899e95 | ||
|
be414e57b3 | ||
|
1600d8cbd1 | ||
|
965416483d | ||
|
78a036ed1a | ||
|
ddc264bbb9 | ||
|
f0092ff154 | ||
|
ed0df18f68 |
@@ -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 *.*
|
||||||
|
12
README.md
12
README.md
@@ -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
|
|||||||
|
|
||||||
| Official Links
|
| 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
|
||||||
|
|
||||||
|
@@ -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:
|
||||||
|
2
builder/data/etc/network/interfaces.d/eth0-cfg
Normal file
2
builder/data/etc/network/interfaces.d/eth0-cfg
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
allow-hotplug eth0
|
||||||
|
iface eth0 inet dhcp
|
2
builder/data/etc/network/interfaces.d/lo-cfg
Normal file
2
builder/data/etc/network/interfaces.d/lo-cfg
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
auto lo
|
||||||
|
iface lo inet loopback
|
7
builder/data/etc/network/interfaces.d/usb0-cfg
Normal file
7
builder/data/etc/network/interfaces.d/usb0-cfg
Normal 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
|
2
builder/data/etc/network/interfaces.d/wlan0-cfg
Normal file
2
builder/data/etc/network/interfaces.d/wlan0-cfg
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
allow-hotplug wlan0
|
||||||
|
iface wlan0 inet static
|
14
builder/data/etc/systemd/system/bettercap.service
Normal file
14
builder/data/etc/systemd/system/bettercap.service
Normal 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
|
15
builder/data/etc/systemd/system/pwnagotchi.service
Normal file
15
builder/data/etc/systemd/system/pwnagotchi.service
Normal 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
|
15
builder/data/etc/systemd/system/pwngrid-peer.service
Normal file
15
builder/data/etc/systemd/system/pwngrid-peer.service
Normal 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
|
11
builder/data/usr/bin/bettercap-launcher
Executable file
11
builder/data/usr/bin/bettercap-launcher
Executable 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
2
builder/data/usr/bin/hdmioff
Executable file
@@ -0,0 +1,2 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
sudo /opt/vc/bin/tvservice -o
|
2
builder/data/usr/bin/hdmion
Executable file
2
builder/data/usr/bin/hdmion
Executable file
@@ -0,0 +1,2 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
sudo /opt/vc/bin/tvservice -p
|
3
builder/data/usr/bin/monstart
Executable file
3
builder/data/usr/bin/monstart
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
source /usr/bin/pwnlib
|
||||||
|
start_monitor_interface
|
3
builder/data/usr/bin/monstop
Executable file
3
builder/data/usr/bin/monstop
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
source /usr/bin/pwnlib
|
||||||
|
stop_monitor_interface
|
11
builder/data/usr/bin/pwnagotchi-launcher
Executable file
11
builder/data/usr/bin/pwnagotchi-launcher
Executable 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
87
builder/data/usr/bin/pwnlib
Executable 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
|
||||||
|
}
|
@@ -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"
|
||||||
},
|
},
|
||||||
|
@@ -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 }}"
|
||||||
|
@@ -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")
|
||||||
|
@@ -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:
|
||||||
|
@@ -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()
|
||||||
|
@@ -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: '(#__#)'
|
||||||
|
BIN
pwnagotchi/locale/ch/LC_MESSAGES/voice.mo
Normal file
BIN
pwnagotchi/locale/ch/LC_MESSAGES/voice.mo
Normal file
Binary file not shown.
225
pwnagotchi/locale/ch/LC_MESSAGES/voice.po
Normal file
225
pwnagotchi/locale/ch/LC_MESSAGES/voice.po
Normal 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 "秒"
|
Binary file not shown.
@@ -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"
|
||||||
|
BIN
pwnagotchi/locale/ua/LC_MESSAGES/voice.mo
Normal file
BIN
pwnagotchi/locale/ua/LC_MESSAGES/voice.mo
Normal file
Binary file not shown.
228
pwnagotchi/locale/ua/LC_MESSAGES/voice.po
Normal file
228
pwnagotchi/locale/ua/LC_MESSAGES/voice.po
Normal 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 "секунда"
|
@@ -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 ""
|
||||||
|
@@ -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')
|
||||||
|
@@ -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 = ""
|
|
||||||
|
@@ -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()
|
||||||
|
@@ -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 ...'})
|
||||||
|
@@ -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))
|
|
||||||
|
@@ -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
|
|
||||||
|
@@ -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)
|
||||||
|
@@ -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)
|
|
||||||
|
@@ -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)
|
|
||||||
|
@@ -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()))
|
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
||||||
|
@@ -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')
|
||||||
|
@@ -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 = ""
|
|
||||||
|
@@ -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
|
||||||
|
@@ -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")
|
||||||
|
@@ -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 ##
|
|
@@ -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()))
|
|
||||||
|
@@ -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)
|
||||||
|
@@ -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
|
|
||||||
|
@@ -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'])
|
||||||
|
@@ -16,6 +16,7 @@ DEMOTIVATED = '(≖__≖)'
|
|||||||
SMART = '(✜‿‿✜)'
|
SMART = '(✜‿‿✜)'
|
||||||
LONELY = '(ب__ب)'
|
LONELY = '(ب__ب)'
|
||||||
SAD = '(╥☁╥ )'
|
SAD = '(╥☁╥ )'
|
||||||
|
ANGRY = "(-_-')"
|
||||||
FRIEND = '(♥‿‿♥)'
|
FRIEND = '(♥‿‿♥)'
|
||||||
BROKEN = '(☓‿‿☓)'
|
BROKEN = '(☓‿‿☓)'
|
||||||
DEBUG = '(#__#)'
|
DEBUG = '(#__#)'
|
||||||
|
@@ -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)
|
||||||
|
|
||||||
|
201
pwnagotchi/ui/hw/libs/waveshare/v29inch/epd2in9.py
Normal file
201
pwnagotchi/ui/hw/libs/waveshare/v29inch/epd2in9.py
Normal 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 ###
|
||||||
|
|
154
pwnagotchi/ui/hw/libs/waveshare/v29inch/epdconfig.py
Normal file
154
pwnagotchi/ui/hw/libs/waveshare/v29inch/epdconfig.py
Normal 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 ###
|
47
pwnagotchi/ui/hw/waveshare29inch.py
Normal file
47
pwnagotchi/ui/hw/waveshare29inch.py
Normal 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)
|
@@ -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)
|
||||||
|
|
||||||
|
@@ -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")
|
|
12
pwnagotchi/ui/web/__init__.py
Normal file
12
pwnagotchi/ui/web/__init__.py
Normal 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)
|
78
pwnagotchi/ui/web/handler.py
Normal file
78
pwnagotchi/ui/web/handler.py
Normal 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')
|
50
pwnagotchi/ui/web/server.py
Normal file
50
pwnagotchi/ui/web/server.py
Normal 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")
|
42
pwnagotchi/ui/web/static/css/style.css
Normal file
42
pwnagotchi/ui/web/static/css/style.css
Normal 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%;
|
||||||
|
}
|
7
pwnagotchi/ui/web/static/js/refresh.js
Normal file
7
pwnagotchi/ui/web/static/js/refresh.js
Normal 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);
|
||||||
|
}
|
29
pwnagotchi/ui/web/templates/index.html
Normal file
29
pwnagotchi/ui/web/templates/index.html
Normal 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>
|
12
pwnagotchi/ui/web/templates/status.html
Normal file
12
pwnagotchi/ui/web/templates/status.html
Normal 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>
|
@@ -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!'),
|
||||||
|
@@ -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
|
@@ -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
16
scripts/restore.sh
Executable 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 /"
|
40
setup.py
40
setup.py
@@ -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.',
|
||||||
|
Reference in New Issue
Block a user