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,14 +1,13 @@
|
||||
# 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/),
|
||||
@@ -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": [{
|
||||
"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
|
||||
}],
|
||||
"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",
|
||||
@@ -18,7 +20,83 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"type":"ansible-local",
|
||||
"type": "file",
|
||||
"source": "data/usr/bin/pwnlib",
|
||||
"destination": "/usr/bin/pwnlib"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/usr/bin/bettercap-launcher",
|
||||
"destination": "/usr/bin/bettercap-launcher"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/usr/bin/pwnagotchi-launcher",
|
||||
"destination": "/usr/bin/pwnagotchi-launcher"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/usr/bin/monstop",
|
||||
"destination": "/usr/bin/monstop"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/usr/bin/monstart",
|
||||
"destination": "/usr/bin/monstart"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/usr/bin/hdmion",
|
||||
"destination": "/usr/bin/hdmion"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/usr/bin/hdmioff",
|
||||
"destination": "/usr/bin/hdmioff"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/etc/network/interfaces.d/lo-cfg",
|
||||
"destination": "/etc/network/interfaces.d/lo-cfg"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/etc/network/interfaces.d/wlan0-cfg",
|
||||
"destination": "/etc/network/interfaces.d/wlan0-cfg"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/etc/network/interfaces.d/usb0-cfg",
|
||||
"destination": "/etc/network/interfaces.d/usb0-cfg"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/etc/network/interfaces.d/eth0-cfg",
|
||||
"destination": "/etc/network/interfaces.d/eth0-cfg"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/etc/systemd/system/pwngrid-peer.service",
|
||||
"destination": "/etc/systemd/system/pwngrid-peer.service"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/etc/systemd/system/pwnagotchi.service",
|
||||
"destination": "/etc/systemd/system/pwnagotchi.service"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"source": "data/etc/systemd/system/bettercap.service",
|
||||
"destination": "/etc/systemd/system/bettercap.service"
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"inline": [
|
||||
"chmod +x /usr/bin/*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "ansible-local",
|
||||
"playbook_file": "pwnagotchi.yml",
|
||||
"command": "ANSIBLE_FORCE_COLOR=1 PYTHONUNBUFFERED=1 PWN_VERSION={{user `pwn_version`}} PWN_HOSTNAME={{user `pwn_hostname`}} ansible-playbook"
|
||||
},
|
||||
|
@@ -13,6 +13,7 @@
|
||||
- "dtparam=spi=on"
|
||||
- "dtparam=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():
|
||||
logging.warning("rebooting ...")
|
||||
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,13 +112,21 @@ 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:
|
||||
logging.warning("agent missed %d interactions -> lonely" % did_miss)
|
||||
self.set_lonely()
|
||||
# after X times being bored, the status is set to sad
|
||||
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 or angry
|
||||
elif self._epoch.inactive_for >= self._config['personality']['sad_num_epochs']:
|
||||
self.set_sad()
|
||||
factor = self._epoch.inactive_for / self._config['personality']['sad_num_epochs']
|
||||
if factor >= 2.0:
|
||||
self.set_angry(factor)
|
||||
else:
|
||||
self.set_sad()
|
||||
# after X times being inactive, the status is set to bored
|
||||
elif self._epoch.inactive_for >= self._config['personality']['bored_num_epochs']:
|
||||
self.set_bored()
|
||||
|
@@ -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
|
||||
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
|
||||
share_internet: false
|
||||
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 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,19 +48,15 @@ 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")):
|
||||
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
|
||||
except Exception as e:
|
||||
logging.warning("error while loading %s: %s" % (filename, e))
|
||||
logging.debug(e, exc_info=True)
|
||||
plugin_name = os.path.basename(filename.replace(".py", ""))
|
||||
if plugin_name in enabled:
|
||||
try:
|
||||
load_from_file(filename)
|
||||
except Exception as e:
|
||||
logging.warning("error while loading %s: %s" % (filename, e))
|
||||
logging.debug(e, exc_info=True)
|
||||
|
||||
return loaded
|
||||
|
||||
@@ -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
|
||||
for name, plugin in loaded.items():
|
||||
plugin.__dict__['OPTIONS'] = config['main']['plugins'][name]
|
||||
load_from_path(custom_path, enabled=enabled)
|
||||
|
||||
# propagate options
|
||||
for name, plugin in loaded.items():
|
||||
plugin.options = config['main']['plugins'][name]
|
||||
|
||||
on('loaded')
|
||||
|
@@ -1,56 +1,57 @@
|
||||
__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():
|
||||
logging.info("aircrackonly plugin loaded")
|
||||
|
||||
def on_handshake(agent, filename, access_point, client_station):
|
||||
display = agent._view
|
||||
todelete = 0
|
||||
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'
|
||||
|
||||
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:
|
||||
logging.info("[AircrackOnly] contains handshake")
|
||||
else:
|
||||
todelete = 1
|
||||
def __init__(self):
|
||||
super().__init__(self)
|
||||
self.text_to_set = ""
|
||||
|
||||
if todelete == 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})
|
||||
def on_loaded(self):
|
||||
logging.info("aircrackonly plugin loaded")
|
||||
|
||||
def on_handshake(self, agent, filename, access_point, client_station):
|
||||
display = agent._view
|
||||
todelete = 0
|
||||
handshakeFound = 0
|
||||
|
||||
result = subprocess.run(('/usr/bin/aircrack-ng ' + filename + ' | grep "1 handshake" | awk \'{print $2}\''),
|
||||
shell=True, stdout=subprocess.PIPE)
|
||||
result = result.stdout.decode('utf-8').translate({ord(c): None for c in string.whitespace})
|
||||
if result:
|
||||
logging.info("[AircrackOnly] contains PMKID")
|
||||
else:
|
||||
todelete = 1
|
||||
handshakeFound = 1
|
||||
logging.info("[AircrackOnly] contains handshake")
|
||||
|
||||
if todelete == 1:
|
||||
os.remove(filename)
|
||||
set_text("Removed an uncrackable pcap")
|
||||
display.update(force=True)
|
||||
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")
|
||||
else:
|
||||
todelete = 1
|
||||
|
||||
text_to_set = "";
|
||||
def set_text(text):
|
||||
global text_to_set
|
||||
text_to_set = text
|
||||
if todelete == 1:
|
||||
os.remove(filename)
|
||||
self.text_to_set = "Removed an uncrackable pcap"
|
||||
display.update(force=True)
|
||||
|
||||
def on_ui_update(ui):
|
||||
global text_to_set
|
||||
if text_to_set:
|
||||
ui.set('face', "(>.<)")
|
||||
ui.set('status', text_to_set)
|
||||
text_to_set = ""
|
||||
def on_ui_update(self, ui):
|
||||
if self.text_to_set:
|
||||
ui.set('face', "(>.<)")
|
||||
ui.set('status', self.text_to_set)
|
||||
self.text_to_set = ""
|
||||
|
@@ -1,49 +1,44 @@
|
||||
__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.")
|
||||
return
|
||||
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
|
||||
self.ready = True
|
||||
logging.info("AUTO-BACKUP: Successfully loaded.")
|
||||
|
||||
if 'commands' not in OPTIONS or ('commands' in OPTIONS and OPTIONS['commands'] is None):
|
||||
logging.error("AUTO-BACKUP: No commands given.")
|
||||
return
|
||||
|
||||
READY = True
|
||||
logging.info("AUTO-BACKUP: Successfully loaded.")
|
||||
|
||||
|
||||
def on_internet_available(agent):
|
||||
global STATUS
|
||||
|
||||
if READY:
|
||||
if STATUS.newer_then_days(OPTIONS['interval']):
|
||||
def on_internet_available(self, agent):
|
||||
if not self.ready:
|
||||
return
|
||||
|
||||
|
||||
if self.options['max_tries'] and self.tries >= self.options['max_tries']:
|
||||
return
|
||||
|
||||
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:
|
||||
display = agent.view()
|
||||
|
||||
@@ -51,10 +46,10 @@ 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")
|
||||
stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
|
||||
process.wait()
|
||||
if process.returncode > 0:
|
||||
raise OSError(f"Command failed (rc: {process.returncode})")
|
||||
@@ -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():
|
||||
"""
|
||||
@@ -59,9 +50,9 @@ class BTNap:
|
||||
"""
|
||||
manager = getattr(BTNap.get_manager, 'cached_obj', None)
|
||||
if not manager:
|
||||
manager = BTNap.get_manager.cached_obj = dbus.Interface(
|
||||
BTNap.get_bus().get_object(BTNap.IFACE_BASE, '/'),
|
||||
'org.freedesktop.DBus.ObjectManager' )
|
||||
manager = BTNap.get_manager.cached_obj = dbus.Interface(
|
||||
BTNap.get_bus().get_object(BTNap.IFACE_BASE, '/'),
|
||||
'org.freedesktop.DBus.ObjectManager')
|
||||
return manager
|
||||
|
||||
@staticmethod
|
||||
@@ -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):
|
||||
"""
|
||||
@@ -98,14 +88,14 @@ class BTNap:
|
||||
"""
|
||||
bus, obj = BTNap.get_bus(), None
|
||||
for path, ifaces in objects.items():
|
||||
adapter = ifaces.get(BTNap.IFACE_ADAPTER)
|
||||
if adapter is None:
|
||||
continue
|
||||
if not pattern or pattern == adapter['Address'] or path.endswith(pattern):
|
||||
obj = bus.get_object(BTNap.IFACE_BASE, path)
|
||||
yield dbus.Interface(obj, BTNap.IFACE_ADAPTER)
|
||||
adapter = ifaces.get(BTNap.IFACE_ADAPTER)
|
||||
if adapter is None:
|
||||
continue
|
||||
if not pattern or pattern == adapter['Address'] or path.endswith(pattern):
|
||||
obj = bus.get_object(BTNap.IFACE_BASE, path)
|
||||
yield dbus.Interface(obj, BTNap.IFACE_ADAPTER)
|
||||
if obj is None:
|
||||
raise BTError('Bluetooth adapter not found')
|
||||
raise BTError('Bluetooth adapter not found')
|
||||
|
||||
@staticmethod
|
||||
def find_device(device_address, 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
|
||||
@@ -227,7 +197,7 @@ class BTNap:
|
||||
try:
|
||||
dev_remote = BTNap.find_device(self._mac, bt_dev)
|
||||
logging.debug("BT-TETHER: Using remote device (addr: %s): %s",
|
||||
BTNap.prop_get(dev_remote, 'Address'), dev_remote.object_path )
|
||||
BTNap.prop_get(dev_remote, 'Address'), dev_remote.object_path)
|
||||
break
|
||||
except BTError:
|
||||
logging.debug("BT-TETHER: Not found yet ...")
|
||||
@@ -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 ...')
|
||||
@@ -267,7 +236,7 @@ class BTNap:
|
||||
try:
|
||||
logging.debug('BT-TETHER: Connecting to profile ...')
|
||||
device.ConnectProfile('nap')
|
||||
except Exception: # raises exception, but still works
|
||||
except Exception: # raises exception, but still works
|
||||
pass
|
||||
|
||||
net = dbus.Interface(device, 'org.bluez.Network1')
|
||||
@@ -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:
|
||||
@@ -297,7 +266,7 @@ class SystemdUnitWrapper:
|
||||
@staticmethod
|
||||
def _action_on_unit(action, unit):
|
||||
process = subprocess.Popen(f"systemctl {action} {unit}", shell=True, stdin=None,
|
||||
stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
|
||||
stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
|
||||
process.wait()
|
||||
if process.returncode > 0:
|
||||
return False
|
||||
@@ -309,7 +278,7 @@ class SystemdUnitWrapper:
|
||||
Calls systemctl daemon-reload
|
||||
"""
|
||||
process = subprocess.Popen("systemctl daemon-reload", shell=True, stdin=None,
|
||||
stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
|
||||
stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
|
||||
process.wait()
|
||||
if process.returncode > 0:
|
||||
return False
|
||||
@@ -387,24 +356,23 @@ class IfaceWrapper:
|
||||
"""
|
||||
return open(f"{self.path}/operstate", 'r').read().rsplit('\n') == 'up'
|
||||
|
||||
|
||||
def set_addr(self, addr):
|
||||
"""
|
||||
Set the netmask
|
||||
"""
|
||||
process = subprocess.Popen(f"ip addr add {addr} dev {self.iface}", shell=True, stdin=None,
|
||||
stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
|
||||
stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
|
||||
process.wait()
|
||||
|
||||
if process.returncode == 2 or process.returncode == 0: # 2 = already set
|
||||
if process.returncode == 2 or process.returncode == 0: # 2 = already set
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def set_route(addr):
|
||||
process = subprocess.Popen(f"ip route replace default via {addr}", shell=True, stdin=None,
|
||||
stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
|
||||
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()
|
||||
|
||||
if process.returncode > 0:
|
||||
@@ -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
|
||||
|
||||
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)
|
||||
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 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")
|
||||
# ensure bluetooth is running
|
||||
bt_unit = SystemdUnitWrapper('bluetooth.service')
|
||||
if not bt_unit.is_active():
|
||||
if not bt_unit.start():
|
||||
logging.error("BT-TETHER: Can't start bluetooth.service")
|
||||
return
|
||||
|
||||
logging.info("BT-TETHER: Sussessfully loaded ...")
|
||||
self.ready = True
|
||||
|
||||
def on_ui_setup(self, ui):
|
||||
ui.add_element('bluetooth', LabeledValue(color=BLACK, label='BT', value='-', position=(ui.width() / 2 - 15, 0),
|
||||
label_font=fonts.Bold, text_font=fonts.Medium))
|
||||
|
||||
def on_ui_update(self, ui):
|
||||
if not self.ready:
|
||||
return
|
||||
|
||||
INTERVAL.update()
|
||||
READY = True
|
||||
devices_to_try = list()
|
||||
connected_priorities = list()
|
||||
any_device_connected = False # if this is true, last status on screen should be C
|
||||
|
||||
for _, device in self.devices.items():
|
||||
if device.connected():
|
||||
connected_priorities.append(device.priority)
|
||||
any_device_connected = True
|
||||
continue
|
||||
|
||||
def on_ui_update(ui):
|
||||
"""
|
||||
Try to connect to device
|
||||
"""
|
||||
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 READY:
|
||||
global INTERVAL
|
||||
if INTERVAL.newer_then_minutes(OPTIONS['interval']):
|
||||
return
|
||||
sorted_devices = sorted(devices_to_try, key=lambda x: x.search_order)
|
||||
|
||||
INTERVAL.update()
|
||||
for device in sorted_devices:
|
||||
bt = BTNap(device.mac)
|
||||
|
||||
bt = BTNap(OPTIONS['mac'])
|
||||
|
||||
logging.debug('BT-TETHER: Check if already connected and paired')
|
||||
dev_remote, connected = bt.is_connected()
|
||||
|
||||
if connected:
|
||||
logging.debug('BT-TETHER: Already connected.')
|
||||
ui.set('bluetooth', 'C')
|
||||
return
|
||||
|
||||
try:
|
||||
logging.info('BT-TETHER: Search device ...')
|
||||
dev_remote = bt.wait_for_device()
|
||||
if dev_remote is None:
|
||||
logging.info('BT-TETHER: Could not find device.')
|
||||
try:
|
||||
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.debug('BT-TETHER: Could not find %s, try again in %d minutes.', device.name, device.interval)
|
||||
ui.set('bluetooth', 'NF')
|
||||
continue
|
||||
except Exception as bt_ex:
|
||||
logging.error(bt_ex)
|
||||
ui.set('bluetooth', 'NF')
|
||||
return
|
||||
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.')
|
||||
paired = bt.is_paired()
|
||||
if not paired:
|
||||
if BTNap.pair(dev_remote):
|
||||
logging.debug('BT-TETHER: Paired with %s.', device.name)
|
||||
else:
|
||||
logging.debug('BT-TETHER: Pairing with %s failed ...', device.name)
|
||||
ui.set('bluetooth', 'PE')
|
||||
continue
|
||||
else:
|
||||
logging.info('BT-TETHER: Pairing failed ...')
|
||||
ui.set('bluetooth', 'PE')
|
||||
return
|
||||
else:
|
||||
logging.debug('BT-TETHER: Already paired.')
|
||||
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!
|
||||
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')
|
||||
resolv.seek(0)
|
||||
resolv.write(nameserver + 'nameserver 9.9.9.9\n')
|
||||
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.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,182 +1,159 @@
|
||||
__author__ = 'evilsocket@gmail.com'
|
||||
__version__ = '1.0.0'
|
||||
__name__ = 'hello_world'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'An example plugin for pwnagotchi that implements all the available callbacks.'
|
||||
|
||||
import logging
|
||||
|
||||
import 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.'
|
||||
|
||||
# called when <host>:<port>/plugins/<pluginname> is opened
|
||||
def on_webhook(response, path):
|
||||
res = "<html><body><a>Hook triggered</a></body></html>"
|
||||
response.send_response(200)
|
||||
response.send_header('Content-type', 'text/html')
|
||||
response.end_headers()
|
||||
def __init__(self):
|
||||
logging.debug("example plugin created")
|
||||
|
||||
try:
|
||||
response.wfile.write(bytes(res, "utf-8"))
|
||||
except Exception as ex:
|
||||
logging.error(ex)
|
||||
# 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():
|
||||
logging.warning("WARNING: plugin %s should be disabled!" % __name__)
|
||||
# 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(self, response, path):
|
||||
res = "<html><body><a>Hook triggered</a></body></html>"
|
||||
response.send_response(200)
|
||||
response.send_header('Content-type', 'text/html')
|
||||
response.end_headers()
|
||||
|
||||
# called in manual mode when there's internet connectivity
|
||||
def on_internet_available(agent):
|
||||
pass
|
||||
try:
|
||||
response.wfile.write(bytes(res, "utf-8"))
|
||||
except Exception as ex:
|
||||
logging.error(ex)
|
||||
|
||||
# called in manual mode when there's internet connectivity
|
||||
def on_internet_available(self, agent):
|
||||
pass
|
||||
|
||||
# called to setup the ui elements
|
||||
def on_ui_setup(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 to setup the ui elements
|
||||
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(self, ui):
|
||||
# update those elements
|
||||
some_voltage = 0.1
|
||||
some_capacity = 100.0
|
||||
ui.set('ups', "%4.2fV/%2i%%" % (some_voltage, some_capacity))
|
||||
|
||||
# called when the ui is updated
|
||||
def on_ui_update(ui):
|
||||
# update those elements
|
||||
some_voltage = 0.1
|
||||
some_capacity = 100.0
|
||||
# called when the hardware display setup is done, display is an hardware specific object
|
||||
def on_display_setup(self, display):
|
||||
pass
|
||||
|
||||
ui.set('ups', "%4.2fV/%2i%%" % (some_voltage, some_capacity))
|
||||
# called when everything is ready and the main loop is about to start
|
||||
def on_ready(self, agent):
|
||||
logging.info("unit is ready")
|
||||
# you can run custom bettercap commands if you want
|
||||
# agent.run('ble.recon on')
|
||||
# or set a custom state
|
||||
# agent.set_bored()
|
||||
|
||||
# called when the AI finished loading
|
||||
def on_ai_ready(self, agent):
|
||||
pass
|
||||
|
||||
# called when the hardware display setup is done, display is an hardware specific object
|
||||
def on_display_setup(display):
|
||||
pass
|
||||
# called when the AI finds a new set of parameters
|
||||
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(self, agent, epochs):
|
||||
pass
|
||||
|
||||
# called when everything is ready and the main loop is about to start
|
||||
def on_ready(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 after the AI completed a training epoch
|
||||
def on_ai_training_step(self, agent, _locals, _globals):
|
||||
pass
|
||||
|
||||
# called when the AI has done training
|
||||
def on_ai_training_end(self, agent):
|
||||
pass
|
||||
|
||||
# called when the AI finished loading
|
||||
def on_ai_ready(agent):
|
||||
pass
|
||||
# called when the AI got the best reward so far
|
||||
def on_ai_best_reward(self, agent, reward):
|
||||
pass
|
||||
|
||||
# called when the AI got the worst reward so far
|
||||
def on_ai_worst_reward(self, agent, reward):
|
||||
pass
|
||||
|
||||
# called when the AI finds a new set of parameters
|
||||
def on_ai_policy(agent, policy):
|
||||
pass
|
||||
# called when a non overlapping wifi channel is found to be free
|
||||
def on_free_channel(self, agent, channel):
|
||||
pass
|
||||
|
||||
# called when the status is set to bored
|
||||
def on_bored(self, agent):
|
||||
pass
|
||||
|
||||
# called when the AI starts training for a given number of epochs
|
||||
def on_ai_training_start(agent, epochs):
|
||||
pass
|
||||
# called when the status is set to sad
|
||||
def on_sad(self, agent):
|
||||
pass
|
||||
|
||||
# called when the status is set to excited
|
||||
def on_excited(aself, gent):
|
||||
pass
|
||||
|
||||
# called after the AI completed a training epoch
|
||||
def on_ai_training_step(agent, _locals, _globals):
|
||||
pass
|
||||
# called when the status is set to lonely
|
||||
def on_lonely(self, agent):
|
||||
pass
|
||||
|
||||
# called when the agent is rebooting the board
|
||||
def on_rebooting(self, agent):
|
||||
pass
|
||||
|
||||
# called when the AI has done training
|
||||
def on_ai_training_end(agent):
|
||||
pass
|
||||
# called when the agent is waiting for t seconds
|
||||
def on_wait(self, agent, t):
|
||||
pass
|
||||
|
||||
# called when the agent is sleeping for t seconds
|
||||
def on_sleep(self, agent, t):
|
||||
pass
|
||||
|
||||
# called when the AI got the best reward so far
|
||||
def on_ai_best_reward(agent, reward):
|
||||
pass
|
||||
# called when the agent refreshed its access points list
|
||||
def on_wifi_update(self, agent, access_points):
|
||||
pass
|
||||
|
||||
# called when the agent is sending an association frame
|
||||
def on_association(self, agent, access_point):
|
||||
pass
|
||||
|
||||
# called when the AI got the worst reward so far
|
||||
def on_ai_worst_reward(agent, reward):
|
||||
pass
|
||||
# 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(self, agent, channel):
|
||||
pass
|
||||
|
||||
# called when a non overlapping wifi channel is found to be free
|
||||
def on_free_channel(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(self, agent, filename, access_point, client_station):
|
||||
pass
|
||||
|
||||
# called when an epoch is over (where an epoch is a single loop of the main algorithm)
|
||||
def on_epoch(self, agent, epoch, epoch_data):
|
||||
pass
|
||||
|
||||
# called when the status is set to bored
|
||||
def on_bored(agent):
|
||||
pass
|
||||
# called when a new peer is detected
|
||||
def on_peer_detected(self, agent, peer):
|
||||
pass
|
||||
|
||||
|
||||
# called when the status is set to sad
|
||||
def on_sad(agent):
|
||||
pass
|
||||
|
||||
|
||||
# called when the status is set to excited
|
||||
def on_excited(agent):
|
||||
pass
|
||||
|
||||
|
||||
# called when the status is set to lonely
|
||||
def on_lonely(agent):
|
||||
pass
|
||||
|
||||
|
||||
# called when the agent is rebooting the board
|
||||
def on_rebooting(agent):
|
||||
pass
|
||||
|
||||
|
||||
# called when the agent is waiting for t seconds
|
||||
def on_wait(agent, t):
|
||||
pass
|
||||
|
||||
|
||||
# called when the agent is sleeping for t seconds
|
||||
def on_sleep(agent, t):
|
||||
pass
|
||||
|
||||
|
||||
# called when the agent refreshed its access points list
|
||||
def on_wifi_update(agent, access_points):
|
||||
pass
|
||||
|
||||
|
||||
# called when the agent is sending an association frame
|
||||
def on_association(agent, access_point):
|
||||
pass
|
||||
|
||||
|
||||
# callend when the agent is deauthenticating a client station from an AP
|
||||
def on_deauthentication(agent, access_point, client_station):
|
||||
pass
|
||||
|
||||
|
||||
# callend when the agent is tuning on a specific channel
|
||||
def on_channel_hop(agent, channel):
|
||||
pass
|
||||
|
||||
|
||||
# called when a new handshake is captured, access_point and client_station are json objects
|
||||
# if the agent could match the BSSIDs to the current list, otherwise they are just the strings of the BSSIDs
|
||||
def on_handshake(agent, filename, access_point, client_station):
|
||||
pass
|
||||
|
||||
|
||||
# called when an epoch is over (where an epoch is a single loop of the main algorithm)
|
||||
def on_epoch(agent, epoch, epoch_data):
|
||||
pass
|
||||
|
||||
|
||||
# called when a new peer is detected
|
||||
def on_peer_detected(agent, peer):
|
||||
pass
|
||||
|
||||
|
||||
# called when a known peer is lost
|
||||
def on_peer_lost(agent, peer):
|
||||
pass
|
||||
# called when a known peer is lost
|
||||
def on_peer_lost(self, agent, peer):
|
||||
pass
|
||||
|
@@ -1,38 +1,40 @@
|
||||
__author__ = 'ratmandu@gmail.com'
|
||||
__version__ = '1.0.0'
|
||||
__name__ = 'gpio_buttons'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'GPIO Button support plugin'
|
||||
|
||||
import logging
|
||||
import RPi.GPIO as GPIO
|
||||
import subprocess
|
||||
|
||||
running = False
|
||||
OPTIONS = dict()
|
||||
GPIOs = {}
|
||||
COMMANDs = None
|
||||
|
||||
def runCommand(channel):
|
||||
command = GPIOs[channel]
|
||||
logging.info(f"Button Pressed! Running command: {command}")
|
||||
process = subprocess.Popen(command, shell=True, stdin=None, stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
|
||||
process.wait()
|
||||
import pwnagotchi.plugins as plugins
|
||||
|
||||
|
||||
def on_loaded():
|
||||
logging.info("GPIO Button plugin loaded.")
|
||||
|
||||
#get list of GPIOs
|
||||
gpios = OPTIONS['gpios']
|
||||
class GPIOButtons(plugins.Plugin):
|
||||
__author__ = 'ratmandu@gmail.com'
|
||||
__version__ = '1.0.0'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'GPIO Button support plugin'
|
||||
|
||||
#set gpio numbering
|
||||
GPIO.setmode(GPIO.BCM)
|
||||
def __init__(self):
|
||||
self.running = False
|
||||
self.ports = {}
|
||||
self.commands = None
|
||||
|
||||
for i in gpios:
|
||||
gpio = list(i)[0]
|
||||
command = i[gpio]
|
||||
GPIOs[gpio] = command
|
||||
GPIO.setup(gpio, GPIO.IN, GPIO.PUD_UP)
|
||||
GPIO.add_event_detect(gpio, GPIO.FALLING, callback=runCommand, bouncetime=250)
|
||||
logging.info("Added command: %s to GPIO #%d", command, gpio)
|
||||
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.wait()
|
||||
|
||||
def on_loaded(self):
|
||||
logging.info("GPIO Button plugin loaded.")
|
||||
|
||||
# get list of GPIOs
|
||||
gpios = self.options['gpios']
|
||||
|
||||
# set gpio numbering
|
||||
GPIO.setmode(GPIO.BCM)
|
||||
|
||||
for i in gpios:
|
||||
gpio = list(i)[0]
|
||||
command = i[gpio]
|
||||
self.ports[gpio] = command
|
||||
GPIO.setup(gpio, GPIO.IN, GPIO.PUD_UP)
|
||||
GPIO.add_event_detect(gpio, GPIO.FALLING, callback=self.runCommand, bouncetime=250)
|
||||
logging.info("Added command: %s to GPIO #%d", command, gpio)
|
||||
|
@@ -1,45 +1,42 @@
|
||||
__author__ = 'evilsocket@gmail.com'
|
||||
__version__ = '1.0.0'
|
||||
__name__ = 'gps'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'Save GPS coordinates whenever an handshake is captured.'
|
||||
|
||||
import logging
|
||||
import 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'])
|
||||
try:
|
||||
agent.run('gps off')
|
||||
except:
|
||||
pass
|
||||
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('gps on')
|
||||
running = True
|
||||
else:
|
||||
logging.warning("no GPS detected")
|
||||
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(self, agent, filename, access_point, client_station):
|
||||
if self.running:
|
||||
info = agent.session()
|
||||
gps = info['gps']
|
||||
gps_filename = filename.replace('.pcap', '.gps.json')
|
||||
|
||||
def on_handshake(agent, filename, access_point, client_station):
|
||||
if running:
|
||||
info = agent.session()
|
||||
gps = info['gps']
|
||||
gps_filename = filename.replace('.pcap', '.gps.json')
|
||||
|
||||
logging.info("saving GPS to %s (%s)" % (gps_filename, gps))
|
||||
with open(gps_filename, 'w+t') as fp:
|
||||
json.dump(gps, fp)
|
||||
logging.info("saving GPS to %s (%s)" % (gps_filename, gps))
|
||||
with open(gps_filename, 'w+t') as fp:
|
||||
json.dump(gps, fp)
|
||||
|
@@ -1,28 +1,13 @@
|
||||
__author__ = 'evilsocket@gmail.com'
|
||||
__version__ = '1.0.1'
|
||||
__name__ = 'grid'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'This plugin signals the unit cryptographic identity and list of pwned networks and list of pwned ' \
|
||||
'networks to api.pwnagotchi.ai '
|
||||
|
||||
import os
|
||||
import 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,93 +41,100 @@ def parse_pcap(filename):
|
||||
return info[WifiInfo.ESSID], info[WifiInfo.BSSID]
|
||||
|
||||
|
||||
def is_excluded(what):
|
||||
for skip in OPTIONS['exclude']:
|
||||
skip = skip.lower()
|
||||
what = what.lower()
|
||||
if skip in what or skip.replace(':', '') in what:
|
||||
return True
|
||||
return False
|
||||
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')
|
||||
|
||||
def set_reported(reported, net_id):
|
||||
global REPORT
|
||||
reported.append(net_id)
|
||||
REPORT.update(data={'reported': reported})
|
||||
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 check_inbox(agent):
|
||||
global REPORT, UNREAD_MESSAGES, TOTAL_MESSAGES
|
||||
def on_loaded(self):
|
||||
logging.info("grid plugin loaded.")
|
||||
|
||||
logging.debug("checking mailbox ...")
|
||||
def set_reported(self, reported, net_id):
|
||||
reported.append(net_id)
|
||||
self.report.update(data={'reported': reported})
|
||||
|
||||
messages = grid.inbox()
|
||||
TOTAL_MESSAGES = len(messages)
|
||||
UNREAD_MESSAGES = len([m for m in messages if m['seen_at'] is None])
|
||||
def check_inbox(self, agent):
|
||||
logging.debug("checking mailbox ...")
|
||||
messages = grid.inbox()
|
||||
self.total_messages = len(messages)
|
||||
self.unread_messages = len([m for m in messages if m['seen_at'] is None])
|
||||
|
||||
if UNREAD_MESSAGES:
|
||||
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(self, agent):
|
||||
logging.debug("checking pcaps")
|
||||
|
||||
def check_handshakes(agent):
|
||||
logging.debug("checking pcaps")
|
||||
pcap_files = glob.glob(os.path.join(agent.config()['bettercap']['handshakes'], "*.pcap"))
|
||||
num_networks = len(pcap_files)
|
||||
reported = self.report.data_field_or('reported', default=[])
|
||||
num_reported = len(reported)
|
||||
num_new = num_networks - num_reported
|
||||
|
||||
pcap_files = glob.glob(os.path.join(agent.config()['bettercap']['handshakes'], "*.pcap"))
|
||||
num_networks = len(pcap_files)
|
||||
reported = REPORT.data_field_or('reported', default=[])
|
||||
num_reported = len(reported)
|
||||
num_new = num_networks - num_reported
|
||||
if num_new > 0:
|
||||
if self.options['report']:
|
||||
logging.info("grid: %d new networks to report" % num_new)
|
||||
logging.debug("self.options: %s" % self.options)
|
||||
logging.debug(" exclude: %s" % self.options['exclude'])
|
||||
|
||||
if num_new > 0:
|
||||
if OPTIONS['report']:
|
||||
logging.info("grid: %d new networks to report" % num_new)
|
||||
logging.debug("OPTIONS: %s" % OPTIONS)
|
||||
logging.debug(" exclude: %s" % OPTIONS['exclude'])
|
||||
for pcap_file in pcap_files:
|
||||
net_id = os.path.basename(pcap_file).replace('.pcap', '')
|
||||
if net_id not in reported:
|
||||
if self.is_excluded(net_id):
|
||||
logging.debug("skipping %s due to exclusion filter" % pcap_file)
|
||||
self.set_reported(reported, net_id)
|
||||
continue
|
||||
|
||||
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):
|
||||
logging.debug("skipping %s due to exclusion filter" % pcap_file)
|
||||
set_reported(reported, net_id)
|
||||
continue
|
||||
|
||||
essid, bssid = parse_pcap(pcap_file)
|
||||
if bssid:
|
||||
if is_excluded(essid) or is_excluded(bssid):
|
||||
logging.debug("not reporting %s due to exclusion filter" % pcap_file)
|
||||
set_reported(reported, net_id)
|
||||
essid, bssid = parse_pcap(pcap_file)
|
||||
if bssid:
|
||||
if self.is_excluded(essid) or self.is_excluded(bssid):
|
||||
logging.debug("not reporting %s due to exclusion filter" % pcap_file)
|
||||
self.set_reported(reported, net_id)
|
||||
else:
|
||||
if grid.report_ap(essid, bssid):
|
||||
self.set_reported(reported, net_id)
|
||||
time.sleep(1.5)
|
||||
else:
|
||||
if grid.report_ap(essid, bssid):
|
||||
set_reported(reported, net_id)
|
||||
time.sleep(1.5)
|
||||
else:
|
||||
logging.warning("no bssid found?!")
|
||||
else:
|
||||
logging.debug("grid: reporting disabled")
|
||||
logging.warning("no bssid found?!")
|
||||
else:
|
||||
logging.debug("grid: reporting disabled")
|
||||
|
||||
def on_internet_available(self, agent):
|
||||
logging.debug("internet available")
|
||||
|
||||
def on_internet_available(agent):
|
||||
global REPORT, UNREAD_MESSAGES, TOTAL_MESSAGES
|
||||
try:
|
||||
grid.update_data(agent.last_session)
|
||||
except Exception as e:
|
||||
logging.error("error connecting to the pwngrid-peer service: %s" % e)
|
||||
logging.debug(e, exc_info=True)
|
||||
return
|
||||
|
||||
logging.debug("internet available")
|
||||
try:
|
||||
self.check_inbox(agent)
|
||||
except Exception as e:
|
||||
logging.error("[grid] error while checking inbox: %s" % e)
|
||||
logging.debug(e, exc_info=True)
|
||||
|
||||
try:
|
||||
grid.update_data(agent.last_session)
|
||||
except Exception as e:
|
||||
logging.error("error connecting to the pwngrid-peer service: %s" % e)
|
||||
logging.debug(e, exc_info=True)
|
||||
return
|
||||
|
||||
try:
|
||||
check_inbox(agent)
|
||||
except Exception as e:
|
||||
logging.error("[grid] error while checking inbox: %s" % e)
|
||||
logging.debug(e, exc_info=True)
|
||||
|
||||
try:
|
||||
check_handshakes(agent)
|
||||
except Exception as e:
|
||||
logging.error("[grid] error while checking pcaps: %s" % e)
|
||||
logging.debug(e, exc_info=True)
|
||||
try:
|
||||
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():
|
||||
logging.info("memtemp plugin loaded.")
|
||||
def on_loaded(self):
|
||||
logging.info("memtemp plugin loaded.")
|
||||
|
||||
def mem_usage(self):
|
||||
return int(pwnagotchi.mem_usage() * 100)
|
||||
|
||||
def mem_usage():
|
||||
return int(pwnagotchi.mem_usage() * 100)
|
||||
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 cpu_load():
|
||||
return int(pwnagotchi.cpu_load() * 100)
|
||||
if self.options['orientation'] == "horizontal":
|
||||
ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value='mem cpu temp\n - - -',
|
||||
position=h_pos,
|
||||
label_font=fonts.Small, text_font=fonts.Small))
|
||||
elif self.options['orientation'] == "vertical":
|
||||
ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value=' mem:-\n cpu:-\ntemp:-',
|
||||
position=v_pos,
|
||||
label_font=fonts.Small, text_font=fonts.Small))
|
||||
|
||||
def on_ui_update(self, ui):
|
||||
if self.options['orientation'] == "horizontal":
|
||||
ui.set('memtemp',
|
||||
" mem cpu temp\n %s%% %s%% %sc" % (self.mem_usage(), self.cpu_load(), pwnagotchi.temperature()))
|
||||
|
||||
def on_ui_setup(ui):
|
||||
if 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),
|
||||
label_font=fonts.Small, text_font=fonts.Small))
|
||||
elif OPTIONS['orientation'] == "vertical":
|
||||
ui.add_element('memtemp', LabeledValue(color=BLACK, label='', value=' mem:-\n cpu:-\ntemp:-',
|
||||
position=(ui.width() / 2 + 55, ui.height() / 2),
|
||||
label_font=fonts.Small, text_font=fonts.Small))
|
||||
|
||||
|
||||
def on_ui_update(ui):
|
||||
if OPTIONS['orientation'] == "horizontal":
|
||||
ui.set('memtemp', " mem cpu temp\n %s%% %s%% %sc" % (mem_usage(), cpu_load(), pwnagotchi.temperature()))
|
||||
|
||||
elif OPTIONS['orientation'] == "vertical":
|
||||
ui.set('memtemp', " mem:%s%%\n cpu:%s%%\ntemp:%sc" % (mem_usage(), cpu_load(), pwnagotchi.temperature()))
|
||||
elif self.options['orientation'] == "vertical":
|
||||
ui.set('memtemp',
|
||||
" mem:%s%%\n cpu:%s%%\ntemp:%sc" % (self.mem_usage(), self.cpu_load(), pwnagotchi.temperature()))
|
||||
|
@@ -1,140 +1,139 @@
|
||||
__author__ = 'zenzen san'
|
||||
__version__ = '2.0.0'
|
||||
__name__ = 'net-pos'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = """Saves a json file with the access points with more signal
|
||||
whenever a handshake is captured.
|
||||
When internet is available the files are converted in geo locations
|
||||
using Mozilla LocationService """
|
||||
|
||||
import logging
|
||||
import 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}'
|
||||
REPORT = StatusFile('/root/.net_pos_saved', data_format='json')
|
||||
SKIP = list()
|
||||
READY = False
|
||||
OPTIONS = dict()
|
||||
|
||||
|
||||
def on_loaded():
|
||||
global READY
|
||||
class NetPos(plugins.Plugin):
|
||||
__author__ = 'zenzen san'
|
||||
__version__ = '2.0.1'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = """Saves a json file with the access points with more signal
|
||||
whenever a handshake is captured.
|
||||
When internet is available the files are converted in geo locations
|
||||
using Mozilla LocationService """
|
||||
|
||||
if 'api_key' not in OPTIONS or ('api_key' in OPTIONS and OPTIONS['api_key'] is None):
|
||||
logging.error("NET-POS: api_key isn't set. Can't use mozilla's api.")
|
||||
return
|
||||
def __init__(self):
|
||||
self.report = StatusFile('/root/.net_pos_saved', data_format='json')
|
||||
self.skip = list()
|
||||
self.ready = False
|
||||
|
||||
READY = True
|
||||
def on_loaded(self):
|
||||
if 'api_key' not in self.options or ('api_key' in self.options and self.options['api_key'] is None):
|
||||
logging.error("NET-POS: api_key isn't set. Can't use mozilla's api.")
|
||||
return
|
||||
|
||||
logging.info("net-pos plugin loaded.")
|
||||
self.ready = True
|
||||
logging.info("net-pos plugin loaded.")
|
||||
|
||||
def _append_saved(path):
|
||||
to_save = list()
|
||||
if isinstance(path, str):
|
||||
to_save.append(path)
|
||||
elif isinstance(path, list):
|
||||
to_save += path
|
||||
else:
|
||||
raise TypeError("Expected list or str, got %s" % type(path))
|
||||
def _append_saved(self, path):
|
||||
to_save = list()
|
||||
if isinstance(path, str):
|
||||
to_save.append(path)
|
||||
elif isinstance(path, list):
|
||||
to_save += path
|
||||
else:
|
||||
raise TypeError("Expected list or str, got %s" % type(path))
|
||||
|
||||
with open('/root/.net_pos_saved', 'a') as saved_file:
|
||||
for x in to_save:
|
||||
saved_file.write(x + "\n")
|
||||
with open('/root/.net_pos_saved', 'a') as saved_file:
|
||||
for x in to_save:
|
||||
saved_file.write(x + "\n")
|
||||
|
||||
def on_internet_available(agent):
|
||||
global SKIP
|
||||
global REPORT
|
||||
def on_internet_available(self, agent):
|
||||
if self.ready:
|
||||
config = agent.config()
|
||||
display = agent.view()
|
||||
reported = self.report.data_field_or('reported', default=list())
|
||||
handshake_dir = config['bettercap']['handshakes']
|
||||
|
||||
if READY:
|
||||
config = agent.config()
|
||||
display = agent.view()
|
||||
reported = 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(self.skip)
|
||||
|
||||
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)
|
||||
|
||||
if new_np_files:
|
||||
logging.info("NET-POS: Found %d new net-pos files. Fetching positions ...", len(new_np_files))
|
||||
display.set('status', f"Found {len(new_np_files)} new net-pos files. Fetching positions ...")
|
||||
display.update(force=True)
|
||||
for idx, np_file in enumerate(new_np_files):
|
||||
|
||||
geo_file = np_file.replace('.net-pos.json', '.geo.json')
|
||||
if os.path.exists(geo_file):
|
||||
# got already the position
|
||||
reported.append(np_file)
|
||||
REPORT.update(data={'reported': reported})
|
||||
continue
|
||||
|
||||
try:
|
||||
geo_data = _get_geo_data(np_file) # returns json obj
|
||||
except requests.exceptions.RequestException as req_e:
|
||||
logging.error("NET-POS: %s", req_e)
|
||||
SKIP += np_file
|
||||
continue
|
||||
except json.JSONDecodeError as js_e:
|
||||
logging.error("NET-POS: %s", js_e)
|
||||
SKIP += np_file
|
||||
continue
|
||||
except OSError as os_e:
|
||||
logging.error("NET-POS: %s", os_e)
|
||||
SKIP += np_file
|
||||
continue
|
||||
|
||||
with open(geo_file, 'w+t') as sf:
|
||||
json.dump(geo_data, sf)
|
||||
|
||||
reported.append(np_file)
|
||||
REPORT.update(data={'reported': reported})
|
||||
|
||||
display.set('status', f"Fetching positions ({idx+1}/{len(new_np_files)})")
|
||||
if new_np_files:
|
||||
logging.info("NET-POS: Found %d new net-pos files. Fetching positions ...", len(new_np_files))
|
||||
display.set('status', f"Found {len(new_np_files)} new net-pos files. Fetching positions ...")
|
||||
display.update(force=True)
|
||||
for idx, np_file in enumerate(new_np_files):
|
||||
|
||||
geo_file = np_file.replace('.net-pos.json', '.geo.json')
|
||||
if os.path.exists(geo_file):
|
||||
# got already the position
|
||||
reported.append(np_file)
|
||||
self.report.update(data={'reported': reported})
|
||||
continue
|
||||
|
||||
def on_handshake(agent, filename, access_point, client_station):
|
||||
netpos = _get_netpos(agent)
|
||||
netpos_filename = filename.replace('.pcap', '.net-pos.json')
|
||||
logging.info("NET-POS: Saving net-location to %s", netpos_filename)
|
||||
try:
|
||||
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)
|
||||
self.skip += np_file
|
||||
continue
|
||||
except json.JSONDecodeError as js_e:
|
||||
logging.error("NET-POS: %s", js_e)
|
||||
self.skip += np_file
|
||||
continue
|
||||
except OSError as os_e:
|
||||
logging.error("NET-POS: %s", os_e)
|
||||
self.skip += np_file
|
||||
continue
|
||||
|
||||
try:
|
||||
with open(netpos_filename, 'w+t') as net_pos_file:
|
||||
json.dump(netpos, net_pos_file)
|
||||
except OSError as os_e:
|
||||
logging.error("NET-POS: %s", os_e)
|
||||
with open(geo_file, 'w+t') as sf:
|
||||
json.dump(geo_data, sf)
|
||||
|
||||
reported.append(np_file)
|
||||
self.report.update(data={'reported': reported})
|
||||
|
||||
def _get_netpos(agent):
|
||||
aps = agent.get_access_points()
|
||||
netpos = dict()
|
||||
netpos['wifiAccessPoints'] = list()
|
||||
# 6 seems a good number to save a wifi networks location
|
||||
for access_point in sorted(aps, key=lambda i: i['rssi'], reverse=True)[:6]:
|
||||
netpos['wifiAccessPoints'].append({'macAddress': access_point['mac'],
|
||||
'signalStrength': access_point['rssi']})
|
||||
return netpos
|
||||
display.set('status', f"Fetching positions ({idx + 1}/{len(new_np_files)})")
|
||||
display.update(force=True)
|
||||
|
||||
def _get_geo_data(path, timeout=30):
|
||||
geourl = MOZILLA_API_URL.format(api=OPTIONS['api_key'])
|
||||
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)
|
||||
|
||||
try:
|
||||
with open(path, "r") as json_file:
|
||||
data = json.load(json_file)
|
||||
except json.JSONDecodeError as js_e:
|
||||
raise js_e
|
||||
except OSError as os_e:
|
||||
raise os_e
|
||||
try:
|
||||
with open(netpos_filename, 'w+t') as net_pos_file:
|
||||
json.dump(netpos, net_pos_file)
|
||||
except OSError as os_e:
|
||||
logging.error("NET-POS: %s", os_e)
|
||||
|
||||
try:
|
||||
result = requests.post(geourl,
|
||||
json=data,
|
||||
timeout=timeout)
|
||||
return result.json()
|
||||
except requests.exceptions.RequestException as req_e:
|
||||
raise req_e
|
||||
def _get_netpos(self, agent):
|
||||
aps = agent.get_access_points()
|
||||
netpos = dict()
|
||||
netpos['wifiAccessPoints'] = list()
|
||||
# 6 seems a good number to save a wifi networks location
|
||||
for access_point in sorted(aps, key=lambda i: i['rssi'], reverse=True)[:6]:
|
||||
netpos['wifiAccessPoints'].append({'macAddress': access_point['mac'],
|
||||
'signalStrength': access_point['rssi']})
|
||||
return netpos
|
||||
|
||||
def _get_geo_data(self, path, timeout=30):
|
||||
geourl = MOZILLA_API_URL.format(api=self.options['api_key'])
|
||||
|
||||
try:
|
||||
with open(path, "r") as json_file:
|
||||
data = json.load(json_file)
|
||||
except json.JSONDecodeError as js_e:
|
||||
raise js_e
|
||||
except OSError as os_e:
|
||||
raise os_e
|
||||
|
||||
try:
|
||||
result = requests.post(geourl,
|
||||
json=data,
|
||||
timeout=timeout)
|
||||
return_geo = result.json()
|
||||
if data["ts"]:
|
||||
return_geo["ts"] = data["ts"]
|
||||
return return_geo
|
||||
except requests.exceptions.RequestException as req_e:
|
||||
raise req_e
|
||||
|
@@ -1,86 +1,105 @@
|
||||
__author__ = '33197631+dadav@users.noreply.github.com'
|
||||
__version__ = '2.0.0'
|
||||
__name__ = 'onlinehashcrack'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'This plugin automatically uploads handshakes to https://onlinehashcrack.com'
|
||||
|
||||
import os
|
||||
import logging
|
||||
import re
|
||||
import requests
|
||||
from pwnagotchi.utils import StatusFile
|
||||
|
||||
READY = False
|
||||
REPORT = StatusFile('/root/.ohc_uploads', data_format='json')
|
||||
SKIP = list()
|
||||
OPTIONS = dict()
|
||||
import pwnagotchi.plugins as plugins
|
||||
|
||||
|
||||
def on_loaded():
|
||||
"""
|
||||
Gets called when the plugin gets loaded
|
||||
"""
|
||||
global READY
|
||||
class OnlineHashCrack(plugins.Plugin):
|
||||
__author__ = '33197631+dadav@users.noreply.github.com'
|
||||
__version__ = '2.0.0'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'This plugin automatically uploads handshakes to https://onlinehashcrack.com'
|
||||
|
||||
if 'email' not in OPTIONS or ('email' in OPTIONS and OPTIONS['email'] is None):
|
||||
logging.error("OHC: Email isn't set. Can't upload to onlinehashcrack.com")
|
||||
return
|
||||
def __init__(self):
|
||||
self.ready = False
|
||||
self.report = StatusFile('/root/.ohc_uploads', data_format='json')
|
||||
self.skip = list()
|
||||
|
||||
READY = True
|
||||
def on_loaded(self):
|
||||
"""
|
||||
Gets called when the plugin gets loaded
|
||||
"""
|
||||
if 'email' not in self.options or ('email' in self.options and self.options['email'] is None):
|
||||
logging.error("OHC: Email isn't set. Can't upload to onlinehashcrack.com")
|
||||
return
|
||||
|
||||
if 'whitelist' not in self.options:
|
||||
self.options['whitelist'] = []
|
||||
|
||||
def _upload_to_ohc(path, timeout=30):
|
||||
"""
|
||||
Uploads the file to onlinehashcrack.com
|
||||
"""
|
||||
with open(path, 'rb') as file_to_upload:
|
||||
data = {'email': OPTIONS['email']}
|
||||
payload = {'file': file_to_upload}
|
||||
# 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']))
|
||||
|
||||
self.ready = True
|
||||
|
||||
def _filter_handshake_file(self, handshake_filename):
|
||||
try:
|
||||
result = requests.post('https://api.onlinehashcrack.com',
|
||||
data=data,
|
||||
files=payload,
|
||||
timeout=timeout)
|
||||
if 'already been sent' in result.text:
|
||||
logging.warning(f"{path} was already uploaded.")
|
||||
except requests.exceptions.RequestException as e:
|
||||
logging.error(f"OHC: Got an exception while uploading {path} -> {e}")
|
||||
raise e
|
||||
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 on_internet_available(agent):
|
||||
"""
|
||||
Called in manual mode when there's internet connectivity
|
||||
"""
|
||||
global REPORT
|
||||
global SKIP
|
||||
if READY:
|
||||
display = agent.view()
|
||||
config = agent.config()
|
||||
reported = REPORT.data_field_or('reported', default=list())
|
||||
def _upload_to_ohc(self, path, timeout=30):
|
||||
"""
|
||||
Uploads the file to onlinehashcrack.com
|
||||
"""
|
||||
with open(path, 'rb') as file_to_upload:
|
||||
data = {'email': self.options['email']}
|
||||
payload = {'file': file_to_upload}
|
||||
|
||||
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)
|
||||
try:
|
||||
result = requests.post('https://api.onlinehashcrack.com',
|
||||
data=data,
|
||||
files=payload,
|
||||
timeout=timeout)
|
||||
if 'already been sent' in result.text:
|
||||
logging.warning(f"{path} was already uploaded.")
|
||||
except requests.exceptions.RequestException as e:
|
||||
logging.error(f"OHC: Got an exception while uploading {path} -> {e}")
|
||||
raise e
|
||||
|
||||
if handshake_new:
|
||||
logging.info("OHC: Internet connectivity detected. Uploading new handshakes to onelinehashcrack.com")
|
||||
def on_internet_available(self, agent):
|
||||
"""
|
||||
Called in manual mode when there's internet connectivity
|
||||
"""
|
||||
if self.ready:
|
||||
display = agent.view()
|
||||
config = agent.config()
|
||||
reported = self.report.data_field_or('reported', default=list())
|
||||
|
||||
for idx, handshake in enumerate(handshake_new):
|
||||
display.set('status', f"Uploading handshake to onlinehashcrack.com ({idx + 1}/{len(handshake_new)})")
|
||||
display.update(force=True)
|
||||
try:
|
||||
_upload_to_ohc(handshake)
|
||||
reported.append(handshake)
|
||||
REPORT.update(data={'reported': reported})
|
||||
logging.info(f"OHC: Successfully uploaded {handshake}")
|
||||
except requests.exceptions.RequestException as req_e:
|
||||
SKIP.append(handshake)
|
||||
logging.error("OHC: %s", req_e)
|
||||
continue
|
||||
except OSError as os_e:
|
||||
SKIP.append(handshake)
|
||||
logging.error("OHC: %s", os_e)
|
||||
continue
|
||||
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')]
|
||||
|
||||
# pull out whitelisted APs
|
||||
handshake_paths = filter(lambda path: self._filter_handshake_file(path), handshake_paths)
|
||||
|
||||
handshake_new = set(handshake_paths) - set(reported) - set(self.skip)
|
||||
|
||||
if handshake_new:
|
||||
logging.info("OHC: Internet connectivity detected. Uploading new handshakes to onelinehashcrack.com")
|
||||
|
||||
for idx, handshake in enumerate(handshake_new):
|
||||
display.set('status',
|
||||
f"Uploading handshake to onlinehashcrack.com ({idx + 1}/{len(handshake_new)})")
|
||||
display.update(force=True)
|
||||
try:
|
||||
self._upload_to_ohc(handshake)
|
||||
reported.append(handshake)
|
||||
self.report.update(data={'reported': reported})
|
||||
logging.info(f"OHC: Successfully uploaded {handshake}")
|
||||
except requests.exceptions.RequestException as req_e:
|
||||
self.skip.append(handshake)
|
||||
logging.error("OHC: %s", req_e)
|
||||
continue
|
||||
except OSError as os_e:
|
||||
self.skip.append(handshake)
|
||||
logging.error("OHC: %s", os_e)
|
||||
continue
|
||||
|
@@ -1,27 +1,27 @@
|
||||
__author__ = 'leont'
|
||||
__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(self):
|
||||
logging.info("PAW-GPS loaded")
|
||||
if 'ip' not in self.options or ('ip' in self.options and self.options['ip'] is None):
|
||||
logging.info("PAW-GPS: No IP Address in the config file is defined, it uses the default (192.168.44.1)")
|
||||
|
||||
def on_loaded():
|
||||
logging.info("PAW-GPS loaded")
|
||||
if 'ip' not in OPTIONS or ('ip' in OPTIONS and OPTIONS['ip'] is None):
|
||||
logging.info("PAW-GPS: No IP Address in the config file is defined, it uses the default (192.168.44.1)")
|
||||
|
||||
def on_handshake(agent, filename, access_point, client_station):
|
||||
if 'ip' not in OPTIONS or ('ip' in OPTIONS and OPTIONS['ip'] is None):
|
||||
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():
|
||||
logging.info("Quick dictionary check plugin loaded")
|
||||
def __init__(self):
|
||||
self.text_to_set = ""
|
||||
|
||||
def on_handshake(agent, filename, access_point, client_station):
|
||||
display = agent._view
|
||||
def on_loaded(self):
|
||||
logging.info("Quick dictionary check plugin loaded")
|
||||
|
||||
result = subprocess.run(('/usr/bin/aircrack-ng '+ filename +' | grep "1 handshake" | awk \'{print $2}\''),shell=True, stdout=subprocess.PIPE)
|
||||
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 = 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)
|
||||
display.update(force=True)
|
||||
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 ' + 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))
|
||||
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:
|
||||
ui.set('face', "(·ω·)")
|
||||
ui.set('status', text_to_set)
|
||||
text_to_set = ""
|
||||
def on_ui_update(self, ui):
|
||||
if self.text_to_set:
|
||||
ui.set('face', "(·ω·)")
|
||||
ui.set('status', self.text_to_set)
|
||||
self.text_to_set = ""
|
||||
|
@@ -1,24 +1,23 @@
|
||||
__author__ = 'pwnagotchi [at] rossmarks [dot] uk'
|
||||
__version__ = '1.0.0'
|
||||
__name__ = 'screen_refresh'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'Refresh he e-ink display after X amount of updates'
|
||||
|
||||
import logging
|
||||
|
||||
OPTIONS = dict()
|
||||
update_count = 0;
|
||||
import pwnagotchi.plugins as plugins
|
||||
|
||||
|
||||
def on_loaded():
|
||||
logging.info("Screen refresh plugin loaded")
|
||||
class ScreenRefresh(plugins.Plugin):
|
||||
__author__ = 'pwnagotchi [at] rossmarks [dot] uk'
|
||||
__version__ = '1.0.0'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'Refresh he e-ink display after X amount of updates'
|
||||
|
||||
def __init__(self):
|
||||
self.update_count = 0;
|
||||
|
||||
def on_ui_update(ui):
|
||||
global update_count
|
||||
update_count += 1
|
||||
if update_count == OPTIONS['refresh_interval']:
|
||||
ui.init_display()
|
||||
ui.set('status', "Screen cleaned")
|
||||
logging.info("Screen refreshing")
|
||||
update_count = 0
|
||||
def on_loaded(self):
|
||||
logging.info("Screen refresh plugin loaded")
|
||||
|
||||
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")
|
||||
self.update_count = 0
|
||||
|
@@ -1,50 +1,49 @@
|
||||
__author__ = '33197631+dadav@users.noreply.github.com'
|
||||
__version__ = '1.0.0'
|
||||
__name__ = 'twitter'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'This plugin creates tweets about the recent activity of pwnagotchi'
|
||||
|
||||
import logging
|
||||
from pwnagotchi.voice import Voice
|
||||
|
||||
OPTIONS = dict()
|
||||
|
||||
def on_loaded():
|
||||
logging.info("twitter plugin loaded.")
|
||||
import pwnagotchi.plugins as plugins
|
||||
|
||||
|
||||
# called in manual mode when there's internet connectivity
|
||||
def on_internet_available(agent):
|
||||
config = agent.config()
|
||||
display = agent.view()
|
||||
last_session = agent.last_session
|
||||
class Twitter(plugins.Plugin):
|
||||
__author__ = 'evilsocket@gmail.com'
|
||||
__version__ = '1.0.0'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'This plugin creates tweets about the recent activity of pwnagotchi'
|
||||
|
||||
if last_session.is_new() and last_session.handshakes > 0:
|
||||
try:
|
||||
import tweepy
|
||||
except ImportError:
|
||||
logging.error("Couldn't import tweepy")
|
||||
return
|
||||
def on_loaded(self):
|
||||
logging.info("twitter plugin loaded.")
|
||||
|
||||
logging.info("detected a new session and internet connectivity!")
|
||||
# called in manual mode when there's internet connectivity
|
||||
def on_internet_available(self, agent):
|
||||
config = agent.config()
|
||||
display = agent.view()
|
||||
last_session = agent.last_session
|
||||
|
||||
picture = '/dev/shm/pwnagotchi.png'
|
||||
if last_session.is_new() and last_session.handshakes > 0:
|
||||
try:
|
||||
import tweepy
|
||||
except ImportError:
|
||||
logging.error("Couldn't import tweepy")
|
||||
return
|
||||
|
||||
display.on_manual_mode(last_session)
|
||||
display.update(force=True)
|
||||
display.image().save(picture, 'png')
|
||||
display.set('status', 'Tweeting...')
|
||||
display.update(force=True)
|
||||
logging.info("detected a new session and internet connectivity!")
|
||||
|
||||
try:
|
||||
auth = tweepy.OAuthHandler(OPTIONS['consumer_key'], OPTIONS['consumer_secret'])
|
||||
auth.set_access_token(OPTIONS['access_token_key'], OPTIONS['access_token_secret'])
|
||||
api = tweepy.API(auth)
|
||||
picture = '/root/pwnagotchi.png'
|
||||
|
||||
tweet = Voice(lang=config['main']['lang']).on_last_session_tweet(last_session)
|
||||
api.update_with_media(filename=picture, status=tweet)
|
||||
last_session.save_session_id()
|
||||
display.on_manual_mode(last_session)
|
||||
display.update(force=True)
|
||||
display.image().save(picture, 'png')
|
||||
display.set('status', 'Tweeting...')
|
||||
display.update(force=True)
|
||||
|
||||
logging.info("tweeted: %s" % tweet)
|
||||
except Exception as e:
|
||||
logging.exception("error while tweeting")
|
||||
try:
|
||||
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)
|
||||
api.update_with_media(filename=picture, status=tweet)
|
||||
last_session.save_session_id()
|
||||
|
||||
logging.info("tweeted: %s" % tweet)
|
||||
except Exception as e:
|
||||
logging.exception("error while tweeting")
|
||||
|
@@ -1,22 +0,0 @@
|
||||
__author__ = 'diemelcw@gmail.com'
|
||||
__version__ = '1.0.0'
|
||||
__name__ = 'unfiltered_example'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'An example plugin for pwnagotchi that implements on_unfiltered_ap_list(agent,aps)'
|
||||
|
||||
import logging
|
||||
|
||||
# Will be set with the options in config.yml config['main']['plugins'][__name__]
|
||||
OPTIONS = dict()
|
||||
|
||||
# called when the plugin is loaded
|
||||
def on_loaded():
|
||||
logging.warning("%s plugin loaded" % __name__)
|
||||
|
||||
# called when AP list is ready, before whitelist filtering has occurred
|
||||
def on_unfiltered_ap_list(agent,aps):
|
||||
logging.info("Unfiltered AP list to follow")
|
||||
for ap in aps:
|
||||
logging.info(ap['hostname'])
|
||||
|
||||
## Additional logic here ##
|
@@ -7,17 +7,12 @@
|
||||
# For Raspberry Pi Zero Ups Power Expansion Board with Integrated Serial Port S3U4
|
||||
# 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(self, ui):
|
||||
ui.add_element('ups', LabeledValue(color=BLACK, label='UPS', value='0%/0V', position=(ui.width() / 2 - 25, 0),
|
||||
label_font=fonts.Bold, text_font=fonts.Medium))
|
||||
|
||||
def on_ui_setup(ui):
|
||||
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([
|
||||
@@ -75,10 +55,11 @@ def _transform_wigle_entry(gps_data, pcap_data):
|
||||
gps_data['Latitude'],
|
||||
gps_data['Longitude'],
|
||||
gps_data['Altitude'],
|
||||
0, # accuracy?
|
||||
0, # accuracy?
|
||||
'WIFI'])
|
||||
return dummy.getvalue()
|
||||
|
||||
|
||||
def _send_to_wigle(lines, api_key, timeout=30):
|
||||
"""
|
||||
Uploads the file to wigle-net
|
||||
@@ -109,87 +90,100 @@ def _send_to_wigle(lines, api_key, timeout=30):
|
||||
raise re_e
|
||||
|
||||
|
||||
def on_internet_available(agent):
|
||||
from scapy.all import Scapy_Exception
|
||||
"""
|
||||
Called in manual mode when there's internet connectivity
|
||||
"""
|
||||
global REPORT
|
||||
global SKIP
|
||||
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'
|
||||
|
||||
if READY:
|
||||
config = agent.config()
|
||||
display = agent.view()
|
||||
reported = REPORT.data_field_or('reported', default=list())
|
||||
def __init__(self):
|
||||
self.ready = False
|
||||
self.report = StatusFile('/root/.wigle_uploads', data_format='json')
|
||||
self.skip = 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)
|
||||
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
|
||||
|
||||
if new_gps_files:
|
||||
logging.info("WIGLE: Internet connectivity detected. Uploading new handshakes to wigle.net")
|
||||
def on_internet_available(self, agent):
|
||||
from scapy.all import Scapy_Exception
|
||||
"""
|
||||
Called in manual mode when there's internet connectivity
|
||||
"""
|
||||
if self.ready:
|
||||
config = agent.config()
|
||||
display = agent.view()
|
||||
reported = self.report.data_field_or('reported', default=list())
|
||||
|
||||
csv_entries = list()
|
||||
no_err_entries = 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(self.skip)
|
||||
|
||||
for gps_file in new_gps_files:
|
||||
pcap_filename = gps_file.replace('.gps.json', '.pcap')
|
||||
if new_gps_files:
|
||||
logging.info("WIGLE: Internet connectivity detected. Uploading new handshakes to wigle.net")
|
||||
|
||||
if not os.path.exists(pcap_filename):
|
||||
logging.error("WIGLE: Can't find pcap for %s", gps_file)
|
||||
SKIP.append(gps_file)
|
||||
continue
|
||||
csv_entries = list()
|
||||
no_err_entries = list()
|
||||
|
||||
try:
|
||||
gps_data = _extract_gps_data(gps_file)
|
||||
except OSError as os_err:
|
||||
logging.error("WIGLE: %s", os_err)
|
||||
SKIP.append(gps_file)
|
||||
continue
|
||||
except json.JSONDecodeError as json_err:
|
||||
logging.error("WIGLE: %s", json_err)
|
||||
SKIP.append(gps_file)
|
||||
continue
|
||||
for gps_file in new_gps_files:
|
||||
pcap_filename = gps_file.replace('.gps.json', '.pcap')
|
||||
|
||||
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)
|
||||
continue
|
||||
if not os.path.exists(pcap_filename):
|
||||
logging.error("WIGLE: Can't find pcap for %s", 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)
|
||||
self.skip.append(gps_file)
|
||||
continue
|
||||
except json.JSONDecodeError as json_err:
|
||||
logging.error("WIGLE: %s", json_err)
|
||||
self.skip.append(gps_file)
|
||||
continue
|
||||
|
||||
try:
|
||||
pcap_data = extract_from_pcap(pcap_filename, [WifiInfo.BSSID,
|
||||
WifiInfo.ESSID,
|
||||
WifiInfo.ENCRYPTION,
|
||||
WifiInfo.CHANNEL,
|
||||
WifiInfo.RSSI])
|
||||
except FieldNotFoundError:
|
||||
logging.error("WIGLE: Could not extract all information. Skip %s", gps_file)
|
||||
SKIP.append(gps_file)
|
||||
continue
|
||||
except Scapy_Exception as sc_e:
|
||||
logging.error("WIGLE: %s", sc_e)
|
||||
SKIP.append(gps_file)
|
||||
continue
|
||||
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)
|
||||
self.skip.append(gps_file)
|
||||
continue
|
||||
|
||||
new_entry = _transform_wigle_entry(gps_data, pcap_data)
|
||||
csv_entries.append(new_entry)
|
||||
no_err_entries.append(gps_file)
|
||||
try:
|
||||
pcap_data = extract_from_pcap(pcap_filename, [WifiInfo.BSSID,
|
||||
WifiInfo.ESSID,
|
||||
WifiInfo.ENCRYPTION,
|
||||
WifiInfo.CHANNEL,
|
||||
WifiInfo.RSSI])
|
||||
except FieldNotFoundError:
|
||||
logging.error("WIGLE: Could not extract all information. Skip %s", gps_file)
|
||||
self.skip.append(gps_file)
|
||||
continue
|
||||
except Scapy_Exception as sc_e:
|
||||
logging.error("WIGLE: %s", sc_e)
|
||||
self.skip.append(gps_file)
|
||||
continue
|
||||
|
||||
if csv_entries:
|
||||
display.set('status', "Uploading gps-data to wigle.net ...")
|
||||
display.update(force=True)
|
||||
try:
|
||||
_send_to_wigle(csv_entries, OPTIONS['api_key'])
|
||||
reported += no_err_entries
|
||||
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
|
||||
logging.error("WIGLE: Got an exception while uploading %s", re_e)
|
||||
except OSError as os_e:
|
||||
SKIP += no_err_entries
|
||||
logging.error("WIGLE: Got the following error: %s", os_e)
|
||||
new_entry = _transform_wigle_entry(gps_data, pcap_data)
|
||||
csv_entries.append(new_entry)
|
||||
no_err_entries.append(gps_file)
|
||||
|
||||
if csv_entries:
|
||||
display.set('status', "Uploading gps-data to wigle.net ...")
|
||||
display.update(force=True)
|
||||
try:
|
||||
_send_to_wigle(csv_entries, self.options['api_key'])
|
||||
reported += no_err_entries
|
||||
self.report.update(data={'reported': reported})
|
||||
logging.info("WIGLE: Successfully uploaded %d files", len(no_err_entries))
|
||||
except requests.exceptions.RequestException as re_e:
|
||||
self.skip += no_err_entries
|
||||
logging.error("WIGLE: Got an exception while uploading %s", re_e)
|
||||
except OSError as os_e:
|
||||
self.skip += no_err_entries
|
||||
logging.error("WIGLE: Got the following error: %s", os_e)
|
||||
|
@@ -1,87 +1,84 @@
|
||||
__author__ = '33197631+dadav@users.noreply.github.com'
|
||||
__version__ = '2.0.1'
|
||||
__name__ = 'wpa-sec'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'This plugin automatically uploads handshakes to https://wpa-sec.stanev.org'
|
||||
|
||||
import os
|
||||
import 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(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': self.options['api_key']}
|
||||
payload = {'file': file_to_upload}
|
||||
|
||||
try:
|
||||
result = requests.post(self.options['api_url'],
|
||||
cookies=cookie,
|
||||
files=payload,
|
||||
timeout=timeout)
|
||||
if ' already submitted' in result.text:
|
||||
logging.warning("%s was already submitted.", path)
|
||||
except requests.exceptions.RequestException as req_e:
|
||||
raise req_e
|
||||
|
||||
def _upload_to_wpasec(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']}
|
||||
payload = {'file': file_to_upload}
|
||||
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
|
||||
|
||||
try:
|
||||
result = requests.post(OPTIONS['api_url'],
|
||||
cookies=cookie,
|
||||
files=payload,
|
||||
timeout=timeout)
|
||||
if ' already submitted' in result.text:
|
||||
logging.warning("%s was already submitted.", path)
|
||||
except requests.exceptions.RequestException as req_e:
|
||||
raise req_e
|
||||
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(agent):
|
||||
"""
|
||||
Called in manual mode when there's internet connectivity
|
||||
"""
|
||||
global REPORT
|
||||
global SKIP
|
||||
if READY:
|
||||
config = agent.config()
|
||||
display = agent.view()
|
||||
reported = REPORT.data_field_or('reported', default=list())
|
||||
def on_internet_available(self, agent):
|
||||
"""
|
||||
Called in manual mode when there's internet connectivity
|
||||
"""
|
||||
if self.ready:
|
||||
config = agent.config()
|
||||
display = agent.view()
|
||||
reported = self.report.data_field_or('reported', default=list())
|
||||
|
||||
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_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(self.skip)
|
||||
|
||||
if handshake_new:
|
||||
logging.info("WPA_SEC: Internet connectivity detected. Uploading new handshakes to wpa-sec.stanev.org")
|
||||
if handshake_new:
|
||||
logging.info("WPA_SEC: Internet connectivity detected. Uploading new handshakes to wpa-sec.stanev.org")
|
||||
|
||||
for idx, handshake in enumerate(handshake_new):
|
||||
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)
|
||||
reported.append(handshake)
|
||||
REPORT.update(data={'reported': reported})
|
||||
logging.info("WPA_SEC: Successfully uploaded %s", handshake)
|
||||
except requests.exceptions.RequestException as req_e:
|
||||
SKIP.append(handshake)
|
||||
logging.error("WPA_SEC: %s", req_e)
|
||||
continue
|
||||
except OSError as os_e:
|
||||
logging.error("WPA_SEC: %s", os_e)
|
||||
continue
|
||||
for idx, handshake in enumerate(handshake_new):
|
||||
display.set('status', f"Uploading handshake to wpa-sec.stanev.org ({idx + 1}/{len(handshake_new)})")
|
||||
display.update(force=True)
|
||||
try:
|
||||
self._upload_to_wpasec(handshake)
|
||||
reported.append(handshake)
|
||||
self.report.update(data={'reported': reported})
|
||||
logging.info("WPA_SEC: Successfully uploaded %s", handshake)
|
||||
except requests.exceptions.RequestException as req_e:
|
||||
self.skip.append(handshake)
|
||||
logging.error("WPA_SEC: %s", req_e)
|
||||
continue
|
||||
except OSError as os_e:
|
||||
logging.error("WPA_SEC: %s", os_e)
|
||||
continue
|
||||
|
@@ -4,7 +4,6 @@ import threading
|
||||
|
||||
import pwnagotchi.plugins as plugins
|
||||
import pwnagotchi.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
|
||||
|
||||
@@ -35,7 +36,10 @@ 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