Merge pull request #1 from hexwaxwing/patch-1

Patch 1
This commit is contained in:
waxwing 2019-10-05 15:31:02 -04:00 committed by GitHub
commit 9992d748f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
56 changed files with 1851 additions and 402 deletions

View File

@ -3,6 +3,7 @@ maintainers:
- caquino
- dadav
- justin-p
- hexwaxwing
features:
- comments

76
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,76 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at pwnagotchi@gmail.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq

View File

@ -5,6 +5,7 @@
<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://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>
</p>
</p>
@ -40,7 +41,4 @@ For hackers to learn reinforcement learning, WiFi networking and have an excuse
## License
`pwnagotchi` is made with ♥ by [@evilsocket](https://twitter.com/evilsocket) and the [amazing dev team](https://github.com/evilsocket/pwnagotchi/graphs/contributors). It's released under the GPL3 license.
`pwnagotchi` is made with ♥ by [@evilsocket](https://twitter.com/evilsocket) and the [amazing dev team](https://github.com/evilsocket/pwnagotchi/graphs/contributors). It's released under the GPL3 license.

View File

@ -3,28 +3,135 @@
- 127.0.0.1
become: yes
vars:
pwn_hostname: "pwnagotchi"
pwn_version: "master"
pwnagotchi:
hostname: "{{ lookup('env', 'PWN_HOSTNAME') | default('pwnagotchi', true) }}"
version: "{{ lookup('env', 'PWN_VERSION') | default('master', true) }} "
system:
boot_options:
- "dtoverlay=dwc2"
- "dtparam=spi=on"
- "dtoverlay=spi1-3cs"
- "dtoverlay=pi3-disable-bt"
- "dtparam=audio=off"
services:
enable:
- dphys-swapfile.service
- getty@ttyGS0.service
disable:
- apt-daily.timer
- apt-daily.service
- apt-daily-upgrade.timer
- apt-daily-upgrade.service
- wpa_supplicant.service
- bluetooth.service
- triggerhappy.service
- ifup@wlan0.service
packages:
pip:
install:
- inky
- smbus2
- absl-py>=0.1.6
- enum34
- gast==0.2.2
- google_pasta
- opt_einsum
- scapy
- gym
- keras_applications>=1.0.6
- keras_preprocessing>=1.0.5
- stable-baselines
- file_read_backwards
- tensorflow_estimator>=1.14.0,<1.15.0
- tensorboard>=1.13.0,<1.14.0
apt:
remove:
- rasberrypi-net-mods
- dhcpcd5
- triggerhappy
- wpa_supplicant
- nfs-common
install:
- vim
- screen
- golang
- git
- build-essential
- python3-pip
- unzip
- gawk
- libopenmpi-dev
- libatlas-base-dev
- libjasper-dev
- libqtgui4
- libqt4-test
- libopenjp2-7
- tcpdump
- lsof
- libilmbase23
- libopenexr23
- libgstreamer1.0-0
- libavcodec58
- libavformat58
- libswscale5
- libpcap-dev
- libusb-1.0-0-dev
- libnetfilter-queue-dev
- dphys-swapfile
- kalipi-kernel
- kalipi-bootloader
- kalipi-re4son-firmware
- kalipi-kernel-headers
- libraspberrypi0
- libraspberrypi-dev
- libraspberrypi-doc
- libraspberrypi-bin
- fonts-dejavu
- fonts-dejavu-core
- fonts-dejavu-extra
- python3-crypto
- python3-requests
- python3-yaml
- python3-smbus
- python3-inkyphat
- python3-numpy
- python3-pil
- python3-tweepy
- python3-opencv
- python3-termcolor
- python3-astor
- python3-backports.weakref
- python3-h5py
- python3-six
- python3-protobuf
- python3-wrapt
- python3-wheel
- python3-mock
- python3-scipy
- python3-cloudpickle
bettercap:
query: "assets[?contains(name, 'armv6l')].browser_download_url"
tasks:
- name: selected hostname
debug:
msg: "{{ pwn_hostname }}"
msg: "{{ pwnagotchi.hostname }}"
- name: build version
debug:
msg: "{{ pwn_version }}"
msg: "{{ pwnagotchi.version }}"
- name: change hostname
hostname:
name: "{{pwn_hostname}}"
name: "{{pwnagotchi.hostname}}"
- name: add hostname to /etc/hosts
lineinfile:
dest: /etc/hosts
regexp: '^127\.0\.0\.1[ \t]+localhost'
line: '127.0.0.1 localhost {{pwn_hostname}} {{pwn_hostname}}.local'
line: '127.0.0.1 localhost {{pwnagotchi.hostname}} {{pwnagotchi.hostname}}.local'
state: present
- name: Add re4son-kernel repo key
@ -41,88 +148,55 @@
apt:
update_cache: yes
- name: remove unecessary apt packages
apt:
name: "{{ packages.apt.remove }}"
state: absent
purge: yes
- name: upgrade apt distro
apt:
upgrade: dist
- name: install packages
apt:
name: "{{ packages }}"
name: "{{ packages.apt.install }}"
state: present
vars:
packages:
- vim
- screen
- golang
- git
- build-essential
- python3-pip
- gawk
- libopenmpi-dev
- libatlas-base-dev
- libjasper-dev
- libqtgui4
- libqt4-test
- libopenjp2-7
- tcpdump
- lsof
- libilmbase23
- libopenexr23
- libgstreamer1.0-0
- libavcodec58
- libavformat58
- libswscale5
- libpcap-dev
- libusb-1.0-0-dev
- libnetfilter-queue-dev
- dphys-swapfile
- kalipi-kernel
- kalipi-bootloader
- kalipi-re4son-firmware
- kalipi-kernel-headers
- libraspberrypi0
- libraspberrypi-dev
- libraspberrypi-doc
- libraspberrypi-bin
- fonts-dejavu
- fonts-dejavu-core
- fonts-dejavu-extra
- name: configure dphys-swapfile
file:
path: /etc/dphys-swapfile
content: "CONF_SWAPSIZE=1024"
- name: disable unecessary services
systemd:
name: "{{services}}"
state: stopped
enabled: no
vars:
services:
- apt-daily.timer
- apt-daily.service
- apt-daily-upgrade.timer
- apt-daily-upgrade.service
- bluetooth.service
- triggerhappy.service
- name: acquire python3 pip target
command: "python3 -c 'import sys;print(sys.path.pop())'"
register: pip_target
- name: enable dphys-swapfile service
systemd:
name: dphys-swapfile.service
state: started
enabled: yes
- name: install pip packages
pip:
name: "{{packages.pip.install}}"
extra_args: "--no-deps --extra-index-url=https://www.piwheels.hostedpi.com/simple/ --prefer-binary --no-cache-dir --platform=armv6l --target={{ pip_target.stdout }}"
- name: build bettercap
command: go get -u github.com/bettercap/bettercap
environment:
GOPATH: /root/go
GOROOT: /usr/lib/go
- name: install grpcio
command: "pip3 install --no-deps --extra-index-url=https://www.piwheels.hostedpi.com/simple/ --no-cache-dir --prefer-binary --platform=armv6l --only-binary=:all: --target={{ pip_target.stdout }} https://www.piwheels.hostedpi.com/simple/grpcio/grpcio-1.24.1-cp37-cp37m-linux_armv6l.whl"
- name: install bettercap
copy:
src: /root/go/bin/bettercap
dest: /usr/bin/bettercap
- name: install tensorflow
command: "pip3 install --no-deps --extra-index-url=https://www.piwheels.hostedpi.com/simple/ --no-cache-dir --prefer-binary --platform=armv6l --only-binary=:all: --target={{ pip_target.stdout }} https://www.piwheels.org/simple/tensorflow/tensorflow-1.13.1-cp37-none-linux_armv6l.whl"
- name: fetch bettercap release information
uri:
url: https://api.github.com/repos/bettercap/bettercap/releases/latest
return_content: yes
register: bettercap_release
- name: download and install bettercap
unarchive:
src: "{{ bettercap_release.content | from_json | json_query(bettercap.query) | first }}"
dest: /usr/bin
remote_src: yes
exclude:
- README.md
- LICENSE.md
mode: 0755
- name: clone bettercap caplets
@ -151,10 +225,6 @@
path: /tmp/pwnagotchi
state: absent
- name: install python modules
pip:
requirements: /root/pwnagotchi/scripts/requirements.txt
- name: create cpuusage script
copy:
dest: /usr/bin/cpuusage
@ -249,11 +319,7 @@
insertafter: EOF
line: '{{ item }}'
with_items:
- "dtoverlay=dwc2"
- "dtparam=spi=on"
- "dtoverlay=spi1-3cs"
- "dtoverlay=pi3-disable-bt"
- "dtparam=audio=off"
- "{{system.boot_options}}"
- name: change root partition
replace:
@ -269,7 +335,7 @@
state: present
backup: no
regexp: '(.*)$'
line: '\1 modules-load=dwc2,g_ether'
line: '\1 modules-load=dwc2,g_cdc'
- name: configure ssh
lineinfile:
@ -281,7 +347,7 @@
- name: configure motd
copy:
dest: /etc/motd
content: "(◕‿‿◕) {{pwn_hostname}} (pwnagotchi-{{pwn_version}})"
content: "(◕‿‿◕) {{pwnagotchi.hostname}} (pwnagotchi-{{pwnagotchi.version}})"
- name: clean apt cache
apt:
@ -291,16 +357,21 @@
apt:
autoremove: yes
- name: enable services
systemd:
name: "{{services.enable}}"
state: started
enabled: yes
- name: disable unecessary services
systemd:
name: "{{services.disable}}"
state: stopped
enabled: no
- name: remove ssh keys
file:
state: absent
path: "{{item}}"
with_items:
- /etc/ssh/ssh_host_rsa_key
- /etc/ssh/ssh_host_rsa_key.pub
- /etc/ssh/ssh_host_dsa_key
- /etc/ssh/ssh_host_dsa_key.pub
- /etc/ssh/ssh_host/ecdsa_key
- /etc/ssh/ssh_host/ecdsa_key.pub
- /etc/ssh/ssh_host_ed25519_key
- /etc/ssh/ssh_host_ed25519_key.pub
with_fileglob:
- "/etc/ssh/ssh_host*_key*"

View File

@ -1,4 +1,4 @@
## About the Project
# About the Project
[Pwnagotchi](https://twitter.com/pwnagotchi) 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 in order to maximize the WPA key material it captures (either passively, or by performing deauthentication and association attacks). This material is collected as PCAP files containing any form of handshake supported by [hashcat](https://hashcat.net/hashcat/), including [PMKIDs](https://www.evilsocket.net/2019/02/13/Pwning-WiFi-networks-with-bettercap-and-the-PMKID-client-less-attack/),
full and half WPA handshakes.

View File

@ -1,11 +1,10 @@
### Connecting to your Pwnagotchi
# Connecting to your Pwnagotchi
Once you wrote the image file on the SD card, there're a few steps you'll have to follow in order to configure your unit properly, first, start with connecting the USB cable to the
data port of the Raspberry Pi and the RPi to your computer. After a few seconds the board will boot and you will see a new Ethernet interface on your host computer.
Once you wrote the image file on the SD card, there're a few steps you'll have to follow in order to configure your unit properly, first, start with connecting the USB cable to the data port of the Raspberry Pi and the RPi to your computer. After a few seconds the board will boot and you will see a new Ethernet interface on your host computer.
You'll need to configure it with a static IP address:
- IP: `10.0.0.2`
- IP: `10.0.0.1`
- Netmask: `255.255.255.0`
- Gateway: `10.0.0.1`
- DNS (if required): `8.8.8.8` (or whatever)
@ -26,26 +25,34 @@ Moreover, it is recommended that you copy your SSH public key among the unit's a
ssh-copy-id -i ~/.ssh/id_rsa.pub pi@10.0.0.2
```
### Configuration
## Configuration
You can now set a new name for your unit by [changing the hostname](https://geek-university.com/raspberry-pi/change-raspberry-pis-hostname/). Create the `/root/custom.yml` file (either via SSH or by direclty editing the SD card contents from a computer) that will override
the [default configuration](https://github.com/evilsocket/pwnagotchi/blob/master/sdcard/rootfs/root/pwnagotchi/config.yml) with your custom values.
You can now set a new name for your unit by [changing the hostname](https://geek-university.com/raspberry-pi/change-raspberry-pis-hostname/). Create the `/root/custom.yml` file (either via SSH or by direclty editing the SD card contents from a computer) that will override the [default configuration](https://github.com/evilsocket/pwnagotchi/blob/master/sdcard/rootfs/root/pwnagotchi/config.yml) with your custom values.
## Language Selection
For instance, you can change `main.lang` to one of the supported languages:
* **english** (default)
* german
* dutch
* greek
* macedonian
* italian
* french
- **english** (default)
- german
- dutch
- greek
- macedonian
- italian
- french
- russian
- swedish
The set the type of display you want to use via `ui.display.type` (if your display does not work after changing this setting, you might need to complete remove power from the Raspberry and make a clean boot).
## Display Selection
Set the type of display you want to use via `ui.display.type` (if your display does not work after changing this setting, you might need to completely remove power from the Raspberry and make a clean boot).
You can configure the refresh interval of the display via `ui.fps`, we advise to use a slow refresh to not shorten the lifetime of your display. The default value is 0, which will only refresh when changes are made to the screen.
### Host Connection Share
## Host Connection Share
If you connect to the unit via `usb0` (thus using the data port), you might want to use the `scripts/linux_connection_share.sh` or
`scripts/macos_connection_share.sh` script to bring the interface up on your end and share internet connectivity from another interface, so you can update the unit and generally download things from the internet on it.
If you connect to the unit via `usb0` (thus using the data port), you might want to use the `scripts/linux_connection_share.sh`, `scripts/macos_connection_share.sh` or `scripts/win_connection_share.ps1` script to bring the interface up on your end and share internet connectivity from another interface, so you can update the unit and generally download things from the internet on it.
## Troubleshooting
If your network connection keeps flapping on your device connecting to your pwnagotchi, check if `usb0` (or equivalent) device is being controlled by NetworkManager. You can check this via `nmcli dev status`.

View File

@ -1,4 +1,4 @@
## Software
# Software
- Raspbian + [nexmon patches](https://re4son-kernel.com/re4son-pi-kernel/) for monitor mode, or any Linux with a monitor mode enabled interface (if you tune config.yml).
@ -21,6 +21,15 @@ usage: ./scripts/create_sibling.sh [OPTIONS]
-d # Only run dependencies checks
-h # Show this help
```
#### Known Issues
`GLib-ERROR **: 20:50:46.361: getauxval () failed: No such file or directory`
- Affected DEB & Versions: QEMU <= 2.11
- Fix: Upgrade QEMU to >= 3.1
- Bug Link: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=923289
## Adding a Language
If you want to add a language use the `language.sh` script. If you want to add for example the language **italian** you would type:

View File

@ -10,4 +10,4 @@ Because Python sucks and TF is huge.
## Why ...?
Because!
Because!

38
docs/hacks.md Normal file
View File

@ -0,0 +1,38 @@
# Unofficial Hacks
---
**IMPORTANT DISCLAIMER:** The information provided on this page is NOT officially supported by the Pwnagotchi development team. These are unofficial "hacks" that users have worked out while customizing their units and decided to document for anybody else who might want to do something similar.
- **Please do NOT open issues if you cannot get something described in this document to work.**
- It (almost) goes without saying, but obviously: **we are NOT responsible if you break your hardware by following any instructions documented here. Use this information at your own risk.**
---
If you test one of these hacks yourself and it still works, it's extra nice if you update the **Last Tested On** table and note any minor adjustments you may have had to make to the instructions to make it work with your particular Pwnagotchi setup. :heart:
## Screens
### Waveshare 3.5" SPI TFT screen
Last tested on | Pwnagotchi version | Working? | Reference
---------------|--------------------|----------|-----------|
2019 October 3 | Unknown | :white_check_mark: | ([link](https://github.com/evilsocket/pwnagotchi/issues/124#issue-502346040))
Some of this guide will work with other framebuffer-based displays.
- First: SSH into your Pwnagotchi, and give it some internet!
- Don't forget to check your default gateway and `apt-get update`.
- Follow the guide here: [www.waveshare.com/wiki/3.5inch_RPi_LCD_(A)#Method_1._Driver_installation](https://www.waveshare.com/wiki/3.5inch_RPi_LCD_(A)#Method_1._Driver_installation)
- At the step with `./LCD35-show`, add `lite` to the command prompt (e.g., `./LCD35-show lite`).
- Reboot.
- As root, make three symlinks:
- `cd ~`
- `ln -s pwnagotchi.png pwnagotchi_1.png`
- `ln -s pwnagotchi.png pwnagotchi_2.png`
- `ln -s pwnagotchi.png pwnagotchi_3.png`
- `apt install fbi`
- Change display type to `inky` in `config.yml`
- Add `modules-load=dwc2,g_ether` to your kernel command line (`/boot/cmdline.txt`) or it will break!
- Also must add `dtoverlay=dwc2` to the bottom of (`/boot/config.txt`)
- Edit `/etc/rc.local` and add: `fbi -T 1 -a -noverbose -t 15 -cachemem 0 /root/pwnagotchi_1.png /root/pwnagotchi_2.png /root/pwnagotchi_3.png &`
- Reboot.
And you should be good!

View File

@ -12,8 +12,9 @@
- [Project Slack](https://join.slack.com/t/pwnagotchi/shared_invite/enQtNzc4NzY3MDE2OTAzLTg5NmNmNDJiMDM3ZWFkMWUwN2Y5NDk0Y2JlZWZjODlhMmRhNDZiOGMwYjJhM2UzNzA3YjA5NjJmZGY5NGI5NmI)
- [Project Twitter](https://twitter.com/pwnagotchi)
- [Project Subreddit](https://www.reddit.com/r/pwnagotchi/)
- [Project Website](https://pwnagotchi.ai/)
## License
`pwnagotchi` is made with ♥ by [@evilsocket](https://twitter.com/evilsocket) and the [amazing dev team](https://github.com/evilsocket/pwnagotchi/graphs/contributors). It's released under the GPL3 license.
`pwnagotchi` is made with ♥ by [@evilsocket](https://twitter.com/evilsocket) and the [amazing dev team](https://github.com/evilsocket/pwnagotchi/graphs/contributors). It's released under the GPL3 license.

View File

@ -1,7 +1,9 @@
# Installation
The project has been developed to run on a Raspberry Pi 0 W configured as an [USB Ethernet gadget](https://learn.adafruit.com/turning-your-raspberry-pi-zero-into-a-usb-gadget/ethernet-gadget) device in order to connect to it via USB.
However, given the proper configuration tweaks, any GNU/Linux computer with a WiFi interface that supports monitor mode could be used.
The project has been developed to run on a Raspberry Pi 0 W configured as an [USB Ethernet gadget](https://learn.adafruit.com/turning-your-raspberry-pi-zero-into-a-usb-gadget/ethernet-gadget) device in order to connect to it via USB. However, given the proper configuration tweaks, any GNU/Linux computer with a WiFi interface that supports monitor mode could be used.
**An important note about the AI:** a network trained with a specific WiFi interface will only work with another interface if it supports
the same exact WiFi channels of the first one. For instance, you can not use a neural network trained on a Raspberry Pi Zero W (that only supports 2.4Ghz channels) with a 5Ghz antenna, but you'll need to train one from scratch for those channels.
## Required Hardware
@ -34,9 +36,7 @@ Color displays have a much slower refresh rate, in some cases it can take up to
The easiest way to create a new Pwnagotchi is downloading the latest stable image from [our release page](https://github.com/evilsocket/pwnagotchi/releases) and write it to your SD card. You will need to use an image writing tool to install the image you have downloaded on your SD card.
[balenaEtcher](https://www.balena.io/etcher/) is a graphical SD card writing tool that works on Mac OS, Linux and Windows,
and is the easiest option for most users. balenaEtcher also supports writing images directly from the zip file,
without any unzipping required. To write your image with balenaEtcher:
[balenaEtcher](https://www.balena.io/etcher/) is a graphical SD card writing tool that works on Mac OS, Linux and Windows, and is the easiest option for most users. balenaEtcher also supports writing images directly from the zip file, without any unzipping required. To write your image with balenaEtcher:
- Download the latest [Pwnagotchi .img file](https://github.com/evilsocket/pwnagotchi/releases).
- Download [balenaEtcher](https://www.balena.io/etcher/) and install it.
@ -45,4 +45,4 @@ without any unzipping required. To write your image with balenaEtcher:
- Select the SD card you wish to write your image to.
- Review your selections and click 'Flash!' to begin writing data to the SD card.
Your SD card is now ready for the first boot!
Your SD card is now ready for the first boot!

View File

@ -1,8 +1,7 @@
# Plugins
Pwnagotchi has a simple plugins system that you can use to customize your unit and its behaviour. You can place your plugins anywhere
as python files and then edit the `config.yml` file (`main.plugins` value) to point to their containing folder. Check the [plugins folder](https://github.com/evilsocket/pwnagotchi/tree/master/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/plugins/default/) for a list of default
plugins and all the callbacks that you can define for your own customizations.
as python files and then edit the `config.yml` file (`main.plugins` value) to point to their containing folder. Check the [plugins folder](https://github.com/evilsocket/pwnagotchi/tree/master/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/plugins/default/) for a list of default plugins and all the callbacks that you can define for your own customizations.
Here's as an example the GPS plugin:

View File

@ -1,4 +1,6 @@
### UI
# Usage
## User Interface
The UI is available either via display if installed, or via http://pwnagotchi.local:8080/ if you connect to the unit via `usb0` and set a static address on the network interface (change `pwnagotchi` with the hostname of your unit).
@ -10,14 +12,107 @@ The UI is available either via display if installed, or via http://pwnagotchi.lo
* **PWND**: Number of handshakes captured in this session and number of unique networks we own at least one handshake of, from the beginning.
* **AUTO**: This indicates that the algorithm is running with AI disabled (or still loading), it disappears once the AI dependencies have been bootrapped and the neural network loaded.
### BetterCAP's Web UI
## Training the AI
Moreover, given that the unit is running bettercap with API and Web UI, you'll be able to use the unit as a WiFi penetration testing portable station
by accessing `http://pwnagotchi.local/`.
At its core Pwnagotchi is a very simple creature: we could summarize its main algorithm as:
```python
# main loop
while True:
# ask bettercap for all visible access points and their clients
aps = get_all_visible_access_points()
# loop each AP
for ap in aps:
# send an association frame in order to grab the PMKID
send_assoc(ap)
# loop each client station of the AP
for client in ap.clients:
# deauthenticate the client to get its half or full handshake
deauthenticate(client)
wait_for_loot()
```
Despite its simplicity, this logic is controlled by several parameters that regulate the wait times, the timeouts, on which channels to hop and so on.
From `config.yml`:
```yaml
personality:
# advertise our presence
advertise: true
# perform a deauthentication attack to client stations in order to get full or half handshakes
deauth: true
# send association frames to APs in order to get the PMKID
associate: true
# list of channels to recon on, or empty for all channels
channels: []
# minimum WiFi signal strength in dBm
min_rssi: -200
# number of seconds for wifi.ap.ttl
ap_ttl: 120
# number of seconds for wifi.sta.ttl
sta_ttl: 300
# time in seconds to wait during channel recon
recon_time: 30
# number of inactive epochs after which recon_time gets multiplied by recon_inactive_multiplier
max_inactive_scale: 2
# if more than max_inactive_scale epochs are inactive, recon_time *= recon_inactive_multiplier
recon_inactive_multiplier: 2
# time in seconds to wait during channel hopping if activity has been performed
hop_recon_time: 10
# time in seconds to wait during channel hopping if no activity has been performed
min_recon_time: 5
# maximum amount of deauths/associations per BSSID per session
max_interactions: 3
# maximum amount of misses before considering the data stale and triggering a new recon
max_misses_for_recon: 5
# number of active epochs that triggers the excited state
excited_num_epochs: 10
# number of inactive epochs that triggers the bored state
bored_num_epochs: 15
# number of inactive epochs that triggers the sad state
sad_num_epochs: 25
```
There is no optimal set of parameters for every situation: when the unit is moving (during a walk for instance) smaller timeouts and RSSI thresholds might be preferred in order to quickly remove routers that are not in range anymore, while when stationary in high density areas (like an office) other parameters might be better. The role of the AI is to observe what's going on at the WiFi level, and adjust those parameters in order to maximize the cumulative reward of that loop / epoch.
## Reward Function
After each iteration of the main loop (an `epoch`), the reward, a score that represents how well the parameters performed, is computed as (an excerpt from `pwnagotchi/ai/reward.py`):
```python
# state contains the information of the last epoch
# epoch_n is the number of the last epoch
tot_epochs = epoch_n + 1e-20 # 1e-20 is added to avoid a division by 0
tot_interactions = max(state['num_deauths'] + state['num_associations'], state['num_handshakes']) + 1e-20
tot_channels = wifi.NumChannels
# ideally, for each interaction we would have an handshake
h = state['num_handshakes'] / tot_interactions
# small positive rewards the more active epochs we have
a = .2 * (state['active_for_epochs'] / tot_epochs)
# make sure we keep hopping on the widest channel spectrum
c = .1 * (state['num_hops'] / tot_channels)
# small negative reward if we don't see aps for a while
b = -.3 * (state['blind_for_epochs'] / tot_epochs)
# small negative reward if we interact with things that are not in range anymore
m = -.3 * (state['missed_interactions'] / tot_interactions)
# small negative reward for inactive epochs
i = -.2 * (state['inactive_for_epochs'] / tot_epochs)
reward = h + a + c + b + i + m
```
By maximizing this reward value, the AI learns over time to find the set of parameters that better perform with the current environmental conditions.
## BetterCAP's Web UI
Moreover, given that the unit is running bettercap with API and Web UI, you'll be able to use the unit as a WiFi penetration testing portable station by accessing `http://pwnagotchi.local/`.
![webui](https://raw.githubusercontent.com/bettercap/media/master/ui-events.png)
### Update your Pwnagotchi
## Update your Pwnagotchi
You can use the `scripts/update_pwnagotchi.sh` script to update to the most recent version of pwnagotchi.
@ -34,7 +129,7 @@ usage: ./update_pwnagitchi.sh [OPTIONS]
```
### Backup your Pwnagotchi
## Backup your Pwnagotchi
You can use the `scripts/backup.sh` script to backup the important files of your unit.
@ -42,12 +137,10 @@ You can use the `scripts/backup.sh` script to backup the important files of your
usage: ./scripts/backup.sh HOSTNAME backup.zip
```
### Random Info
- **On a rpi0w, it'll take approximately 30 minutes to load the AI**.
- `/var/log/pwnagotchi.log` is your friend.
- if connected to a laptop via usb data port, with internet connectivity shared, magic things will happen.
- checkout the `ui.video` section of the `config.yml` - if you don't want to use a display, you can connect to it with the browser and a cable.
- If you get `[FAILED] Failed to start Remount Root and Kernel File Systems.` while booting pwnagotchi, make sure
the `PARTUUID`s for `rootfs` and `boot` partitions are the same in `/etc/fstab`. Use `sudo blkid` to find those values when you are using `create_sibling.sh`.
## Random Info
* **On a rpi0w, it'll take approximately 30 minutes to load the AI**.
* `/var/log/pwnagotchi.log` is your friend.
* if connected to a laptop via usb data port, with internet connectivity shared, magic things will happen.
* checkout the `ui.video` section of the `config.yml` - if you don't want to use a display, you can connect to it with the browser and a cable.
* If you get `[FAILED] Failed to start Remount Root and Kernel File Systems.` while booting pwnagotchi, make sure the `PARTUUID`s for `rootfs` and `boot` partitions are the same in `/etc/fstab`. Use `sudo blkid` to find those values when you are using `create_sibling.sh`.

View File

@ -5,7 +5,7 @@
set -eu
REQUIREMENTS=( wget gunzip git dd e2fsck resize2fs parted losetup qemu-system-x86_64 )
DEBREQUIREMENTS=( wget gzip git parted qemu-system-x86 qemu-user-static bmap-tools )
DEBREQUIREMENTS=( wget gzip git parted qemu-system-x86 qemu-user-static )
REPO_DIR="$(dirname "$(dirname "$(realpath "$0")")")"
TMP_DIR="${REPO_DIR}/tmp"
MNT_DIR="${TMP_DIR}/mnt"
@ -93,15 +93,13 @@ function provide_raspbian() {
function setup_raspbian(){
# Detect the ability to create sparse files
if [ "${OPT_SPARSE}" -eq 0 ];
then
which bmaptool >/dev/null 2>&1
if [ $? -eq 0 ];
then
if [ "${OPT_SPARSE}" -eq 0 ]; then
if [ which bmaptool -eq 0 ]; then
echo "[!] bmaptool not available, not creating a sparse image"
else
echo "[+] Defaulting to sparse image generation as bmaptool is available"
OPT_SPARSE=1
else
echo "[!] bmaptool not available, not creating a sparse image"
fi
fi
@ -335,7 +333,13 @@ fi
setup_raspbian
provision_raspbian
echo -e "[+] Congratz, it's a boy (⌐■_■)!"
#Make a baby with a random gender, maybe do something fun with this later!
gender[0]="boy"
gender[1]="girl"
rand=$[ $RANDOM % 2 ]
echo -e "[+] Congratz, it's a ${gender[$rand]} (⌐■_■)!"
echo -e "[+] One more step: dd if=../${PWNI_OUTPUT} of=<PATH_TO_SDCARD> bs=4M status=progress"
if [ "${OPT_SPARSE}" -eq 1 ];

View File

@ -1,7 +1,7 @@
#!/bin/bash
# nothing to see here, just a utility i use to create new releases ^_^
VERSION_FILE=$(dirname "${BASH_SOURCE[0]}")/../sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/version.py
VERSION_FILE=$(dirname "${BASH_SOURCE[0]}")/../sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/__init__.py
echo "version file is $VERSION_FILE"
CURRENT_VERSION=$(cat $VERSION_FILE | grep version | cut -d"'" -f2)
TO_UPDATE=(

View File

@ -89,7 +89,7 @@ fi
if [ $BACKUPCONFIG -eq 1 ]; then
echo "[+] Creating backup of config.yml and hostname references"
mv /root/pwnagotchi/config.yml /root/config.bak -f
mv /root/pwnagotchi/config.yml /root/config.yml.bak -f
mv /etc/hosts /root/hosts.bak -f
mv /etc/hostname /root/hostname.bak -f
mv /etc/motd /etc/motd.bak -f

View File

@ -0,0 +1,290 @@
<#
.SYNOPSIS
A script that setups Internet Connection Sharing for Pwnagotchi.
.DESCRIPTION
A script that setups Internet Connection Sharing for Pwnagotchi.
Note: Internet Connection Sharing on Windows can be a bit unstable on between reboots.
You might need to run this script occasionally to disable and re-enable Internet Connection Sharing.
.PARAMETER EnableInternetConnectionSharing
Enable Internet Connection Sharing
.PARAMETER DisableInternetConnectionSharing
Disable Internet Connection Sharing
.PARAMETER SetPwnagotchiSubnet
Change the Internet Connection Sharing subnet to the Pwnagotchi subnet. The USB Gadget Interface IP will default to 10.0.0.1.
.PARAMETER ScopeAddress
Custom ScopeAddress (The IP Address of the USB Gadget Interface.)
.EXAMPLE
# Enable Internet Connection Sharing
PS C:\> .\win_connection_share -EnableInternetConnectionSharing
.EXAMPLE
# Disable Internet Connection Sharing
PS C:\> .\win_connection_share -DisableInternetConnectionSharing
.EXAMPLE
# Change the regkeys of Internet Connection Sharing to the Pwnagotchi Subnet
PS C:\> .\win_connection_share -SetPwnagotchiSubnet
.EXAMPLE
# Change the regkeys of Internet Connection Sharing to the Pwnagotchi Subnet with a custom ScopeAddress (The IP Address of the USB Gadget Interface.)
PS C:\> .\win_connection_share -SetPwnagotchiSubnet -ScopeAddress 10.0.0.10
#>
#Requires -Version 5
#Requires -RunAsAdministrator
[Cmdletbinding()]
Param (
[switch]$EnableInternetConnectionSharing,
[switch]$DisableInternetConnectionSharing,
[switch]$SetPwnagotchiSubnet,
[ipaddress]$ScopeAddress = '10.0.0.1'
)
# Load helper functions
Function Create-HNetObjects {
<#
.SYNOPSIS
A helper function that does the heavy lifiting with NetCfg.HNetShare
.DESCRIPTION
A helper function that does the heavy lifiting with NetCfg.HNetShare. This returns a PSObject containing the `INetSharingConfigurationForINetConnection` info of 2 Adapters.
.PARAMETER InternetAdaptor
The output of Get-NetAdaptor filtered down to the 'main' uplink interface.
.PARAMETER RNDISGadget
The output of Get-NetAdaptor filtered down to the 'USB Ethernet/RNDIS Gadget' interface.
.EXAMPLE
PS> $HNetObject = Create-HNetObjects
PS> $HNetObject
RNDISIntConfig InternetIntConfig
-------------- -----------------
System.__ComObject System.__ComObject
#>
[Cmdletbinding()]
Param (
$InternetAdaptor = $(Select-NetAdaptor -Message "Please select your main a ethernet adaptor with internet access that will be used for internet sharing."),
$RNDISGadget = $(Select-NetAdaptor -Message "Please select your 'USB Ethernet/RNDIS Gadget' adaptor")
)
Begin {
regsvr32.exe /s hnetcfg.dll
$HNetShare = New-Object -ComObject HNetCfg.HNetShare
}
Process {
if ($HNetShare.EnumEveryConnection -ne $null) {
$InternetInt = $HNetShare.EnumEveryConnection | Where-Object { $HNetShare.NetConnectionProps.Invoke($_).Name -eq ($InternetAdaptor).Name }
$InternetIntConfig = $HNetShare.INetSharingConfigurationForINetConnection.Invoke($InternetInt)
$RNDISInt = $HNetShare.EnumEveryConnection | Where-Object { $HNetShare.NetConnectionProps.Invoke($_).Name -eq ($RNDISGadget).Name }
$RNDISIntConfig = $HNetShare.INetSharingConfigurationForINetConnection.Invoke($RNDISInt)
}
}
End {
Return $(New-Object -TypeName PSObject -Property @{InternetIntConfig=$InternetIntConfig;RNDISIntConfig=$RNDISIntConfig;})
}
}
Function Enable-InternetConnectionSharing {
<#
.SYNOPSIS
Enables internet connection sharing between the 'main' uplink interface and the 'USB Ethernet/RNDIS Gadget' interface.
.DESCRIPTION
Enables internet connection sharing between the 'main' uplink interface and the 'USB Ethernet/RNDIS Gadget' interface.
.EXAMPLE
PS> Enable-InternetConnectionSharing
#>
[Cmdletbinding()]
$HNetObject = Create-HNetObjects
$HNetObject.InternetIntConfig.EnableSharing(0)
$HNetObject.RNDISIntConfig.EnableSharing(1)
Write-Output "[x] Enabled Internet Connection Sharing."
}
Function Disable-InternetConnectionSharing {
<#
.SYNOPSIS
Disables internet connection sharing between the 'main' uplink interface and the 'USB Ethernet/RNDIS Gadget' interface.
.DESCRIPTION
Disables internet connection sharing between the 'main' uplink interface and the 'USB Ethernet/RNDIS Gadget' interface.
.EXAMPLE
PS> Disable-InternetConnectionSharing
#>
[Cmdletbinding()]
$HNetObject = $(Create-HNetObjects)
$HNetObject.InternetIntConfig.DisableSharing()
$HNetObject.RNDISIntConfig.DisableSharing()
Write-Output "[x] Disabled Internet Connection Sharing."
}
Function Test-PwnagotchiSubnet {
<#
.SYNOPSIS
Tests the registry for the correct ScopeAddress.
.DESCRIPTION
Tests the registry for the correct ScopeAddress. By default windows uses a 192.168.137.x subnet for Internet Connection Sharing. This value can be changed
in the registry.
.EXAMPLE
PS> Test-PwnagotchiSubnet
[!] By default Internet Connection Sharing uses a 192.168.137.x subnet. Run Set-PwnagotchiSubnet to ensure you and your little friend are on the same subnet.
#>
[Cmdletbinding()]
$RegKeys = Get-ItemProperty HKLM:\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters -ErrorAction Stop
If ($RegKeys.ScopeAddress -notmatch '10.0.0.') {
Write-Error "By default Internet Connection Sharing uses a 192.168.137.x subnet. Run Set-PwnagotchiSubnet to ensure you and your little friend are on the same subnet." -ErrorAction Stop
}
If ($RegKeys.ScopeAddressBackup -notmatch '10.0.0.') {
Write-Error "By default Internet Connection Sharing uses a 192.168.137.x subnet. Run Set-PwnagotchiSubnet to ensure you and your little friend are on the same subnet." -ErrorAction Stop
}
}
Function Set-PwnagotchiSubnet {
<#
.SYNOPSIS
Set the registry for the correct ScopeAddress.
.DESCRIPTION
Set the registry for the correct ScopeAddress. By default windows uses a 192.168.137.x subnet for Internet Connection Sharing. This value can be changed
in the registry. By default it will be changed to 10.0.0.1
.PARAMETER ScopeAddress
The IP address the USB Gadget interface should use.
.EXAMPLE
Set-PwnagotchiSubnet
#>
[Cmdletbinding()]
Param (
$ScopeAddress = '10.0.0.1'
)
Try {
[void]([ipaddress]$ScopeAddress)
[void]([byte[]] $ScopeAddress.split('.'))
} Catch {
Write-Error "$ScopeAddress is not a valid IP."
}
Try {
Set-ItemProperty -Name ScopeAddress -Path "HKLM:\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\" -Value $ScopeAddress -ErrorAction Stop
Set-ItemProperty -Name ScopeAddressBackup -Path "HKLM:\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\" -Value $ScopeAddress -ErrorAction Stop
Write-Warning "The Internet Connection Sharing subnet has been updated. A reboot of windows is required !"
} Catch {
$PSCmdlet.ThrowTerminatingError($PSItem)
}
}
# Main Function
Function Setup-PwnagotchiNetwork {
<#
.SYNOPSIS
Function to setup networking.
.DESCRIPTION
Function to setup networking. Main function calls helpers functions.
.PARAMETER EnableInternetConnectionSharing
Enable Internet Connection Sharing
.PARAMETER DisableInternetConnectionSharing
Disable Internet Connection Sharing
.PARAMETER SetPwnagotchiSubnet
Change the Internet Connection Sharing subnet to the Pwnagotchi. Defaults to 10.0.0.1.
.PARAMETER ScopeAddress
Custom ScopeAddress (the ICS ip address)
.EXAMPLE
PS> Setup-PwnagotchiNetwork -EnableInternetConnectionSharing
#>
Param (
[switch]$EnableInternetConnectionSharing,
[switch]$DisableInternetConnectionSharing,
[switch]$SetPwnagotchiSubnet,
$ScopeAddress = '10.0.0.1'
)
Begin {
Try {
Write-Debug "Begin"
$ErrorSplat=@{ErrorAction="stop"}
Write-Debug "Testing subnet"
Try {
Test-PwnagotchiSubnet @ErrorSplat
} Catch {
If ($SetPwnagotchiSubnet) {
Write-Debug "Setting subnet"
Set-PwnagotchiSubnet -ScopeAddress $ScopeAddress @ErrorSplat
} Else {
Write-Error "By default Internet Connection Sharing uses a 192.168.137.x subnet. Run this script with the -SetPwnagotchiSubnet to setup the network." -ErrorAction Stop
}
}
} Catch {
$PSCmdlet.ThrowTerminatingError($PSItem)
}
}
Process {
Write-Debug "Process"
Try {
If ($EnableInternetConnectionSharing) {
Write-Debug "Enable network Sharing"
Enable-InternetConnectionSharing @ErrorSplat
} ElseIf ($DisableInternetConnectionSharing) {
Write-Debug "Disable network Sharing"
Disable-InternetConnectionSharing @ErrorSplat
}
} Catch {
$PSCmdlet.ThrowTerminatingError($PSItem)
}
}
End {
Write-Debug "End"
Try {
# Nothing to return.
} Catch {
$PSCmdlet.ThrowTerminatingError($PSItem)
}
}
}
Function Select-NetAdaptor {
<#
.SYNOPSIS
A menu function to select the correct network adaptors.
.DESCRIPTION
A menu function to select the correct network adaptors.
.PARAMETER Message
Message that will be displayed during the question.
#>
Param (
$Message
)
$Adaptors = Get-NetAdapter | Where-Object {$_.MediaConnectionState -eq 'Connected'} | Sort-Object LinkSpeed -Descending
do {
Write-Host $Message
$index = 1
foreach ($Adaptor in $Adaptors) {
Write-Host "[$index] $($Adaptor.Name), $($Adaptor.InterfaceDescription)"
$index++
}
$Selection = Read-Host "Number"
} until ($Adaptors[$selection-1])
Return $Adaptors[$selection-1]
}
# Dynamically create params for Setup-PwnagotchiNetwork function based of param input of script.
Setup-PwnagotchiNetwork @psBoundParameters

View File

@ -1,9 +1,45 @@
# main algorithm configuration
main:
# currently implemented: en (default), de, nl, it
# currently implemented: en (default), de, el, fr, it, mk, nl, ru, se
lang: en
# custom plugins path, if null only default plugins with be loaded
plugins: null
custom_plugins:
# which plugins to load and enable
plugins:
auto-update:
enabled: false
interval: 1 # every day
auto-backup:
enabled: false
interval: 1 # every day
files:
- /root/brain.nn
- /root/brain.json
- /root/custom.yml
- /root/handshakes
- /etc/ssh
- /etc/hostname
- /etc/hosts
- /etc/motd
- /var/log/pwnagotchi.log
commands:
- 'tar czf /tmp/backup.tar.gz {files}'
- 'scp /tmp/backup.tar.gz pwnagotchi@10.0.0.1:/home/pwnagotchi/backups/backup-$(date +%s).tar.gz'
gps:
enabled: false
twitter:
enabled: false
consumer_key: aaa
consumer_secret: aaa
access_token_key: aaa
access_token_secret: aaa
onlinehashcrack:
enabled: false
email: ~
wpa-sec:
enabled: false
api_key: ~
# monitor interface to use
iface: mon0
# command to run to bring the mon interface up in case it's not up already
@ -15,7 +51,9 @@ main:
# if true, will not restart the wifi module
no_restart: false
# access points to ignore
whitelist: []
whitelist:
- EXAMPLE_NETWORK
- ANOTHER_EXAMPLE_NETWORK
# if not null, filter access points by this regular expression
filter: null
# cryptographic key for identity
@ -108,13 +146,6 @@ ui:
address: '10.0.0.2'
port: 8080
# twitter bot data
twitter:
enabled: false
consumer_key: aaa
consumer_secret: aaa
access_token_key: aaa
access_token_secret: aaa
# bettercap rest api configuration
bettercap:
@ -143,6 +174,3 @@ bettercap:
- wifi.ap.new
- wifi.ap.lost
- mod.started

View File

@ -1,48 +0,0 @@
import glob
import os
import time
import subprocess
def secs_to_hhmmss(secs):
mins, secs = divmod(secs, 60)
hours, mins = divmod(mins, 60)
return '%02d:%02d:%02d' % (hours, mins, secs)
def total_unique_handshakes(path):
expr = os.path.join(path, "*.pcap")
return len(glob.glob(expr))
def iface_address(ifname):
output = subprocess.getoutput("/usr/sbin/ifconfig %s" % ifname)
for line in output.split("\n"):
line = line.strip()
if line.startswith("inet "):
return line.split(' ')[1].strip()
return None
def iface_channels(ifname):
channels = []
output = subprocess.getoutput("/sbin/iwlist %s freq" % ifname)
for line in output.split("\n"):
line = line.strip()
if line.startswith("Channel "):
channels.append(int(line.split()[1]))
return channels
def led(on=True):
with open('/sys/class/leds/led0/brightness', 'w+t') as fp:
fp.write("%d" % (0 if on is True else 1))
def blink(times=1, delay=0.3):
for t in range(0, times):
led(True)
time.sleep(delay)
led(False)
time.sleep(delay)
led(True)

View File

@ -1,18 +1,13 @@
#!/usr/bin/python3
import os
import argparse
import time
import logging
import yaml
import pwnagotchi
import pwnagotchi.utils as utils
import pwnagotchi.version as version
import pwnagotchi.plugins as plugins
from pwnagotchi.log import SessionParser
from pwnagotchi.voice import Voice
from pwnagotchi.agent import Agent
from pwnagotchi.ui.display import Display
@ -34,119 +29,75 @@ args = parser.parse_args()
config = utils.load_config(args)
utils.setup_logging(args, config)
if args.do_clear:
print("clearing the display ...")
cleardisplay = config['ui']['display']['type']
if cleardisplay in ('inkyphat', 'inky'):
print("inky display")
from inky import InkyPHAT
epd = InkyPHAT(config['ui']['display']['color'])
epd.set_border(InkyPHAT.BLACK)
self._render_cb = self._inky_render
elif cleardisplay in ('papirus', 'papi'):
print("papirus display")
from pwnagotchi.ui.papirus.epd import EPD
os.environ['EPD_SIZE'] = '2.0'
epd = EPD()
epd.clear()
elif cleardisplay in ('waveshare_1', 'ws_1', 'waveshare1', 'ws1'):
print("waveshare v1 display")
from pwnagotchi.ui.waveshare.v1.epd2in13 import EPD
epd = EPD()
epd.init(epd.lut_full_update)
epd.Clear(0xFF)
elif cleardisplay in ('waveshare_2', 'ws_2', 'waveshare2', 'ws2'):
print("waveshare v2 display")
from pwnagotchi.ui.waveshare.v2.waveshare import EPD
epd = EPD()
epd.init(epd.FULL_UPDATE)
epd.Clear(0xff)
else:
print("unknown display type %s" % cleardisplay)
quit()
plugins.load_from_path(plugins.default_path)
if 'plugins' in config['main'] and config['main']['plugins'] is not None:
plugins.load_from_path(config['main']['plugins'])
plugins.on('loaded')
plugins.load(config)
display = Display(config=config, state={'name': '%s>' % pwnagotchi.name()})
agent = Agent(view=display, config=config)
logging.info("%s@%s (v%s)" % (pwnagotchi.name(), agent._identity, version.version))
# for key, value in config['personality'].items():
# logging.info(" %s: %s" % (key, value))
logging.info("%s@%s (v%s)" % (pwnagotchi.name(), agent._identity, pwnagotchi.version))
for _, plugin in plugins.loaded.items():
logging.info("plugin '%s' v%s loaded from %s" % (plugin.__name__, plugin.__version__, plugin.__file__))
logging.debug("plugin '%s' v%s loaded from %s" % (plugin.__name__, plugin.__version__, plugin.__file__))
if args.do_manual:
if args.do_clear:
logging.info("clearing the display ...")
display.clear()
elif args.do_manual:
logging.info("entering manual mode ...")
log = SessionParser(config['main']['log'])
logging.info("the last session lasted %s (%d completed epochs, trained for %d), average reward:%s (min:%s max:%s)" % (
log.duration_human,
log.epochs,
log.train_epochs,
log.avg_reward,
log.min_reward,
log.max_reward))
log = SessionParser(config)
logging.info(
"the last session lasted %s (%d completed epochs, trained for %d), average reward:%s (min:%s max:%s)" % (
log.duration_human,
log.epochs,
log.train_epochs,
log.avg_reward,
log.min_reward,
log.max_reward))
while True:
display.on_manual_mode(log)
time.sleep(1)
if Agent.is_connected():
plugins.on('internet_available', config, log)
plugins.on('internet_available', display, config, log)
quit()
else:
logging.info("entering auto mode ...")
agent.start_ai()
agent.setup_events()
agent.set_starting()
agent.start_monitor_mode()
agent.start_event_polling()
agent.start()
# print initial stats
agent.next_epoch()
while True:
try:
# recon on all channels
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)
agent.set_ready()
if not agent.is_stale() and agent.any_activity():
logging.info("%d access points on channel %d" % (len(aps), ch))
while True:
try:
# recon on all channels
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)
# for each ap on this channel
for ap in aps:
# send an association frame in order to get for a PMKID
agent.associate(ap)
# deauth all client stations in order to get a full handshake
for sta in ap['clients']:
agent.deauth(ap, sta)
if not agent.is_stale() and agent.any_activity():
logging.info("%d access points on channel %d" % (len(aps), ch))
# for each ap on this channel
for ap in aps:
# send an association frame in order to get for a PMKID
agent.associate(ap)
# deauth all client stations in order to get a full handshake
for sta in ap['clients']:
agent.deauth(ap, sta)
# An interesting effect of this:
#
# From Pwnagotchi's perspective, the more new access points
# and / or client stations nearby, the longer one epoch of
# its relative time will take ... basically, in Pwnagotchi's universe,
# WiFi electromagnetic fields affect time like gravitational fields
# affect ours ... neat ^_^
agent.next_epoch()
except Exception as e:
logging.exception("main loop exception")
# An interesting effect of this:
#
# From Pwnagotchi's perspective, the more new access points
# and / or client stations nearby, the longer one epoch of
# its relative time will take ... basically, in Pwnagotchi's universe,
# WiFi electromagnetic fields affect time like gravitational fields
# affect ours ... neat ^_^
agent.next_epoch()
except Exception as e:
logging.exception("main loop exception")

View File

@ -1,5 +1,7 @@
import subprocess
version = '1.0.0plz3'
_name = None

View File

@ -7,10 +7,9 @@ from datetime import datetime
import logging
import _thread
import core
import pwnagotchi.utils as utils
import pwnagotchi.plugins as plugins
from bettercap.client import Client
from pwnagotchi.bettercap import Client
from pwnagotchi.mesh.utils import AsyncAdvertiser
from pwnagotchi.ai.train import AsyncTrainer
@ -30,7 +29,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
self._started_at = time.time()
self._filter = None if config['main']['filter'] is None else re.compile(config['main']['filter'])
self._current_channel = 0
self._supported_channels = core.iface_channels(config['main']['iface'])
self._supported_channels = utils.iface_channels(config['main']['iface'])
self._view = view
self._access_points = []
self._last_pwnd = None
@ -130,14 +129,24 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
wifi_running = self.is_module_running('wifi')
if wifi_running and restart:
logging.debug("restarting wifi module ...")
self.restart('wifi.recon')
self.restart_module('wifi.recon')
self.run('wifi.clear')
elif not wifi_running:
logging.debug("starting wifi module ...")
self.start('wifi.recon')
self.start_module('wifi.recon')
self.start_advertising()
def start(self):
self.start_ai()
self.setup_events()
self.set_starting()
self.start_monitor_mode()
self.start_event_polling()
# print initial stats
self.next_epoch()
self.set_ready()
def wait_for(self, t, sleeping=True):
plugins.on('sleep' if sleeping else 'wait', self, t)
self._view.wait(t, sleeping)
@ -242,7 +251,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
def _update_uptime(self, s):
secs = time.time() - self._started_at
self._view.set('uptime', core.secs_to_hhmmss(secs))
self._view.set('uptime', utils.secs_to_hhmmss(secs))
self._view.set('epoch', '%04d' % self._epoch.epoch)
def _update_counters(self):
@ -262,7 +271,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
if new_shakes > 0:
self._epoch.track(handshake=True, inc=new_shakes)
tot = core.total_unique_handshakes(self._config['bettercap']['handshakes'])
tot = utils.total_unique_handshakes(self._config['bettercap']['handshakes'])
txt = '%d (%d)' % (len(self._handshakes), tot)
if self._last_pwnd is not None:
@ -275,7 +284,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
def _update_advertisement(self, s):
run_handshakes = len(self._handshakes)
tot_handshakes = core.total_unique_handshakes(self._config['bettercap']['handshakes'])
tot_handshakes = utils.total_unique_handshakes(self._config['bettercap']['handshakes'])
started = s['started_at'].split('.')[0]
started = datetime.strptime(started, '%Y-%m-%dT%H:%M:%S')
started = time.mktime(started.timetuple())
@ -379,10 +388,10 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
return m['running']
return False
def start(self, module):
def start_module(self, module):
self.run('%s on' % module)
def restart(self, module):
def restart_module(self, module):
self.run('%s off; %s on' % (module, module))
def _has_handshake(self, bssid):

View File

@ -2,8 +2,8 @@ import time
import threading
import logging
import core
import pwnagotchi
import pwnagotchi.utils as utils
import pwnagotchi.mesh.wifi as wifi
from pwnagotchi.ai.reward import RewardFunction
@ -174,22 +174,22 @@ class Epoch(object):
self._epoch_data_ready.set()
logging.info("[epoch %d] duration=%s slept_for=%s blind=%d inactive=%d active=%d hops=%d missed=%d "
"deauths=%d assocs=%d handshakes=%d cpu=%d%% mem=%d%% temperature=%dC reward=%s" % (
self.epoch,
core.secs_to_hhmmss(self.epoch_duration),
core.secs_to_hhmmss(self.num_slept),
self.blind_for,
self.inactive_for,
self.active_for,
self.num_hops,
self.num_missed,
self.num_deauths,
self.num_assocs,
self.num_shakes,
cpu * 100,
mem * 100,
temp,
self._epoch_data['reward']))
"deauths=%d assocs=%d handshakes=%d cpu=%d%% mem=%d%% temperature=%dC reward=%s" % (
self.epoch,
utils.secs_to_hhmmss(self.epoch_duration),
utils.secs_to_hhmmss(self.num_slept),
self.blind_for,
self.inactive_for,
self.active_for,
self.num_hops,
self.num_missed,
self.num_deauths,
self.num_assocs,
self.num_shakes,
cpu * 100,
mem * 100,
temp,
self._epoch_data['reward']))
self.epoch += 1
self.epoch_started = now

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-03 16:42+0200\n"
"POT-Creation-Date: 2019-10-05 14:10+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"
@ -188,3 +188,21 @@ msgstr ""
"Ich war {duration} am Pwnen und habe {deauthed} Clients gekickt! Außerdem "
"habe ich {associated} neue Freunde getroffen und {handshakes} Handshakes "
"gefressen! #pwnagotchi #pwnlog #pwnlife #hacktheplanet #skynet"
msgid "hours"
msgstr "Stunden"
msgid "minutes"
msgstr "Minuten"
msgid "seconds"
msgstr "Sekunden"
msgid "hour"
msgstr "Stunde"
msgid "minute"
msgstr "Minute"
msgid "second"
msgstr "Sekunde"

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-10-03 16:44+0200\n"
"POT-Creation-Date: 2019-10-05 14:10+0200\n"
"PO-Revision-Date: 2019-10-03 08:00+0000\n"
"Last-Translator: Periklis Fregkos <fregkos@gmail.com>\n"
"Language-Team: pwnagotchi <33197631+dadav@users.noreply.github.com>\n"
@ -189,3 +189,21 @@ msgstr ""
"Pwnαρα για {duration} και έριξα {deauthed} πελάτες! Επίσης γνώρισα "
"{associated} νέους φίλους και καταβρόχθισα {handshakes} χειραψίες! "
"#pwnagotchi #pwnlog #pwnlife #hacktheplanet #skynet"
msgid "hours"
msgstr ""
msgid "minutes"
msgstr ""
msgid "seconds"
msgstr ""
msgid "hour"
msgstr ""
msgid "minute"
msgstr ""
msgid "second"
msgstr ""

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-10-03 16:47+0200\n"
"POT-Creation-Date: 2019-10-05 14:10+0200\n"
"PO-Revision-Date: 2019-10-03 10:34+0200\n"
"Last-Translator: quantumsheep <7271496+quantumsheep@users.noreply.github."
"com>\n"
@ -190,3 +190,21 @@ msgstr ""
"J'ai pwn durant {duration} et kick {deauthed} clients! J'ai aussi rencontré "
"{associated} nouveaux amis and mangé {handshakes} handshakes! #pwnagotchi "
"#pwnlog #pwnlife #hacktheplanet #skynet"
msgid "hours"
msgstr ""
msgid "minutes"
msgstr ""
msgid "seconds"
msgstr ""
msgid "hour"
msgstr ""
msgid "minute"
msgstr ""
msgid "second"
msgstr ""

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-10-03 16:43+0200\n"
"POT-Creation-Date: 2019-10-05 14:10+0200\n"
"PO-Revision-Date: 2019-10-02 17:20+0000\n"
"Language-Team: pwnagotchi <33197631+dadav@users.noreply.github.com>\n"
"Language: italian\n"
@ -187,3 +187,21 @@ msgstr ""
"Ho lavorato per {duration} e preso a calci {deauthed} clients! Ho anche "
"incontrato {associate} nuovi amici e ho mangiato {handshakes} handshakes! "
"#pwnagotchi #pwnlog #pwnlife #hacktheplanet #skynet"
msgid "hours"
msgstr "ore"
msgid "minutes"
msgstr "minuti"
msgid "seconds"
msgstr "secondi"
msgid "hour"
msgstr "ora"
msgid "minute"
msgstr "minuto"
msgid "second"
msgstr "secondo"

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-10-03 16:35+0200\n"
"POT-Creation-Date: 2019-10-05 14:10+0200\n"
"PO-Revision-Date: 2019-09-30 23:53+0200\n"
"Last-Translator: kovach <2214005+kovachwt@users.noreply.github.com>\n"
"Language-Team: \n"
@ -189,3 +189,21 @@ msgstr ""
"Си газам веќе {duration} и избацив {deauthed} клиенти! Запознав {associated} "
"нови другарчиња и лапнав {handshakes} ракувања! #pwnagotchi #pwnlog #pwnlife "
"#hacktheplanet #skynet"
msgid "hours"
msgstr ""
msgid "minutes"
msgstr ""
msgid "seconds"
msgstr ""
msgid "hour"
msgstr ""
msgid "minute"
msgstr ""
msgid "second"
msgstr ""

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-10-03 16:43+0200\n"
"POT-Creation-Date: 2019-10-05 14:10+0200\n"
"PO-Revision-Date: 2019-09-29 14:00+0200\n"
"Last-Translator: Justin-P <justin-p@users.noreply.github.com>\n"
"Language-Team: pwnagotchi <33197631+dadav@users.noreply.github.com>\n"
@ -188,3 +188,21 @@ msgstr ""
"Ik heb gepwned voor {duration} and heb {deauthed} clients gekicked! Ik heb "
"ook {associated} nieuwe vrienden gevonden en heb {handshakes} handshakes "
"gegeten! #pwnagotchi #pwnlog #pwnlife #hacktheplanet #skynet"
msgid "hours"
msgstr ""
msgid "minutes"
msgstr ""
msgid "seconds"
msgstr ""
msgid "hour"
msgstr ""
msgid "minute"
msgstr ""
msgid "second"
msgstr ""

View File

@ -0,0 +1,205 @@
# 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 <25989971+adolfaka@users.noreply.github.com>, 2019.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-10-03 16:47+0200\n"
"PO-Revision-Date: 2019-10-05 18:50+0300\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.2.4\n"
"Last-Translator: Elliot Manson\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n"
"Language: ru_RU\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 "Нейронная сеть готова."
#, 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. {name}"
msgstr "Привет, {name}! Приятно познакомиться. {name}"
#, python-brace-format
msgid "Unit {name} is nearby! {name}"
msgstr "Цель {name} близко! {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 "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}c)"
#, python-brace-format
msgid "Waiting for {secs}s ..."
msgstr "Ждем {secs}c …"
#, 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 "Йоy {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} новое рукопожатие!"
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 "hour"
msgstr "час"
msgid "minutes"
msgstr "минут"
msgid "minute"
msgstr "минуту"

View File

@ -0,0 +1,202 @@
# 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 <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-10-03 16:47+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Mike Eriksson <mike@swedishmike.org>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: swedish\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 "Hej, jag är Pwnagotchi! Startar ..."
msgid "New day, new hunt, new pwns!"
msgstr "Ny dag, ny jakt, nya pwns!"
msgid "Hack the Planet!"
msgstr "Hacka planeten!"
msgid "AI ready."
msgstr "AI klar."
msgid "The neural network is ready."
msgstr "Det neurala nätverket är klart."
#, python-brace-format
msgid "Hey, channel {channel} is free! Your AP will say thanks."
msgstr "Du, kanal {channel} är ledig! Din AP will gilla detta."
msgid "I'm bored ..."
msgstr "Jag har det så tråkigt..."
msgid "Let's go for a walk!"
msgstr "Dags för en promenad!"
msgid "This is the best day of my life!"
msgstr "Det här är den bästa dagen i mitt liv!"
msgid "Shitty day :/"
msgstr "Idag suger :/"
msgid "I'm extremely bored ..."
msgstr "Jag är extremt uttråkad ..."
msgid "I'm very sad ..."
msgstr "Jag är jätteledsen ..."
msgid "I'm sad"
msgstr "Jag är ledsen"
msgid "I'm living the life!"
msgstr "Nu leker livet!"
msgid "I pwn therefore I am."
msgstr "Jag pwnar därför är jag."
msgid "So many networks!!!"
msgstr "Så många nätverk!!!"
msgid "I'm having so much fun!"
msgstr "Fan vad skoj jag har!"
msgid "My crime is that of curiosity ..."
msgstr "Mitt brott är att vara nyfiken ..."
#, python-brace-format
msgid "Hello {name}! Nice to meet you. {name}"
msgstr "Hejsan {name}! Trevligt att träffas {name}"
#, python-brace-format
msgid "Unit {name} is nearby! {name}"
msgstr "Enheten {name} är nära! {name}"
#, python-brace-format
msgid "Uhm ... goodbye {name}"
msgstr "Uhm ... farväl {name}"
#, python-brace-format
msgid "{name} is gone ..."
msgstr "{name} är borta ..."
#, python-brace-format
msgid "Whoops ... {name} is gone."
msgstr "Hoppsan ... {name} är borta."
#, python-brace-format
msgid "{name} missed!"
msgstr "{name} missade!"
msgid "Missed!"
msgstr "Bom!"
msgid "Nobody wants to play with me ..."
msgstr "Ingen vill leka med mig ..."
msgid "I feel so alone ..."
msgstr "Jag är så ensam ..."
msgid "Where's everybody?!"
msgstr "Var är alla?!"
#, python-brace-format
msgid "Napping for {secs}s ..."
msgstr "Sover för {secs}s ..."
msgid "Zzzzz"
msgstr ""
#, python-brace-format
msgid "ZzzZzzz ({secs}s)"
msgstr ""
#, python-brace-format
msgid "Waiting for {secs}s ..."
msgstr "Väntar {secs}s ..."
#, python-brace-format
msgid "Looking around ({secs}s)"
msgstr "Tittar omkring mig ({secs}s)"
#, python-brace-format
msgid "Hey {what} let's be friends!"
msgstr "Hejsan {what} låt oss vara vänner"
#, python-brace-format
msgid "Associating to {what}"
msgstr "Ansluter till {what}"
#, python-brace-format
msgid "Yo {what}!"
msgstr ""
#, python-brace-format
msgid "Just decided that {mac} needs no WiFi!"
msgstr "Jag bestämde just att {mac} inte behöver WiFi!"
#, python-brace-format
msgid "Deauthenticating {mac}"
msgstr ""
#, python-brace-format
msgid "Kickbanning {mac}!"
msgstr ""
#, python-brace-format
msgid "Cool, we got {num} new handshake{plural}!"
msgstr "Lysande, vi har {num} ny handskakningar{plural}!"
msgid "Ops, something went wrong ... Rebooting ..."
msgstr "Hoppsan, någpt gick fel ... Startar om ..."
#, python-brace-format
msgid "Kicked {num} stations\n"
msgstr "Sparkade {num} stationer\n"
#, python-brace-format
msgid "Made {num} new friends\n"
msgstr "Har {num} nya vänner\n"
#, python-brace-format
msgid "Got {num} handshakes\n"
msgstr "Har {num} handskakningar\n"
msgid "Met 1 peer"
msgstr "Mötte 1 jämlike"
#, python-brace-format
msgid "Met {num} peers"
msgstr "Mötte {num} jämlikar"
#, 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 "Jag har pwnat för {duration} och sparkat ut {deauthed} klienter, Jag "
"har också träffat {associated} nya vänner och har skakat {handshakes} händer! "
"#pwnagotchi #pwnlog #pwnlife #hacktheplanet #skynet"
msgid "hours"
msgstr "timmar"
msgid "hour"
msgstr "timme"
msgid "minutes"
msgstr "minuter"
msgid "minute"
msgstr "minut"

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-10-03 16:47+0200\n"
"POT-Creation-Date: 2019-10-05 14:10+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"
@ -186,3 +186,21 @@ msgid ""
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
"#pwnlog #pwnlife #hacktheplanet #skynet"
msgstr ""
msgid "hours"
msgstr ""
msgid "minutes"
msgstr ""
msgid "seconds"
msgstr ""
msgid "hour"
msgstr ""
msgid "minute"
msgstr ""
msgid "second"
msgstr ""

View File

@ -1,10 +1,10 @@
import os
import hashlib
import time
import re
import os
from datetime import datetime
from pwnagotchi.voice import Voice
from pwnagotchi.mesh.peer import Peer
from file_read_backwards import FileReadBackwards
@ -125,17 +125,19 @@ class SessionParser(object):
self.duration = '%02d:%02d:%02d' % (hours, mins, secs)
self.duration_human = []
if hours > 0:
self.duration_human.append('%d hours' % hours)
self.duration_human.append('%d %s' % (hours, self.voice.hhmmss(hours, 'h')))
if mins > 0:
self.duration_human.append('%d minutes' % mins)
self.duration_human.append('%d %s' % (mins, self.voice.hhmmss(mins, 'm')))
if secs > 0:
self.duration_human.append('%d seconds' % secs)
self.duration_human.append('%d %s' % (secs, self.voice.hhmmss(secs, 's')))
self.duration_human = ', '.join(self.duration_human)
self.avg_reward /= (self.epochs if self.epochs else 1)
def __init__(self, path='/var/log/pwnagotchi.log'):
self.path = path
def __init__(self, config):
self.config = config
self.voice = Voice(lang=config['main']['lang'])
self.path = config['main']['log']
self.last_session = None
self.last_session_id = ''
self.last_saved_session_id = ''

View File

@ -86,7 +86,9 @@ class Advertiser(object):
logging.info("started advertiser thread (period:%s sid:%s) ..." % (str(self._period), self._me.session_id))
while self._running:
try:
sendp(self._frame, iface=self._iface, verbose=False, count=5, inter=self._period)
sendp(self._frame, iface=self._iface, verbose=False, count=1, inter=self._period)
except OSError as ose:
logging.warning("non critical issue while sending advertising packet: %s" % ose)
except Exception as e:
logging.exception("error")
time.sleep(self._period)

View File

@ -2,7 +2,6 @@ import _thread
import logging
import pwnagotchi
import pwnagotchi.version as version
import pwnagotchi.plugins as plugins
from pwnagotchi.mesh import get_identity
@ -24,7 +23,7 @@ class AsyncAdvertiser(object):
self._advertiser = Advertiser(
self._config['main']['iface'],
pwnagotchi.name(),
version.version,
pwnagotchi.version,
self._identity,
period=0.3,
data=self._config['personality'])

View File

@ -27,17 +27,34 @@ def load_from_file(filename):
return plugin_name, instance
def load_from_path(path):
def load_from_path(path, enabled=()):
global loaded
for filename in glob.glob(os.path.join(path, "*.py")):
name, plugin = load_from_file(filename)
if name in loaded:
raise Exception("plugin %s already loaded from %s" % (name, plugin.__file__))
elif not plugin.__enabled__:
elif name not in enabled:
# print("plugin %s is not enabled" % name)
pass
else:
loaded[name] = plugin
return loaded
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 custom ones
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]
on('loaded')

View File

@ -0,0 +1,63 @@
__author__ = '33197631+dadav@users.noreply.github.com'
__version__ = '1.0.0'
__name__ = 'auto-backup'
__license__ = 'GPL3'
__description__ = 'This plugin backups files when internet is availaible.'
from pwnagotchi.utils import StatusFile
import logging
import os
import subprocess
OPTIONS = dict()
READY = False
STATUS = StatusFile('/root/.auto-backup')
def on_loaded():
global READY
if 'files' not in OPTIONS or ('files' in OPTIONS and OPTIONS['files'] is None):
logging.error("AUTO-BACKUP: No files to backup.")
return
if 'interval' not in OPTIONS or ('interval' in OPTIONS and OPTIONS['interval'] is None):
logging.error("AUTO-BACKUP: Interval is not set.")
return
if 'commands' not in OPTIONS or ('commands' in OPTIONS and OPTIONS['commands'] is None):
logging.error("AUTO-BACKUP: No commands given.")
return
READY = True
logging.info("AUTO-BACKUP: Successfuly loaded.")
def on_internet_available(display, config, log):
global STATUS
if READY:
if STATUS.newer_then_days(OPTIONS['interval']):
return
files_to_backup = " ".join(OPTIONS['files'])
try:
logging.info("AUTO-BACKUP: Backing up ...")
display.set('status', 'Backing up ...')
display.update()
for cmd in 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")
process.wait()
if process.returncode > 0:
raise OSError(f"Command failed (rc: {process.returncode})")
logging.info("AUTO-BACKUP: backup done")
STATUS.update()
except OSError as os_e:
logging.info(f"AUTO-BACKUP: Error: {os_e}")
display.set('status', 'Backup done!')
display.update()

View File

@ -0,0 +1,56 @@
__author__ = 'evilsocket@gmail.com'
__version__ = '1.0.0'
__name__ = 'auto-update'
__license__ = 'GPL3'
__description__ = 'This plugin performs an "apt update && apt upgrade" when internet is availaible.'
import logging
import subprocess
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("AUTO-UPDATE: Interval is not set.")
return
READY = True
def on_internet_available(display, config, log):
global STATUS
if READY:
if STATUS.newer_then_days(OPTIONS['interval']):
return
try:
display.set('status', 'Updating ...')
display.update()
logging.info("AUTO-UPDATE: updating packages index ...")
update = subprocess.Popen('apt update -y', shell=True, stdin=None,
stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
update.wait()
logging.info("AUTO-UPDATE: updating packages ...")
upgrade = subprocess.Popen('apt upgrade -y', shell=True, stdin=None,
stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
upgrade.wait()
logging.info("AUTO-UPDATE: complete.")
STATUS.update()
except Exception as e:
logging.exception("AUTO-UPDATE ERROR")
display.set('status', 'Updated!')
display.update()

View File

@ -3,7 +3,6 @@ __version__ = '1.0.0'
__name__ = 'hello_world'
__license__ = 'GPL3'
__description__ = 'An example plugin for pwnagotchi that implements all the available callbacks.'
__enabled__ = False # IMPORTANT: set this to True to enable your plugin.
import logging
@ -12,13 +11,16 @@ 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()
# called when the plugin is loaded
def on_loaded():
logging.warning("WARNING: plugin %s should be disabled!" % __name__)
# called in manual mode when there's internet connectivity
def on_internet_available(config, log):
def on_internet_available(ui, config, log):
pass
@ -82,7 +84,7 @@ def on_ai_best_reward(agent, reward):
pass
# called when the AI got the best reward so far
# called when the AI got the worst reward so far
def on_ai_worst_reward(agent, reward):
pass

View File

@ -3,7 +3,6 @@ __version__ = '1.0.0'
__name__ = 'gps'
__license__ = 'GPL3'
__description__ = 'Save GPS coordinates whenever an handshake is captured.'
__enabled__ = True # set to false if you just don't use GPS
import logging
import json
@ -15,14 +14,14 @@ running = False
def on_loaded():
logging.info("GPS plugin loaded for %s" % device)
logging.info("gps plugin loaded for %s" % device)
def on_ready(agent):
global running
if os.path.exists(device):
logging.info("enabling GPS bettercap's module for %s" % device)
logging.info("enabling gps bettercap's module for %s" % device)
try:
agent.run('gps off')
except:

View File

@ -7,7 +7,6 @@ __version__ = '1.0.0'
__name__ = 'memtemp'
__license__ = 'GPL3'
__description__ = 'A plugin that will add a memory and temperature indicator'
__enabled__ = False
import struct

View File

@ -0,0 +1,84 @@
__author__ = '33197631+dadav@users.noreply.github.com'
__version__ = '1.0.0'
__name__ = 'onlinehashcrack'
__license__ = 'GPL3'
__description__ = 'This plugin automatically uploades handshakes to https://onlinehashcrack.com'
import os
import logging
import requests
READY = False
ALREADY_UPLOADED = None
OPTIONS = dict()
def on_loaded():
"""
Gets called when the plugin gets loaded
"""
global READY
global EMAIL
global ALREADY_UPLOADED
if not 'email' 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
try:
with open('/root/.ohc_uploads', 'r') as f:
ALREADY_UPLOADED = f.read().splitlines()
except OSError:
logging.warning('OHC: No upload-file found.')
ALREADY_UPLOADED = []
READY = True
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}
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
def on_internet_available(display, config, log):
"""
Called in manual mode when there's internet connectivity
"""
if READY:
handshake_dir = config['bettercap']['handshakes']
handshake_filenames = os.listdir(handshake_dir)
handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames]
handshake_new = set(handshake_paths) - set(ALREADY_UPLOADED)
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:
_upload_to_ohc(handshake)
ALREADY_UPLOADED.append(handshake)
with open('/root/.ohc_uploads', 'a') as f:
f.write(handshake + "\n")
logging.info(f"OHC: Successfuly uploaded {handshake}")
except requests.exceptions.RequestException:
pass
except OSError as os_e:
logging.error(f"OHC: Got the following error: {os_e}")

View File

@ -3,21 +3,19 @@ __version__ = '1.0.0'
__name__ = 'twitter'
__license__ = 'GPL3'
__description__ = 'This plugin creates tweets about the recent activity of pwnagotchi'
__enabled__ = True
import logging
from pwnagotchi.voice import Voice
UI = None
OPTIONS = dict()
def on_loaded():
logging.info("Twitter plugin loaded.")
logging.info("twitter plugin loaded.")
# called in manual mode when there's internet connectivity
def on_internet_available(config, log):
if config['twitter']['enabled'] and log.is_new() and log.handshakes > 0 and UI:
def on_internet_available(ui, config, log):
if log.is_new() and log.handshakes > 0:
try:
import tweepy
except ImportError:
@ -28,15 +26,15 @@ def on_internet_available(config, log):
picture = '/dev/shm/pwnagotchi.png'
UI.on_manual_mode(log)
UI.update(force=True)
UI.image().save(picture, 'png')
UI.set('status', 'Tweeting...')
UI.update(force=True)
ui.on_manual_mode(log)
ui.update(force=True)
ui.image().save(picture, 'png')
ui.set('status', 'Tweeting...')
ui.update(force=True)
try:
auth = tweepy.OAuthHandler(config['twitter']['consumer_key'], config['twitter']['consumer_secret'])
auth.set_access_token(config['twitter']['access_token_key'], config['twitter']['access_token_secret'])
auth = tweepy.OAuthHandler(OPTIONS['consumer_key'], OPTIONS['consumer_secret'])
auth.set_access_token(OPTIONS['access_token_key'], OPTIONS['access_token_secret'])
api = tweepy.API(auth)
tweet = Voice(lang=config['main']['lang']).on_log_tweet(log)
@ -46,9 +44,3 @@ def on_internet_available(config, log):
logging.info("tweeted: %s" % tweet)
except Exception as e:
logging.exception("error while tweeting")
def on_ui_setup(ui):
# need that object
global UI
UI = ui

View File

@ -12,7 +12,6 @@ __version__ = '1.0.0'
__name__ = 'ups_lite'
__license__ = 'GPL3'
__description__ = 'A plugin that will add a voltage indicator for the UPS Lite v1.1'
__enabled__ = False
import struct

View File

@ -0,0 +1,83 @@
__author__ = '33197631+dadav@users.noreply.github.com'
__version__ = '1.0.0'
__name__ = 'wpa-sec'
__license__ = 'GPL3'
__description__ = 'This plugin automatically uploades handshakes to https://wpa-sec.stanev.org'
import os
import logging
import requests
READY = False
ALREADY_UPLOADED = None
def on_loaded():
"""
Gets called when the plugin gets loaded
"""
global READY
global API_KEY
global ALREADY_UPLOADED
if not 'api_key' 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
try:
with open('/root/.wpa_sec_uploads', 'r') as f:
ALREADY_UPLOADED = f.read().splitlines()
except OSError:
logging.warning('WPA_SEC: No upload-file found.')
ALREADY_UPLOADED = []
READY = True
def _upload_to_wpasec(path, timeout=30):
"""
Uploads the file to wpa-sec.stanev.org
"""
with open(path, 'rb') as file_to_upload:
headers = {'key': OPTIONS['api_key']}
payload = {'file': file_to_upload}
try:
result = requests.post('https://wpa-sec.stanev.org/?submit',
headers=headers,
files=payload,
timeout=timeout)
if ' already submitted' in result.text:
logging.warning(f"{path} was already submitted.")
except requests.exceptions.RequestException as e:
logging.error(f"WPA_SEC: Got an exception while uploading {path} -> {e}")
raise e
def on_internet_available(display, config, log):
"""
Called in manual mode when there's internet connectivity
"""
if READY:
handshake_dir = config['bettercap']['handshakes']
handshake_filenames = os.listdir(handshake_dir)
handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames]
handshake_new = set(handshake_paths) - set(ALREADY_UPLOADED)
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)
ALREADY_UPLOADED.append(handshake)
with open('/root/.wpa_sec_uploads', 'a') as f:
f.write(handshake + "\n")
logging.info(f"WPA_SEC: Successfuly uploaded {handshake}")
except requests.exceptions.RequestException:
pass
except OSError as os_e:
logging.error(f"WPA_SEC: Got the following error: {os_e}")

View File

@ -102,12 +102,15 @@ class Display(View):
def _is_papirus(self):
return self._display_type in ('papirus', 'papi')
def _is_waveshare1(self):
def _is_waveshare_v1(self):
return self._display_type in ('waveshare_1', 'ws_1', 'waveshare1', 'ws1')
def _is_waveshare2(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 _init_display(self):
if self._is_inky():
logging.info("initializing inky display")
@ -124,7 +127,7 @@ class Display(View):
self._display.clear()
self._render_cb = self._papirus_render
elif self._is_waveshare1():
elif self._is_waveshare_v1():
logging.info("initializing waveshare v1 display")
from pwnagotchi.ui.waveshare.v1.epd2in13 import EPD
self._display = EPD()
@ -133,7 +136,7 @@ class Display(View):
self._display.init(self._display.lut_partial_update)
self._render_cb = self._waveshare_render
elif self._is_waveshare2():
elif self._is_waveshare_v2():
logging.info("initializing waveshare v2 display")
from pwnagotchi.ui.waveshare.v2.waveshare import EPD
self._display = EPD()
@ -149,6 +152,18 @@ class Display(View):
self.on_render(self._on_view_rendered)
def clear(self):
if self._display is None:
logging.error("no display object created")
elif self._is_inky():
self._display.Clear()
elif self._is_papirus():
self._display.clear()
elif self._is_waveshare():
self._display.Clear(WHITE)
else:
logging.critical("unknown display type %s" % self._display_type)
def _inky_render(self):
if self._display_color != 'mono':
display_colors = 3
@ -175,7 +190,10 @@ class Display(View):
])
self._display.set_image(img_buffer)
self._display.show()
try:
self._display.show()
except:
print("")
def _papirus_render(self):
self._display.display(self._canvas)
@ -183,9 +201,9 @@ class Display(View):
def _waveshare_render(self):
buf = self._display.getbuffer(self._canvas)
if self._is_waveshare1():
if self._is_waveshare_v1():
self._display.display(buf)
elif self._is_waveshare2():
elif self._is_waveshare_v2():
self._display.displayPartial(buf)
def image(self):

View File

@ -4,7 +4,7 @@ import time
import logging
from PIL import Image, ImageDraw
import core
import pwnagotchi.utils as utils
import pwnagotchi.plugins as plugins
from pwnagotchi.voice import Voice
@ -86,8 +86,9 @@ class View(object):
'face': Text(value=faces.SLEEP, position=face_pos, color=BLACK, font=fonts.Huge),
'friend_face': Text(value=None, position=(0, 90), font=fonts.Bold, color=BLACK),
'friend_name': Text(value=None, position=(40, 93), font=fonts.BoldSmall, color=BLACK),
'friend_face': Text(value=None, position=(0, (self._height * 0.88) - 15), font=fonts.Bold, color=BLACK),
'friend_name': Text(value=None, position=(40, (self._height * 0.88) - 13), font=fonts.BoldSmall,
color=BLACK),
'name': Text(value='%s>' % 'pwnagotchi', position=name_pos, color=BLACK, font=fonts.Bold),
@ -166,7 +167,7 @@ class View(object):
self.set('channel', '-')
self.set('aps', "%d" % log.associated)
self.set('shakes', '%d (%s)' % (log.handshakes, \
core.total_unique_handshakes(self._config['bettercap']['handshakes'])))
utils.total_unique_handshakes(self._config['bettercap']['handshakes'])))
self.set_closest_peer(log.last_peer)
def is_normal(self):

View File

@ -1,6 +1,10 @@
import yaml
import os
from datetime import datetime
import logging
import glob
import os
import time
import subprocess
import yaml
# https://stackoverflow.com/questions/823196/yaml-merge-in-python
@ -40,3 +44,55 @@ def setup_logging(args, config):
console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)
root.addHandler(console_handler)
def secs_to_hhmmss(secs):
mins, secs = divmod(secs, 60)
hours, mins = divmod(mins, 60)
return '%02d:%02d:%02d' % (hours, mins, secs)
def total_unique_handshakes(path):
expr = os.path.join(path, "*.pcap")
return len(glob.glob(expr))
def iface_channels(ifname):
channels = []
output = subprocess.getoutput("/sbin/iwlist %s freq" % ifname)
for line in output.split("\n"):
line = line.strip()
if line.startswith("Channel "):
channels.append(int(line.split()[1]))
return channels
def led(on=True):
with open('/sys/class/leds/led0/brightness', 'w+t') as fp:
fp.write("%d" % (0 if on is True else 1))
def blink(times=1, delay=0.3):
for t in range(0, times):
led(True)
time.sleep(delay)
led(False)
time.sleep(delay)
led(True)
class StatusFile(object):
def __init__(self, path):
self._path = path
self._updated = None
if os.path.exists(path):
self._updated = datetime.fromtimestamp(os.path.getmtime(path))
def newer_then_days(self, days):
return self._updated is not None and (datetime.now() - self._updated).days < days
def update(self, data=None):
self._updated = datetime.now()
with open(self._path, 'w') as fp:
fp.write(str(self._updated) if data is None else data)

View File

@ -1 +0,0 @@
version = '1.0.0plz2'

View File

@ -138,5 +138,21 @@ class Voice:
associated=log.associated,
handshakes=log.handshakes)
def custom(self, text):
return self._(text)
def hhmmss(self, count, fmt):
if count > 1:
# plural
if fmt == "h":
return self._("hours")
if fmt == "m":
return self._("minutes")
if fmt == "s":
return self._("seconds")
else:
# sing
if fmt == "h":
return self._("hour")
if fmt == "m":
return self._("minute")
if fmt == "s":
return self._("second")
return fmt