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 pwnagotchi *.py
|
||||
recursive-include pwnagotchi *.yml
|
||||
recursive-include pwnagotchi *.*
|
||||
|
12
README.md
12
README.md
@@ -1,15 +1,14 @@
|
||||
# Pwnagotchi
|
||||
|
||||
<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/blob/master/LICENSE.md"><img alt="Software License" src="https://img.shields.io/badge/license-GPL3-brightgreen.svg?style=flat-square"></a>
|
||||
<a href="https://github.com/evilsocket/pwnagotchi/graphs/contributors"><img alt="Contributors" src="https://img.shields.io/github/contributors/evilsocket/pwnagotchi"/></a>
|
||||
<a href="https://travis-ci.org/evilsocket/pwnagotchi"><img alt="Travis" src="https://img.shields.io/travis/evilsocket/pwnagotchi/master.svg?style=flat-square"></a>
|
||||
<a href="https://pwnagotchi.herokuapp.com/"><img alt="Slack" src="https://pwnagotchi.herokuapp.com/badge.svg"></a>
|
||||
<a href="https://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>
|
||||
</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/),
|
||||
full and half WPA handshakes.
|
||||
@@ -32,10 +31,11 @@ https://www.pwnagotchi.ai
|
||||
|
||||
| Official Links
|
||||
---------|-------
|
||||
Slack | [pwnagotchi.slack.com](https://pwnagotchi.herokuapp.com)
|
||||
Twitter | [@pwnagotchi](https://twitter.com/pwnagotchi)
|
||||
Subreddit | [r/pwnagotchi](https://www.reddit.com/r/pwnagotchi/)
|
||||
Website | [pwnagotchi.ai](https://pwnagotchi.ai/)
|
||||
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
|
||||
|
||||
|
@@ -31,7 +31,15 @@ if __name__ == '__main__':
|
||||
parser.add_argument('--debug', dest="debug", action="store_true", default=False,
|
||||
help="Enable debug logs.")
|
||||
|
||||
parser.add_argument('--version', dest="version", action="store_true", default=False,
|
||||
help="Prints the version.")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.version:
|
||||
print(pwnagotchi.version)
|
||||
SystemExit(0)
|
||||
|
||||
config = utils.load_config(args)
|
||||
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))
|
||||
|
||||
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:
|
||||
logging.info("clearing the display ...")
|
||||
@@ -57,6 +65,7 @@ if __name__ == '__main__':
|
||||
elif args.do_manual:
|
||||
logging.info("entering manual mode ...")
|
||||
|
||||
agent.mode = 'manual'
|
||||
agent.last_session.parse(agent.view(), args.skip_session)
|
||||
if not args.skip_session:
|
||||
logging.info(
|
||||
@@ -77,6 +86,7 @@ if __name__ == '__main__':
|
||||
else:
|
||||
logging.info("entering auto mode ...")
|
||||
|
||||
agent.mode = 'auto'
|
||||
agent.start()
|
||||
|
||||
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",
|
||||
"iso_url": "https://downloads.raspberrypi.org/raspbian_lite/images/raspbian_lite-2019-07-12/2019-07-10-raspbian-buster-lite.zip",
|
||||
"iso_checksum_type": "sha256",
|
||||
"iso_checksum": "9e5cf24ce483bb96e7736ea75ca422e3560e7b455eee63dd28f66fa1825db70e",
|
||||
"last_partition_extra_size": 3221225472
|
||||
}],
|
||||
}
|
||||
],
|
||||
"provisioners": [
|
||||
{
|
||||
"type": "shell",
|
||||
@@ -17,6 +19,82 @@
|
||||
"apt-get install -y ansible"
|
||||
]
|
||||
},
|
||||
{
|
||||
"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",
|
||||
|
@@ -13,6 +13,7 @@
|
||||
- "dtparam=spi=on"
|
||||
- "dtparam=i2c_arm=on"
|
||||
- "dtparam=i2c1=on"
|
||||
- "gpu_mem=16"
|
||||
modules:
|
||||
- "i2c-dev"
|
||||
services:
|
||||
@@ -98,6 +99,9 @@
|
||||
- bc
|
||||
- fonts-freefont-ttf
|
||||
- fbi
|
||||
- python3-flask
|
||||
- python3-flask-cors
|
||||
- python3-flaskext.wtf
|
||||
|
||||
tasks:
|
||||
- name: change hostname
|
||||
@@ -279,93 +283,6 @@
|
||||
remote_src: yes
|
||||
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
|
||||
blockinfile:
|
||||
path: /etc/rc.local
|
||||
@@ -399,39 +316,6 @@
|
||||
#
|
||||
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
|
||||
file:
|
||||
path: /boot/ssh
|
||||
@@ -471,7 +355,7 @@
|
||||
copy:
|
||||
dest: /etc/motd
|
||||
content: |
|
||||
(◕‿‿◕) {{pwnagotchi.hostname}} (pwnagotchi-{{pwnagotchi.version}})
|
||||
(◕‿‿◕) {{pwnagotchi.hostname}}
|
||||
|
||||
Hi! I'm a pwnagotchi, please take good care of me!
|
||||
Here are some basic things you need to know to raise me properly!
|
||||
@@ -507,71 +391,6 @@
|
||||
apt:
|
||||
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
|
||||
systemd:
|
||||
name: "{{ item }}"
|
||||
|
@@ -6,7 +6,7 @@ import re
|
||||
import pwnagotchi.ui.view as view
|
||||
import pwnagotchi
|
||||
|
||||
version = '1.1.0'
|
||||
version = '1.2.1'
|
||||
|
||||
_name = None
|
||||
|
||||
@@ -103,12 +103,39 @@ def shutdown():
|
||||
if view.ROOT:
|
||||
view.ROOT.on_shutdown()
|
||||
# give it some time to refresh the ui
|
||||
time.sleep(5)
|
||||
time.sleep(10)
|
||||
os.system("sync")
|
||||
os.system("halt")
|
||||
|
||||
|
||||
def reboot():
|
||||
def restart(mode):
|
||||
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("shutdown -r now")
|
||||
|
@@ -8,6 +8,7 @@ import _thread
|
||||
import pwnagotchi
|
||||
import pwnagotchi.utils as utils
|
||||
import pwnagotchi.plugins as plugins
|
||||
from pwnagotchi.ui.web.server import Server
|
||||
from pwnagotchi.automata import Automata
|
||||
from pwnagotchi.log import LastSession
|
||||
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._view = view
|
||||
self._view.set_agent(self)
|
||||
self._web_ui = Server(self, self._config['ui']['display'])
|
||||
|
||||
self._access_points = []
|
||||
self._last_pwnd = None
|
||||
self._history = {}
|
||||
self._handshakes = {}
|
||||
self.last_session = LastSession(self._config)
|
||||
self.mode = 'auto'
|
||||
|
||||
if not os.path.exists(config['bettercap']['handshakes']):
|
||||
os.makedirs(config['bettercap']['handshakes'])
|
||||
@@ -170,7 +174,9 @@ class Agent(Client, Automata, AsyncAdvertiser, AsyncTrainer):
|
||||
s = self.session()
|
||||
plugins.on("unfiltered_ap_list", self, 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):
|
||||
aps.append(ap)
|
||||
except Exception as e:
|
||||
|
@@ -75,6 +75,15 @@ class Automata(object):
|
||||
logging.info("unit is grateful instead of sad")
|
||||
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):
|
||||
logging.warning("%d epochs with activity -> excited" % self._epoch.active_for)
|
||||
self._view.on_excited()
|
||||
@@ -103,12 +112,20 @@ class Automata(object):
|
||||
|
||||
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:
|
||||
factor = did_miss / self._config['personality']['max_misses_for_recon']
|
||||
if factor >= 2.0:
|
||||
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
|
||||
# after X times being bored, the status is set to sad or angry
|
||||
elif self._epoch.inactive_for >= self._config['personality']['sad_num_epochs']:
|
||||
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
|
||||
elif self._epoch.inactive_for >= self._config['personality']['bored_num_epochs']:
|
||||
|
@@ -21,13 +21,14 @@ main:
|
||||
- YourHomeNetworkHere
|
||||
|
||||
auto-update:
|
||||
enabled: false
|
||||
interval: 12 # every 12 hours
|
||||
enabled: true
|
||||
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:
|
||||
enabled: false
|
||||
interval: 1 # every day
|
||||
max_tries: 0 # 0=infinity
|
||||
files:
|
||||
- /root/brain.nn
|
||||
- /root/brain.json
|
||||
@@ -71,11 +72,29 @@ main:
|
||||
enabled: false
|
||||
bt-tether:
|
||||
enabled: false # if you want to use this, set ui.display.video.address to 0.0.0.0
|
||||
devices:
|
||||
android-phone:
|
||||
enabled: false
|
||||
search_order: 1 # search for this first
|
||||
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 x minutes for device
|
||||
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
|
||||
enabled: false
|
||||
orientation: horizontal # horizontal/vertical
|
||||
@@ -199,6 +218,7 @@ ui:
|
||||
smart: '(✜‿‿✜)'
|
||||
lonely: '(ب__ب)'
|
||||
sad: '(╥☁╥ )'
|
||||
angry: "(-_-')"
|
||||
friend: '(♥‿‿♥)'
|
||||
broken: '(☓‿‿☓)'
|
||||
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 ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 0.0.2\n"
|
||||
"Project-Id-Version: 0.1.0\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"
|
||||
"Last-Translator: gkrs <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."
|
||||
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 ..."
|
||||
msgstr "Nudzi mi się ..."
|
||||
|
||||
@@ -62,6 +69,12 @@ msgstr "Jest mi bardzo smutno ..."
|
||||
msgid "I'm sad"
|
||||
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!"
|
||||
msgstr "Cieszę się życiem!"
|
||||
|
||||
@@ -81,6 +94,14 @@ msgstr "Moją zbrodnią jest ciekawość ..."
|
||||
msgid "Hello {name}! Nice to meet you."
|
||||
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
|
||||
msgid "Unit {name} is nearby!"
|
||||
msgstr "Urządzenie {name} jest w pobliżu!"
|
||||
@@ -104,6 +125,12 @@ msgstr "{name} pudło!"
|
||||
msgid "Missed!"
|
||||
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 ..."
|
||||
msgstr "Nikt nie chce się ze mną bawić ..."
|
||||
|
||||
@@ -166,6 +193,10 @@ msgstr "Banuję {mac}!"
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
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 ..."
|
||||
msgstr "Ups, coś poszło nie tak ... Restaruję ..."
|
||||
|
||||
@@ -195,8 +226,8 @@ msgid ""
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
msgstr ""
|
||||
"Pwnowałem {duration} i wyrzuciłem {deauthed} klientów! Spotkałem także "
|
||||
"{associated} nowych przyjaciół i zjadłem {handshakes} handshake'ow! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
"{associated} nowych przyjaciół i zjadłem {handshakes} handshake'ow! "
|
||||
"#pwnagotchi #pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
|
||||
msgid "hours"
|
||||
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 ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -42,6 +42,13 @@ msgstr ""
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr ""
|
||||
|
||||
msgid "Reading last session logs ..."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Read {lines_so_far} log lines so far ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm bored ..."
|
||||
msgstr ""
|
||||
|
||||
@@ -63,6 +70,12 @@ msgstr ""
|
||||
msgid "I'm sad"
|
||||
msgstr ""
|
||||
|
||||
msgid "Leave me alone ..."
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm mad at you!"
|
||||
msgstr ""
|
||||
|
||||
msgid "I'm living the life!"
|
||||
msgstr ""
|
||||
|
||||
@@ -82,6 +95,14 @@ msgstr ""
|
||||
msgid "Hello {name}! Nice to meet you."
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Yo {name}! Sup?"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Hey {name} how are you doing?"
|
||||
msgstr ""
|
||||
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby!"
|
||||
msgstr ""
|
||||
|
@@ -7,20 +7,20 @@ default_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "defaul
|
||||
loaded = {}
|
||||
|
||||
|
||||
def dummy_callback():
|
||||
pass
|
||||
class Plugin:
|
||||
@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):
|
||||
global loaded
|
||||
cb_name = 'on_%s' % event_name
|
||||
for plugin_name, plugin in loaded.items():
|
||||
if cb_name in plugin.__dict__:
|
||||
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)
|
||||
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:
|
||||
plugin = loaded[plugin_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:
|
||||
plugin.__dict__[cb_name](*args, **kwargs)
|
||||
callback(*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 load_from_file(filename):
|
||||
logging.debug("loading %s" % filename)
|
||||
plugin_name = os.path.basename(filename.replace(".py", ""))
|
||||
spec = importlib.util.spec_from_file_location(plugin_name, filename)
|
||||
instance = importlib.util.module_from_spec(spec)
|
||||
@@ -46,16 +48,12 @@ def load_from_file(filename):
|
||||
|
||||
def load_from_path(path, enabled=()):
|
||||
global loaded
|
||||
logging.debug("loading plugins from %s - enabled: %s" % (path, enabled))
|
||||
for filename in glob.glob(os.path.join(path, "*.py")):
|
||||
plugin_name = os.path.basename(filename.replace(".py", ""))
|
||||
if plugin_name in enabled:
|
||||
try:
|
||||
name, plugin = load_from_file(filename)
|
||||
if name in loaded:
|
||||
raise Exception("plugin %s already loaded from %s" % (name, plugin.__file__))
|
||||
elif name not in enabled:
|
||||
# print("plugin %s is not enabled" % name)
|
||||
pass
|
||||
else:
|
||||
loaded[name] = plugin
|
||||
load_from_file(filename)
|
||||
except Exception as e:
|
||||
logging.warning("error while loading %s: %s" % (filename, e))
|
||||
logging.debug(e, exc_info=True)
|
||||
@@ -66,17 +64,17 @@ def load_from_path(path, enabled=()):
|
||||
def load(config):
|
||||
enabled = [name for name, options in config['main']['plugins'].items() if
|
||||
'enabled' in options and options['enabled']]
|
||||
custom_path = config['main']['custom_plugins'] if 'custom_plugins' in config['main'] else None
|
||||
|
||||
# load default plugins
|
||||
loaded = load_from_path(default_path, enabled=enabled)
|
||||
# set the options
|
||||
for name, plugin in loaded.items():
|
||||
plugin.__dict__['OPTIONS'] = config['main']['plugins'][name]
|
||||
load_from_path(default_path, enabled=enabled)
|
||||
|
||||
# load custom ones
|
||||
custom_path = config['main']['custom_plugins'] if 'custom_plugins' in config['main'] else None
|
||||
if custom_path is not None:
|
||||
loaded = load_from_path(custom_path, enabled=enabled)
|
||||
# set the options
|
||||
load_from_path(custom_path, enabled=enabled)
|
||||
|
||||
# propagate options
|
||||
for name, plugin in loaded.items():
|
||||
plugin.__dict__['OPTIONS'] = config['main']['plugins'][name]
|
||||
plugin.options = config['main']['plugins'][name]
|
||||
|
||||
on('loaded')
|
||||
|
@@ -1,37 +1,44 @@
|
||||
__author__ = 'pwnagotchi [at] rossmarks [dot] uk'
|
||||
__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 pwnagotchi.plugins as plugins
|
||||
|
||||
import logging
|
||||
import subprocess
|
||||
import string
|
||||
import os
|
||||
|
||||
OPTIONS = dict()
|
||||
'''
|
||||
Aircrack-ng needed, to install:
|
||||
> apt-get install aircrack-ng
|
||||
'''
|
||||
|
||||
def on_loaded():
|
||||
|
||||
class AircrackOnly(plugins.Plugin):
|
||||
__author__ = 'pwnagotchi [at] rossmarks [dot] uk'
|
||||
__version__ = '1.0.1'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'confirm pcap contains handshake/PMKID or delete it'
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(self)
|
||||
self.text_to_set = ""
|
||||
|
||||
def on_loaded(self):
|
||||
logging.info("aircrackonly plugin loaded")
|
||||
|
||||
def on_handshake(agent, filename, access_point, client_station):
|
||||
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 = 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:
|
||||
handshakeFound = 1
|
||||
logging.info("[AircrackOnly] contains handshake")
|
||||
else:
|
||||
todelete = 1
|
||||
|
||||
if todelete == 0:
|
||||
result = subprocess.run(('/usr/bin/aircrack-ng '+ filename +' | grep "PMKID" | awk \'{print $2}\''),shell=True, stdout=subprocess.PIPE)
|
||||
if handshakeFound == 0:
|
||||
result = subprocess.run(('/usr/bin/aircrack-ng ' + filename + ' | grep "PMKID" | 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:
|
||||
logging.info("[AircrackOnly] contains PMKID")
|
||||
@@ -40,17 +47,11 @@ def on_handshake(agent, filename, access_point, client_station):
|
||||
|
||||
if todelete == 1:
|
||||
os.remove(filename)
|
||||
set_text("Removed an uncrackable pcap")
|
||||
self.text_to_set = "Removed an uncrackable pcap"
|
||||
display.update(force=True)
|
||||
|
||||
text_to_set = "";
|
||||
def set_text(text):
|
||||
global text_to_set
|
||||
text_to_set = text
|
||||
|
||||
def on_ui_update(ui):
|
||||
global text_to_set
|
||||
if text_to_set:
|
||||
def on_ui_update(self, ui):
|
||||
if self.text_to_set:
|
||||
ui.set('face', "(>.<)")
|
||||
ui.set('status', text_to_set)
|
||||
text_to_set = ""
|
||||
ui.set('status', self.text_to_set)
|
||||
self.text_to_set = ""
|
||||
|
@@ -1,47 +1,42 @@
|
||||
__author__ = '33197631+dadav@users.noreply.github.com'
|
||||
__version__ = '1.0.0'
|
||||
__name__ = 'auto-backup'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'This plugin backups files when internet is available.'
|
||||
|
||||
import pwnagotchi.plugins as plugins
|
||||
from pwnagotchi.utils import StatusFile
|
||||
import logging
|
||||
import os
|
||||
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():
|
||||
global READY
|
||||
def __init__(self):
|
||||
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):
|
||||
logging.error("AUTO-BACKUP: No files to backup.")
|
||||
def on_loaded(self):
|
||||
for opt in ['files', 'interval', 'commands', 'max_tries']:
|
||||
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):
|
||||
logging.error("AUTO-BACKUP: Interval is not set.")
|
||||
return
|
||||
|
||||
if 'commands' not in OPTIONS or ('commands' in OPTIONS and OPTIONS['commands'] is None):
|
||||
logging.error("AUTO-BACKUP: No commands given.")
|
||||
return
|
||||
|
||||
READY = True
|
||||
self.ready = True
|
||||
logging.info("AUTO-BACKUP: Successfully loaded.")
|
||||
|
||||
def on_internet_available(self, agent):
|
||||
if not self.ready:
|
||||
return
|
||||
|
||||
def on_internet_available(agent):
|
||||
global STATUS
|
||||
if self.options['max_tries'] and self.tries >= self.options['max_tries']:
|
||||
return
|
||||
|
||||
if READY:
|
||||
if STATUS.newer_then_days(OPTIONS['interval']):
|
||||
if self.status.newer_then_days(self.options['interval']):
|
||||
return
|
||||
|
||||
# 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)
|
||||
|
||||
try:
|
||||
@@ -51,7 +46,7 @@ def on_internet_available(agent):
|
||||
display.set('status', 'Backing up ...')
|
||||
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)}")
|
||||
process = subprocess.Popen(cmd.format(files=files_to_backup), shell=True, stdin=None,
|
||||
stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
|
||||
@@ -62,8 +57,9 @@ def on_internet_available(agent):
|
||||
logging.info("AUTO-BACKUP: backup done")
|
||||
display.set('status', 'Backup done!')
|
||||
display.update()
|
||||
STATUS.update()
|
||||
self.status.update()
|
||||
except OSError as os_e:
|
||||
self.tries += 1
|
||||
logging.info(f"AUTO-BACKUP: Error: {os_e}")
|
||||
display.set('status', 'Backup failed!')
|
||||
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 re
|
||||
import logging
|
||||
import subprocess
|
||||
import requests
|
||||
@@ -14,21 +9,9 @@ import glob
|
||||
import pkg_resources
|
||||
|
||||
import pwnagotchi
|
||||
import pwnagotchi.plugins as plugins
|
||||
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):
|
||||
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):
|
||||
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)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def on_internet_available(agent):
|
||||
global STATUS
|
||||
def parse_version(cmd):
|
||||
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:
|
||||
if STATUS.newer_then_hours(OPTIONS['interval']):
|
||||
logging.debug("[update] last check happened less than %d hours ago" % OPTIONS['interval'])
|
||||
class AutoUpdate(plugins.Plugin):
|
||||
__author__ = 'evilsocket@gmail.com'
|
||||
__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
|
||||
|
||||
logging.info("[update] checking for updates ...")
|
||||
@@ -167,9 +178,8 @@ def on_internet_available(agent):
|
||||
|
||||
to_install = []
|
||||
to_check = [
|
||||
('bettercap/bettercap', subprocess.getoutput('bettercap -version').split(' ')[1].replace('v', ''),
|
||||
True, 'bettercap'),
|
||||
('evilsocket/pwngrid', subprocess.getoutput('pwngrid -version').replace('v', ''), True, 'pwngrid-peer'),
|
||||
('bettercap/bettercap', parse_version('bettercap -version'), True, 'bettercap'),
|
||||
('evilsocket/pwngrid', parse_version('pwngrid -version'), True, 'pwngrid-peer'),
|
||||
('evilsocket/pwnagotchi', pwnagotchi.version, False, 'pwnagotchi')
|
||||
]
|
||||
|
||||
@@ -177,7 +187,8 @@ def on_internet_available(agent):
|
||||
info = check(local_version, repo, is_native)
|
||||
if info['url'] is not None:
|
||||
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
|
||||
to_install.append(info)
|
||||
|
||||
@@ -185,7 +196,7 @@ def on_internet_available(agent):
|
||||
num_installed = 0
|
||||
|
||||
if num_updates > 0:
|
||||
if OPTIONS['install']:
|
||||
if self.options['install']:
|
||||
for update in to_install:
|
||||
if install(display, update):
|
||||
num_installed += 1
|
||||
@@ -194,7 +205,7 @@ def on_internet_available(agent):
|
||||
|
||||
logging.info("[update] done")
|
||||
|
||||
STATUS.update()
|
||||
self.status.update()
|
||||
|
||||
if num_installed > 0:
|
||||
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 time
|
||||
import re
|
||||
@@ -14,10 +8,7 @@ from pwnagotchi.ui.components import LabeledValue
|
||||
from pwnagotchi.ui.view import BLACK
|
||||
import pwnagotchi.ui.fonts as fonts
|
||||
from pwnagotchi.utils import StatusFile
|
||||
|
||||
READY = False
|
||||
INTERVAL = StatusFile('/root/.bt-tether')
|
||||
OPTIONS = dict()
|
||||
import pwnagotchi.plugins as plugins
|
||||
|
||||
|
||||
class BTError(Exception):
|
||||
@@ -26,6 +17,7 @@ class BTError(Exception):
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class BTNap:
|
||||
"""
|
||||
This class creates a bluetooth connection to the specified bt-mac
|
||||
@@ -41,7 +33,6 @@ class BTNap:
|
||||
def __init__(self, mac):
|
||||
self._mac = mac
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_bus():
|
||||
"""
|
||||
@@ -82,7 +73,6 @@ class BTNap:
|
||||
iface = obj.dbus_interface
|
||||
return obj.Set(iface, k, v, dbus_interface=BTNap.IFACE_PROPS)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def find_adapter(pattern=None):
|
||||
"""
|
||||
@@ -132,7 +122,7 @@ class BTNap:
|
||||
device = ifaces.get(BTNap.IFACE_DEV)
|
||||
if device is None:
|
||||
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)
|
||||
return dbus.Interface(obj, BTNap.IFACE_DEV)
|
||||
raise BTError('Bluetooth device not found')
|
||||
@@ -159,25 +149,6 @@ class BTNap:
|
||||
|
||||
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):
|
||||
"""
|
||||
@@ -198,7 +169,6 @@ class BTNap:
|
||||
logging.debug("BT-TETHER: Device is not paired.")
|
||||
return False
|
||||
|
||||
|
||||
def wait_for_device(self, timeout=15):
|
||||
"""
|
||||
Wait for device
|
||||
@@ -249,7 +219,7 @@ class BTNap:
|
||||
logging.debug('BT-TETHER: Trying to pair ...')
|
||||
try:
|
||||
device.Pair()
|
||||
logging.info('BT-TETHER: Successful paired with device ;)')
|
||||
logging.debug('BT-TETHER: Successful paired with device ;)')
|
||||
return True
|
||||
except dbus.exceptions.DBusException as err:
|
||||
if err.get_dbus_name() == 'org.bluez.Error.AlreadyExists':
|
||||
@@ -259,7 +229,6 @@ class BTNap:
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
@staticmethod
|
||||
def nap(device):
|
||||
logging.debug('BT-TETHER: Trying to nap ...')
|
||||
@@ -275,15 +244,15 @@ class BTNap:
|
||||
try:
|
||||
logging.debug('BT-TETHER: Connecting to nap network ...')
|
||||
net.Connect('nap')
|
||||
return True
|
||||
return net, True
|
||||
except dbus.exceptions.DBusException as err:
|
||||
if err.get_dbus_name() == 'org.bluez.Error.AlreadyConnected':
|
||||
return True
|
||||
return net, True
|
||||
|
||||
connected = BTNap.prop_get(net, 'Connected')
|
||||
if not connected:
|
||||
return False
|
||||
return True
|
||||
return None, False
|
||||
return net, True
|
||||
|
||||
|
||||
class SystemdUnitWrapper:
|
||||
@@ -387,7 +356,6 @@ class IfaceWrapper:
|
||||
"""
|
||||
return open(f"{self.path}/operstate", 'r').read().rsplit('\n') == 'up'
|
||||
|
||||
|
||||
def set_addr(self, addr):
|
||||
"""
|
||||
Set the netmask
|
||||
@@ -402,8 +370,8 @@ class IfaceWrapper:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def set_route(addr):
|
||||
process = subprocess.Popen(f"ip route replace default via {addr}", shell=True, stdin=None,
|
||||
def set_route(gateway, device):
|
||||
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")
|
||||
process.wait()
|
||||
|
||||
@@ -413,122 +381,190 @@ class IfaceWrapper:
|
||||
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():
|
||||
"""
|
||||
Gets called when the plugin gets loaded
|
||||
"""
|
||||
global READY
|
||||
global INTERVAL
|
||||
self.max_tries = max_tries
|
||||
self.search_order = search_order
|
||||
self.share_internet = share_internet
|
||||
self.ip = ip
|
||||
self.netmask = netmask
|
||||
self.interval = interval
|
||||
self.mac = mac
|
||||
self.scantime = scantime
|
||||
self.priority = priority
|
||||
|
||||
def connected(self):
|
||||
"""
|
||||
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 OPTIONS or (opt in OPTIONS and OPTIONS[opt] is None):
|
||||
logging.error("BT-TET: Please specify the %s in your config.yml.", opt)
|
||||
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
|
||||
|
||||
# ensure bluetooth is running
|
||||
bt_unit = SystemdUnitWrapper('bluetooth.service')
|
||||
if not bt_unit.is_active():
|
||||
if not bt_unit.start():
|
||||
logging.error("BT-TET: Can't start bluetooth.service")
|
||||
logging.error("BT-TETHER: Can't start bluetooth.service")
|
||||
return
|
||||
|
||||
INTERVAL.update()
|
||||
READY = True
|
||||
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(ui):
|
||||
"""
|
||||
Try to connect to device
|
||||
"""
|
||||
|
||||
if READY:
|
||||
global INTERVAL
|
||||
if INTERVAL.newer_then_minutes(OPTIONS['interval']):
|
||||
def on_ui_update(self, ui):
|
||||
if not self.ready:
|
||||
return
|
||||
|
||||
INTERVAL.update()
|
||||
devices_to_try = list()
|
||||
connected_priorities = list()
|
||||
any_device_connected = False # if this is true, last status on screen should be C
|
||||
|
||||
bt = BTNap(OPTIONS['mac'])
|
||||
for _, device in self.devices.items():
|
||||
if device.connected():
|
||||
connected_priorities.append(device.priority)
|
||||
any_device_connected = True
|
||||
continue
|
||||
|
||||
logging.debug('BT-TETHER: Check if already connected and paired')
|
||||
dev_remote, connected = bt.is_connected()
|
||||
if not device.max_tries or (device.max_tries > device.tries):
|
||||
if not device.status.newer_then_minutes(device.interval):
|
||||
devices_to_try.append(device)
|
||||
device.status.update()
|
||||
device.tries += 1
|
||||
|
||||
if connected:
|
||||
logging.debug('BT-TETHER: Already connected.')
|
||||
ui.set('bluetooth', 'C')
|
||||
return
|
||||
sorted_devices = sorted(devices_to_try, key=lambda x: x.search_order)
|
||||
|
||||
for device in sorted_devices:
|
||||
bt = BTNap(device.mac)
|
||||
|
||||
try:
|
||||
logging.info('BT-TETHER: Search device ...')
|
||||
dev_remote = bt.wait_for_device()
|
||||
logging.debug('BT-TETHER: Search %d secs for %s ...', device.scantime, device.name)
|
||||
dev_remote = bt.wait_for_device(timeout=device.scantime)
|
||||
if dev_remote is None:
|
||||
logging.info('BT-TETHER: Could not find device.')
|
||||
logging.debug('BT-TETHER: Could not find %s, try again in %d minutes.', device.name, device.interval)
|
||||
ui.set('bluetooth', 'NF')
|
||||
return
|
||||
continue
|
||||
except Exception as bt_ex:
|
||||
logging.error(bt_ex)
|
||||
ui.set('bluetooth', 'NF')
|
||||
return
|
||||
continue
|
||||
|
||||
paired = bt.is_paired()
|
||||
if not paired:
|
||||
if BTNap.pair(dev_remote):
|
||||
logging.info('BT-TETHER: Paired with device.')
|
||||
logging.debug('BT-TETHER: Paired with %s.', device.name)
|
||||
else:
|
||||
logging.info('BT-TETHER: Pairing failed ...')
|
||||
logging.debug('BT-TETHER: Pairing with %s failed ...', device.name)
|
||||
ui.set('bluetooth', 'PE')
|
||||
return
|
||||
continue
|
||||
else:
|
||||
logging.debug('BT-TETHER: Already paired.')
|
||||
|
||||
|
||||
btnap_iface = IfaceWrapper('bnep0')
|
||||
logging.debug('BT-TETHER: Check interface')
|
||||
if not btnap_iface.exists():
|
||||
# connected and paired but not napping
|
||||
logging.debug('BT-TETHER: Try to connect to nap ...')
|
||||
if BTNap.nap(dev_remote):
|
||||
logging.info('BT-TETHER: Napping!')
|
||||
logging.debug('BT-TETHER: Try to create nap connection with %s ...', device.name)
|
||||
device.network, success = BTNap.nap(dev_remote)
|
||||
interface = None
|
||||
|
||||
if success:
|
||||
try:
|
||||
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')
|
||||
time.sleep(5)
|
||||
any_device_connected = True
|
||||
device.tries = 0 # reset tries
|
||||
else:
|
||||
logging.info('BT-TETHER: Napping failed ...')
|
||||
logging.debug('BT-TETHER: Could not establish nap connection with %s', device.name)
|
||||
ui.set('bluetooth', 'NF')
|
||||
return
|
||||
continue
|
||||
|
||||
if btnap_iface.exists():
|
||||
logging.debug('BT-TETHER: Interface found')
|
||||
addr = f"{device.ip}/{device.netmask}"
|
||||
gateway = ".".join(device.ip.split('.')[:-1] + ['1'])
|
||||
|
||||
# check ip
|
||||
addr = f"{OPTIONS['ip']}/{OPTIONS['netmask']}"
|
||||
|
||||
logging.debug('BT-TETHER: Try to set ADDR to interface')
|
||||
if not btnap_iface.set_addr(addr):
|
||||
wrapped_interface = IfaceWrapper(interface)
|
||||
logging.debug('BT-TETHER: Add ip to %s', interface)
|
||||
if not wrapped_interface.set_addr(addr):
|
||||
ui.set('bluetooth', 'AE')
|
||||
logging.error("BT-TETHER: Could not set ip of bnep0 to %s", addr)
|
||||
return
|
||||
logging.debug("BT-TETHER: Could not add ip to %s", interface)
|
||||
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
|
||||
if OPTIONS['share_internet']:
|
||||
logging.debug('BT-TETHER: Set routing and change resolv.conf')
|
||||
IfaceWrapper.set_route(".".join(OPTIONS['ip'].split('.')[:-1] + ['1'])) # im not proud about that
|
||||
# fix resolv.conf; dns over https ftw!
|
||||
logging.debug('BT-TETHER: Change resolv.conf if necessary ...')
|
||||
with open('/etc/resolv.conf', 'r+') as resolv:
|
||||
nameserver = resolv.read()
|
||||
if 'nameserver 9.9.9.9' not in nameserver:
|
||||
logging.info('BT-TETHER: Added nameserver')
|
||||
logging.debug('BT-TETHER: Added nameserver')
|
||||
resolv.seek(0)
|
||||
resolv.write(nameserver + 'nameserver 9.9.9.9\n')
|
||||
|
||||
if any_device_connected:
|
||||
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,21 +1,31 @@
|
||||
__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 pwnagotchi.plugins as plugins
|
||||
from pwnagotchi.ui.components import LabeledValue
|
||||
from pwnagotchi.ui.view import BLACK
|
||||
import pwnagotchi.ui.fonts as fonts
|
||||
|
||||
|
||||
# Will be set with the options in config.yml config['main']['plugins'][__name__]
|
||||
OPTIONS = dict()
|
||||
class Example(plugins.Plugin):
|
||||
__author__ = 'evilsocket@gmail.com'
|
||||
__version__ = '1.0.0'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'An example plugin for pwnagotchi that implements all the available callbacks.'
|
||||
|
||||
def __init__(self):
|
||||
logging.debug("example plugin created")
|
||||
|
||||
# called when http://<host>:<port>/plugins/<plugin>/ is called
|
||||
# must return a response
|
||||
def on_webhook(self, path, args, req_method):
|
||||
pass
|
||||
|
||||
# called when the plugin is loaded
|
||||
def on_loaded(self):
|
||||
logging.warning("WARNING: this plugin should be disabled! options = " % self.options)
|
||||
|
||||
# called when <host>:<port>/plugins/<pluginname> is opened
|
||||
def on_webhook(response, path):
|
||||
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')
|
||||
@@ -26,157 +36,124 @@ def on_webhook(response, path):
|
||||
except Exception as ex:
|
||||
logging.error(ex)
|
||||
|
||||
# called when the plugin is loaded
|
||||
def on_loaded():
|
||||
logging.warning("WARNING: plugin %s should be disabled!" % __name__)
|
||||
|
||||
|
||||
# called in manual mode when there's internet connectivity
|
||||
def on_internet_available(agent):
|
||||
def on_internet_available(self, agent):
|
||||
pass
|
||||
|
||||
|
||||
# called to setup the ui elements
|
||||
def on_ui_setup(ui):
|
||||
def on_ui_setup(self, ui):
|
||||
# add custom UI elements
|
||||
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))
|
||||
|
||||
|
||||
# called when the ui is updated
|
||||
def on_ui_update(ui):
|
||||
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 hardware display setup is done, display is an hardware specific object
|
||||
def on_display_setup(display):
|
||||
def on_display_setup(self, display):
|
||||
pass
|
||||
|
||||
|
||||
# called when everything is ready and the main loop is about to start
|
||||
def on_ready(agent):
|
||||
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(agent):
|
||||
def on_ai_ready(self, agent):
|
||||
pass
|
||||
|
||||
|
||||
# called when the AI finds a new set of parameters
|
||||
def on_ai_policy(agent, policy):
|
||||
def on_ai_policy(self, agent, policy):
|
||||
pass
|
||||
|
||||
|
||||
# called when the AI starts training for a given number of epochs
|
||||
def on_ai_training_start(agent, epochs):
|
||||
def on_ai_training_start(self, agent, epochs):
|
||||
pass
|
||||
|
||||
|
||||
# called after the AI completed a training epoch
|
||||
def on_ai_training_step(agent, _locals, _globals):
|
||||
def on_ai_training_step(self, agent, _locals, _globals):
|
||||
pass
|
||||
|
||||
|
||||
# called when the AI has done training
|
||||
def on_ai_training_end(agent):
|
||||
def on_ai_training_end(self, agent):
|
||||
pass
|
||||
|
||||
|
||||
# called when the AI got the best reward so far
|
||||
def on_ai_best_reward(agent, reward):
|
||||
def on_ai_best_reward(self, agent, reward):
|
||||
pass
|
||||
|
||||
|
||||
# called when the AI got the worst reward so far
|
||||
def on_ai_worst_reward(agent, reward):
|
||||
def on_ai_worst_reward(self, agent, reward):
|
||||
pass
|
||||
|
||||
|
||||
# called when a non overlapping wifi channel is found to be free
|
||||
def on_free_channel(agent, channel):
|
||||
def on_free_channel(self, agent, channel):
|
||||
pass
|
||||
|
||||
|
||||
# called when the status is set to bored
|
||||
def on_bored(agent):
|
||||
def on_bored(self, agent):
|
||||
pass
|
||||
|
||||
|
||||
# called when the status is set to sad
|
||||
def on_sad(agent):
|
||||
def on_sad(self, agent):
|
||||
pass
|
||||
|
||||
|
||||
# called when the status is set to excited
|
||||
def on_excited(agent):
|
||||
def on_excited(aself, gent):
|
||||
pass
|
||||
|
||||
|
||||
# called when the status is set to lonely
|
||||
def on_lonely(agent):
|
||||
def on_lonely(self, agent):
|
||||
pass
|
||||
|
||||
|
||||
# called when the agent is rebooting the board
|
||||
def on_rebooting(agent):
|
||||
def on_rebooting(self, agent):
|
||||
pass
|
||||
|
||||
|
||||
# called when the agent is waiting for t seconds
|
||||
def on_wait(agent, t):
|
||||
def on_wait(self, agent, t):
|
||||
pass
|
||||
|
||||
|
||||
# called when the agent is sleeping for t seconds
|
||||
def on_sleep(agent, t):
|
||||
def on_sleep(self, agent, t):
|
||||
pass
|
||||
|
||||
|
||||
# called when the agent refreshed its access points list
|
||||
def on_wifi_update(agent, access_points):
|
||||
def on_wifi_update(self, agent, access_points):
|
||||
pass
|
||||
|
||||
|
||||
# called when the agent is sending an association frame
|
||||
def on_association(agent, access_point):
|
||||
def on_association(self, agent, access_point):
|
||||
pass
|
||||
|
||||
|
||||
# callend when the agent is deauthenticating a client station from an AP
|
||||
def on_deauthentication(agent, access_point, client_station):
|
||||
# called when the agent is deauthenticating a client station from an AP
|
||||
def on_deauthentication(self, agent, access_point, client_station):
|
||||
pass
|
||||
|
||||
|
||||
# callend when the agent is tuning on a specific channel
|
||||
def on_channel_hop(agent, channel):
|
||||
def on_channel_hop(self, 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):
|
||||
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(agent, epoch, epoch_data):
|
||||
def on_epoch(self, agent, epoch, epoch_data):
|
||||
pass
|
||||
|
||||
|
||||
# called when a new peer is detected
|
||||
def on_peer_detected(agent, peer):
|
||||
def on_peer_detected(self, agent, peer):
|
||||
pass
|
||||
|
||||
|
||||
# called when a known peer is lost
|
||||
def on_peer_lost(agent, peer):
|
||||
def on_peer_lost(self, agent, peer):
|
||||
pass
|
||||
|
@@ -1,30 +1,32 @@
|
||||
__author__ = 'ratmandu@gmail.com'
|
||||
__version__ = '1.0.0'
|
||||
__name__ = 'gpio_buttons'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'GPIO Button support plugin'
|
||||
|
||||
import logging
|
||||
import RPi.GPIO as GPIO
|
||||
import subprocess
|
||||
import pwnagotchi.plugins as plugins
|
||||
|
||||
running = False
|
||||
OPTIONS = dict()
|
||||
GPIOs = {}
|
||||
COMMANDs = None
|
||||
|
||||
def runCommand(channel):
|
||||
command = GPIOs[channel]
|
||||
class GPIOButtons(plugins.Plugin):
|
||||
__author__ = 'ratmandu@gmail.com'
|
||||
__version__ = '1.0.0'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'GPIO Button support plugin'
|
||||
|
||||
def __init__(self):
|
||||
self.running = False
|
||||
self.ports = {}
|
||||
self.commands = None
|
||||
|
||||
def runCommand(self, channel):
|
||||
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 = subprocess.Popen(command, shell=True, stdin=None, stdout=open("/dev/null", "w"), stderr=None,
|
||||
executable="/bin/bash")
|
||||
process.wait()
|
||||
|
||||
|
||||
def on_loaded():
|
||||
def on_loaded(self):
|
||||
logging.info("GPIO Button plugin loaded.")
|
||||
|
||||
# get list of GPIOs
|
||||
gpios = OPTIONS['gpios']
|
||||
gpios = self.options['gpios']
|
||||
|
||||
# set gpio numbering
|
||||
GPIO.setmode(GPIO.BCM)
|
||||
@@ -32,7 +34,7 @@ def on_loaded():
|
||||
for i in gpios:
|
||||
gpio = list(i)[0]
|
||||
command = i[gpio]
|
||||
GPIOs[gpio] = command
|
||||
self.ports[gpio] = command
|
||||
GPIO.setup(gpio, GPIO.IN, GPIO.PUD_UP)
|
||||
GPIO.add_event_detect(gpio, GPIO.FALLING, callback=runCommand, bouncetime=250)
|
||||
GPIO.add_event_detect(gpio, GPIO.FALLING, callback=self.runCommand, bouncetime=250)
|
||||
logging.info("Added command: %s to GPIO #%d", command, gpio)
|
||||
|
@@ -1,41 +1,38 @@
|
||||
__author__ = 'evilsocket@gmail.com'
|
||||
__version__ = '1.0.0'
|
||||
__name__ = 'gps'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'Save GPS coordinates whenever an handshake is captured.'
|
||||
|
||||
import logging
|
||||
import json
|
||||
import os
|
||||
|
||||
running = False
|
||||
OPTIONS = dict()
|
||||
import pwnagotchi.plugins as plugins
|
||||
|
||||
|
||||
def on_loaded():
|
||||
logging.info("gps plugin loaded for %s" % OPTIONS['device'])
|
||||
class GPS(plugins.Plugin):
|
||||
__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):
|
||||
global running
|
||||
def on_loaded(self):
|
||||
logging.info("gps plugin loaded for %s" % self.options['device'])
|
||||
|
||||
if os.path.exists(OPTIONS['device']):
|
||||
logging.info("enabling gps bettercap's module for %s" % OPTIONS['device'])
|
||||
def on_ready(self, agent):
|
||||
if os.path.exists(self.options['device']):
|
||||
logging.info("enabling gps bettercap's module for %s" % self.options['device'])
|
||||
try:
|
||||
agent.run('gps off')
|
||||
except:
|
||||
pass
|
||||
|
||||
agent.run('set gps.device %s' % OPTIONS['device'])
|
||||
agent.run('set gps.speed %d' % OPTIONS['speed'])
|
||||
agent.run('set gps.device %s' % self.options['device'])
|
||||
agent.run('set gps.speed %d' % self.options['speed'])
|
||||
agent.run('gps on')
|
||||
running = True
|
||||
else:
|
||||
logging.warning("no GPS detected")
|
||||
|
||||
|
||||
def on_handshake(agent, filename, access_point, client_station):
|
||||
if running:
|
||||
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')
|
||||
|
@@ -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 logging
|
||||
import time
|
||||
import glob
|
||||
import re
|
||||
|
||||
import pwnagotchi.grid as grid
|
||||
import pwnagotchi.plugins as plugins
|
||||
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):
|
||||
logging.info("grid: parsing %s ..." % filename)
|
||||
@@ -36,6 +21,10 @@ def parse_pcap(filename):
|
||||
# /root/handshakes/BSSID.pcap
|
||||
essid, bssid = '', net_id
|
||||
|
||||
mac_re = re.compile('[0-9a-fA-F]{12}')
|
||||
if not mac_re.match(bssid):
|
||||
return '', ''
|
||||
|
||||
it = iter(bssid)
|
||||
bssid = ':'.join([a + b for a, b in zip(it, it)])
|
||||
|
||||
@@ -52,76 +41,83 @@ def parse_pcap(filename):
|
||||
return info[WifiInfo.ESSID], info[WifiInfo.BSSID]
|
||||
|
||||
|
||||
def is_excluded(what):
|
||||
for skip in OPTIONS['exclude']:
|
||||
class Grid(plugins.Plugin):
|
||||
__author__ = 'evilsocket@gmail.com'
|
||||
__version__ = '1.0.1'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'This plugin signals the unit cryptographic identity and list of pwned networks and list of pwned ' \
|
||||
'networks to api.pwnagotchi.ai '
|
||||
|
||||
def __init__(self):
|
||||
self.options = dict()
|
||||
self.report = StatusFile('/root/.api-report.json', data_format='json')
|
||||
|
||||
self.unread_messages = 0
|
||||
self.total_messages = 0
|
||||
|
||||
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 on_loaded(self):
|
||||
logging.info("grid plugin loaded.")
|
||||
|
||||
def set_reported(reported, net_id):
|
||||
global REPORT
|
||||
def set_reported(self, reported, net_id):
|
||||
reported.append(net_id)
|
||||
REPORT.update(data={'reported': reported})
|
||||
|
||||
|
||||
def check_inbox(agent):
|
||||
global REPORT, UNREAD_MESSAGES, TOTAL_MESSAGES
|
||||
self.report.update(data={'reported': reported})
|
||||
|
||||
def check_inbox(self, agent):
|
||||
logging.debug("checking mailbox ...")
|
||||
|
||||
messages = grid.inbox()
|
||||
TOTAL_MESSAGES = len(messages)
|
||||
UNREAD_MESSAGES = len([m for m in messages if m['seen_at'] is None])
|
||||
self.total_messages = len(messages)
|
||||
self.unread_messages = len([m for m in messages if m['seen_at'] is None])
|
||||
|
||||
if UNREAD_MESSAGES:
|
||||
logging.debug("[grid] unread:%d total:%d" % (UNREAD_MESSAGES, TOTAL_MESSAGES))
|
||||
agent.view().on_unread_messages(UNREAD_MESSAGES, TOTAL_MESSAGES)
|
||||
if self.unread_messages:
|
||||
logging.debug("[grid] unread:%d total:%d" % (self.unread_messages, self.total_messages))
|
||||
agent.view().on_unread_messages(self.unread_messages, self.total_messages)
|
||||
|
||||
|
||||
def check_handshakes(agent):
|
||||
def check_handshakes(self, agent):
|
||||
logging.debug("checking pcaps")
|
||||
|
||||
pcap_files = glob.glob(os.path.join(agent.config()['bettercap']['handshakes'], "*.pcap"))
|
||||
num_networks = len(pcap_files)
|
||||
reported = REPORT.data_field_or('reported', default=[])
|
||||
reported = self.report.data_field_or('reported', default=[])
|
||||
num_reported = len(reported)
|
||||
num_new = num_networks - num_reported
|
||||
|
||||
if num_new > 0:
|
||||
if OPTIONS['report']:
|
||||
if self.options['report']:
|
||||
logging.info("grid: %d new networks to report" % num_new)
|
||||
logging.debug("OPTIONS: %s" % OPTIONS)
|
||||
logging.debug(" exclude: %s" % OPTIONS['exclude'])
|
||||
logging.debug("self.options: %s" % self.options)
|
||||
logging.debug(" exclude: %s" % self.options['exclude'])
|
||||
|
||||
for pcap_file in pcap_files:
|
||||
net_id = os.path.basename(pcap_file).replace('.pcap', '')
|
||||
if net_id not in reported:
|
||||
if is_excluded(net_id):
|
||||
if self.is_excluded(net_id):
|
||||
logging.debug("skipping %s due to exclusion filter" % pcap_file)
|
||||
set_reported(reported, net_id)
|
||||
self.set_reported(reported, net_id)
|
||||
continue
|
||||
|
||||
essid, bssid = parse_pcap(pcap_file)
|
||||
if bssid:
|
||||
if is_excluded(essid) or is_excluded(bssid):
|
||||
if self.is_excluded(essid) or self.is_excluded(bssid):
|
||||
logging.debug("not reporting %s due to exclusion filter" % pcap_file)
|
||||
set_reported(reported, net_id)
|
||||
self.set_reported(reported, net_id)
|
||||
else:
|
||||
if grid.report_ap(essid, bssid):
|
||||
set_reported(reported, net_id)
|
||||
self.set_reported(reported, net_id)
|
||||
time.sleep(1.5)
|
||||
else:
|
||||
logging.warning("no bssid found?!")
|
||||
else:
|
||||
logging.debug("grid: reporting disabled")
|
||||
|
||||
|
||||
def on_internet_available(agent):
|
||||
global REPORT, UNREAD_MESSAGES, TOTAL_MESSAGES
|
||||
|
||||
def on_internet_available(self, agent):
|
||||
logging.debug("internet available")
|
||||
|
||||
try:
|
||||
@@ -132,13 +128,13 @@ def on_internet_available(agent):
|
||||
return
|
||||
|
||||
try:
|
||||
check_inbox(agent)
|
||||
self.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)
|
||||
self.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
|
||||
#
|
||||
###############################################################
|
||||
|
||||
__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.view import BLACK
|
||||
import pwnagotchi.ui.fonts as fonts
|
||||
import pwnagotchi.plugins as plugins
|
||||
import pwnagotchi
|
||||
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.")
|
||||
|
||||
|
||||
def mem_usage():
|
||||
def mem_usage(self):
|
||||
return int(pwnagotchi.mem_usage() * 100)
|
||||
|
||||
|
||||
def cpu_load():
|
||||
def cpu_load(self):
|
||||
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 on_ui_setup(ui):
|
||||
if OPTIONS['orientation'] == "horizontal":
|
||||
if self.options['orientation'] == "horizontal":
|
||||
ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value='mem cpu temp\n - - -',
|
||||
position=(ui.width() / 2 + 30, ui.height() / 2 + 15),
|
||||
position=h_pos,
|
||||
label_font=fonts.Small, text_font=fonts.Small))
|
||||
elif OPTIONS['orientation'] == "vertical":
|
||||
elif self.options['orientation'] == "vertical":
|
||||
ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value=' mem:-\n cpu:-\ntemp:-',
|
||||
position=(ui.width() / 2 + 55, ui.height() / 2),
|
||||
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_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()))
|
||||
elif self.options['orientation'] == "vertical":
|
||||
ui.set('memtemp',
|
||||
" mem:%s%%\n cpu:%s%%\ntemp:%sc" % (self.mem_usage(), self.cpu_load(), pwnagotchi.temperature()))
|
||||
|
@@ -1,37 +1,37 @@
|
||||
import logging
|
||||
import json
|
||||
import os
|
||||
import requests
|
||||
import time
|
||||
import pwnagotchi.plugins as plugins
|
||||
from pwnagotchi.utils import StatusFile
|
||||
|
||||
MOZILLA_API_URL = 'https://location.services.mozilla.com/v1/geolocate?key={api}'
|
||||
|
||||
|
||||
class NetPos(plugins.Plugin):
|
||||
__author__ = 'zenzen san'
|
||||
__version__ = '2.0.0'
|
||||
__name__ = 'net-pos'
|
||||
__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 """
|
||||
|
||||
import logging
|
||||
import json
|
||||
import os
|
||||
import requests
|
||||
from pwnagotchi.utils import StatusFile
|
||||
def __init__(self):
|
||||
self.report = StatusFile('/root/.net_pos_saved', data_format='json')
|
||||
self.skip = list()
|
||||
self.ready = False
|
||||
|
||||
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():
|
||||
global READY
|
||||
|
||||
if 'api_key' not in OPTIONS or ('api_key' in OPTIONS and OPTIONS['api_key'] is None):
|
||||
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
|
||||
|
||||
READY = True
|
||||
|
||||
self.ready = True
|
||||
logging.info("net-pos plugin loaded.")
|
||||
|
||||
def _append_saved(path):
|
||||
def _append_saved(self, path):
|
||||
to_save = list()
|
||||
if isinstance(path, str):
|
||||
to_save.append(path)
|
||||
@@ -44,21 +44,18 @@ def _append_saved(path):
|
||||
for x in to_save:
|
||||
saved_file.write(x + "\n")
|
||||
|
||||
def on_internet_available(agent):
|
||||
global SKIP
|
||||
global REPORT
|
||||
|
||||
if READY:
|
||||
def on_internet_available(self, agent):
|
||||
if self.ready:
|
||||
config = agent.config()
|
||||
display = agent.view()
|
||||
reported = REPORT.data_field_or('reported', default=list())
|
||||
reported = self.report.data_field_or('reported', default=list())
|
||||
handshake_dir = config['bettercap']['handshakes']
|
||||
|
||||
all_files = os.listdir(handshake_dir)
|
||||
all_np_files = [os.path.join(handshake_dir, filename)
|
||||
for filename in all_files
|
||||
if filename.endswith('.net-pos.json')]
|
||||
new_np_files = set(all_np_files) - set(reported) - set(SKIP)
|
||||
new_np_files = set(all_np_files) - set(reported) - set(self.skip)
|
||||
|
||||
if new_np_files:
|
||||
logging.info("NET-POS: Found %d new net-pos files. Fetching positions ...", len(new_np_files))
|
||||
@@ -70,36 +67,36 @@ def on_internet_available(agent):
|
||||
if os.path.exists(geo_file):
|
||||
# got already the position
|
||||
reported.append(np_file)
|
||||
REPORT.update(data={'reported': reported})
|
||||
self.report.update(data={'reported': reported})
|
||||
continue
|
||||
|
||||
try:
|
||||
geo_data = _get_geo_data(np_file) # returns json obj
|
||||
geo_data = self._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
|
||||
self.skip += np_file
|
||||
continue
|
||||
except json.JSONDecodeError as js_e:
|
||||
logging.error("NET-POS: %s", js_e)
|
||||
SKIP += np_file
|
||||
self.skip += np_file
|
||||
continue
|
||||
except OSError as os_e:
|
||||
logging.error("NET-POS: %s", os_e)
|
||||
SKIP += np_file
|
||||
self.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})
|
||||
self.report.update(data={'reported': reported})
|
||||
|
||||
display.set('status', f"Fetching positions ({idx + 1}/{len(new_np_files)})")
|
||||
display.update(force=True)
|
||||
|
||||
|
||||
def on_handshake(agent, filename, access_point, client_station):
|
||||
netpos = _get_netpos(agent)
|
||||
def on_handshake(self, agent, filename, access_point, client_station):
|
||||
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)
|
||||
|
||||
@@ -109,8 +106,7 @@ def on_handshake(agent, filename, access_point, client_station):
|
||||
except OSError as os_e:
|
||||
logging.error("NET-POS: %s", os_e)
|
||||
|
||||
|
||||
def _get_netpos(agent):
|
||||
def _get_netpos(self, agent):
|
||||
aps = agent.get_access_points()
|
||||
netpos = dict()
|
||||
netpos['wifiAccessPoints'] = list()
|
||||
@@ -120,8 +116,8 @@ def _get_netpos(agent):
|
||||
'signalStrength': access_point['rssi']})
|
||||
return netpos
|
||||
|
||||
def _get_geo_data(path, timeout=30):
|
||||
geourl = MOZILLA_API_URL.format(api=OPTIONS['api_key'])
|
||||
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:
|
||||
@@ -135,6 +131,9 @@ def _get_geo_data(path, timeout=30):
|
||||
result = requests.post(geourl,
|
||||
json=data,
|
||||
timeout=timeout)
|
||||
return result.json()
|
||||
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,39 +1,56 @@
|
||||
import os
|
||||
import logging
|
||||
import re
|
||||
import requests
|
||||
from pwnagotchi.utils import StatusFile
|
||||
import pwnagotchi.plugins as plugins
|
||||
|
||||
|
||||
class OnlineHashCrack(plugins.Plugin):
|
||||
__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 logging
|
||||
import requests
|
||||
from pwnagotchi.utils import StatusFile
|
||||
def __init__(self):
|
||||
self.ready = False
|
||||
self.report = StatusFile('/root/.ohc_uploads', data_format='json')
|
||||
self.skip = list()
|
||||
|
||||
READY = False
|
||||
REPORT = StatusFile('/root/.ohc_uploads', data_format='json')
|
||||
SKIP = list()
|
||||
OPTIONS = dict()
|
||||
|
||||
|
||||
def on_loaded():
|
||||
def on_loaded(self):
|
||||
"""
|
||||
Gets called when the plugin gets loaded
|
||||
"""
|
||||
global READY
|
||||
|
||||
if 'email' not in OPTIONS or ('email' in OPTIONS and OPTIONS['email'] is None):
|
||||
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
|
||||
|
||||
READY = True
|
||||
if 'whitelist' not in self.options:
|
||||
self.options['whitelist'] = []
|
||||
|
||||
# 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']))
|
||||
|
||||
def _upload_to_ohc(path, timeout=30):
|
||||
self.ready = True
|
||||
|
||||
def _filter_handshake_file(self, handshake_filename):
|
||||
try:
|
||||
basename = os.path.basename(handshake_filename)
|
||||
ssid, bssid = basename.split('_')
|
||||
# remove the ".pcap" from the bssid (which is really just the end of the filename)
|
||||
bssid = bssid[:-5]
|
||||
except:
|
||||
# something failed in our parsing of the filename. let the file through
|
||||
return True
|
||||
|
||||
return ssid not in self.options['whitelist'] and bssid not in self.options['whitelist']
|
||||
|
||||
def _upload_to_ohc(self, path, timeout=30):
|
||||
"""
|
||||
Uploads the file to onlinehashcrack.com
|
||||
"""
|
||||
with open(path, 'rb') as file_to_upload:
|
||||
data = {'email': OPTIONS['email']}
|
||||
data = {'email': self.options['email']}
|
||||
payload = {'file': file_to_upload}
|
||||
|
||||
try:
|
||||
@@ -47,40 +64,42 @@ def _upload_to_ohc(path, timeout=30):
|
||||
logging.error(f"OHC: Got an exception while uploading {path} -> {e}")
|
||||
raise e
|
||||
|
||||
|
||||
def on_internet_available(agent):
|
||||
def on_internet_available(self, agent):
|
||||
"""
|
||||
Called in manual mode when there's internet connectivity
|
||||
"""
|
||||
global REPORT
|
||||
global SKIP
|
||||
if READY:
|
||||
if self.ready:
|
||||
display = agent.view()
|
||||
config = agent.config()
|
||||
reported = REPORT.data_field_or('reported', default=list())
|
||||
reported = self.report.data_field_or('reported', default=list())
|
||||
|
||||
handshake_dir = config['bettercap']['handshakes']
|
||||
handshake_filenames = os.listdir(handshake_dir)
|
||||
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)
|
||||
handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if
|
||||
filename.endswith('.pcap')]
|
||||
|
||||
# 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.set('status',
|
||||
f"Uploading handshake to onlinehashcrack.com ({idx + 1}/{len(handshake_new)})")
|
||||
display.update(force=True)
|
||||
try:
|
||||
_upload_to_ohc(handshake)
|
||||
self._upload_to_ohc(handshake)
|
||||
reported.append(handshake)
|
||||
REPORT.update(data={'reported': reported})
|
||||
self.report.update(data={'reported': reported})
|
||||
logging.info(f"OHC: Successfully uploaded {handshake}")
|
||||
except requests.exceptions.RequestException as req_e:
|
||||
SKIP.append(handshake)
|
||||
self.skip.append(handshake)
|
||||
logging.error("OHC: %s", req_e)
|
||||
continue
|
||||
except OSError as os_e:
|
||||
SKIP.append(handshake)
|
||||
self.skip.append(handshake)
|
||||
logging.error("OHC: %s", os_e)
|
||||
continue
|
||||
|
||||
|
@@ -1,27 +1,27 @@
|
||||
__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 '
|
||||
import logging
|
||||
import requests
|
||||
import pwnagotchi.plugins as plugins
|
||||
|
||||
'''
|
||||
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
|
||||
'''
|
||||
|
||||
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():
|
||||
def on_loaded(self):
|
||||
logging.info("PAW-GPS loaded")
|
||||
if 'ip' not in OPTIONS or ('ip' in OPTIONS and OPTIONS['ip'] is None):
|
||||
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_handshake(agent, filename, access_point, client_station):
|
||||
if 'ip' not in OPTIONS or ('ip' in OPTIONS and OPTIONS['ip'] is None):
|
||||
def on_handshake(self, agent, filename, access_point, client_station):
|
||||
if 'ip' not in self.options or ('ip' in self.options and self.options['ip'] is None):
|
||||
ip = "192.168.44.1"
|
||||
|
||||
gps = requests.get('http://' + ip + '/gps.xhtml')
|
||||
|
@@ -1,8 +1,8 @@
|
||||
__author__ = 'pwnagotchi [at] rossmarks [dot] uk'
|
||||
__version__ = '1.0.0'
|
||||
__name__ = 'quickdic'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'Run a quick dictionary scan against captured handshakes'
|
||||
import logging
|
||||
import subprocess
|
||||
import string
|
||||
import re
|
||||
import pwnagotchi.plugins as plugins
|
||||
|
||||
'''
|
||||
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
|
||||
'''
|
||||
|
||||
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):
|
||||
self.text_to_set = ""
|
||||
|
||||
def on_loaded(self):
|
||||
logging.info("Quick dictionary check plugin loaded")
|
||||
|
||||
def on_handshake(agent, filename, access_point, client_station):
|
||||
display = agent._view
|
||||
|
||||
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):
|
||||
display = agent.view()
|
||||
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 not result:
|
||||
logging.info("[quickdic] No handshake")
|
||||
else:
|
||||
logging.info("[quickdic] Handshake confirmed")
|
||||
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)
|
||||
result2 = subprocess.run(('aircrack-ng -w `echo ' + self.options[
|
||||
'wordlist_folder'] + '*.txt | sed \'s/\ /,/g\'` -l ' + filename + '.cracked -q -b ' + result + ' ' + filename + ' | grep KEY'),
|
||||
shell=True, stdout=subprocess.PIPE)
|
||||
result2 = result2.stdout.decode('utf-8').strip()
|
||||
logging.info("[quickdic] " + result2)
|
||||
if result2 != "KEY NOT FOUND":
|
||||
key = re.search('\[(.*)\]', result2)
|
||||
pwd = str(key.group(1))
|
||||
set_text("Cracked password: "+pwd)
|
||||
self.text_to_set = "Cracked password: " + pwd
|
||||
display.update(force=True)
|
||||
|
||||
text_to_set = "";
|
||||
def set_text(text):
|
||||
global text_to_set
|
||||
text_to_set = text
|
||||
|
||||
def on_ui_update(ui):
|
||||
global text_to_set
|
||||
if text_to_set:
|
||||
def on_ui_update(self, ui):
|
||||
if self.text_to_set:
|
||||
ui.set('face', "(·ω·)")
|
||||
ui.set('status', text_to_set)
|
||||
text_to_set = ""
|
||||
ui.set('status', self.text_to_set)
|
||||
self.text_to_set = ""
|
||||
|
@@ -1,24 +1,23 @@
|
||||
import logging
|
||||
import pwnagotchi.plugins as plugins
|
||||
|
||||
|
||||
class ScreenRefresh(plugins.Plugin):
|
||||
__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
|
||||
def __init__(self):
|
||||
self.update_count = 0;
|
||||
|
||||
OPTIONS = dict()
|
||||
update_count = 0;
|
||||
|
||||
|
||||
def on_loaded():
|
||||
def on_loaded(self):
|
||||
logging.info("Screen refresh plugin loaded")
|
||||
|
||||
|
||||
def on_ui_update(ui):
|
||||
global update_count
|
||||
update_count += 1
|
||||
if update_count == OPTIONS['refresh_interval']:
|
||||
def on_ui_update(self, ui):
|
||||
self.update_count += 1
|
||||
if self.update_count == self.options['refresh_interval']:
|
||||
ui.init_display()
|
||||
ui.set('status', "Screen cleaned")
|
||||
logging.info("Screen refreshing")
|
||||
update_count = 0
|
||||
self.update_count = 0
|
||||
|
@@ -1,20 +1,19 @@
|
||||
__author__ = '33197631+dadav@users.noreply.github.com'
|
||||
import logging
|
||||
from pwnagotchi.voice import Voice
|
||||
import pwnagotchi.plugins as plugins
|
||||
|
||||
|
||||
class Twitter(plugins.Plugin):
|
||||
__author__ = 'evilsocket@gmail.com'
|
||||
__version__ = '1.0.0'
|
||||
__name__ = 'twitter'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'This plugin creates tweets about the recent activity of pwnagotchi'
|
||||
|
||||
import logging
|
||||
from pwnagotchi.voice import Voice
|
||||
|
||||
OPTIONS = dict()
|
||||
|
||||
def on_loaded():
|
||||
def on_loaded(self):
|
||||
logging.info("twitter plugin loaded.")
|
||||
|
||||
|
||||
# called in manual mode when there's internet connectivity
|
||||
def on_internet_available(agent):
|
||||
def on_internet_available(self, agent):
|
||||
config = agent.config()
|
||||
display = agent.view()
|
||||
last_session = agent.last_session
|
||||
@@ -28,7 +27,7 @@ def on_internet_available(agent):
|
||||
|
||||
logging.info("detected a new session and internet connectivity!")
|
||||
|
||||
picture = '/dev/shm/pwnagotchi.png'
|
||||
picture = '/root/pwnagotchi.png'
|
||||
|
||||
display.on_manual_mode(last_session)
|
||||
display.update(force=True)
|
||||
@@ -37,8 +36,8 @@ def on_internet_available(agent):
|
||||
display.update(force=True)
|
||||
|
||||
try:
|
||||
auth = tweepy.OAuthHandler(OPTIONS['consumer_key'], OPTIONS['consumer_secret'])
|
||||
auth.set_access_token(OPTIONS['access_token_key'], OPTIONS['access_token_secret'])
|
||||
auth = tweepy.OAuthHandler(self.options['consumer_key'], self.options['consumer_secret'])
|
||||
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)
|
||||
|
@@ -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
|
||||
# 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
|
||||
__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
|
||||
|
||||
from pwnagotchi.ui.components import LabeledValue
|
||||
from pwnagotchi.ui.view import BLACK
|
||||
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
|
||||
@@ -47,18 +42,21 @@ class UPS:
|
||||
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():
|
||||
global ups
|
||||
ups = UPS()
|
||||
def on_loaded(self):
|
||||
self.ups = UPS()
|
||||
|
||||
|
||||
def on_ui_setup(ui):
|
||||
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_update(ui):
|
||||
ui.set('ups', "%4.2fV/%2i%%" % (ups.voltage(), ups.capacity()))
|
||||
def on_ui_update(self, ui):
|
||||
ui.set('ups', "%4.2fV/%2i%%" % (self.ups.voltage(), self.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 logging
|
||||
import json
|
||||
@@ -12,24 +6,7 @@ import csv
|
||||
from datetime import datetime
|
||||
import requests
|
||||
from pwnagotchi.utils import WifiInfo, FieldNotFoundError, extract_from_pcap, StatusFile
|
||||
|
||||
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
|
||||
import pwnagotchi.plugins as plugins
|
||||
|
||||
|
||||
def _extract_gps_data(path):
|
||||
@@ -54,14 +31,17 @@ def _format_auth(data):
|
||||
out = f"{out}[{auth}]"
|
||||
return out
|
||||
|
||||
|
||||
def _transform_wigle_entry(gps_data, pcap_data):
|
||||
"""
|
||||
Transform to wigle entry in file
|
||||
"""
|
||||
dummy = StringIO()
|
||||
# write kismet header
|
||||
dummy.write("WigleWifi-1.4,appRelease=20190201,model=Kismet,release=2019.02.01.{},device=kismet,display=kismet,board=kismet,brand=kismet\n")
|
||||
dummy.write("MAC,SSID,AuthMode,FirstSeen,Channel,RSSI,CurrentLatitude,CurrentLongitude,AltitudeMeters,AccuracyMeters,Type")
|
||||
dummy.write(
|
||||
"WigleWifi-1.4,appRelease=20190201,model=Kismet,release=2019.02.01.{},device=kismet,display=kismet,board=kismet,brand=kismet\n")
|
||||
dummy.write(
|
||||
"MAC,SSID,AuthMode,FirstSeen,Channel,RSSI,CurrentLatitude,CurrentLongitude,AltitudeMeters,AccuracyMeters,Type")
|
||||
|
||||
writer = csv.writer(dummy, delimiter=",", quoting=csv.QUOTE_NONE, escapechar="\\")
|
||||
writer.writerow([
|
||||
@@ -79,6 +59,7 @@ def _transform_wigle_entry(gps_data, pcap_data):
|
||||
'WIFI'])
|
||||
return dummy.getvalue()
|
||||
|
||||
|
||||
def _send_to_wigle(lines, api_key, timeout=30):
|
||||
"""
|
||||
Uploads the file to wigle-net
|
||||
@@ -109,25 +90,39 @@ def _send_to_wigle(lines, api_key, timeout=30):
|
||||
raise re_e
|
||||
|
||||
|
||||
def on_internet_available(agent):
|
||||
class Wigle(plugins.Plugin):
|
||||
__author__ = '33197631+dadav@users.noreply.github.com'
|
||||
__version__ = '2.0.0'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'This plugin automatically uploads collected wifis to wigle.net'
|
||||
|
||||
def __init__(self):
|
||||
self.ready = False
|
||||
self.report = StatusFile('/root/.wigle_uploads', data_format='json')
|
||||
self.skip = list()
|
||||
|
||||
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("WIGLE: api_key isn't set. Can't upload to wigle.net")
|
||||
return
|
||||
self.ready = True
|
||||
|
||||
def on_internet_available(self, agent):
|
||||
from scapy.all import Scapy_Exception
|
||||
"""
|
||||
Called in manual mode when there's internet connectivity
|
||||
"""
|
||||
global REPORT
|
||||
global SKIP
|
||||
|
||||
if READY:
|
||||
if self.ready:
|
||||
config = agent.config()
|
||||
display = agent.view()
|
||||
reported = REPORT.data_field_or('reported', default=list())
|
||||
reported = self.report.data_field_or('reported', default=list())
|
||||
|
||||
handshake_dir = config['bettercap']['handshakes']
|
||||
all_files = os.listdir(handshake_dir)
|
||||
all_gps_files = [os.path.join(handshake_dir, filename)
|
||||
for filename in all_files
|
||||
if filename.endswith('.gps.json')]
|
||||
new_gps_files = set(all_gps_files) - set(reported) - set(SKIP)
|
||||
new_gps_files = set(all_gps_files) - set(reported) - set(self.skip)
|
||||
|
||||
if new_gps_files:
|
||||
logging.info("WIGLE: Internet connectivity detected. Uploading new handshakes to wigle.net")
|
||||
@@ -140,26 +135,25 @@ def on_internet_available(agent):
|
||||
|
||||
if not os.path.exists(pcap_filename):
|
||||
logging.error("WIGLE: Can't find pcap for %s", gps_file)
|
||||
SKIP.append(gps_file)
|
||||
self.skip.append(gps_file)
|
||||
continue
|
||||
|
||||
try:
|
||||
gps_data = _extract_gps_data(gps_file)
|
||||
except OSError as os_err:
|
||||
logging.error("WIGLE: %s", os_err)
|
||||
SKIP.append(gps_file)
|
||||
self.skip.append(gps_file)
|
||||
continue
|
||||
except json.JSONDecodeError as json_err:
|
||||
logging.error("WIGLE: %s", json_err)
|
||||
SKIP.append(gps_file)
|
||||
self.skip.append(gps_file)
|
||||
continue
|
||||
|
||||
if gps_data['Latitude'] == 0 and gps_data['Longitude'] == 0:
|
||||
logging.warning("WIGLE: Not enough gps-information for %s. Trying again next time.", gps_file)
|
||||
SKIP.append(gps_file)
|
||||
self.skip.append(gps_file)
|
||||
continue
|
||||
|
||||
|
||||
try:
|
||||
pcap_data = extract_from_pcap(pcap_filename, [WifiInfo.BSSID,
|
||||
WifiInfo.ESSID,
|
||||
@@ -168,11 +162,11 @@ def on_internet_available(agent):
|
||||
WifiInfo.RSSI])
|
||||
except FieldNotFoundError:
|
||||
logging.error("WIGLE: Could not extract all information. Skip %s", gps_file)
|
||||
SKIP.append(gps_file)
|
||||
self.skip.append(gps_file)
|
||||
continue
|
||||
except Scapy_Exception as sc_e:
|
||||
logging.error("WIGLE: %s", sc_e)
|
||||
SKIP.append(gps_file)
|
||||
self.skip.append(gps_file)
|
||||
continue
|
||||
|
||||
new_entry = _transform_wigle_entry(gps_data, pcap_data)
|
||||
@@ -183,13 +177,13 @@ def on_internet_available(agent):
|
||||
display.set('status', "Uploading gps-data to wigle.net ...")
|
||||
display.update(force=True)
|
||||
try:
|
||||
_send_to_wigle(csv_entries, OPTIONS['api_key'])
|
||||
_send_to_wigle(csv_entries, self.options['api_key'])
|
||||
reported += no_err_entries
|
||||
REPORT.update(data={'reported': reported})
|
||||
self.report.update(data={'reported': reported})
|
||||
logging.info("WIGLE: Successfully uploaded %d files", len(no_err_entries))
|
||||
except requests.exceptions.RequestException as re_e:
|
||||
SKIP += no_err_entries
|
||||
self.skip += no_err_entries
|
||||
logging.error("WIGLE: Got an exception while uploading %s", re_e)
|
||||
except OSError as os_e:
|
||||
SKIP += no_err_entries
|
||||
self.skip += no_err_entries
|
||||
logging.error("WIGLE: Got the following error: %s", os_e)
|
||||
|
@@ -1,47 +1,32 @@
|
||||
__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 logging
|
||||
import requests
|
||||
from pwnagotchi.utils import StatusFile
|
||||
|
||||
READY = False
|
||||
REPORT = StatusFile('/root/.wpa_sec_uploads', data_format='json')
|
||||
OPTIONS = dict()
|
||||
SKIP = list()
|
||||
import pwnagotchi.plugins as plugins
|
||||
|
||||
|
||||
def on_loaded():
|
||||
"""
|
||||
Gets called when the plugin gets loaded
|
||||
"""
|
||||
global READY
|
||||
class WpaSec(plugins.Plugin):
|
||||
__author__ = '33197631+dadav@users.noreply.github.com'
|
||||
__version__ = '2.0.1'
|
||||
__license__ = 'GPL3'
|
||||
__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):
|
||||
logging.error("WPA_SEC: API-KEY isn't set. Can't upload to wpa-sec.stanev.org")
|
||||
return
|
||||
def __init__(self):
|
||||
self.ready = False
|
||||
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):
|
||||
logging.error("WPA_SEC: API-URL isn't set. Can't upload, no endpoint configured.")
|
||||
return
|
||||
|
||||
READY = True
|
||||
|
||||
|
||||
def _upload_to_wpasec(path, timeout=30):
|
||||
def _upload_to_wpasec(self, path, timeout=30):
|
||||
"""
|
||||
Uploads the file to https://wpa-sec.stanev.org, or another endpoint.
|
||||
"""
|
||||
with open(path, 'rb') as file_to_upload:
|
||||
cookie = {'key': OPTIONS['api_key']}
|
||||
cookie = {'key': self.options['api_key']}
|
||||
payload = {'file': file_to_upload}
|
||||
|
||||
try:
|
||||
result = requests.post(OPTIONS['api_url'],
|
||||
result = requests.post(self.options['api_url'],
|
||||
cookies=cookie,
|
||||
files=payload,
|
||||
timeout=timeout)
|
||||
@@ -50,22 +35,34 @@ def _upload_to_wpasec(path, timeout=30):
|
||||
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 on_internet_available(agent):
|
||||
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.")
|
||||
return
|
||||
|
||||
self.ready = True
|
||||
|
||||
def on_internet_available(self, agent):
|
||||
"""
|
||||
Called in manual mode when there's internet connectivity
|
||||
"""
|
||||
global REPORT
|
||||
global SKIP
|
||||
if READY:
|
||||
if self.ready:
|
||||
config = agent.config()
|
||||
display = agent.view()
|
||||
reported = REPORT.data_field_or('reported', default=list())
|
||||
reported = self.report.data_field_or('reported', default=list())
|
||||
|
||||
handshake_dir = config['bettercap']['handshakes']
|
||||
handshake_filenames = os.listdir(handshake_dir)
|
||||
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)
|
||||
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(self.skip)
|
||||
|
||||
if handshake_new:
|
||||
logging.info("WPA_SEC: Internet connectivity detected. Uploading new handshakes to wpa-sec.stanev.org")
|
||||
@@ -74,12 +71,12 @@ def on_internet_available(agent):
|
||||
display.set('status', f"Uploading handshake to wpa-sec.stanev.org ({idx + 1}/{len(handshake_new)})")
|
||||
display.update(force=True)
|
||||
try:
|
||||
_upload_to_wpasec(handshake)
|
||||
self._upload_to_wpasec(handshake)
|
||||
reported.append(handshake)
|
||||
REPORT.update(data={'reported': reported})
|
||||
self.report.update(data={'reported': reported})
|
||||
logging.info("WPA_SEC: Successfully uploaded %s", handshake)
|
||||
except requests.exceptions.RequestException as req_e:
|
||||
SKIP.append(handshake)
|
||||
self.skip.append(handshake)
|
||||
logging.error("WPA_SEC: %s", req_e)
|
||||
continue
|
||||
except OSError as os_e:
|
||||
|
@@ -4,7 +4,6 @@ import threading
|
||||
|
||||
import pwnagotchi.plugins as plugins
|
||||
import pwnagotchi.ui.hw as hw
|
||||
import pwnagotchi.ui.web as web
|
||||
from pwnagotchi.ui.view import View
|
||||
|
||||
|
||||
@@ -15,7 +14,6 @@ class Display(View):
|
||||
|
||||
self._enabled = config['enabled']
|
||||
self._rotation = config['rotation']
|
||||
self._webui = web.Server(config)
|
||||
|
||||
self.init_display()
|
||||
|
||||
@@ -27,6 +25,9 @@ class Display(View):
|
||||
)
|
||||
self._render_thread_instance.start()
|
||||
|
||||
def set_ready(self):
|
||||
self._webui.start()
|
||||
|
||||
def is_inky(self):
|
||||
return self._implementation.name == 'inky'
|
||||
|
||||
@@ -42,6 +43,9 @@ class Display(View):
|
||||
def is_waveshare27inch(self):
|
||||
return self._implementation.name == 'waveshare27inch'
|
||||
|
||||
def is_waveshare29inch(self):
|
||||
return self._implementation.name == 'waveshare29inch'
|
||||
|
||||
def is_oledhat(self):
|
||||
return self._implementation.name == 'oledhat'
|
||||
|
||||
@@ -86,7 +90,6 @@ class Display(View):
|
||||
self._implementation.render(self._canvas_next)
|
||||
|
||||
def _on_view_rendered(self, img):
|
||||
web.update_frame(img)
|
||||
try:
|
||||
if self._config['ui']['display']['video']['on_frame'] != '':
|
||||
os.system(self._config['ui']['display']['video']['on_frame'])
|
||||
|
@@ -16,6 +16,7 @@ DEMOTIVATED = '(≖__≖)'
|
||||
SMART = '(✜‿‿✜)'
|
||||
LONELY = '(ب__ب)'
|
||||
SAD = '(╥☁╥ )'
|
||||
ANGRY = "(-_-')"
|
||||
FRIEND = '(♥‿‿♥)'
|
||||
BROKEN = '(☓‿‿☓)'
|
||||
DEBUG = '(#__#)'
|
||||
|
@@ -6,6 +6,7 @@ from pwnagotchi.ui.hw.dfrobot import DFRobot
|
||||
from pwnagotchi.ui.hw.waveshare1 import WaveshareV1
|
||||
from pwnagotchi.ui.hw.waveshare2 import WaveshareV2
|
||||
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.waveshare213d import Waveshare213d
|
||||
|
||||
@@ -36,6 +37,9 @@ def display_for(config):
|
||||
elif config['ui']['display']['type'] == 'waveshare27inch':
|
||||
return Waveshare27inch(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare29inch':
|
||||
return Waveshare29inch(config)
|
||||
|
||||
elif config['ui']['display']['type'] == 'waveshare154inch':
|
||||
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
|
||||
from PIL import ImageDraw
|
||||
|
||||
import pwnagotchi
|
||||
import pwnagotchi.utils as utils
|
||||
import pwnagotchi.plugins as plugins
|
||||
from pwnagotchi.voice import Voice
|
||||
|
||||
import pwnagotchi.ui.web as web
|
||||
import pwnagotchi.ui.fonts as fonts
|
||||
import pwnagotchi.ui.faces as faces
|
||||
from pwnagotchi.ui.components import *
|
||||
@@ -132,7 +134,7 @@ class View(object):
|
||||
return self._state.get(key)
|
||||
|
||||
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)
|
||||
|
||||
def on_ai_ready(self):
|
||||
@@ -284,6 +286,11 @@ class View(object):
|
||||
self.set('status', self._voice.on_sad())
|
||||
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):
|
||||
self.set('face', faces.MOTIVATED)
|
||||
self.set('status', self._voice.on_motivated(reward))
|
||||
@@ -363,6 +370,8 @@ class View(object):
|
||||
for key, lv in self._state.items():
|
||||
lv.draw(self._canvas, drawer)
|
||||
|
||||
web.update_frame(self._canvas)
|
||||
|
||||
for cb in self._render_cbs:
|
||||
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'),
|
||||
'...'])
|
||||
|
||||
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):
|
||||
return random.choice([
|
||||
self._('I\'m living the life!'),
|
||||
|
@@ -14,3 +14,6 @@ smbus2==0.3.0
|
||||
Pillow==5.4.1
|
||||
spidev==3.4
|
||||
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
|
||||
UNIT_HOSTNAME=${1:-10.0.0.2}
|
||||
# output backup zip file
|
||||
OUTPUT=${2:-pwnagotchi-backup.zip}
|
||||
OUTPUT=${2:-pwnagotchi-backup.tgz}
|
||||
# username to use for ssh
|
||||
USERNAME=${3:-pi}
|
||||
# what to backup
|
||||
@@ -19,38 +19,10 @@ FILES_TO_BACKUP=(
|
||||
/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 || {
|
||||
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 "@ backing up $UNIT_HOSTNAME to $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
|
||||
|
||||
ssh "${USERNAME}@${UNIT_HOSTNAME}" "sudo tar cv ${FILES_TO_BACKUP[@]}" | gzip -9 > "$OUTPUT"
|
||||
|
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
|
||||
# -*- coding: utf-8 -*-
|
||||
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 = []
|
||||
with open('requirements.txt') as fp:
|
||||
@@ -10,6 +46,8 @@ with open('requirements.txt') as fp:
|
||||
if line != "":
|
||||
required.append(line)
|
||||
|
||||
import pwnagotchi
|
||||
|
||||
setup(name='pwnagotchi',
|
||||
version=pwnagotchi.version,
|
||||
description='(⌐■_■) - Deep Reinforcement Learning instrumenting bettercap for WiFI pwning.',
|
||||
|
Reference in New Issue
Block a user