81 Commits

Author SHA1 Message Date
Simone Margaritelli
99d7017785 releasing v1.0.0RC3 2019-10-13 00:07:50 +02:00
Simone Margaritelli
8bc421952b merge 2019-10-12 23:02:37 +02:00
Simone Margaritelli
b47f3c6b28 fix: fixed restored status after rsa keys generation 2019-10-12 22:01:52 +02:00
evilsocket
17d20837a3 Merge pull request #263 from 0xRoM/cleancap
Cleancap
2019-10-12 20:30:00 +02:00
root
0cccfef14e replaced with os.remove() 2019-10-12 19:29:04 +01:00
root
b46f751e7d modified to better describe plugin 2019-10-12 19:23:41 +01:00
Simone Margaritelli
0f8f77c2be new: new text while generating keys ... 2019-10-12 20:00:50 +02:00
Simone Margaritelli
a9123922c0 fix: better error handling if rsa key files are corrupted (ref #268) 2019-10-12 19:55:20 +02:00
Simone Margaritelli
66e5f89a96 merge 2019-10-12 19:40:42 +02:00
Simone Margaritelli
f84dd00295 fix: ui fixes for inky displays (which i hate) 2019-10-12 19:39:10 +02:00
evilsocket
3535329708 Merge pull request #266 from gpotter2/patch-1
Remove Dot11FCS workaround
2019-10-12 19:17:12 +02:00
Simone Margaritelli
b50c71cf14 fix: removed unused configuration field 2019-10-12 19:13:04 +02:00
Simone Margaritelli
68aebbf126 misc: small fix or general refactoring i did not bother commenting 2019-10-12 19:08:46 +02:00
Simone Margaritelli
c3de66d704 misc: small fix or general refactoring i did not bother commenting 2019-10-12 19:07:32 +02:00
Simone Margaritelli
08a46a5524 misc: small fix or general refactoring i did not bother commenting 2019-10-12 17:56:40 +02:00
Simone Margaritelli
f3e7841b1b misc: small fix or general refactoring i did not bother commenting 2019-10-12 17:53:18 +02:00
Simone Margaritelli
79ba5102d7 fix: cosmetic fixes for inky displays 2019-10-12 17:47:51 +02:00
gpotter2
20036f370d Remove Dot11FCS workaround
Scapy 2.4.3+ (pinned for pwnagotchi) has fixed this issue.

Signed-off-by: gpotter2 <gabriel@potter.fr>
2019-10-12 16:53:29 +02:00
Simone Margaritelli
34b52a11cd fix: fixed sudo in Makefile 2019-10-12 16:48:38 +02:00
root
2d78b52294 cleancap plugin added 2019-10-12 14:49:42 +01:00
root
3cc31686c2 cleancap plugin added 2019-10-12 14:46:25 +01:00
Simone Margaritelli
80159533bc fix: pinning new version of pwngrid 2019-10-12 15:33:42 +02:00
evilsocket
947a41da90 Merge pull request #92 from daswisher/display-fix
Basic display fix for waveshare v1 tri-color
2019-10-12 15:09:58 +02:00
Simone Margaritelli
dea990a531 merge 2019-10-11 23:29:53 +02:00
Simone Margaritelli
dfaf3418af new: grid plugin can now do messaging 2019-10-11 23:29:34 +02:00
evilsocket
36ab3b7655 Merge pull request #260 from chksome/fix-motd-typos
Fix motd typo and some grammar
2019-10-11 20:57:14 +02:00
chksome
5a32a77870 Fix motd typo and some grammar
Signed-off-by: chksome <chksome@protonmail.com>
2019-10-11 13:53:15 -04:00
Simone Margaritelli
7520d4dd6f fix: removed bogus legacy feature (fixes #257) 2019-10-11 19:47:54 +02:00
evilsocket
f73a695747 Merge pull request #259 from python273/patch-1
Fix typo in grid plugin
2019-10-11 19:22:23 +02:00
evilsocket
d94ca76817 Merge pull request #256 from dsopas/master
New portuguese translation added
2019-10-11 19:22:11 +02:00
Kirill
2cfaae1993 Fix typo in grid plugin 2019-10-11 20:13:01 +03:00
Simone Margaritelli
5ed2f2df78 misc: pwngrid 1.5.7 2019-10-11 17:56:57 +02:00
David Sopas
ee55ed7168 Added pt to the list
Added pt (portuguese) language to the "currently implemented" list
2019-10-11 16:52:47 +01:00
David Sopas
ce338e8fef Delete readme.md 2019-10-11 16:42:19 +01:00
David Sopas
9cfa365ec9 Portuguese translation added
Portuguese (european) translation added
2019-10-11 16:41:57 +01:00
David Sopas
fc23415d57 Create readme.md 2019-10-11 16:40:58 +01:00
Simone Margaritelli
fc3367181b fix: better data encapsulation 2019-10-11 17:24:27 +02:00
Simone Margaritelli
8210c0bb71 fix: using pwngrid-peer service from the grid plugin 2019-10-11 16:19:40 +02:00
Simone Margaritelli
3ddc717009 fix: configuring pwngrid-peer service to wait for rsa keys on first boot 2019-10-11 15:34:10 +02:00
Simone Margaritelli
d700e4fd0c new: added pwngrid service to the builder 2019-10-11 15:12:56 +02:00
evilsocket
5eb23e2c84 Merge pull request #254 from caquino/caquino/headers
add motd and defaults.yml disclaimer
2019-10-11 14:17:36 +02:00
Cassiano Aquino
b187b17f9a fix typo 2019-10-11 13:09:19 +01:00
Cassiano Aquino
06e1115cef add motd and defaults.yml disclaimer 2019-10-11 12:51:57 +01:00
evilsocket
71cdaf855d Merge pull request #253 from 0xRoM/quickdic
fix multiple dictionary issue
2019-10-11 11:52:52 +02:00
Michael V. Swisher
9d580ffc0f Updating waveshare logging to specify color vs monochromatic mode 2019-10-11 02:45:22 -07:00
root
f80eeff8fc fix multiple dictionary issue 2019-10-11 10:20:48 +01:00
Simone Margaritelli
be75fc53d4 fix: fixed grid plugin exclusion list use 2019-10-11 09:37:50 +02:00
evilsocket
e6777eba8a Merge pull request #251 from 0xRoM/quickdic
Run a quick dictionary scan against captured handshakes
2019-10-11 08:58:58 +02:00
evilsocket
69d49e1395 Merge pull request #252 from caquino/caquino/firmware
Add hold for firmware and minor cleanup
2019-10-11 08:58:22 +02:00
root
9f3f71ce3d custom face 2019-10-11 00:11:27 +01:00
Cassiano Aquino
28e5ba4e13 Add hold for firmware and minor cleanup 2019-10-10 23:46:10 +01:00
root
e48f9bfcc7 code tidy 2019-10-10 23:34:15 +01:00
root
6c44d7f0f6 quickdic plugin 2019-10-10 19:42:40 +01:00
Simone Margaritelli
86649c8c46 merge 2019-10-10 16:44:11 +02:00
Simone Margaritelli
3d052c5dc1 fix: reporting software version from the grid plugin 2019-10-10 16:43:48 +02:00
Michael V. Swisher
41abfbc981 Typo on self._canvas 2019-10-10 03:23:36 -07:00
Michael V. Swisher
3480b99a45 Updating with master 2019-10-09 21:55:56 -07:00
evilsocket
89dc01c23a Merge pull request #244 from diegopastor/add-es-lang
Add support for spanish language
2019-10-10 00:08:11 +02:00
diego
8a06819979 Merge Master 2019-10-09 16:57:15 -05:00
diego
d72c1d9c93 Add support for spanish language 2019-10-09 16:44:55 -05:00
Simone Margaritelli
078ab63249 new: grid plugin now reports brain.json info 2019-10-09 23:14:02 +02:00
evilsocket
f153f15e9f Merge pull request #239 from caquino/caquino/pt-BR
add pt-BR translation
2019-10-09 23:08:00 +02:00
Cassiano Aquino
d0f34f9528 add pt-BT to the lang comment on config.yml 2019-10-09 20:42:16 +01:00
Cassiano Aquino
da116ea2ad add pt-BR translation 2019-10-09 19:15:40 +01:00
Cassiano Aquino
ad87ea4791 add pt-BR translation 2019-10-09 19:14:42 +01:00
Cassiano Aquino
19b0e00bf5 add pt-BR translation 2019-10-09 19:13:27 +01:00
Cassiano Aquino
315bfd29e5 add pt-BR translation 2019-10-09 19:11:14 +01:00
evilsocket
327bd7d3da Merge pull request #238 from caquino/caquino/travis-image-hash
generate sha256sum from the generated image, add it to the release
2019-10-09 19:55:43 +02:00
Cassiano Aquino
b2a7462b44 Fix typo
add sha256 to extension
2019-10-09 18:55:22 +01:00
evilsocket
a4186e2bfd Merge pull request #236 from caquino/caquino/issue-234
add papirus display system requirements
2019-10-09 19:55:09 +02:00
evilsocket
6de26795af Merge pull request #237 from bitwave/update-de-translation
updated german translation
2019-10-09 19:54:45 +02:00
Cassiano Aquino
2c1a9c471c generate sha256sum from the generated image, add it to the release 2019-10-09 17:50:53 +01:00
Cassiano Aquino
63d95a53c0 add papirus display system requirements 2019-10-09 16:57:00 +01:00
bitwave
d2c160308c updated german translation 2019-10-09 17:46:53 +02:00
evilsocket
a2fa33f2fb Create FUNDING.yml 2019-10-09 16:03:12 +02:00
Michael V. Swisher
1ebb8599b3 Fixing function name mismatch 2019-10-07 07:30:23 -07:00
Michael Swisher
32f87437ec Merge branch 'master' into display-fix 2019-10-05 07:00:51 -07:00
Michael V. Swisher
23cd8ad599 Updating so waveshare 2.13 b/c models only have to maintain black color channel. Color channels aren't used so no need to maintain them 2019-10-05 06:55:04 -07:00
Michael Swisher
defaa154e8 Merge pull request #1 from evilsocket/master
Resync with master
2019-10-03 04:41:07 -07:00
Michael V. Swisher
1b813f41f5 Removing refresh trigger handler since it prevented pwnagotchi ui from displaying 2019-10-03 04:25:28 -07:00
Andreas Kupfer
d3a8dc85c3 add support for 3 colored Waveshare-Display 2019-10-01 22:10:20 +02:00
28 changed files with 1364 additions and 208 deletions

12
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHubSponsors-enabled usernames e.g., [user1, user2]
patreon: evilsocket
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View File

@@ -12,7 +12,9 @@ deploy:
secure: vBUokTv94n8s65STUgTiD6I0Iy8KXbBRvQUrAof8XG+U4ZMsH5PmDTpS+wz+SaxI6o0PRkfyOiPVdARhiKAFnfatG3q9EHllMQwqRR2YIju51A3aCxgEJ5uWDoybwQdipERUMMYwUO/8XZaRRpwFD2bdQBFWkBtQyMcAkrEL8BXckwQQ531oDN2hK5gAiTllqsOswV2idwUlBRU9jOtStzff+UgUYsp/ZebsRodyOYkEB2Ev15yARo2HTXbyZ2icwHPtMbx5zmNUSRtxs9a4hfzaK3m6ctK8qLYYUdQvXub/ruuACapdw4Ez88LY1agTecbZhFYmJzv8oANH1e4VUI4owuHnZCpU6LRutS4wOhglrkOrGo6lSUlJeA+RtQjyjBugjej9DDtDyyIlRU1ZaBF3qWR9N5EXKuquf0olOfmUR67ap1NykE9VUpzkYjkoVRTiPs/e2onM/nRNOvAQcIt75FD13u+Y/DcYQ8r7KpMIu1HNdtbVx8gMeq76bRhP1YdDg2jm+DdJ21KWjf5QHsbyoXDfJzdKlCloLIlAU3EPJhMoXsnNzre0/FXeUl6dfteR1axNS6U7e/vKsQ9rlUFZWIQaeVPjfXmFKblNNVQ5uFrrsB/EGHcJl7IUx5fvcRT5hMMNwC660YxVkBXDbRb5fxMW5/+K0BOi9cP6en8=
skip_cleanup: true
file_glob: true
file: pwnagotchi-*.zip
file:
- pwnagotchi-*.zip
- pwnagotchi-*.sha256
on:
tags: true
repo: evilsocket/pwnagotchi

View File

@@ -6,17 +6,18 @@ all: install image clean
install:
curl https://releases.hashicorp.com/packer/1.3.5/packer_1.3.5_linux_amd64.zip -o /tmp/packer.zip
unzip /tmp/packer.zip -d /tmp
mv /tmp/packer /usr/bin/packer
sudo mv /tmp/packer /usr/bin/packer
git clone https://github.com/solo-io/packer-builder-arm-image /tmp/packer-builder-arm-image
cd /tmp/packer-builder-arm-image && go get -d ./... && go build
cp /tmp/packer-builder-arm-image/packer-builder-arm-image /usr/bin
sudo cp /tmp/packer-builder-arm-image/packer-builder-arm-image /usr/bin
image:
cd builder && sudo /usr/bin/packer build -var "pwn_hostname=$(PWN_HOSTNAME)" -var "pwn_version=$(PWN_VERSION)" pwnagotchi.json
mv builder/output-pwnagotchi/image pwnagotchi-raspbian-lite-$(PWN_VERSION).img
zip pwnagotchi-raspbian-lite-$(PWN_VERSION).zip pwnagotchi-raspbian-lite-$(PWN_VERSION).img
sudo mv builder/output-pwnagotchi/image pwnagotchi-raspbian-lite-$(PWN_VERSION).img
sudo sha256sum pwnagotchi-raspbian-lite-$(PWN_VERSION).img > pwnagotchi-raspbian-lite-$(PWN_VERSION).sha256
sudo zip pwnagotchi-raspbian-lite-$(PWN_VERSION).zip pwnagotchi-raspbian-lite-$(PWN_VERSION).sha256 pwnagotchi-raspbian-lite-$(PWN_VERSION).img
clean:
rm -rf /tmp/packer-builder-arm-image
rm -f pwnagotchi-raspbian-lite.img
rm -f pwnagotchi-raspbian-lite-*.zip pwnagotchi-raspbian-lite-*.img pwnagotchi-raspbian-lite-*.sha256
rm -rf builder/output-pwnagotchi builder/packer_cache

View File

@@ -33,8 +33,8 @@ if __name__ == '__main__':
plugins.load(config)
keypair = KeyPair()
display = Display(config=config, state={'name': '%s>' % pwnagotchi.name()})
keypair = KeyPair(view=display)
agent = Agent(view=display, config=config, keypair=keypair)
logging.info("%s@%s (v%s)" % (pwnagotchi.name(), agent._keypair.fingerprint, pwnagotchi.version))
@@ -78,8 +78,6 @@ if __name__ == '__main__':
agent.recon()
# get nearby access points grouped by channel
channels = agent.get_access_points_by_channel()
# check for free channels to use
agent.check_channels(channels)
# for each channel
for ch, aps in channels:
agent.set_channel(ch)

View File

@@ -11,11 +11,15 @@
- "dtoverlay=dwc2"
- "dtparam=spi=on"
- "dtoverlay=spi1-3cs"
- "dtoverlay=i2c_arm=on"
- "dtoverlay=i2c1=on"
services:
enable:
- dphys-swapfile.service
- pwnagotchi.service
- bettercap.service
- pwngrid-peer.service
- epd-fuse.service
disable:
- apt-daily.timer
- apt-daily.service
@@ -29,7 +33,15 @@
bettercap:
url: "https://github.com/bettercap/bettercap/releases/download/v2.25/bettercap_linux_armv6l_2.25.zip"
ui: "https://github.com/bettercap/ui/releases/download/v1.3.0/ui.zip"
pwngrid:
url: "https://github.com/evilsocket/pwngrid/releases/download/v1.6.3/pwngrid_linux_armv6l_v1.6.3.zip"
apt:
hold:
- firmware-atheros
- firmware-brcm80211
- firmware-libertas
- firmware-misc-nonfree
- firmware-realtek
remove:
- rasberrypi-net-mods
- dhcpcd5
@@ -79,6 +91,10 @@
- fonts-dejavu-core
- fonts-dejavu-extra
- python3-pil
- python3-smbus
- libfuse-dev
- bc
- fonts-freefont-ttf
tasks:
@@ -111,6 +127,12 @@
repo: deb http://http.re4son-kernel.com/re4son/ kali-pi main
state: present
- name: add firmware packages to hold
dpkg_selections:
name: "{{ item }}"
selection: hold
with_items: "{{ packages.apt.hold }}"
- name: update apt package cache
apt:
update_cache: yes
@@ -135,6 +157,33 @@
path: /etc/dphys-swapfile
content: "CONF_SWAPSIZE=1024"
- name: clone papirus repository
git:
repo: https://github.com/repaper/gratis.git
dest: /usr/local/src/gratis
- name: build papirus service
make:
chdir: /usr/local/src/gratis
target: rpi
params:
EPD_IO: epd_io.h
PANEL_VERSION: 'V231_G2'
- name: install papirus service
make:
chdir: /usr/local/src/gratis
target: rpi-install
params:
EPD_IO: epd_io.h
PANEL_VERSION: 'V231_G2'
- name: configure papirus display size
lineinfile:
dest: /etc/default/epd-fuse
regexp: "#EPD_SIZE=2.0"
line: "EPD_SIZE=2.0"
- name: acquire python3 pip target
command: "python3 -c 'import sys;print(sys.path.pop())'"
register: pip_target
@@ -164,6 +213,13 @@
name: "{{ lookup('fileglob', '/usr/local/src/pwnagotchi/dist/pwnagotchi*.whl') }}"
extra_args: "--no-cache-dir"
- name: download and install pwngrid
unarchive:
src: "{{ packages.pwngrid.url }}"
dest: /usr/bin
remote_src: yes
mode: 0755
- name: download and install bettercap
unarchive:
src: "{{ packages.bettercap.url }}"
@@ -309,34 +365,48 @@
/opt/vc/bin/tvservice -o
fi
- name: create /etc/pwnagotchi/config.yml
blockinfile:
- name: create /etc/pwnagotchi folder
file:
path: /etc/pwnagotchi
state: directory
- name: check if user configuration exists
stat:
path: /etc/pwnagotchi/config.yml
create: yes
block: |
# put here your custom configuration overrides
register: user_config
- name: create /etc/pwnagotchi/config.yml
copy:
dest: /etc/pwnagotchi/config.yml
content: |
# Add your configuration overrides on this file any configuration changes done to defaults.yml will be lost!
# Example:
#
# ui:
# display:
# type: 'inkyphat'
# color: 'black'
#
when: not user_config.stat.exists
- name: configure lo interface
blockinfile:
path: /etc/network/interfaces.d/lo-cfg
create: yes
block: |
copy:
dest: /etc/network/interfaces.d/lo-cfg
content: |
auto lo
iface lo inet loopback
- name: configure wlan interface
blockinfile:
path: /etc/network/interfaces.d/wlan0-cfg
create: yes
block: |
copy:
dest: /etc/network/interfaces.d/wlan0-cfg
content: |
allow-hotplug wlan0
iface wlan0 inet static
- name: configure usb interface
blockinfile:
path: /etc/network/interfaces.d/usb0-cfg
create: yes
block: |
copy:
dest: /etc/network/interfaces.d/usb0-cfg
content: |
allow-hotplug usb0
iface usb0 inet static
address 10.0.0.2
@@ -346,10 +416,9 @@
gateway 10.0.0.1
- name: configure eth0 interface (pi2/3/4)
blockinfile:
path: /etc/network/interfaces.d/eth0-cfg
create: yes
block: |
copy:
dest: /etc/network/interfaces.d/eth0-cfg
content: |
allow-hotplug eth0
iface eth0 inet dhcp
@@ -363,8 +432,7 @@
dest: /boot/config.txt
insertafter: EOF
line: '{{ item }}'
with_items:
- "{{system.boot_options}}"
with_items: "{{system.boot_options}}"
- name: change root partition
replace:
@@ -385,7 +453,33 @@
- name: configure motd
copy:
dest: /etc/motd
content: "(◕‿‿◕) {{pwnagotchi.hostname}} (pwnagotchi-{{pwnagotchi.version}})"
content: |
(◕‿‿◕) {{pwnagotchi.hostname}} (pwnagotchi-{{pwnagotchi.version}})
Hi! I'm a pwnagotchi, please take good care of me!
Here are some basic things you need to know to raise me properly!
If you want to change my configuration, use /etc/pwnagotchi/config.yml
All the configuration options can be found on /etc/pwnagotchi/defaults.yml,
but don't change this file because I will recreate it every time I'm restarted!
I'm managed by systemd. Here are some basic commands.
If you want to know what I'm doing, you can check my logs with the command
journalctl -fu pwnagotchi
If you want to know if I'm running, you can use
systemctl status pwnagotchi
You can restart me using
systemctl restart pwnagotchi
But be aware I will go into MANUAL mode when restarted!
You can put me back into AUTO mode using
touch /root/.pwnagotchi-auto && systemctl restart pwnagotchi
You learn more about me at https://pwnagotchi.ai/
- name: clean apt cache
apt:
@@ -395,6 +489,28 @@
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
After=network.target
[Service]
Type=simple
PermissionsStartOnly=true
ExecStart=/usr/bin/pwngrid -keys /etc/pwnagotchi -address 127.0.0.1:8666 -wait -log /var/log/pwngrid-peer.log
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
@@ -466,5 +582,3 @@
- name: reload systemd services
systemd:
daemon_reload: yes

View File

@@ -4,7 +4,7 @@ import logging
import time
import pwnagotchi.ui.view as view
version = '1.0.0RC2'
version = '1.0.0RC3'
_name = None

View File

@@ -160,23 +160,6 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
self._view.wait(t, sleeping)
self._epoch.track(sleep=True, inc=t)
def check_channels(self, channels):
busy_channels = [ch for ch, aps in channels]
# if we're hopping and no filter is configured
if self._config['personality']['channels'] == [] and self._config['main']['filter'] is None:
# check if any of the non overlapping channels is free
for ch in self._epoch.non_overlapping_channels:
if ch not in busy_channels:
self._epoch.non_overlapping_channels[ch] += 1
logging.info("channel %d is free from %d epochs" % (ch, self._epoch.non_overlapping_channels[ch]))
elif self._epoch.non_overlapping_channels[ch] > 0:
self._epoch.non_overlapping_channels[ch] -= 1
# report any channel that has been free for at least 3 epochs
for ch, num_epochs_free in self._epoch.non_overlapping_channels.items():
if num_epochs_free >= 3:
logging.info("channel %d has been free for %d epochs" % (ch, num_epochs_free))
self.set_free_channel(ch)
def recon(self):
recon_time = self._config['personality']['recon_time']
max_inactive = self._config['personality']['max_inactive_scale']

View File

@@ -1,6 +1,12 @@
# WARNING WARNING WARNING WARNING
#
# This file is recreated with default settings on every pwnagotchi restart,
# use /etc/pwnagotchi/config.yml to configure this unit.
#
#
# main algorithm configuration
main:
# currently implemented: en (default), de, el, fr, it, mk, nl, ru, se
# currently implemented: en (default), de, el, fr, it, mk, nl, ru, se, pt-BR, es, pt
lang: en
# custom plugins path, if null only default plugins with be loaded
custom_plugins:
@@ -55,7 +61,11 @@ main:
screen_refresh:
enabled: false
refresh_interval: 50
quickdic:
enabled: false
wordlist_folder: /opt/wordlists/
AircrackOnly:
enabled: false
# monitor interface to use
iface: mon0
# command to run to bring the mon interface up in case it's not up already
@@ -72,8 +82,6 @@ main:
- ANOTHER_EXAMPLE_NETWORK
# if not null, filter access points by this regular expression
filter: null
# cryptographic key for identity
pubkey: /etc/ssh/ssh_host_rsa_key.pub
ai:
# if false, only the default 'personality' will be used

View File

@@ -10,20 +10,27 @@ DefaultPath = "/etc/pwnagotchi/"
class KeyPair(object):
def __init__(self, path=DefaultPath):
def __init__(self, path=DefaultPath, view=None):
self.path = path
self.priv_path = os.path.join(path, "id_rsa")
self.priv_key = None
self.pub_path = "%s.pub" % self.priv_path
self.pub_key = None
self._view = view
if not os.path.exists(self.path):
os.makedirs(self.path)
while True:
# first time, generate new keys
if not os.path.exists(self.priv_path) or not os.path.exists(self.pub_path):
self._view.on_keys_generation()
logging.info("generating %s ..." % self.priv_path)
os.system("/usr/bin/ssh-keygen -t rsa -m PEM -b 4096 -N '' -f '%s'" % self.priv_path)
# load keys: they might be corrupted if the unit has been turned off during the generation, in this case
# the exception will remove the files and go back at the beginning of this loop.
try:
with open(self.priv_path) as fp:
self.priv_key = RSA.importKey(fp.read())
@@ -34,10 +41,23 @@ class KeyPair(object):
if 'RSA PUBLIC KEY' not in self.pub_key_pem:
self.pub_key_pem = self.pub_key_pem.replace('PUBLIC KEY', 'RSA PUBLIC KEY')
pem = self.pub_key_pem.encode("ascii")
pem_ascii = self.pub_key_pem.encode("ascii")
self.pub_key_pem_b64 = base64.b64encode(pem).decode("ascii")
self.fingerprint = hashlib.sha256(pem).hexdigest()
self.pub_key_pem_b64 = base64.b64encode(pem_ascii).decode("ascii")
self.fingerprint = hashlib.sha256(pem_ascii).hexdigest()
# no exception, keys loaded correctly.
self._view.on_starting()
return
except Exception as e:
# if we're here, loading the keys broke something ...
logging.exception("error loading keys, maybe corrupted, deleting and regenerating ...")
try:
os.remove(self.priv_path)
os.remove(self.pub_path)
except:
pass
def sign(self, message):
hasher = SHA256.new(message.encode("ascii"))

View File

@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-10-05 14:10+0200\n"
"POT-Creation-Date: 2019-10-09 17:42+0200\n"
"PO-Revision-Date: 2019-09-29 14:00+0200\n"
"Last-Translator: dadav <33197631+dadav@users.noreply.github.com>\n"
"Language-Team: DE <33197631+dadav@users.noreply.github.com>\n"
@@ -121,6 +121,12 @@ msgstr ""
msgid "ZzzZzzz ({secs}s)"
msgstr ""
msgid "Good night."
msgstr "Gute Nacht."
msgid "Zzz"
msgstr ""
#, python-brace-format
msgid "Waiting for {secs}s ..."
msgstr "Warte für {secs}s ..."
@@ -139,7 +145,7 @@ msgstr "Verbinde mit {what}"
#, python-brace-format
msgid "Yo {what}!"
msgstr ""
msgstr "Jo {what}!"
#, python-brace-format
msgid "Just decided that {mac} needs no WiFi!"

Binary file not shown.

View File

@@ -0,0 +1,214 @@
# pwnagotchi voice data
# Copyright (C) 2019
# This file is distributed under the same license as the pwnagotchi package.
# FIRST AUTHOR diegopastor <dpastor29@alumnos.uaq.mx>, 2019.
#
msgid ""
msgstr ""
"Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-10-09 17:42+0200\n"
"PO-Revision-Date: 2019-10-09 21:07+0000\n"
"Last-Translator: diegopastor <dpastor29@alumnos.uaq.mx>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: spanish\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "ZzzzZZzzzzZzzz"
msgstr "ZzzzZZzzzzZzzz"
msgid "Hi, I'm Pwnagotchi! Starting ..."
msgstr "Hola, soy Pwnagotchi! Empezando ..."
msgid "New day, new hunt, new pwns!"
msgstr "Nuevo día, nueva cazería, nuevos pwns!"
msgid "Hack the Planet!"
msgstr "Hackea el planeta!"
msgid "AI ready."
msgstr "IA lista."
msgid "The neural network is ready."
msgstr "La red neuronal está lista."
#, python-brace-format
msgid "Hey, channel {channel} is free! Your AP will say thanks."
msgstr "Oye, el canal {channel} está libre! Tú AP lo agradecerá."
msgid "I'm bored ..."
msgstr "Estoy aburrido ..."
msgid "Let's go for a walk!"
msgstr "Vamos por un paseo!"
msgid "This is the best day of my life!"
msgstr "Este es el mejor día de mi vida!"
msgid "Shitty day :/"
msgstr "Día de mierda :/"
msgid "I'm extremely bored ..."
msgstr "Estoy extremadamente aburrido ..."
msgid "I'm very sad ..."
msgstr "Estoy muy triste ..."
msgid "I'm sad"
msgstr "Estoy triste."
msgid "I'm living the life!"
msgstr "Estoy viviendo la vida!"
msgid "I pwn therefore I am."
msgstr "Pwneo, por lo tanto, existo"
msgid "So many networks!!!"
msgstr "Cuantas redes!!!"
msgid "I'm having so much fun!"
msgstr "Me estoy divirtiendo mucho!"
msgid "My crime is that of curiosity ..."
msgstr "Mi único crimen es la curiosidad ..."
#, python-brace-format
msgid "Hello {name}! Nice to meet you. {name}"
msgstr "Hola {name}! encantado de conocerte."
#, python-brace-format
msgid "Unit {name} is nearby! {name}"
msgstr "La unidad {name} está cerca!"
#, python-brace-format
msgid "Uhm ... goodbye {name}"
msgstr "Uhm ... adiós {name}"
#, python-brace-format
msgid "{name} is gone ..."
msgstr "{name} se fue ..."
#, python-brace-format
msgid "Whoops ... {name} is gone."
msgstr "Uy ... {name} se fue"
#, python-brace-format
msgid "{name} missed!"
msgstr "{name} perdido!"
msgid "Missed!"
msgstr "Perdido!"
msgid "Nobody wants to play with me ..."
msgstr "Nadie quiere jugar conmigo ..."
msgid "I feel so alone ..."
msgstr "Me siento tan solo ..."
msgid "Where's everybody?!"
msgstr "Dónde está todo el mundo?"
#, python-brace-format
msgid "Napping for {secs}s ..."
msgstr "Tomándo una siesta por {secs}s ..."
msgid "Zzzzz"
msgstr "Zzzzz"
#, python-brace-format
msgid "ZzzZzzz ({secs}s)"
msgstr "ZzzZzzz ({secs}s)"
msgid "Good night."
msgstr "Buenas noches."
msgid "Zzz"
msgstr "Zzz"
#, python-brace-format
msgid "Waiting for {secs}s ..."
msgstr "Esperando {secs}s .."
#, python-brace-format
msgid "Looking around ({secs}s)"
msgstr "Mirando al rededor ({secs}s)"
#, python-brace-format
msgid "Hey {what} let's be friends!"
msgstr "Oye {what} seamos amigos!"
#, python-brace-format
msgid "Associating to {what}"
msgstr "Asociando a {what}"
#, python-brace-format
msgid "Yo {what}!"
msgstr "Ey {what}!"
#, python-brace-format
msgid "Just decided that {mac} needs no WiFi!"
msgstr "Acabo de decidir que {mac} no necesita WiFi!"
#, python-brace-format
msgid "Deauthenticating {mac}"
msgstr "Desautenticando a {mac}"
#, python-brace-format
msgid "Kickbanning {mac}!"
msgstr "Expulsando y banneando a {mac}!"
#, python-brace-format
msgid "Cool, we got {num} new handshake{plural}!"
msgstr "Genial, obtuvimos {num} nuevo{plural} handshake{plural}!"
msgid "Ops, something went wrong ... Rebooting ..."
msgstr "Oops, algo salió mal ... Reiniciándo ..."
#, python-brace-format
msgid "Kicked {num} stations\n"
msgstr "Expulsamos {num} estaciones\n"
#, python-brace-format
msgid "Made {num} new friends\n"
msgstr "Hicimos {num} nuevos amigos\n"
#, python-brace-format
msgid "Got {num} handshakes\n"
msgstr "Obtuvimos {num} handshakes\n"
msgid "Met 1 peer"
msgstr "Conocí 1 igual"
#, python-brace-format
msgid "Met {num} peers"
msgstr "Conocí {num} iguales"
#, python-brace-format
msgid ""
"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
"#pwnlog #pwnlife #hacktheplanet #skynet"
msgstr ""
"He estado pwneando por {duration} y expulsé {deauthed} clientes! También conocí"
"{associated} nuevos amigos y me comí {handshakes} handshakes! #pwnagotchi "
"#pwnlog #pwnlife #hacktheplanet #skynet"
msgid "hours"
msgstr "horas"
msgid "minutes"
msgstr "minutos"
msgid "seconds"
msgstr "segundos"
msgid "hour"
msgstr "hora"
msgid "minute"
msgstr "minuto"
msgid "second"
msgstr "segundo"

Binary file not shown.

View File

@@ -0,0 +1,209 @@
# pwnagotchi Brazilian Portuguese translation file.
# Copyright (C) 2019 Cassiano Aquino
# This file is distributed under the same license as the pwnagotchi package.
# Cassiano Aquino <cassianoaquino@me.com>, 2019.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-10-05 14:10+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Cassiano Aquino <cassianoaquino@me.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: Brazilian Portuguese\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "ZzzzZZzzzzZzzz"
msgstr "ZzzzZZzzzzZzzz"
msgid "Hi, I'm Pwnagotchi! Starting ..."
msgstr "Oi! Eu sou o Pwnagotchi! Iniciando ..."
msgid "New day, new hunt, new pwns!"
msgstr "Novo dia, Nova caça, Novos pwns!"
msgid "Hack the Planet!"
msgstr "Hackeie o Planeta!"
msgid "AI ready."
msgstr "AI pronta."
msgid "The neural network is ready."
msgstr "A rede neural está pronta."
#, python-brace-format
msgid "Hey, channel {channel} is free! Your AP will say thanks."
msgstr "Ei, o canal {channel} está livre! Seu AP ira agradecer."
msgid "I'm bored ..."
msgstr "Estou entediado ..."
msgid "Let's go for a walk!"
msgstr "Vamos dar uma caminhada!"
msgid "This is the best day of my life!"
msgstr "Este e o melhor dia da minha vida!"
msgid "Shitty day :/"
msgstr "Dia de merda :/"
msgid "I'm extremely bored ..."
msgstr "Estou extremamente entediado ..."
msgid "I'm very sad ..."
msgstr "Estou muito triste ..."
msgid "I'm sad"
msgstr "Estou triste"
msgid "I'm living the life!"
msgstr "Estou aproveitando a vida!"
msgid "I pwn therefore I am."
msgstr "pwn, logo existo."
msgid "So many networks!!!"
msgstr "Quantas redes!!!"
msgid "I'm having so much fun!"
msgstr "Estou me divertindo muito!"
msgid "My crime is that of curiosity ..."
msgstr "Meu crime é ser curioso ..."
#, python-brace-format
msgid "Hello {name}! Nice to meet you. {name}"
msgstr "Olá {name}! Prazer em conhecê-lo. {name}"
#, python-brace-format
msgid "Unit {name} is nearby! {name}"
msgstr "Unidade {name} está próxima! {name}"
#, python-brace-format
msgid "Uhm ... goodbye {name}"
msgstr "Uhm ... até logo {name}"
#, python-brace-format
msgid "{name} is gone ..."
msgstr "{name} desapareceu ..."
#, python-brace-format
msgid "Whoops ... {name} is gone."
msgstr "Oops ... {name} desapareceu."
#, python-brace-format
msgid "{name} missed!"
msgstr "{name} perdido!"
msgid "Missed!"
msgstr "Perdido!"
msgid "Nobody wants to play with me ..."
msgstr "Ninguém quer brincar comigo ..."
msgid "I feel so alone ..."
msgstr "Estou tão sozinho ..."
msgid "Where's everybody?!"
msgstr "Aonde está todo mundo?!"
#, python-brace-format
msgid "Napping for {secs}s ..."
msgstr "Cochilando por {secs}s ..."
msgid "Zzzzz"
msgstr "Zzzzz"
#, python-brace-format
msgid "ZzzZzzz ({secs}s)"
msgstr "ZzzZzzz ({secs}s)"
#, python-brace-format
msgid "Waiting for {secs}s ..."
msgstr "Aguardando por {secs}s ..."
#, python-brace-format
msgid "Looking around ({secs}s)"
msgstr "Olhando ao redor ({secs}s)"
#, python-brace-format
msgid "Hey {what} let's be friends!"
msgstr "Ei {what} vamos ser amigos!"
#, python-brace-format
msgid "Associating to {what}"
msgstr "Associando com {what}"
#, python-brace-format
msgid "Yo {what}!"
msgstr "Oi {what}!"
#, python-brace-format
msgid "Just decided that {mac} needs no WiFi!"
msgstr "Acabei de decidir que {mac} não precisa de WiFi!"
#, python-brace-format
msgid "Deauthenticating {mac}"
msgstr "De-autenticando {mac}"
#, python-brace-format
msgid "Kickbanning {mac}!"
msgstr "Kickbanning {mac}"
#, python-brace-format
msgid "Cool, we got {num} new handshake{plural}!"
msgstr "Legal, nos capturamos {num} handshake{plural} novo{plural}!"
msgid "Ops, something went wrong ... Rebooting ..."
msgstr "Ops, algo falhou ... Reiniciando ..."
#, python-brace-format
msgid "Kicked {num} stations\n"
msgstr "Kickei {num} estações\n"
#, python-brace-format
msgid "Made {num} new friends\n"
msgstr "Fiz {num} novos amigos\n"
#, python-brace-format
msgid "Got {num} handshakes\n"
msgstr "Peguei {num} handshakes\n"
msgid "Met 1 peer"
msgstr "Conheci 1 peer"
#, python-brace-format
msgid "Met {num} peers"
msgstr "Conheci {num} peers"
#, python-brace-format
msgid ""
"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
"#pwnlog #pwnlife #hacktheplanet #skynet"
msgstr ""
"Eu estou pwning fazem {duration} e kickei {deauthed} clientes! Eu também conheci "
"{associated} novos amigos e comi {handshakes} handshakes! #pwnagotchi "
"#pwnlog #pwnlife #hacktheplanet #skynet"
msgid "hours"
msgstr "horas"
msgid "minutes"
msgstr "minutos"
msgid "seconds"
msgstr "segundos"
msgid "hour"
msgstr "hora"
msgid "minute"
msgstr "minuto"
msgid "second"
msgstr "segundo"

Binary file not shown.

View File

@@ -0,0 +1,214 @@
# pwnagotchi Portuguese (european) translation file.
# Copyright (C) 2019 David Sopas
# This file is distributed under the same license as the PACKAGE package.
# David Sopas <email@aleatorio.xyz>, 2019.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-10-09 17:42+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: David Sopas <email@aleatorio.xyz>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: Portuguese\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "ZzzzZZzzzzZzzz"
msgstr "ZzzzZZzzzzZzzz"
msgid "Hi, I'm Pwnagotchi! Starting ..."
msgstr "Olá, eu sou o Pwnagotchi! A iniciar ..."
msgid "New day, new hunt, new pwns!"
msgstr "Novo dia, nova caçada, novos pwns!"
msgid "Hack the Planet!"
msgstr "Hacka o Planeta!"
msgid "AI ready."
msgstr "IA pronta."
msgid "The neural network is ready."
msgstr "A rede neural está pronta."
#, python-brace-format
msgid "Hey, channel {channel} is free! Your AP will say thanks."
msgstr "Hey, o canal {channel} está livre! O teu AP irá agradecer."
msgid "I'm bored ..."
msgstr "Estou aborrecido ..."
msgid "Let's go for a walk!"
msgstr "Vamos fazer uma caminhada!"
msgid "This is the best day of my life!"
msgstr "Este é o melhor dia da minha vida!"
msgid "Shitty day :/"
msgstr "Que merda de dia :/"
msgid "I'm extremely bored ..."
msgstr "Estou muito aborrecido ..."
msgid "I'm very sad ..."
msgstr "Estou muito triste ..."
msgid "I'm sad"
msgstr "Estou triste"
msgid "I'm living the life!"
msgstr "Estou aproveitar a vida!"
msgid "I pwn therefore I am."
msgstr "Eu pwn, logo existo."
msgid "So many networks!!!"
msgstr "Tantas redes!!!"
msgid "I'm having so much fun!"
msgstr "Estou a divertir-me tanto!"
msgid "My crime is that of curiosity ..."
msgstr "O meu crime é ser curioso ..."
#, python-brace-format
msgid "Hello {name}! Nice to meet you. {name}"
msgstr "Olá {name}! Prazer em conhecer-te. {name}"
#, python-brace-format
msgid "Unit {name} is nearby! {name}"
msgstr "A unidade {name} está perto! {name}"
#, python-brace-format
msgid "Uhm ... goodbye {name}"
msgstr "Uhm ... adeus {name}"
#, python-brace-format
msgid "{name} is gone ..."
msgstr "{name} desapareceu ..."
#, python-brace-format
msgid "Whoops ... {name} is gone."
msgstr "Ups ... {name} desaparecey."
#, python-brace-format
msgid "{name} missed!"
msgstr "{name} perdido!"
msgid "Missed!"
msgstr "Perdido!"
msgid "Nobody wants to play with me ..."
msgstr "Ninguém quer brincar comigo ..."
msgid "I feel so alone ..."
msgstr "Sinto-me tão só ..."
msgid "Where's everybody?!"
msgstr "Onde estão todos?"
#, python-brace-format
msgid "Napping for {secs}s ..."
msgstr "A fazer uma sesta durante {secs}s ..."
msgid "Zzzzz"
msgstr "Zzzzz"
#, python-brace-format
msgid "ZzzZzzz ({secs}s)"
msgstr "ZzzZzzz ({secs}s)"
msgid "Good night."
msgstr "Boa noite."
msgid "Zzz"
msgstr "Zzz"
#, python-brace-format
msgid "Waiting for {secs}s ..."
msgstr "A aguardar durante {secs}s ..."
#, python-brace-format
msgid "Looking around ({secs}s)"
msgstr "A dar uma olhada ({secs}s)"
#, python-brace-format
msgid "Hey {what} let's be friends!"
msgstr "Hey {what} vamos ser amigos!"
#, python-brace-format
msgid "Associating to {what}"
msgstr "A associar a {what}"
#, python-brace-format
msgid "Yo {what}!"
msgstr "Yo {what}!"
#, python-brace-format
msgid "Just decided that {mac} needs no WiFi!"
msgstr "Decidi que o {mac} não precisa de WiFi!"
#, python-brace-format
msgid "Deauthenticating {mac}"
msgstr "A fazer deauth {mac}"
#, python-brace-format
msgid "Kickbanning {mac}!"
msgstr "A chutar {mac}!"
#, python-brace-format
msgid "Cool, we got {num} new handshake{plural}!"
msgstr "Porreiro, temos {num} novo handshake{plural}!"
msgid "Ops, something went wrong ... Rebooting ..."
msgstr "Ups, algo correu mal ... A reiniciar ..."
#, python-brace-format
msgid "Kicked {num} stations\n"
msgstr "Chutei {num} estações\n"
#, python-brace-format
msgid "Made {num} new friends\n"
msgstr "Fiz {num} novos amigos\n"
#, python-brace-format
msgid "Got {num} handshakes\n"
msgstr "Obti {num} handshakes\n"
msgid "Met 1 peer"
msgstr "Conheci 1 peer"
#, python-brace-format
msgid "Met {num} peers"
msgstr "Conheci {num} peers"
#, python-brace-format
msgid ""
"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
"#pwnlog #pwnlife #hacktheplanet #skynet"
msgstr "Tenho estado a pwnar durante {duration} e chutei {deauthed} clientes! Também conheci "
"{associated} novos amigos e comi {handshakes} handshakes! #pwnagotchu "
"#pwnlog #pwnlife #hacktheplanet #skynet"
msgid "hours"
msgstr "horas"
msgid "minutes"
msgstr "minutos"
msgid "seconds"
msgstr "segundos"
msgid "hour"
msgstr "hora"
msgid "minute"
msgstr "minuto"
msgid "second"
msgstr "segundo"

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-10-05 14:10+0200\n"
"POT-Creation-Date: 2019-10-09 17:42+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -122,6 +122,12 @@ msgstr ""
msgid "ZzzZzzz ({secs}s)"
msgstr ""
msgid "Good night."
msgstr ""
msgid "Zzz"
msgstr ""
#, python-brace-format
msgid "Waiting for {secs}s ..."
msgstr ""

View File

@@ -3,7 +3,7 @@ import json
import _thread
import threading
import logging
from scapy.all import Dot11, Dot11FCS, Dot11Elt, RadioTap, sendp, sniff
from scapy.all import Dot11, Dot11Elt, RadioTap, sendp, sniff
import pwnagotchi.ui.faces as faces
@@ -141,13 +141,7 @@ class Advertiser(object):
dot11.addr3 != self._me.session_id
def _on_packet(self, p):
# https://github.com/secdev/scapy/issues/1590
if p.haslayer(Dot11):
dot11 = p[Dot11]
elif p.haslayer(Dot11FCS):
dot11 = p[Dot11FCS]
else:
dot11 = None
dot11 = p.getlayer(Dot11)
if self._is_broadcasted_advertisement(dot11):
try:

View File

@@ -0,0 +1,57 @@
__author__ = 'pwnagotchi [at] rossmarks [dot] uk'
__version__ = '1.0.0'
__name__ = 'AircrackOnly'
__license__ = 'GPL3'
__description__ = 'confirm pcap contains handshake/PMKID or delete it'
'''
Aircrack-ng needed, to install:
> apt-get install aircrack-ng
'''
import logging
import subprocess
import string
import re
import os
OPTIONS = dict()
def on_loaded():
logging.info("cleancap plugin loaded")
def on_handshake(agent, filename, access_point, client_station):
display = agent._view
todelete = 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 handshake")
else:
todetele = 1
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})
if result:
logging.info("[AircrackOnly] contains PMKID")
else:
todetele = 1
if todelete == 1:
os.remove(filename)
set_text("uncrackable pcap")
display.update(force=True)
text_to_set = "";
def set_text(text):
global text_to_set
text_to_set = text
def on_ui_update(ui):
global text_to_set
if text_to_set:
ui.set('face', "(>.<)")
ui.set('status', text_to_set)
text_to_set = ""

View File

@@ -8,66 +8,26 @@ import os
import logging
import requests
import glob
import json
import subprocess
import pwnagotchi
import pwnagotchi.utils as utils
from pwnagotchi.ui.components import LabeledValue
from pwnagotchi.ui.view import BLACK
import pwnagotchi.ui.fonts as fonts
from pwnagotchi.utils import WifiInfo, extract_from_pcap
OPTIONS = dict()
AUTH = utils.StatusFile('/root/.api-enrollment.json', data_format='json')
REPORT = utils.StatusFile('/root/.api-report.json', data_format='json')
UNREAD_MESSAGES = 0
TOTAL_MESSAGES = 0
def on_loaded():
logging.info("grid plugin loaded.")
def get_api_token(last_session, keys):
global AUTH
if AUTH.newer_then_minutes(25) and AUTH.data is not None and 'token' in AUTH.data:
return AUTH.data['token']
if AUTH.data is None:
logging.info("grid: enrolling unit ...")
else:
logging.info("grid: refreshing token ...")
identity = "%s@%s" % (pwnagotchi.name(), keys.fingerprint)
# sign the identity string to prove we own both keys
_, signature_b64 = keys.sign(identity)
api_address = 'https://api.pwnagotchi.ai/api/v1/unit/enroll'
enrollment = {
'identity': identity,
'public_key': keys.pub_key_pem_b64,
'signature': signature_b64,
'data': {
'duration': last_session.duration,
'epochs': last_session.epochs,
'train_epochs': last_session.train_epochs,
'avg_reward': last_session.avg_reward,
'min_reward': last_session.min_reward,
'max_reward': last_session.max_reward,
'deauthed': last_session.deauthed,
'associated': last_session.associated,
'handshakes': last_session.handshakes,
'peers': last_session.peers,
'uname': subprocess.getoutput("uname -a")
}
}
r = requests.post(api_address, json=enrollment)
if r.status_code != 200:
raise Exception("(status %d) %s" % (r.status_code, r.json()))
AUTH.update(data=r.json())
logging.info("grid: done")
return AUTH.data["token"]
def parse_pcap(filename):
logging.info("grid: parsing %s ..." % filename)
@@ -96,68 +56,146 @@ def parse_pcap(filename):
return info[WifiInfo.ESSID], info[WifiInfo.BSSID]
def api_report_ap(last_session, keys, token, essid, bssid):
while True:
token = AUTH.data['token']
logging.info("grid: reporting %s (%s)" % (essid, bssid))
try:
api_address = 'https://api.pwnagotchi.ai/api/v1/unit/report/ap'
headers = {'Authorization': 'access_token %s' % token}
report = {
'essid': essid,
'bssid': bssid,
}
r = requests.post(api_address, headers=headers, json=report)
if r.status_code != 200:
if r.status_code == 401:
logging.warning("token expired")
token = get_api_token(last_session, keys)
continue
else:
raise Exception("(status %d) %s" % (r.status_code, r.text))
else:
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
except Exception as e:
logging.error("grid: %s" % e)
return False
def grid_call(path, obj=None):
# pwngrid-peer is running on port 8666
api_address = 'http://127.0.0.1:8666/api/v1%s' % path
if obj is None:
r = requests.get(api_address, headers=None)
else:
r = requests.post(api_address, headers=None, json=obj)
if r.status_code != 200:
raise Exception("(status %d) %s" % (r.status_code, r.text))
return r.json()
def grid_update_data(last_session):
brain = {}
try:
with open('/root/brain.json') as fp:
brain = json.load(fp)
except:
pass
data = {
'session': {
'duration': last_session.duration,
'epochs': last_session.epochs,
'train_epochs': last_session.train_epochs,
'avg_reward': last_session.avg_reward,
'min_reward': last_session.min_reward,
'max_reward': last_session.max_reward,
'deauthed': last_session.deauthed,
'associated': last_session.associated,
'handshakes': last_session.handshakes,
'peers': last_session.peers,
},
'uname': subprocess.getoutput("uname -a"),
'brain': brain,
'version': pwnagotchi.version
}
logging.debug("updating grid data: %s" % data)
grid_call("/data", data)
def grid_report_ap(essid, bssid):
try:
grid_call("/report/ap", {
'essid': essid,
'bssid': bssid,
})
return True
except Exception as e:
logging.exception("error while reporting ap %s(%s)" % (essid, bssid))
return False
def grid_inbox():
return grid_call("/inbox")["messages"]
def on_ui_update(ui):
new_value = ' %d (%d)' % (UNREAD_MESSAGES, TOTAL_MESSAGES)
if not ui.has_element('mailbox') and TOTAL_MESSAGES > 0:
if ui.is_inky():
pos=(80, 0)
else:
pos=(100,0)
ui.add_element('mailbox',
LabeledValue(color=BLACK, label='MSG', value=new_value,
position=pos,
label_font=fonts.Bold,
text_font=fonts.Medium))
ui.set('mailbox', new_value)
def on_internet_available(agent):
global REPORT
global REPORT, UNREAD_MESSAGES, TOTAL_MESSAGES
logging.debug("internet available")
try:
config = agent.config()
keys = agent.keypair()
grid_update_data(agent.last_session)
except Exception as e:
logging.error("error connecting to the pwngrid-peer service: %s" % e)
return
pcap_files = glob.glob(os.path.join(config['bettercap']['handshakes'], "*.pcap"))
try:
logging.debug("checking mailbox ...")
messages = grid_inbox()
TOTAL_MESSAGES = len(messages)
UNREAD_MESSAGES = len([m for m in messages if m['seen_at'] is None])
if TOTAL_MESSAGES:
on_ui_update(agent.view())
logging.debug( " %d unread messages of %d total" % (UNREAD_MESSAGES, TOTAL_MESSAGES))
logging.debug("checking pcaps")
pcap_files = glob.glob(os.path.join(agent.config()['bettercap']['handshakes'], "*.pcap"))
num_networks = len(pcap_files)
reported = REPORT.data_field_or('reported', default=[])
num_reported = len(reported)
num_new = num_networks - num_reported
token = get_api_token(agent.last_session, agent.keypair())
if num_new > 0:
if OPTIONS['report']:
logging.info("grid: %d new networks to report" % num_new)
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', '')
do_skip = False
for skip in OPTIONS['exclude']:
skip = skip.lower()
net = net_id.lower()
if skip in net or skip.replace(':', '') in net:
do_skip = True
break
if net_id not in reported:
if is_excluded(net_id):
logging.info("skipping %s due to exclusion filter" % pcap_file)
continue
if net_id not in reported and not do_skip:
essid, bssid = parse_pcap(pcap_file)
if bssid:
if api_report_ap(agent.last_session, keys, token, essid, bssid):
if is_excluded(essid) or is_excluded(bssid):
logging.debug("not reporting %s due to exclusion filter" % pcap_file)
elif grid_report_ap(essid, bssid):
reported.append(net_id)
REPORT.update(data={'reported': reported})
else:
logging.warning("no bssid found?!")
else:
logging.debug("grid: reporting disabled")
except Exception as e:
logging.exception("error while enrolling the unit")
logging.exception("grid api error")

View File

@@ -0,0 +1,52 @@
__author__ = 'pwnagotchi [at] rossmarks [dot] uk'
__version__ = '1.0.0'
__name__ = 'quickdic'
__license__ = 'GPL3'
__description__ = 'Run a quick dictionary scan against captured handshakes'
'''
Aircrack-ng needed, to install:
> apt-get install aircrack-ng
Upload wordlist files in .txt format to folder in config file (Default: /opt/wordlists/)
Cracked handshakes stored in handshake folder as [essid].pcap.cracked
'''
import logging
import subprocess
import string
import re
OPTIONS = dict()
def on_loaded():
logging.info("Quick dictionary check plugin loaded")
def on_handshake(agent, filename, access_point, client_station):
display = agent._view
result = subprocess.run(('/usr/bin/aircrack-ng '+ filename +' | grep "1 handshake" | awk \'{print $2}\''),shell=True, stdout=subprocess.PIPE)
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)
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 = ""

View File

@@ -1,5 +1,6 @@
import _thread
from threading import Lock
from PIL import Image
import shutil
import logging
@@ -117,30 +118,30 @@ class Display(View):
else:
logging.info("could not get ip of usb0, video server not starting")
def _is_inky(self):
def is_inky(self):
return self._display_type in ('inkyphat', 'inky')
def _is_papirus(self):
def is_papirus(self):
return self._display_type in ('papirus', 'papi')
def _is_waveshare_v1(self):
def is_waveshare_v1(self):
return self._display_type in ('waveshare_1', 'ws_1', 'waveshare1', 'ws1')
def _is_waveshare_v2(self):
def is_waveshare_v2(self):
return self._display_type in ('waveshare_2', 'ws_2', 'waveshare2', 'ws2')
def _is_waveshare(self):
return self._is_waveshare_v1() or self._is_waveshare_v2()
def is_waveshare_any(self):
return self.is_waveshare_v1() or self.is_waveshare_v2()
def _init_display(self):
if self._is_inky():
if self.is_inky():
logging.info("initializing inky display")
from inky import InkyPHAT
self._display = InkyPHAT(self._display_color)
self._display.set_border(InkyPHAT.BLACK)
self._render_cb = self._inky_render
elif self._is_papirus():
elif self.is_papirus():
logging.info("initializing papirus display")
from pwnagotchi.ui.papirus.epd import EPD
os.environ['EPD_SIZE'] = '2.0'
@@ -148,8 +149,9 @@ class Display(View):
self._display.clear()
self._render_cb = self._papirus_render
elif self._is_waveshare_v1():
logging.info("initializing waveshare v1 display")
elif self.is_waveshare_v1():
if self._display_color == 'black':
logging.info("initializing waveshare v1 display in monochromatic mode")
from pwnagotchi.ui.waveshare.v1.epd2in13 import EPD
self._display = EPD()
self._display.init(self._display.lut_full_update)
@@ -157,7 +159,15 @@ class Display(View):
self._display.init(self._display.lut_partial_update)
self._render_cb = self._waveshare_render
elif self._is_waveshare_v2():
else:
logging.info("initializing waveshare v1 display 3-color mode")
from pwnagotchi.ui.waveshare.v1.epd2in13bc import EPD
self._display = EPD()
self._display.init()
self._display.Clear()
self._render_cb = self._waveshare_bc_render
elif self.is_waveshare_v2():
logging.info("initializing waveshare v2 display")
from pwnagotchi.ui.waveshare.v2.waveshare import EPD
self._display = EPD()
@@ -176,11 +186,11 @@ class Display(View):
def clear(self):
if self._display is None:
logging.error("no display object created")
elif self._is_inky():
elif self.is_inky():
self._display.Clear()
elif self._is_papirus():
elif self.is_papirus():
self._display.clear()
elif self._is_waveshare():
elif self.is_waveshare_any():
self._display.Clear(WHITE)
else:
logging.critical("unknown display type %s" % self._display_type)
@@ -214,7 +224,7 @@ class Display(View):
try:
self._display.show()
except:
print("")
logging.exception("error while rendering on inky")
def _papirus_render(self):
self._display.display(self._canvas)
@@ -222,11 +232,21 @@ class Display(View):
def _waveshare_render(self):
buf = self._display.getbuffer(self._canvas)
if self._is_waveshare_v1():
if self.is_waveshare_v1():
self._display.display(buf)
elif self._is_waveshare_v2():
elif self.is_waveshare_v2():
self._display.displayPartial(buf)
def _waveshare_bc_render(self):
buf_black = self._display.getbuffer(self._canvas)
# emptyImage = Image.new('1', (self._display.height, self._display.width), 255)
# buf_color = self._display.getbuffer(emptyImage)
# self._display.display(buf_black,buf_color)
# Custom display function that only handles black
# Was included in epd2in13bc.py
self._display.displayBlack(buf_black)
def image(self):
img = None
if self._canvas is not None:

View File

@@ -4,7 +4,9 @@ PATH = '/usr/share/fonts/truetype/dejavu/DejaVuSansMono'
Bold = ImageFont.truetype("%s-Bold.ttf" % PATH, 10)
BoldSmall = ImageFont.truetype("%s-Bold.ttf" % PATH, 8)
BoldBig = ImageFont.truetype("%s-Bold.ttf" % PATH, 25)
Medium = ImageFont.truetype("%s.ttf" % PATH, 10)
Small = ImageFont.truetype("%s.ttf" % PATH, 9)
Huge = ImageFont.truetype("%s-Bold.ttf" % PATH, 25)

View File

@@ -12,6 +12,13 @@ class State(object):
self._state[key] = elem
self._changes[key] = True
def has_element(self, key):
return key in self._state
def remove_element(self, key):
del self._state[key]
self._changes[key] = True
def add_listener(self, key, cb):
with self._lock:
self._listeners[key] = cb

View File

@@ -2,7 +2,7 @@ import _thread
from threading import Lock
import time
import logging
from PIL import Image, ImageDraw
from PIL import ImageDraw
import pwnagotchi.utils as utils
import pwnagotchi.plugins as plugins
@@ -17,21 +17,26 @@ WHITE = 0xff
BLACK = 0x00
ROOT = None
def setup_display_specifics(config):
width = 0
height = 0
face_pos = (0, 0)
name_pos = (0, 0)
status_pos = (0, 0)
status_font = fonts.Medium
status_max_length = None
if config['ui']['display']['type'] in ('inky', 'inkyphat'):
fonts.setup(10, 8, 10, 25)
fonts.setup(10, 8, 10, 28)
width = 212
height = 104
face_pos = (0, int(height / 4))
name_pos = (5, int(height * .15))
status_pos = (int(width / 2) - 15, int(height * .15))
face_pos = (0, 37)
name_pos = (5, 18)
status_pos = (102, 18)
status_font = fonts.Small
status_max_length = 20
elif config['ui']['display']['type'] in ('papirus', 'papi'):
fonts.setup(10, 8, 10, 23)
@@ -41,9 +46,12 @@ def setup_display_specifics(config):
face_pos = (0, int(height / 4))
name_pos = (5, int(height * .15))
status_pos = (int(width / 2) - 15, int(height * .15))
status_font = fonts.Medium
status_max_length = (width - status_pos[0]) // 6
elif config['ui']['display']['type'] in ('ws_1', 'ws1', 'waveshare_1', 'waveshare1',
'ws_2', 'ws2', 'waveshare_2', 'waveshare2'):
if config['ui']['display']['color'] == 'black':
fonts.setup(10, 9, 10, 35)
width = 250
@@ -51,8 +59,19 @@ def setup_display_specifics(config):
face_pos = (0, 40)
name_pos = (5, 20)
status_pos = (125, 20)
status_font = fonts.Medium
else:
fonts.setup(10, 8, 10, 25)
return width, height, face_pos, name_pos, status_pos
width = 212
height = 104
face_pos = (0, int(height / 4))
name_pos = (5, int(height * .15))
status_pos = (int(width / 2) - 15, int(height * .15))
status_font = fonts.Medium
status_max_length = (width - status_pos[0]) // 6
return width, height, face_pos, name_pos, status_pos, status_font, status_max_length
class View(object):
@@ -67,7 +86,7 @@ class View(object):
self._voice = Voice(lang=config['main']['lang'])
self._width, self._height, \
face_pos, name_pos, status_pos = setup_display_specifics(config)
face_pos, name_pos, status_pos, status_font, status_max_length = setup_display_specifics(config)
self._state = State(state={
'channel': LabeledValue(color=BLACK, label='CH', value='00', position=(0, 0), label_font=fonts.Bold,
@@ -98,10 +117,10 @@ class View(object):
'status': Text(value=self._voice.default(),
position=status_pos,
color=BLACK,
font=fonts.Medium,
font=status_font,
wrap=True,
# the current maximum number of characters per line, assuming each character is 6 pixels wide
max_length=(self._width - status_pos[0]) // 6),
max_length=status_max_length),
'shakes': LabeledValue(label='PWND ', value='0 (00)', color=BLACK,
position=(0, self._height - int(self._height * .12) + 1), label_font=fonts.Bold,
@@ -124,9 +143,15 @@ class View(object):
ROOT = self
def has_element(self, key):
self._state.has_element(key)
def add_element(self, key, elem):
self._state.add_element(key, elem)
def remove_element(self, key):
self._state.remove_element(key)
def width(self):
return self._width
@@ -188,6 +213,11 @@ class View(object):
faces.SAD,
faces.LONELY)
def on_keys_generation(self):
self.set('face', faces.AWAKE)
self.set('status', self._voice.on_keys_generation())
self.update()
def on_normal(self):
self.set('face', faces.AWAKE)
self.set('status', self._voice.on_normal())

View File

@@ -0,0 +1,165 @@
# *****************************************************************************
# * | File : epd2in13bc.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
from PIL import Image
import RPi.GPIO as GPIO
# import numpy as np
# Display resolution
EPD_WIDTH = 104
EPD_HEIGHT = 212
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
# Hardware reset
def reset(self):
epdconfig.digital_write(self.reset_pin, GPIO.HIGH)
epdconfig.delay_ms(200)
epdconfig.digital_write(self.reset_pin, GPIO.LOW) # module reset
epdconfig.delay_ms(200)
epdconfig.digital_write(self.reset_pin, GPIO.HIGH)
epdconfig.delay_ms(200)
def send_command(self, command):
epdconfig.digital_write(self.dc_pin, GPIO.LOW)
epdconfig.digital_write(self.cs_pin, GPIO.LOW)
epdconfig.spi_writebyte([command])
epdconfig.digital_write(self.cs_pin, GPIO.HIGH)
def send_data(self, data):
epdconfig.digital_write(self.dc_pin, GPIO.HIGH)
epdconfig.digital_write(self.cs_pin, GPIO.LOW)
epdconfig.spi_writebyte([data])
epdconfig.digital_write(self.cs_pin, GPIO.HIGH)
def ReadBusy(self):
while(epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy
epdconfig.delay_ms(100)
def init(self):
if (epdconfig.module_init() != 0):
return -1
# EPD hardware init start
self.reset()
self.send_command(0x06) # BOOSTER_SOFT_START
self.send_data(0x17)
self.send_data(0x17)
self.send_data(0x17)
self.send_command(0x04) # POWER_ON
self.ReadBusy()
self.send_command(0x00) # PANEL_SETTING
self.send_data(0x8F)
self.send_command(0x50) # VCOM_AND_DATA_INTERVAL_SETTING
self.send_data(0xF0)
self.send_command(0x61) # RESOLUTION_SETTING
self.send_data(self.width & 0xff)
self.send_data(self.height >> 8)
self.send_data(self.height & 0xff)
return 0
def getbuffer(self, image):
buf = [0xFF] * (int(self.width/8) * self.height)
image_monocolor = image.convert('1')
imwidth, imheight = image_monocolor.size
pixels = image_monocolor.load()
if(imwidth == self.width and imheight == self.height):
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):
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 displayBlack(self, imageblack):
self.send_command(0x10)
for i in range(0, int(self.width * self.height / 8)):
self.send_data(imageblack[i])
self.send_command(0x92)
self.send_command(0x12) # REFRESH
self.ReadBusy()
def display(self, imageblack, imagecolor):
self.send_command(0x10)
for i in range(0, int(self.width * self.height / 8)):
self.send_data(imageblack[i])
self.send_command(0x92)
self.send_command(0x13)
for i in range(0, int(self.width * self.height / 8)):
self.send_data(imagecolor[i])
self.send_command(0x92)
self.send_command(0x12) # REFRESH
self.ReadBusy()
def Clear(self):
self.send_command(0x10)
for i in range(0, int(self.width * self.height / 8)):
self.send_data(0xFF)
self.send_command(0x92)
self.send_command(0x13)
for i in range(0, int(self.width * self.height / 8)):
self.send_data(0xFF)
self.send_command(0x92)
self.send_command(0x12) # REFRESH
self.ReadBusy()
def sleep(self):
self.send_command(0x02) # POWER_OFF
self.ReadBusy()
self.send_command(0x07) # DEEP_SLEEP
self.send_data(0xA5) # check code
# epdconfig.module_exit()
### END OF FILE ###

View File

@@ -28,6 +28,10 @@ class Voice:
self._('AI ready.'),
self._('The neural network is ready.')])
def on_keys_generation(self):
return random.choice([
self._('Generating keys, do not turn off ...')])
def on_normal(self):
return random.choice([
'',