Merge branch 'master' into display-fix
This commit is contained in:
commit
32f87437ec
.DEREK.yml
.github
.gitignore.travis.ymlCODE_OF_CONDUCT.mdCONTRIBUTING.mdREADME.mdbuilder
docs
scripts
backup.shcreate_sibling.shlanguage.shpreview.pyrelease.shupdate_pwnagotchi.shwin_connection_share.ps1
sdcard/rootfs/root/pwnagotchi
config.yml
scripts
bettercap
core
main.pypwnagotchi
__init__.pyagent.pylog.py
ai
bettercap.pylocale
de/LC_MESSAGES
el/LC_MESSAGES
fr/LC_MESSAGES
it/LC_MESSAGES
mk/LC_MESSAGES
nl/LC_MESSAGES
se/LC_MESSAGES
voice.potmesh
plugins
__init__.py
default
ui
utils.pyversion.pyvoice.py@ -3,6 +3,7 @@ maintainers:
|
||||
- caquino
|
||||
- dadav
|
||||
- justin-p
|
||||
- hexwaxwing
|
||||
|
||||
features:
|
||||
- comments
|
||||
|
1
.github/CODEOWNERS
vendored
Normal file
1
.github/CODEOWNERS
vendored
Normal file
@ -0,0 +1 @@
|
||||
evilsocket
|
36
.github/ISSUE_TEMPLATE.md
vendored
Normal file
36
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
<!--- Provide a general summary of the issue in the Title above -->
|
||||
|
||||
## Expected Behaviour
|
||||
<!--- If you're describing a bug, tell us what should happen -->
|
||||
<!--- If you're suggesting a change/improvement, tell us how it should work -->
|
||||
|
||||
## Current Behaviour
|
||||
<!--- If describing a bug, tell us what happens instead of the expected behavior -->
|
||||
<!--- If suggesting a change/improvement, explain the difference from current behavior -->
|
||||
|
||||
## Possible Solution
|
||||
<!--- Not obligatory, but suggest a fix/reason for the bug, -->
|
||||
<!--- or ideas how to implement the addition or change -->
|
||||
|
||||
## Steps to Reproduce (for bugs)
|
||||
<!--- Provide a link to a live example, or an unambiguous set of steps to -->
|
||||
<!--- reproduce this bug. Include code to reproduce, if relevant -->
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
4.
|
||||
|
||||
## Context
|
||||
<!--- How has this issue affected you? What are you trying to accomplish? -->
|
||||
<!--- Providing context helps us come up with a solution that is most useful in the real world -->
|
||||
|
||||
## Your Environment
|
||||
|
||||
- [ ] You're using the official images
|
||||
|
||||
- [ ] You're using a raspberry pi 0
|
||||
|
||||
- [ ] You're using a supported LCD
|
||||
|
||||
|
||||
<!--- Include as many relevant details about the environment you experienced the bug in -->
|
32
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
32
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: "[BUG]"
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. ...
|
||||
2. ...
|
||||
3. ...
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Environment (please complete the following information):**
|
||||
- Pwnagotchi version
|
||||
- OS version
|
||||
- Type of hardware
|
||||
- Any additional hardware used
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
10
.github/ISSUE_TEMPLATE/other.md
vendored
Normal file
10
.github/ISSUE_TEMPLATE/other.md
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
---
|
||||
name: Other
|
||||
about: Describe this issue template's purpose here.
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
30
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
30
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
<!--- Provide a general summary of your changes in the Title above -->
|
||||
|
||||
## Description
|
||||
<!--- Describe your changes in detail -->
|
||||
|
||||
## Motivation and Context
|
||||
<!--- Why is this change required? What problem does it solve? -->
|
||||
<!--- If it fixes an open issue, please link to the issue here. -->
|
||||
- [ ] I have raised an issue to propose this change ([required](https://github.com/evilsocket/pwnagotchi/blob/master/CONTRIBUTING.md))
|
||||
|
||||
|
||||
## How Has This Been Tested?
|
||||
<!--- Please describe in detail how you tested your changes. -->
|
||||
<!--- Include details of your testing environment, and the tests you ran to -->
|
||||
<!--- see how your change affects other areas of the code, etc. -->
|
||||
|
||||
## Types of changes
|
||||
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
|
||||
|
||||
## Checklist:
|
||||
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
|
||||
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
|
||||
- [ ] My code follows the code style of this project.
|
||||
- [ ] My change requires a change to the documentation.
|
||||
- [ ] I have updated the documentation accordingly.
|
||||
- [ ] I've read the [CONTRIBUTION](https://github.com/evilsocket/pwnagotchi/blob/master/CONTRIBUTING.md) guide
|
||||
- [ ] I have signed-off my commits with `git commit -s`
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,10 +1,12 @@
|
||||
*.img
|
||||
*.img.bmap
|
||||
*.pcap
|
||||
*.po~
|
||||
__pycache__
|
||||
_backups
|
||||
_emulation
|
||||
_utils
|
||||
config.laptop.yml
|
||||
.idea
|
||||
tmp
|
||||
packer_cache
|
||||
output-pwnagotchi
|
||||
|
25
.travis.yml
25
.travis.yml
@ -1,52 +1,41 @@
|
||||
dist: bionic
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.x
|
||||
|
||||
env:
|
||||
global:
|
||||
- LANG=C
|
||||
- LC_ALL=C
|
||||
|
||||
deploy:
|
||||
provider: releases
|
||||
api_key:
|
||||
secure: "vBUokTv94n8s65STUgTiD6I0Iy8KXbBRvQUrAof8XG+U4ZMsH5PmDTpS+wz+SaxI6o0PRkfyOiPVdARhiKAFnfatG3q9EHllMQwqRR2YIju51A3aCxgEJ5uWDoybwQdipERUMMYwUO/8XZaRRpwFD2bdQBFWkBtQyMcAkrEL8BXckwQQ531oDN2hK5gAiTllqsOswV2idwUlBRU9jOtStzff+UgUYsp/ZebsRodyOYkEB2Ev15yARo2HTXbyZ2icwHPtMbx5zmNUSRtxs9a4hfzaK3m6ctK8qLYYUdQvXub/ruuACapdw4Ez88LY1agTecbZhFYmJzv8oANH1e4VUI4owuHnZCpU6LRutS4wOhglrkOrGo6lSUlJeA+RtQjyjBugjej9DDtDyyIlRU1ZaBF3qWR9N5EXKuquf0olOfmUR67ap1NykE9VUpzkYjkoVRTiPs/e2onM/nRNOvAQcIt75FD13u+Y/DcYQ8r7KpMIu1HNdtbVx8gMeq76bRhP1YdDg2jm+DdJ21KWjf5QHsbyoXDfJzdKlCloLIlAU3EPJhMoXsnNzre0/FXeUl6dfteR1axNS6U7e/vKsQ9rlUFZWIQaeVPjfXmFKblNNVQ5uFrrsB/EGHcJl7IUx5fvcRT5hMMNwC660YxVkBXDbRb5fxMW5/+K0BOi9cP6en8="
|
||||
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
|
||||
on:
|
||||
tags: true
|
||||
repo: evilsocket/pwnagotchi
|
||||
|
||||
branches:
|
||||
only:
|
||||
- /^v[0-9]+\.[0-9]+\.[0-9]+[A-Za-z0-9]+?$/
|
||||
|
||||
- "/^v[0-9]+\\.[0-9]+\\.[0-9]+[A-Za-z0-9]+?$/"
|
||||
cache:
|
||||
apt: true
|
||||
|
||||
before_script:
|
||||
- wget https://download.qemu.org/qemu-4.1.0.tar.xz
|
||||
- tar xvJf qemu-4.1.0.tar.xz
|
||||
- cd qemu-4.1.0
|
||||
- ./configure --target-list=arm-softmmu
|
||||
- "./configure --target-list=arm-softmmu"
|
||||
- make -j$(nproc)
|
||||
- sudo make install
|
||||
- cd $TRAVIS_BUILD_DIR
|
||||
- sudo apt-get -y update
|
||||
- sudo apt-get -y update || true
|
||||
- sudo apt-get -y install qemu-user-static binfmt-support bmap-tools kpartx
|
||||
- sudo update-binfmts --display
|
||||
|
||||
script:
|
||||
- sudo make clean
|
||||
- sudo -E env "PATH=$PATH" make install
|
||||
- sudo make image -e PWN_HOSTNAME=pwnagotchi VERSION=$TRAVIS_TAG
|
||||
|
||||
before_deploy:
|
||||
# Set up git user name and tag this commit
|
||||
- git config --local user.name "evilsocket"
|
||||
- git config --local user.email "evilsocket@gmail.com"
|
||||
- export TRAVIS_TAG=${TRAVIS_TAG:-$(date +'%Y%m%d%H%M%S')-$(git log --format=%h -1)}
|
||||
- git tag $TRAVIS_TAG
|
||||
notifications:
|
||||
slack:
|
||||
secure: aovN87lswg+TTLobxJpevC0p2F4omTAlsOzeKqLysRW55o5rRhRC1SgwRkWUl19yr49nsyffwmv/b7OcyQiWIVnz1bxxE9XOKP8zgRMA/bKKcyAcPktPqHXsALIQDseXyl0kz7fwdkRWg0UC2HpKqi5koAhmBYTX/fbzieyeHCbcQ7lbFfVFIepE1401y9m1IqUHcHuGfFhMvTaSDIpXrDXnWdA8+gDAl0HKJv41MIsgmffbh/QhD2jLBWzItjxFC3llmNfy88pnzCk0+HBMY/4272LXb0czX7et5HJeM74oxPqkb3aKXFxZgNaDl7cYdV+kzj9dfKUk47hAqwbxlirit5WvHI1Br1VyA90+PFvcC/p41J8gCv0IlcB5vjWN8NKWA1J+Y1F+KvrujMvGtgd0foHZvaSutuRODhI1cBh5rYAiLCroRSlvKMw3IJRyCRstYgUlMIJ3cI2Ova/kU44KtDVmjT9VE/pPkhkHBPvcYThL6skZTdl19E/RlormLu3XObG1aHLZ+Znxe/aL7tWHi0KMOlpy+TMDdps4go7URnJ8yitHtIvU/zMtBrztIwN0Oy2JLKXrS5qIijmRAkBLxe0NxuG01DYFzEO3KtnRirP4uSe3QcrjyP4sqPrVhrjl3TR6gwg8V1juvDXB4e2h8yCpaUW5AdSBOlx9riY=
|
||||
|
76
CODE_OF_CONDUCT.md
Normal file
76
CODE_OF_CONDUCT.md
Normal 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
|
71
CONTRIBUTING.md
Normal file
71
CONTRIBUTING.md
Normal file
@ -0,0 +1,71 @@
|
||||
## Contributing
|
||||
|
||||
### Guidelines
|
||||
|
||||
Here are a few guidelines for contributing:
|
||||
|
||||
* If you would like to contribute to the codebase **please raise an issue to propose the change**
|
||||
* Do not mix feature changes or fixes with refactoring - it makes the code harder to review and means there is more for the maintainers (with limited time) to test
|
||||
|
||||
* If you have found a bug please raise an issue and fill out the whole template.
|
||||
* If the documentation can be improved / translated etc please raise an issue to discuss.
|
||||
* Please always provide a summary of what you changed, how you did it and how it can be tested.
|
||||
|
||||
### License
|
||||
|
||||
This project is licensed under the GPL3 License.
|
||||
|
||||
#### Sign your work
|
||||
|
||||
The sign-off is a simple line at the end of the explanation for a patch. Your
|
||||
signature certifies that you wrote the patch or otherwise have the right to pass
|
||||
it on as an open-source patch. The rules are pretty simple: if you can certify
|
||||
the below (from [developercertificate.org](http://developercertificate.org/)):
|
||||
|
||||
```
|
||||
Developer Certificate of Origin
|
||||
Version 1.1
|
||||
|
||||
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
|
||||
1 Letterman Drive
|
||||
Suite D4700
|
||||
San Francisco, CA, 94129
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this
|
||||
license document, but changing it is not allowed.
|
||||
|
||||
Developer's Certificate of Origin 1.1
|
||||
|
||||
By making a contribution to this project, I certify that:
|
||||
|
||||
(a) The contribution was created in whole or in part by me and I
|
||||
have the right to submit it under the open source license
|
||||
indicated in the file; or
|
||||
|
||||
(b) The contribution is based upon previous work that, to the best
|
||||
of my knowledge, is covered under an appropriate open source
|
||||
license and I have the right under that license to submit that
|
||||
work with modifications, whether created in whole or in part
|
||||
by me, under the same open source license (unless I am
|
||||
permitted to submit under a different license), as indicated
|
||||
in the file; or
|
||||
|
||||
(c) The contribution was provided directly to me by some other
|
||||
person who certified (a), (b) or (c) and I have not modified
|
||||
it.
|
||||
|
||||
(d) I understand and agree that this project and the contribution
|
||||
are public and that a record of the contribution (including all
|
||||
personal information I submit with it, including my sign-off) is
|
||||
maintained indefinitely and may be redistributed consistent with
|
||||
this project or the open source license(s) involved.
|
||||
```
|
||||
|
||||
Then you just add a line to every git commit message:
|
||||
|
||||
Signed-off-by: Joe Smith <joe.smith@email.com>
|
||||
|
||||
If you set your `user.name` and `user.email` git configs, you can sign your
|
||||
commit automatically with `git commit -s`.
|
||||
|
||||
* Please sign your commits with `git commit -s` so that commits are traceable.
|
226
README.md
226
README.md
@ -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>
|
||||
|
||||
@ -18,227 +19,26 @@ Instead of playing [Super Mario or Atari games](https://becominghuman.ai/getting
|
||||
|
||||
Multiple units can talk to each other, advertising their own presence using a parasite protocol I've built on top of the existing dot11 standard, by broadcasting custom information elements. Over time, two or more units learn to cooperate if they detect each other's presence, by dividing the available channels among them.
|
||||
|
||||

|
||||
|
||||
Depending on the status of the unit, several states and states transitions are configurable and represented on the display as different moods, expressions and sentences.
|
||||
|
||||
If instead you just want to use your own parameters and save battery and CPU cycles, you can disable the AI in `config.yml` and enjoy an automated deauther, WPA handshake sniffer and portable bettercap + webui dedicated hardware.
|
||||
|
||||
**NOTE:** The software **requires at least bettercap v2.25**.
|
||||
|
||||

|
||||
|
||||
## Why
|
||||
|
||||
For hackers to learn reinforcement learning, WiFi networking and have an excuse to take a walk more often. And **it's cute as f---**.
|
||||
|
||||
## Documentation
|
||||
|
||||
**THIS IS STILL ALPHA STAGE SOFTWARE, IF YOU DECIDE TO TRY TO USE IT, YOU ARE ON YOUR OWN, NO SUPPORT WILL BE PROVIDED, NEITHER FOR INSTALLATION OR FOR BUGS**
|
||||
- [About the Project](https://github.com/evilsocket/pwnagotchi/blob/master/docs/about.md)
|
||||
- [How to Install](https://github.com/evilsocket/pwnagotchi/blob/master/docs/install.md)
|
||||
- [Configuration](https://github.com/evilsocket/pwnagotchi/blob/master/docs/configure.md)
|
||||
- [Usage](https://github.com/evilsocket/pwnagotchi/blob/master/docs/usage.md)
|
||||
- [Plugins](https://github.com/evilsocket/pwnagotchi/blob/master/docs/plugins.md)
|
||||
- [Developement](https://github.com/evilsocket/pwnagotchi/blob/master/docs/dev.md)
|
||||
- [FAQ](https://github.com/evilsocket/pwnagotchi/blob/master/docs/faq.md)
|
||||
|
||||
However, there's [a Slack channel](https://join.slack.com/t/pwnagotchi/shared_invite/enQtNzc4NzY3MDE2OTAzLTg5NmNmNDJiMDM3ZWFkMWUwN2Y5NDk0Y2JlZWZjODlhMmRhNDZiOGMwYjJhM2UzNzA3YjA5NjJmZGY5NGI5NmI).
|
||||
### Hardware
|
||||
## Links
|
||||
|
||||
- Raspberry Pi Zero W
|
||||
- A decent power bank (with 1500 mAh you get ~2 hours with AI on)
|
||||
|
||||
#### Display (optional)
|
||||
|
||||
The display is optional if you connect to `usb0` (by using the data port on the unit) and point your browser to the web ui (see config.yml).
|
||||
|
||||
The supported models are:
|
||||
|
||||
- [Waveshare eInk Display (both V1 and V2)](https://www.waveshare.com/2.13inch-e-paper-hat.htm)
|
||||
- [Pimoroni Inky pHAT](https://shop.pimoroni.com/products/inky-phat)
|
||||
- [PaPiRus eInk Screen](https://uk.pi-supply.com/products/papirus-zero-epaper-screen-phat-pi-zero)
|
||||
|
||||
The only kind of displays supported are the ones listed above, but we are always happy to receive pull requests supporting new displays.
|
||||
|
||||
You need to configure the display type in `config.yml` where you can find `ui.display.type`.
|
||||
|
||||
One thing to note, not all displays are created equaly, TFT displays for example work similar to an HDMI display, and they are not supported, currently all the displays supported are I2C displays.
|
||||
|
||||
### Color and Black & White displays
|
||||
|
||||
Some of the supported displays support Black & White and Coloured versions, one common question is regarding refresh speed of said displays.
|
||||
|
||||
Color displays have a much slower refresh rate, in some cases it can take up to 15 seconds, if slow refresh rates is something that you want to avoid we advise you to use Black & White displays
|
||||
|
||||
### FPS
|
||||
|
||||
You can configure the refresh interval of the display on config.yml, we advise to use a slow refresh to not shorten the lifetime of your display.
|
||||
|
||||
Another option is to change fps to 0, which will only refresh when changes are made to the screen.
|
||||
|
||||
### 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).
|
||||
|
||||
**Do not try with Kali on the Raspberry Pi 0 W, it is compiled without hardware floating point support and TensorFlow is simply not available for it, use Raspbian.**
|
||||
|
||||
#### Automatically create an image
|
||||
|
||||
You can use the `scripts/create_sibling.sh` script to create an - ready to flash - rasbian image with pwnagotchi.
|
||||
|
||||
```shell
|
||||
usage: ./scripts/create_sibling.sh [OPTIONS]
|
||||
|
||||
Options:
|
||||
-n <name> # Name of the pwnagotchi (default: pwnagotchi)
|
||||
-i <file> # Provide the path of an already downloaded raspbian image
|
||||
-o <file> # Name of the img-file (default: pwnagotchi.img)
|
||||
-s <size> # Size which should be added to second partition (in Gigabyte) (default: 4)
|
||||
-v <version> # Version of raspbian (Supported: latest; default: latest)
|
||||
-p # Only run provisioning (assumes the image is already mounted)
|
||||
-d # Only run dependencies checks
|
||||
-h # Show this help
|
||||
```
|
||||
|
||||
#### 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` 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.
|
||||
|
||||
#### Update your pwnagotchi
|
||||
|
||||
You can use the `scripts/update_pwnagotchi.sh` script to update to the most recent version of pwnagotchi.
|
||||
|
||||
```shell
|
||||
usage: ./update_pwnagitchi.sh [OPTIONS]
|
||||
|
||||
Options:
|
||||
-v # Version to update to, can be a branch or commit. (default: master)
|
||||
-u # Url to clone from. (default: https://github.com/evilsocket/pwnagotchi)
|
||||
-m # Mode to restart to. (Supported: auto manual; default: auto)
|
||||
-b # Backup the current pwnagotchi config.
|
||||
-r # Restore the current pwnagotchi config. -b will be enabled.
|
||||
-h # Shows this help. Shows this help.
|
||||
|
||||
```
|
||||
|
||||
### UI
|
||||
|
||||
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).
|
||||
|
||||

|
||||
|
||||
* **CH**: Current channel the unit is operating on or `*` when hopping on all channels.
|
||||
* **APS**: Number of access points on the current channel and total visible access points.
|
||||
* **UP**: Time since the unit has been activated.
|
||||
* **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.
|
||||
|
||||
#### Languages
|
||||
|
||||
Pwnagotchi is able to speak multiple languages!! Currently supported are:
|
||||
|
||||
* **english** (default)
|
||||
* german
|
||||
* dutch
|
||||
* greek
|
||||
* macedonian
|
||||
* italian
|
||||
* french
|
||||
|
||||
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:
|
||||
|
||||
```shell
|
||||
./scripts/language.sh add it
|
||||
# Now make your changes to the file
|
||||
# sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/locale/it/LC_MESSAGES/voice.po
|
||||
./scripts/language.sh compile it
|
||||
# DONE
|
||||
```
|
||||
|
||||
If you changed the `voice.py`- File, the translations need an update. Do it like this:
|
||||
|
||||
```shell
|
||||
./scripts/language.sh update it
|
||||
# Now make your changes to the file (changed lines are marked with "fuzzy")
|
||||
# sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/locale/it/LC_MESSAGES/voice.po
|
||||
./scripts/language.sh compile it
|
||||
# DONE
|
||||
```
|
||||
|
||||
Now you can use the `preview.py`-script to preview the changes:
|
||||
|
||||
```shell
|
||||
./scripts/preview.py --lang it --display ws2 --port 8080 &
|
||||
./scripts/preview.py --lang it --display inky --port 8081 &
|
||||
# Now open http://localhost:8080 and http://localhost:8081
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
Here's as an example the GPS plugin:
|
||||
|
||||
```python
|
||||
__author__ = 'evilsocket@gmail.com'
|
||||
__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 core
|
||||
import json
|
||||
import os
|
||||
|
||||
device = '/dev/ttyUSB0'
|
||||
speed = 19200
|
||||
running = False
|
||||
|
||||
|
||||
def on_loaded():
|
||||
core.log("GPS plugin loaded for %s" % device)
|
||||
|
||||
|
||||
def on_ready(agent):
|
||||
global running
|
||||
|
||||
if os.path.exists(device):
|
||||
core.log("enabling GPS bettercap's module for %s" % device)
|
||||
try:
|
||||
agent.run('gps off')
|
||||
except:
|
||||
pass
|
||||
|
||||
agent.run('set gps.device %s' % device)
|
||||
agent.run('set gps.speed %d' % speed)
|
||||
agent.run('gps on')
|
||||
running = True
|
||||
else:
|
||||
core.log("no GPS detected")
|
||||
|
||||
|
||||
def on_handshake(agent, filename, access_point, client_station):
|
||||
if running:
|
||||
info = agent.session()
|
||||
gps = info['gps']
|
||||
gps_filename = filename.replace('.pcap', '.gps.json')
|
||||
|
||||
core.log("saving GPS to %s (%s)" % (gps_filename, gps))
|
||||
with open(gps_filename, 'w+t') as fp:
|
||||
json.dump(gps, fp)
|
||||
```
|
||||
|
||||
### Random Info
|
||||
|
||||
- `hostname` sets the unit name.
|
||||
- At first boot, each unit generates a unique RSA keypair that can be used to authenticate advertising packets.
|
||||
- **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`.
|
||||
- [Project Slack](https://join.slack.com/t/pwnagotchi/shared_invite/enQtNzc4NzY3MDE2OTAzLTg5NmNmNDJiMDM3ZWFkMWUwN2Y5NDk0Y2JlZWZjODlhMmRhNDZiOGMwYjJhM2UzNzA3YjA5NjJmZGY5NGI5NmI)
|
||||
- [Project Twitter](https://twitter.com/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.
|
@ -172,7 +172,7 @@
|
||||
dest: /usr/bin/memusage
|
||||
mode: 0755
|
||||
content: |
|
||||
#!/usr/bin/env
|
||||
#!/usr/bin/env bash
|
||||
free -m | awk '/Mem/ { printf( "%d %", $3 / $2 * 100 + 0.5 ) }'
|
||||
|
||||
- name: create monstart script
|
||||
|
23
docs/about.md
Normal file
23
docs/about.md
Normal file
@ -0,0 +1,23 @@
|
||||
# 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.
|
||||
|
||||

|
||||
|
||||
Instead of merely playing [Super Mario or Atari games](https://becominghuman.ai/getting-mario-back-into-the-gym-setting-up-super-mario-bros-in-openais-gym-8e39a96c1e41?gi=c4b66c3d5ced) like most reinforcement learning based "AI" *(yawn)*, Pwnagotchi tunes [its own parameters](https://github.com/evilsocket/pwnagotchi/blob/master/sdcard/rootfs/root/pwnagotchi/config.yml#L54) over time to **get better at pwning WiFi things** in the environments you expose it to.
|
||||
|
||||
**Keep in mind:** Unlike the usual RL simulations, Pwnagotchi actually learns over time. Time for a Pwnagotchi is measured in epochs; a single epoch can last from a few seconds to minutes, depending on how many access points and client stations are visible. Do not expect your Pwnagotchi to perform amazingly well at the very beginning, as it will be [exploring](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752) several combinations of [key parameters](https://github.com/evilsocket/pwnagotchi/blob/master/docs/usage.md#training-the-ai) to determine ideal adjustments for pwning the particular environment you are exposing it to during its beginning epochs ... but **definitely listen to your pwnagotchi when it tells you it's bored!** Bring it into novel WiFi environments with you and have it observe new networks and capture new handshakes—and you'll see. :)
|
||||
|
||||
Multiple units within close physical proximity can "talk" to each other, advertising their own presence to each other by broadcasting custom information elements using a parasite protocol I've built on top of the existing dot11 standard. Over time, two or more units trained together will learn to cooperate upon detecting each other's presence by dividing the available channels among them for optimal pwnage.
|
||||
|
||||

|
||||
|
||||
[Depending on the status of the unit](), several states and states transitions are configurable and represented on the display as different moods, expressions and sentences. Pwnagotchi speaks [many languages](https://github.com/evilsocket/pwnagotchi/blob/master/docs/configure.md#configuration), too!
|
||||
|
||||
Of course, it is possible to run your Pwnagotchi with the AI disabled (configurable in `config.yml`). Why might you want to do this? Perhaps you simply want to use your own fixed parameters (instead of letting the AI decide for you), or maybe you want to save battery and CPU cycles, or maybe it's just you have strong concerns about aiding and abetting baby Skynet. Whatever your particular reasons may be: an AI-disabled Pwnagotchi is still a simple and very effective automated deauther, WPA handshake sniffer, and portable [bettercap](https://www.bettercap.org/) + [webui](https://github.com/evilsocket/pwnagotchi/blob/master/docs/usage.md#bettercaps-web-ui) dedicated hardware.
|
||||
|
||||
|
||||
## 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.
|
56
docs/configure.md
Normal file
56
docs/configure.md
Normal file
@ -0,0 +1,56 @@
|
||||
# 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.
|
||||
|
||||
You'll need to configure it with a static IP address:
|
||||
|
||||
- IP: `10.0.0.1`
|
||||
- Netmask: `255.255.255.0`
|
||||
- Gateway: `10.0.0.1`
|
||||
- DNS (if required): `8.8.8.8` (or whatever)
|
||||
|
||||
If everything's been configured properly, you will now be able to `ping` both `10.0.0.2` or `pwnagotchi.local` (if you haven't customized the hostname yet).
|
||||
|
||||
You can now connect to your unit using SSH:
|
||||
|
||||
```bash
|
||||
ssh pi@10.0.0.2
|
||||
```
|
||||
|
||||
The default password is `raspberry`, you should change it as soon as you log in for the first time by issuing the `passwd`command and selecting a new and more complex passphrase.
|
||||
|
||||
Moreover, it is recommended that you copy your SSH public key among the unit's authorized ones, so you can directly log in without entering a password:
|
||||
|
||||
```bash
|
||||
ssh-copy-id -i ~/.ssh/id_rsa.pub pi@10.0.0.2
|
||||
```
|
||||
|
||||
## 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.
|
||||
|
||||
## Language Selection
|
||||
|
||||
For instance, you can change `main.lang` to one of the supported languages:
|
||||
|
||||
- **english** (default)
|
||||
- german
|
||||
- dutch
|
||||
- greek
|
||||
- macedonian
|
||||
- italian
|
||||
- french
|
||||
|
||||
## 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
|
||||
|
||||
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`.
|
61
docs/dev.md
Normal file
61
docs/dev.md
Normal file
@ -0,0 +1,61 @@
|
||||
# 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).
|
||||
|
||||
**Do not try with Kali on the Raspberry Pi 0 W, it is compiled without hardware floating point support and TensorFlow is simply not available for it, use Raspbian.**
|
||||
|
||||
## Creating an Image
|
||||
|
||||
You can use the `scripts/create_sibling.sh` script to create an - ready to flash - rasbian image with pwnagotchi.
|
||||
|
||||
```shell
|
||||
usage: ./scripts/create_sibling.sh [OPTIONS]
|
||||
|
||||
Options:
|
||||
-n <name> # Name of the pwnagotchi (default: pwnagotchi)
|
||||
-i <file> # Provide the path of an already downloaded raspbian image
|
||||
-o <file> # Name of the img-file (default: pwnagotchi.img)
|
||||
-s <size> # Size which should be added to second partition (in Gigabyte) (default: 4)
|
||||
-v <version> # Version of raspbian (Supported: latest; default: latest)
|
||||
-p # Only run provisioning (assumes the image is already mounted)
|
||||
-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:
|
||||
|
||||
```shell
|
||||
./scripts/language.sh add it
|
||||
# Now make your changes to the file
|
||||
# sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/locale/it/LC_MESSAGES/voice.po
|
||||
./scripts/language.sh compile it
|
||||
# DONE
|
||||
```
|
||||
|
||||
If you changed the `voice.py`- File, the translations need an update. Do it like this:
|
||||
|
||||
```shell
|
||||
./scripts/language.sh update it
|
||||
# Now make your changes to the file (changed lines are marked with "fuzzy")
|
||||
# sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/locale/it/LC_MESSAGES/voice.po
|
||||
./scripts/language.sh compile it
|
||||
# DONE
|
||||
```
|
||||
|
||||
Now you can use the `preview.py`-script to preview the changes:
|
||||
|
||||
```shell
|
||||
./scripts/preview.py --lang it --display ws2 --port 8080 &
|
||||
./scripts/preview.py --lang it --display inky --port 8081 &
|
||||
# Now open http://localhost:8080 and http://localhost:8081
|
||||
```
|
13
docs/faq.md
Normal file
13
docs/faq.md
Normal file
@ -0,0 +1,13 @@
|
||||
# FAQ
|
||||
|
||||
## Why eINK?
|
||||
|
||||
Because!
|
||||
|
||||
## Why the AI takes 30 minutes to load?
|
||||
|
||||
Because Python sucks and TF is huge.
|
||||
|
||||
## Why ...?
|
||||
|
||||
Because!
|
20
docs/index.md
Normal file
20
docs/index.md
Normal file
@ -0,0 +1,20 @@
|
||||
# Documentation
|
||||
|
||||
- [About the Project](https://github.com/evilsocket/pwnagotchi/blob/master/docs/about.md)
|
||||
- [How to Install](https://github.com/evilsocket/pwnagotchi/blob/master/docs/install.md)
|
||||
- [Configuration](https://github.com/evilsocket/pwnagotchi/blob/master/docs/configure.md)
|
||||
- [Usage](https://github.com/evilsocket/pwnagotchi/blob/master/docs/usage.md)
|
||||
- [Plugins](https://github.com/evilsocket/pwnagotchi/blob/master/docs/plugins.md)
|
||||
- [Developement](https://github.com/evilsocket/pwnagotchi/blob/master/docs/dev.md)
|
||||
- [FAQ](https://github.com/evilsocket/pwnagotchi/blob/master/docs/faq.md)
|
||||
|
||||
## Links
|
||||
|
||||
- [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.
|
45
docs/install.md
Normal file
45
docs/install.md
Normal file
@ -0,0 +1,45 @@
|
||||
# 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.
|
||||
|
||||
## Required Hardware
|
||||
|
||||
- [Raspberry Pi Zero W](https://www.raspberrypi.org/products/raspberry-pi-zero-w/).
|
||||
- A micro SD card, 8GB recomended, **preferably of good quality and speed**.
|
||||
- A decent power bank (with 1500 mAh you get ~2 hours with AI on).
|
||||
- One of the supported displays (optional).
|
||||
|
||||
### Display
|
||||
|
||||
The display is an optional component as the UI is also rendered via a web interface available via the USB cable. If you connect to `usb0` (by using the data port on the unit) and point your browser to the web ui (see config.yml), your unit can work in "headless mode".
|
||||
|
||||
If instead you want to fully enjoy walking around and literally looking at your unit's face, the supported display models are:
|
||||
|
||||
- [Waveshare eInk Display (both V1 and V2)](https://www.waveshare.com/2.13inch-e-paper-hat.htm)
|
||||
- [Pimoroni Inky pHAT](https://shop.pimoroni.com/products/inky-phat)
|
||||
- [PaPiRus eInk Screen](https://uk.pi-supply.com/products/papirus-zero-epaper-screen-phat-pi-zero)
|
||||
|
||||
Needless to say, we are always happy to receive pull requests adding support for new models.
|
||||
|
||||
One thing to note, not all displays are created equaly, TFT displays for example work similar to an HDMI display, and they are not supported, currently all the displays supported are I2C displays.
|
||||
|
||||
#### Color and Black & White displays
|
||||
|
||||
Some of the supported displays support Black & White and Coloured versions, one common question is regarding refresh speed of said displays.
|
||||
|
||||
Color displays have a much slower refresh rate, in some cases it can take up to 15 seconds, if slow refresh rates is something that you want to avoid we advise you to use Black & White displays
|
||||
|
||||
## Flashing an Image
|
||||
|
||||
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:
|
||||
|
||||
- Download the latest [Pwnagotchi .img file](https://github.com/evilsocket/pwnagotchi/releases).
|
||||
- Download [balenaEtcher](https://www.balena.io/etcher/) and install it.
|
||||
- Connect an SD card reader with the SD card inside.
|
||||
- Open balenaEtcher and select from your hard drive the Raspberry Pi .img or .zip file you wish to write to the SD card.
|
||||
- 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!
|
56
docs/plugins.md
Normal file
56
docs/plugins.md
Normal file
@ -0,0 +1,56 @@
|
||||
# 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.
|
||||
|
||||
Here's as an example the GPS plugin:
|
||||
|
||||
```python
|
||||
__author__ = 'evilsocket@gmail.com'
|
||||
__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 core
|
||||
import json
|
||||
import os
|
||||
|
||||
device = '/dev/ttyUSB0'
|
||||
speed = 19200
|
||||
running = False
|
||||
|
||||
|
||||
def on_loaded():
|
||||
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)
|
||||
try:
|
||||
agent.run('gps off')
|
||||
except:
|
||||
pass
|
||||
|
||||
agent.run('set gps.device %s' % device)
|
||||
agent.run('set gps.speed %d' % speed)
|
||||
agent.run('gps on')
|
||||
running = True
|
||||
else:
|
||||
logging.info("no GPS detected")
|
||||
|
||||
|
||||
def on_handshake(agent, filename, access_point, client_station):
|
||||
if running:
|
||||
info = agent.session()
|
||||
gps = info['gps']
|
||||
gps_filename = filename.replace('.pcap', '.gps.json')
|
||||
|
||||
logging.info("saving GPS to %s (%s)" % (gps_filename, gps))
|
||||
with open(gps_filename, 'w+t') as fp:
|
||||
json.dump(gps, fp)
|
||||
```
|
146
docs/usage.md
Normal file
146
docs/usage.md
Normal file
@ -0,0 +1,146 @@
|
||||
# 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).
|
||||
|
||||

|
||||
|
||||
* **CH**: Current channel the unit is operating on or `*` when hopping on all channels.
|
||||
* **APS**: Number of access points on the current channel and total visible access points.
|
||||
* **UP**: Time since the unit has been activated.
|
||||
* **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.
|
||||
|
||||
## Training the AI
|
||||
|
||||
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/`.
|
||||
|
||||

|
||||
|
||||
## Update your Pwnagotchi
|
||||
|
||||
You can use the `scripts/update_pwnagotchi.sh` script to update to the most recent version of pwnagotchi.
|
||||
|
||||
```shell
|
||||
usage: ./update_pwnagitchi.sh [OPTIONS]
|
||||
|
||||
Options:
|
||||
-v # Version to update to, can be a branch or commit. (default: master)
|
||||
-u # Url to clone from. (default: https://github.com/evilsocket/pwnagotchi)
|
||||
-m # Mode to restart to. (Supported: auto manual; default: auto)
|
||||
-b # Backup the current pwnagotchi config.
|
||||
-r # Restore the current pwnagotchi config. -b will be enabled.
|
||||
-h # Shows this help. Shows this help.
|
||||
|
||||
```
|
||||
|
||||
## Backup your Pwnagotchi
|
||||
|
||||
You can use the `scripts/backup.sh` script to backup the important files of your unit.
|
||||
|
||||
```shell
|
||||
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`.
|
42
scripts/backup.sh
Executable file
42
scripts/backup.sh
Executable file
@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# name of the ethernet gadget interface on the host
|
||||
UNIT_HOSTNAME=${1:-10.0.0.2}
|
||||
# output backup zip file
|
||||
OUTPUT=${2:-pwnagotchi-backup.zip}
|
||||
# temporary folder
|
||||
TEMP_BACKUP_FOLDER=/tmp/pwnagotchi_backup
|
||||
# what to backup
|
||||
FILES_TO_BACKUP=(
|
||||
/root/brain.nn
|
||||
/root/brain.json
|
||||
/root/custom.yaml
|
||||
/root/handshakes
|
||||
/etc/ssh
|
||||
/etc/hostname
|
||||
/etc/hosts
|
||||
/etc/motd
|
||||
/var/log/pwnagotchi.log
|
||||
)
|
||||
|
||||
ping -c 1 $UNIT_HOSTNAME >/dev/null || {
|
||||
echo "@ unit $UNIT_HOSTNAME can't be reached, make sure it's connected and a static IP assigned to the USB interface."
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "@ backing up $UNIT_HOSTNAME to $OUTPUT ..."
|
||||
|
||||
rm -rf "$TEMP_BACKUP_FOLDER"
|
||||
|
||||
for file in "${FILES_TO_BACKUP[@]}"; do
|
||||
dir=$(dirname $file)
|
||||
echo " $file -> $TEMP_BACKUP_FOLDER$dir/"
|
||||
mkdir -p "$TEMP_BACKUP_FOLDER/$dir"
|
||||
scp -Cr root@$UNIT_HOSTNAME:$file "$TEMP_BACKUP_FOLDER$dir/"
|
||||
done
|
||||
|
||||
ZIPFILE="$PWD/$OUTPUT"
|
||||
pushd $PWD
|
||||
cd "$TEMP_BACKUP_FOLDER"
|
||||
zip -r -9 -q "$ZIPFILE" .
|
||||
popd
|
@ -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 ];
|
||||
|
@ -51,7 +51,7 @@ function comp_lang() {
|
||||
}
|
||||
|
||||
function update_lang() {
|
||||
xgettext -d voice -o "$LOCALE_DIR/voice.pot" "$VOICE_FILE"
|
||||
xgettext --no-location -d voice -o "$LOCALE_DIR/voice.pot" "$VOICE_FILE"
|
||||
msgmerge --update "$LOCALE_DIR/$1/LC_MESSAGES/voice.po" "$LOCALE_DIR/voice.pot"
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ import time
|
||||
import argparse
|
||||
from http.server import HTTPServer
|
||||
import shutil
|
||||
import logging
|
||||
import yaml
|
||||
|
||||
sys.path.insert(0,
|
||||
@ -13,7 +14,6 @@ sys.path.insert(0,
|
||||
'../sdcard/rootfs/root/pwnagotchi/scripts/'))
|
||||
|
||||
from pwnagotchi.ui.display import Display, VideoHandler
|
||||
import core
|
||||
|
||||
|
||||
class CustomDisplay(Display):
|
||||
@ -22,11 +22,11 @@ class CustomDisplay(Display):
|
||||
if self._video_address is not None:
|
||||
self._httpd = HTTPServer((self._video_address, self._video_port),
|
||||
CustomVideoHandler)
|
||||
core.log("ui available at http://%s:%d/" % (self._video_address,
|
||||
logging.info("ui available at http://%s:%d/" % (self._video_address,
|
||||
self._video_port))
|
||||
self._httpd.serve_forever()
|
||||
else:
|
||||
core.log("could not get ip of usb0, video server not starting")
|
||||
logging.info("could not get ip of usb0, video server not starting")
|
||||
|
||||
def _on_view_rendered(self, img):
|
||||
CustomVideoHandler.render(img)
|
||||
@ -45,7 +45,7 @@ class CustomVideoHandler(VideoHandler):
|
||||
try:
|
||||
img.save("/tmp/pwnagotchi-{rand}.png".format(rand=id(CustomVideoHandler)), format='PNG')
|
||||
except BaseException:
|
||||
core.log("could not write preview")
|
||||
logging.exception("could not write preview")
|
||||
|
||||
def do_GET(self):
|
||||
if self.path == '/':
|
||||
@ -69,7 +69,7 @@ class CustomVideoHandler(VideoHandler):
|
||||
with open("/tmp/pwnagotchi-{rand}.png".format(rand=id(CustomVideoHandler)), 'rb') as fp:
|
||||
shutil.copyfileobj(fp, self.wfile)
|
||||
except BaseException:
|
||||
core.log("could not open preview")
|
||||
logging.exception("could not open preview")
|
||||
else:
|
||||
self.send_response(404)
|
||||
|
||||
|
@ -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=(
|
||||
|
34
scripts/update_pwnagotchi.sh
Normal file → Executable file
34
scripts/update_pwnagotchi.sh
Normal file → Executable file
@ -13,13 +13,14 @@ function usage() {
|
||||
cat <<EOF
|
||||
|
||||
usage: $0 [OPTIONS]
|
||||
|
||||
Note: This should be run from the pwnagotchi itself!
|
||||
|
||||
Options:
|
||||
-v # Version to update to, can be a branch or commit. (default: master)
|
||||
-u # Url to clone from. (default: https://github.com/evilsocket/pwnagotchi)
|
||||
-m # Mode to restart to. (Supported: ${SUPPORTED_RESTART_MODES[*]}; default: auto)
|
||||
-b # Backup the current pwnagotchi config and hostname references.
|
||||
-r # Restore the current pwnagotchi config and hostname references. (-b will be enabled.)
|
||||
-b # Backup the current pwnagotchi config and hostname references, then overwrite with defaults.
|
||||
-r # Restore the current pwnagotchi config and hostname references after upgrade. (-b will be enabled.)
|
||||
-h # Shows this help.
|
||||
|
||||
EOF
|
||||
@ -41,11 +42,7 @@ function test_github() {
|
||||
fi
|
||||
}
|
||||
|
||||
echo "[+] Checking prerequisites."
|
||||
test_root
|
||||
test_github
|
||||
|
||||
while getopts ":v:u:m:b:r:h" o; do
|
||||
while getopts "v:u:m:brh" o; do
|
||||
case "${o}" in
|
||||
v)
|
||||
VERSION="${OPTARG}"
|
||||
@ -58,15 +55,15 @@ while getopts ":v:u:m:b:r:h" o; do
|
||||
MODE="${OPTARG}"
|
||||
else
|
||||
usage
|
||||
fi
|
||||
;;
|
||||
fi
|
||||
;;
|
||||
b)
|
||||
BACKUPCONFIG=1
|
||||
;;
|
||||
r)
|
||||
BACKUPCONFIG=1
|
||||
BACKUPCONFIG=1
|
||||
RESTORECONFIG=1
|
||||
;;
|
||||
;;
|
||||
h)
|
||||
usage
|
||||
;;
|
||||
@ -77,6 +74,10 @@ while getopts ":v:u:m:b:r:h" o; do
|
||||
done
|
||||
shift $((OPTIND-1))
|
||||
|
||||
echo "[+] Checking prerequisites."
|
||||
test_root
|
||||
test_github
|
||||
|
||||
# clean up old files, clone master, set checkout to commit if needed.
|
||||
echo "[+] Cloning to $GIT_FOLDER..."
|
||||
rm $GIT_FOLDER -rf
|
||||
@ -85,27 +86,30 @@ cd $GIT_FOLDER
|
||||
if [ $VERSION != "master" ]; then
|
||||
git checkout $VERSION -q
|
||||
fi
|
||||
echo "[+] Installing $(git log -1 --format="%h")"
|
||||
|
||||
echo "[+] Updating..."
|
||||
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
|
||||
mv /etc/network/interfaces /root/interfaces.bak -f
|
||||
fi
|
||||
|
||||
echo "[+] Installing $(git log -1 --format="%h")"
|
||||
rm /root/pwnagotchi -rf # ensures old files are removed
|
||||
rsync -aPq $GIT_FOLDER/sdcard/boot/* /boot/
|
||||
rsync -aPq $GIT_FOLDER/sdcard/rootfs/* /
|
||||
cd /tmp
|
||||
rm $GIT_FOLDER -rf
|
||||
|
||||
if [ $RESTORECONFIG -eq 1 ]; then
|
||||
echo "[+] Restoring backup of config.yml and hostname references"
|
||||
mv /root/config.yml.bak /root/pwnagotchi/config.yml -f
|
||||
mv /root/hosts.bak /etc/hosts -f
|
||||
mv /root/hostname.bak /etc/hostname -f
|
||||
mv /root/interfaces.bak /etc/network/interfaces -f
|
||||
mv /etc/motd.bak /etc/motd -f
|
||||
fi
|
||||
|
||||
echo "[+] Restarting pwnagotchi in $MODE mode. $( screen -X -S pwnagotchi quit)"
|
||||
|
265
scripts/win_connection_share.ps1
Normal file
265
scripts/win_connection_share.ps1
Normal file
@ -0,0 +1,265 @@
|
||||
<#
|
||||
.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 windows 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. Defaults 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.
|
||||
By default it tries to get the correct interfaces. This method might not be foolproof for every setup, but should work in most default senarios, if this causes issues these
|
||||
could be passed as a param, they would need to be implemented in Setup-PwnagotchiNetwork and the Param block of this file.
|
||||
|
||||
.PARAMETER InternetAdaptor
|
||||
The output of Get-NetAdaptor filtered down to the 'main' uplink interface. Should default to the correct value.
|
||||
|
||||
.PARAMETER RNDISGadget
|
||||
The output of Get-NetAdaptor filtered down to the 'USB Ethernet/RNDIS Gadget' interface. Should default to the correct value.
|
||||
|
||||
.EXAMPLE
|
||||
PS> $HNetObject = Create-HNetObjects
|
||||
PS> $HNetObject
|
||||
RNDISIntConfig InternetIntConfig
|
||||
-------------- -----------------
|
||||
System.__ComObject System.__ComObject
|
||||
#>
|
||||
[Cmdletbinding()]
|
||||
Param (
|
||||
$InternetAdaptor = $(Get-NetAdapter | Where-Object {$_.MediaConnectionState -eq 'Connected' -and $_.PhysicalMediaType -ne 'Unspecified'} | Sort-Object LinkSpeed -Descending),
|
||||
$RNDISGadget = $(Get-NetAdapter | Where-Object {$_.MediaConnectionState -eq 'Connected' -and $_.InterfaceDescription -eq "USB Ethernet/RNDIS Gadget"})
|
||||
)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Dynamically create params for Setup-PwnagotchiNetwork function based of param input of script.
|
||||
Setup-PwnagotchiNetwork @psBoundParameters
|
@ -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, 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.yaml
|
||||
- /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).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
|
||||
@ -23,7 +61,7 @@ main:
|
||||
|
||||
ai:
|
||||
# if false, only the default 'personality' will be used
|
||||
enabled: false
|
||||
enabled: true
|
||||
path: /root/brain.nn
|
||||
# 1.0 - laziness = probability of start training
|
||||
laziness: 0.1
|
||||
@ -95,7 +133,7 @@ ui:
|
||||
# IMPORTANT: The lifespan of an eINK display depends on the cumulative amount of refreshes. If you want to
|
||||
# preserve your display over time, you should set this value to 0.0 so that the display will be refreshed only
|
||||
# if any of the important data fields changed (the uptime and blinking cursor won't trigger a refresh).
|
||||
fps: 0.3
|
||||
fps: 0.0
|
||||
display:
|
||||
enabled: true
|
||||
rotation: 180
|
||||
@ -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
|
||||
|
||||
|
||||
|
||||
|
@ -1,65 +0,0 @@
|
||||
import sys
|
||||
import glob
|
||||
import os
|
||||
import time
|
||||
import subprocess
|
||||
from threading import Lock
|
||||
from datetime import datetime
|
||||
|
||||
logfile = None
|
||||
loglock = Lock()
|
||||
|
||||
|
||||
def log(msg):
|
||||
tstamp = str(datetime.now())
|
||||
line = "[%s] %s" % (tstamp, msg.rstrip())
|
||||
print(line)
|
||||
sys.stdout.flush()
|
||||
if logfile is not None:
|
||||
with loglock:
|
||||
with open(logfile, 'a+t') as fp:
|
||||
fp.write("%s\n" % line)
|
||||
|
||||
|
||||
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)
|
@ -1,170 +1,103 @@
|
||||
#!/usr/bin/python3
|
||||
import argparse
|
||||
import yaml
|
||||
import time
|
||||
import traceback
|
||||
import logging
|
||||
|
||||
import core
|
||||
import pwnagotchi, pwnagotchi.plugins as plugins
|
||||
import pwnagotchi
|
||||
import pwnagotchi.utils as utils
|
||||
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
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
|
||||
parser.add_argument('-C', '--config', action='store', dest='config', default='/root/pwnagotchi/config.yml')
|
||||
parser.add_argument('-C', '--config', action='store', dest='config', default='/root/pwnagotchi/config.yml',
|
||||
help='Main configuration file.')
|
||||
parser.add_argument('-U', '--user-config', action='store', dest='user_config', default='/root/custom.yml',
|
||||
help='If this file exists, configuration will be merged and this will override default values.')
|
||||
|
||||
parser.add_argument('--manual', dest="do_manual", action="store_true", default=False, help="Manual mode.")
|
||||
parser.add_argument('--clear', dest="do_clear", action="store_true", default=False,
|
||||
help="Clear the ePaper display and exit.")
|
||||
|
||||
parser.add_argument('--debug', dest="debug", action="store_true", default=False,
|
||||
help="Enable debug logs.")
|
||||
|
||||
args = parser.parse_args()
|
||||
config = utils.load_config(args)
|
||||
utils.setup_logging(args, config)
|
||||
|
||||
if args.do_clear:
|
||||
print("clearing the display ...")
|
||||
with open(args.config, 'rt') as fp:
|
||||
config = yaml.safe_load(fp)
|
||||
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()
|
||||
|
||||
with open(args.config, 'rt') as fp:
|
||||
config = yaml.safe_load(fp)
|
||||
|
||||
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)
|
||||
|
||||
core.log("%s@%s (v%s)" % (pwnagotchi.name(), agent._identity, pwnagotchi.version))
|
||||
# for key, value in config['personality'].items():
|
||||
# core.log(" %s: %s" % (key, value))
|
||||
logging.info("%s@%s (v%s)" % (pwnagotchi.name(), agent._identity, pwnagotchi.version))
|
||||
|
||||
for _, plugin in plugins.loaded.items():
|
||||
core.log("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:
|
||||
core.log("entering manual mode ...")
|
||||
if args.do_clear:
|
||||
logging.info("clearing the display ...")
|
||||
display.clear()
|
||||
|
||||
log = SessionParser(config['main']['log'])
|
||||
elif args.do_manual:
|
||||
logging.info("entering manual mode ...")
|
||||
|
||||
core.log("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 config['twitter']['enabled'] and log.is_new() and Agent.is_connected() and log.handshakes > 0:
|
||||
import tweepy
|
||||
|
||||
core.log("detected a new session and internet connectivity!")
|
||||
if Agent.is_connected():
|
||||
plugins.on('internet_available', display, config, log)
|
||||
|
||||
picture = '/dev/shm/pwnagotchi.png'
|
||||
else:
|
||||
logging.info("entering auto mode ...")
|
||||
|
||||
display.update()
|
||||
display.image().save(picture, 'png')
|
||||
display.set('status', 'Tweeting...')
|
||||
display.update()
|
||||
agent.start()
|
||||
|
||||
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'])
|
||||
api = tweepy.API(auth)
|
||||
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)
|
||||
|
||||
tweet = Voice(lang=config['main']['lang']).on_log_tweet(log)
|
||||
api.update_with_media(filename=picture, status=tweet)
|
||||
log.save_session_id()
|
||||
if not agent.is_stale() and agent.any_activity():
|
||||
logging.info("%d access points on channel %d" % (len(aps), ch))
|
||||
|
||||
core.log("tweeted: %s" % tweet)
|
||||
except Exception as e:
|
||||
core.log("error: %s" % e)
|
||||
# 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)
|
||||
|
||||
quit()
|
||||
|
||||
core.logfile = config['main']['log']
|
||||
|
||||
agent.start_ai()
|
||||
agent.setup_events()
|
||||
agent.set_starting()
|
||||
agent.start_monitor_mode()
|
||||
agent.start_event_polling()
|
||||
|
||||
# print initial stats
|
||||
agent.next_epoch()
|
||||
|
||||
agent.set_ready()
|
||||
|
||||
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)
|
||||
|
||||
if not agent.is_stale() and agent.any_activity():
|
||||
core.log("%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:
|
||||
core.log("main loop exception: %s" % e)
|
||||
core.log("%s" % traceback.format_exc())
|
||||
# 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")
|
||||
|
@ -1,5 +1,7 @@
|
||||
import subprocess
|
||||
|
||||
version = '1.0.0plz2'
|
||||
|
||||
_name = None
|
||||
|
||||
|
||||
|
@ -4,12 +4,12 @@ import os
|
||||
import re
|
||||
import socket
|
||||
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
|
||||
|
||||
@ -29,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
|
||||
@ -82,7 +82,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
plugins.on('rebooting', self)
|
||||
|
||||
def setup_events(self):
|
||||
core.log("connecting to %s ..." % self.url)
|
||||
logging.info("connecting to %s ..." % self.url)
|
||||
|
||||
for tag in self._config['bettercap']['silence']:
|
||||
try:
|
||||
@ -109,34 +109,44 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
s = self.session()
|
||||
for iface in s['interfaces']:
|
||||
if iface['name'] == mon_iface:
|
||||
core.log("found monitor interface: %s" % iface['name'])
|
||||
logging.info("found monitor interface: %s" % iface['name'])
|
||||
has_mon = True
|
||||
break
|
||||
|
||||
if has_mon is False:
|
||||
if mon_start_cmd is not None and mon_start_cmd != '':
|
||||
core.log("starting monitor interface ...")
|
||||
logging.info("starting monitor interface ...")
|
||||
self.run('!%s' % mon_start_cmd)
|
||||
else:
|
||||
core.log("waiting for monitor interface %s ..." % mon_iface)
|
||||
logging.info("waiting for monitor interface %s ..." % mon_iface)
|
||||
time.sleep(1)
|
||||
|
||||
core.log("supported channels: %s" % self._supported_channels)
|
||||
core.log("handshakes will be collected inside %s" % self._config['bettercap']['handshakes'])
|
||||
logging.info("supported channels: %s" % self._supported_channels)
|
||||
logging.info("handshakes will be collected inside %s" % self._config['bettercap']['handshakes'])
|
||||
|
||||
self._reset_wifi_settings()
|
||||
|
||||
wifi_running = self.is_module_running('wifi')
|
||||
if wifi_running and restart:
|
||||
core.log("restarting wifi module ...")
|
||||
self.restart('wifi.recon')
|
||||
logging.debug("restarting wifi module ...")
|
||||
self.restart_module('wifi.recon')
|
||||
self.run('wifi.clear')
|
||||
elif not wifi_running:
|
||||
core.log("starting wifi module ...")
|
||||
self.start('wifi.recon')
|
||||
logging.debug("starting wifi module ...")
|
||||
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)
|
||||
@ -150,13 +160,13 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
for ch in self._epoch.non_overlapping_channels:
|
||||
if ch not in busy_channels:
|
||||
self._epoch.non_overlapping_channels[ch] += 1
|
||||
core.log("channel %d is free from %d epochs" % (ch, self._epoch.non_overlapping_channels[ch]))
|
||||
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:
|
||||
core.log("channel %d has been free for %d epochs" % (ch, num_epochs_free))
|
||||
logging.info("channel %d has been free for %d epochs" % (ch, num_epochs_free))
|
||||
self.set_free_channel(ch)
|
||||
|
||||
def recon(self):
|
||||
@ -172,14 +182,14 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
|
||||
if not channels:
|
||||
self._current_channel = 0
|
||||
# core.log("RECON %ds" % recon_time)
|
||||
logging.debug("RECON %ds" % recon_time)
|
||||
self.run('wifi.recon.channel clear')
|
||||
else:
|
||||
# core.log("RECON %ds ON CHANNELS %s" % (recon_time, ','.join(map(str, channels))))
|
||||
logging.debug("RECON %ds ON CHANNELS %s" % (recon_time, ','.join(map(str, channels))))
|
||||
try:
|
||||
self.run('wifi.recon.channel %s' % ','.join(map(str, channels)))
|
||||
except Exception as e:
|
||||
core.log("error: %s" % e)
|
||||
logging.exception("error")
|
||||
|
||||
self.wait_for(recon_time, sleeping=False)
|
||||
|
||||
@ -204,7 +214,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
if self._filter_included(ap):
|
||||
aps.append(ap)
|
||||
except Exception as e:
|
||||
core.log("error: %s" % e)
|
||||
logging.exception("error")
|
||||
|
||||
aps.sort(key=lambda ap: ap['channel'])
|
||||
return self.set_access_points(aps)
|
||||
@ -241,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):
|
||||
@ -261,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:
|
||||
@ -274,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())
|
||||
@ -289,7 +299,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
self._view.set_closest_peer(peer)
|
||||
|
||||
def _save_recovery_data(self):
|
||||
core.log("writing recovery data to %s ..." % RECOVERY_DATA_FILE)
|
||||
logging.warning("writing recovery data to %s ..." % RECOVERY_DATA_FILE)
|
||||
with open(RECOVERY_DATA_FILE, 'w') as fp:
|
||||
data = {
|
||||
'started_at': self._started_at,
|
||||
@ -304,7 +314,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
try:
|
||||
with open(RECOVERY_DATA_FILE, 'rt') as fp:
|
||||
data = json.load(fp)
|
||||
core.log("found recovery data: %s" % data)
|
||||
logging.info("found recovery data: %s" % data)
|
||||
self._started_at = data['started_at']
|
||||
self._epoch.epoch = data['epoch']
|
||||
self._handshakes = data['handshakes']
|
||||
@ -312,7 +322,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
self._last_pwnd = data['last_pwnd']
|
||||
|
||||
if delete:
|
||||
core.log("deleting %s" % RECOVERY_DATA_FILE)
|
||||
logging.info("deleting %s" % RECOVERY_DATA_FILE)
|
||||
os.unlink(RECOVERY_DATA_FILE)
|
||||
except:
|
||||
if not no_exceptions:
|
||||
@ -323,7 +333,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
|
||||
self.run('events.clear')
|
||||
|
||||
core.log("event polling started ...")
|
||||
logging.debug("event polling started ...")
|
||||
while True:
|
||||
time.sleep(1)
|
||||
|
||||
@ -349,21 +359,21 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
new_shakes += 1
|
||||
ap_and_station = self._find_ap_sta_in(sta_mac, ap_mac, s)
|
||||
if ap_and_station is None:
|
||||
core.log("!!! captured new handshake: %s !!!" % key)
|
||||
logging.warning("!!! captured new handshake: %s !!!" % key)
|
||||
self._last_pwnd = ap_mac
|
||||
plugins.on('handshake', self, filename, ap_mac, sta_mac)
|
||||
else:
|
||||
(ap, sta) = ap_and_station
|
||||
self._last_pwnd = ap['hostname'] if ap['hostname'] != '' and ap[
|
||||
'hostname'] != '<hidden>' else ap_mac
|
||||
core.log("!!! captured new handshake on channel %d: %s (%s) -> %s [%s (%s)] !!!" % ( \
|
||||
logging.warning("!!! captured new handshake on channel %d: %s (%s) -> %s [%s (%s)] !!!" % ( \
|
||||
ap['channel'],
|
||||
sta['mac'], sta['vendor'],
|
||||
ap['hostname'], ap['mac'], ap['vendor']))
|
||||
plugins.on('handshake', self, filename, ap, sta)
|
||||
|
||||
except Exception as e:
|
||||
core.log("error: %s" % e)
|
||||
logging.exception("error")
|
||||
|
||||
finally:
|
||||
self._update_handshakes(new_shakes)
|
||||
@ -378,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):
|
||||
@ -404,7 +414,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
return self._history[who] < self._config['personality']['max_interactions']
|
||||
|
||||
def _on_miss(self, who):
|
||||
core.log("it looks like %s is not in range anymore :/" % who)
|
||||
logging.info("it looks like %s is not in range anymore :/" % who)
|
||||
self._epoch.track(miss=True)
|
||||
self._view.on_miss(who)
|
||||
|
||||
@ -416,18 +426,18 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
if 'is an unknown BSSID' in error:
|
||||
self._on_miss(who)
|
||||
else:
|
||||
core.log("error: %s" % e)
|
||||
logging.error("%s" % e)
|
||||
|
||||
def associate(self, ap, throttle=0):
|
||||
if self.is_stale():
|
||||
core.log("recon is stale, skipping assoc(%s)" % ap['mac'])
|
||||
logging.debug("recon is stale, skipping assoc(%s)" % ap['mac'])
|
||||
return
|
||||
|
||||
if self._config['personality']['associate'] and self._should_interact(ap['mac']):
|
||||
self._view.on_assoc(ap)
|
||||
|
||||
try:
|
||||
core.log("sending association frame to %s (%s %s) on channel %d [%d clients]..." % ( \
|
||||
logging.info("sending association frame to %s (%s %s) on channel %d [%d clients]..." % ( \
|
||||
ap['hostname'], ap['mac'], ap['vendor'], ap['channel'], len(ap['clients'])))
|
||||
self.run('wifi.assoc %s' % ap['mac'])
|
||||
self._epoch.track(assoc=True)
|
||||
@ -441,14 +451,14 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
|
||||
def deauth(self, ap, sta, throttle=0):
|
||||
if self.is_stale():
|
||||
core.log("recon is stale, skipping deauth(%s)" % sta['mac'])
|
||||
logging.debug("recon is stale, skipping deauth(%s)" % sta['mac'])
|
||||
return
|
||||
|
||||
if self._config['personality']['deauth'] and self._should_interact(sta['mac']):
|
||||
self._view.on_deauth(sta)
|
||||
|
||||
try:
|
||||
core.log("deauthing %s (%s) from %s (%s %s) on channel %d ..." % (
|
||||
logging.info("deauthing %s (%s) from %s (%s %s) on channel %d ..." % (
|
||||
sta['mac'], sta['vendor'], ap['hostname'], ap['mac'], ap['vendor'], ap['channel']))
|
||||
self.run('wifi.deauth %s' % sta['mac'])
|
||||
self._epoch.track(deauth=True)
|
||||
@ -462,7 +472,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
|
||||
def set_channel(self, channel, verbose=True):
|
||||
if self.is_stale():
|
||||
core.log("recon is stale, skipping set_channel(%d)" % channel)
|
||||
logging.debug("recon is stale, skipping set_channel(%d)" % channel)
|
||||
return
|
||||
|
||||
# if in the previous loop no client stations has been deauthenticated
|
||||
@ -478,10 +488,12 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
if channel != self._current_channel:
|
||||
if self._current_channel != 0 and wait > 0:
|
||||
if verbose:
|
||||
core.log("waiting for %ds on channel %d ..." % (wait, self._current_channel))
|
||||
logging.info("waiting for %ds on channel %d ..." % (wait, self._current_channel))
|
||||
else:
|
||||
logging.debug("waiting for %ds on channel %d ..." % (wait, self._current_channel))
|
||||
self.wait_for(wait)
|
||||
if verbose and self._epoch.any_activity:
|
||||
core.log("CHANNEL %d" % channel)
|
||||
logging.info("CHANNEL %d" % channel)
|
||||
try:
|
||||
self.run('wifi.recon.channel %d' % channel)
|
||||
self._current_channel = channel
|
||||
@ -491,7 +503,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
plugins.on('channel_hop', self, channel)
|
||||
|
||||
except Exception as e:
|
||||
core.log("error: %s" % e)
|
||||
logging.error("error: %s" % e)
|
||||
|
||||
def is_stale(self):
|
||||
return self._epoch.num_missed > self._config['personality']['max_misses_for_recon']
|
||||
@ -502,7 +514,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
def _reboot(self):
|
||||
self.set_rebooting()
|
||||
self._save_recovery_data()
|
||||
core.log("rebooting the system ...")
|
||||
logging.warning("rebooting the system ...")
|
||||
os.system("/usr/bin/sync")
|
||||
os.system("/usr/sbin/shutdown -r now")
|
||||
|
||||
@ -514,24 +526,24 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
|
||||
|
||||
# after X misses during an epoch, set the status to lonely
|
||||
if was_stale:
|
||||
core.log("agent missed %d interactions -> lonely" % did_miss)
|
||||
logging.warning("agent missed %d interactions -> lonely" % did_miss)
|
||||
self.set_lonely()
|
||||
# after X times being bored, the status is set to sad
|
||||
elif self._epoch.inactive_for >= self._config['personality']['sad_num_epochs']:
|
||||
core.log("%d epochs with no activity -> sad" % self._epoch.inactive_for)
|
||||
logging.warning("%d epochs with no activity -> sad" % self._epoch.inactive_for)
|
||||
self.set_sad()
|
||||
# after X times being inactive, the status is set to bored
|
||||
elif self._epoch.inactive_for >= self._config['personality']['bored_num_epochs']:
|
||||
core.log("%d epochs with no activity -> bored" % self._epoch.inactive_for)
|
||||
logging.warning("%d epochs with no activity -> bored" % self._epoch.inactive_for)
|
||||
self.set_bored()
|
||||
# after X times being active, the status is set to happy / excited
|
||||
elif self._epoch.active_for >= self._config['personality']['excited_num_epochs']:
|
||||
core.log("%d epochs with activity -> excited" % self._epoch.active_for)
|
||||
logging.warning("%d epochs with activity -> excited" % self._epoch.active_for)
|
||||
self.set_excited()
|
||||
|
||||
plugins.on('epoch', self, self._epoch.epoch - 1, self._epoch.data())
|
||||
|
||||
if self._epoch.blind_for >= self._config['main']['mon_max_blind_epochs']:
|
||||
core.log("%d epochs without visible access points -> rebooting ..." % self._epoch.blind_for)
|
||||
logging.critical("%d epochs without visible access points -> rebooting ..." % self._epoch.blind_for)
|
||||
self._reboot()
|
||||
self._epoch.blind_for = 0
|
||||
|
@ -7,16 +7,16 @@ import warnings
|
||||
# https://stackoverflow.com/questions/15777951/how-to-suppress-pandas-future-warning
|
||||
warnings.simplefilter(action='ignore', category=FutureWarning)
|
||||
|
||||
import core
|
||||
import logging
|
||||
|
||||
|
||||
def load(config, agent, epoch, from_disk=True):
|
||||
config = config['ai']
|
||||
if not config['enabled']:
|
||||
core.log("ai disabled")
|
||||
logging.info("ai disabled")
|
||||
return False
|
||||
|
||||
core.log("[ai] bootstrapping dependencies ...")
|
||||
logging.info("[ai] bootstrapping dependencies ...")
|
||||
|
||||
from stable_baselines import A2C
|
||||
from stable_baselines.common.policies import MlpLstmPolicy
|
||||
@ -27,16 +27,16 @@ def load(config, agent, epoch, from_disk=True):
|
||||
env = wrappers.Environment(agent, epoch)
|
||||
env = DummyVecEnv([lambda: env])
|
||||
|
||||
core.log("[ai] bootstrapping model ...")
|
||||
logging.info("[ai] bootstrapping model ...")
|
||||
|
||||
a2c = A2C(MlpLstmPolicy, env, **config['params'])
|
||||
|
||||
if from_disk and os.path.exists(config['path']):
|
||||
core.log("[ai] loading %s ..." % config['path'])
|
||||
logging.info("[ai] loading %s ..." % config['path'])
|
||||
a2c.load(config['path'], env)
|
||||
else:
|
||||
core.log("[ai] model created:")
|
||||
logging.info("[ai] model created:")
|
||||
for key, value in config['params'].items():
|
||||
core.log(" %s: %s" % (key, value))
|
||||
logging.info(" %s: %s" % (key, value))
|
||||
|
||||
return a2c
|
||||
|
@ -1,8 +1,9 @@
|
||||
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
|
||||
@ -87,13 +88,13 @@ class Epoch(object):
|
||||
aps_per_chan[ch_idx] += 1.0
|
||||
sta_per_chan[ch_idx] += len(ap['clients'])
|
||||
except IndexError as e:
|
||||
core.log("got data on channel %d, we can store %d channels" % (ap['channel'], wifi.NumChannels))
|
||||
logging.error("got data on channel %d, we can store %d channels" % (ap['channel'], wifi.NumChannels))
|
||||
|
||||
for peer in peers:
|
||||
try:
|
||||
peers_per_chan[peer.last_channel - 1] += 1.0
|
||||
except IndexError as e:
|
||||
core.log(
|
||||
logging.error(
|
||||
"got peer data on channel %d, we can store %d channels" % (peer.last_channel, wifi.NumChannels))
|
||||
|
||||
# normalize
|
||||
@ -172,23 +173,23 @@ class Epoch(object):
|
||||
self._epoch_data['reward'] = self._reward(self.epoch + 1, self._epoch_data)
|
||||
self._epoch_data_ready.set()
|
||||
|
||||
core.log("[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']))
|
||||
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,
|
||||
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
|
||||
|
@ -1,8 +1,8 @@
|
||||
import logging
|
||||
import gym
|
||||
from gym import spaces
|
||||
import numpy as np
|
||||
|
||||
import core
|
||||
import pwnagotchi.ai.featurizer as featurizer
|
||||
import pwnagotchi.ai.reward as reward
|
||||
from pwnagotchi.ai.parameter import Parameter
|
||||
@ -83,7 +83,7 @@ class Environment(gym.Env):
|
||||
return params
|
||||
|
||||
def _next_epoch(self):
|
||||
# core.log("[ai] waiting for epoch to finish ...")
|
||||
logging.debug("[ai] waiting for epoch to finish ...")
|
||||
return self._epoch.wait_for_epoch_data()
|
||||
|
||||
def _apply_policy(self, policy):
|
||||
@ -110,7 +110,7 @@ class Environment(gym.Env):
|
||||
return self.last['state_v'], self.last['reward'], not self._agent.is_training(), {}
|
||||
|
||||
def reset(self):
|
||||
# core.log("[ai] resetting environment ...")
|
||||
# logging.info("[ai] resetting environment ...")
|
||||
self._epoch_num = 0
|
||||
state = self._next_epoch()
|
||||
self.last['state'] = state
|
||||
@ -120,7 +120,7 @@ class Environment(gym.Env):
|
||||
def _render_histogram(self, hist):
|
||||
for ch in range(featurizer.histogram_size):
|
||||
if hist[ch]:
|
||||
core.log(" CH %d: %s" % (ch + 1, hist[ch]))
|
||||
logging.info(" CH %d: %s" % (ch + 1, hist[ch]))
|
||||
|
||||
def render(self, mode='human', close=False, force=False):
|
||||
# when using a vectorialized environment, render gets called twice
|
||||
@ -133,18 +133,13 @@ class Environment(gym.Env):
|
||||
|
||||
self._last_render = self._epoch_num
|
||||
|
||||
core.log("[ai] --- training epoch %d/%d ---" % (self._epoch_num, self._agent.training_epochs()))
|
||||
core.log("[ai] REWARD: %f" % self.last['reward'])
|
||||
logging.info("[ai] --- training epoch %d/%d ---" % (self._epoch_num, self._agent.training_epochs()))
|
||||
logging.info("[ai] REWARD: %f" % self.last['reward'])
|
||||
|
||||
# core.log("[ai] policy: %s" % ', '.join("%s:%s" % (name, value) for name, value in self.last['params'].items()))
|
||||
logging.debug("[ai] policy: %s" % ', '.join("%s:%s" % (name, value) for name, value in self.last['params'].items()))
|
||||
|
||||
core.log("[ai] observation:")
|
||||
logging.info("[ai] observation:")
|
||||
for name, value in self.last['state'].items():
|
||||
if 'histogram' in name:
|
||||
core.log(" %s" % name.replace('_histogram', ''))
|
||||
logging.info(" %s" % name.replace('_histogram', ''))
|
||||
self._render_histogram(value)
|
||||
|
||||
# core.log("[ai] outcome:")
|
||||
# for name, value in self.last['state'].items():
|
||||
# if 'histogram' not in name:
|
||||
# core.log(" %s: %s" % (name, value))
|
||||
|
@ -4,8 +4,7 @@ import time
|
||||
import random
|
||||
import os
|
||||
import json
|
||||
|
||||
import core
|
||||
import logging
|
||||
|
||||
import pwnagotchi.plugins as plugins
|
||||
import pwnagotchi.ai as ai
|
||||
@ -56,7 +55,7 @@ class Stats(object):
|
||||
def load(self):
|
||||
with self._lock:
|
||||
if os.path.exists(self.path) and os.path.getsize(self.path) > 0:
|
||||
core.log("[ai] loading %s" % self.path)
|
||||
logging.info("[ai] loading %s" % self.path)
|
||||
with open(self.path, 'rt') as fp:
|
||||
obj = json.load(fp)
|
||||
|
||||
@ -66,7 +65,7 @@ class Stats(object):
|
||||
|
||||
def save(self):
|
||||
with self._lock:
|
||||
core.log("[ai] saving %s" % self.path)
|
||||
logging.info("[ai] saving %s" % self.path)
|
||||
|
||||
data = json.dumps({
|
||||
'born_at': self.born_at,
|
||||
@ -114,7 +113,7 @@ class AsyncTrainer(object):
|
||||
_thread.start_new_thread(self._ai_worker, ())
|
||||
|
||||
def _save_ai(self):
|
||||
core.log("[ai] saving model to %s ..." % self._nn_path)
|
||||
logging.info("[ai] saving model to %s ..." % self._nn_path)
|
||||
temp = "%s.tmp" % self._nn_path
|
||||
self._model.save(temp)
|
||||
os.replace(temp, self._nn_path)
|
||||
@ -133,15 +132,15 @@ class AsyncTrainer(object):
|
||||
|
||||
def on_ai_policy(self, new_params):
|
||||
plugins.on('ai_policy', self, new_params)
|
||||
core.log("[ai] setting new policy:")
|
||||
logging.info("[ai] setting new policy:")
|
||||
for name, value in new_params.items():
|
||||
if name in self._config['personality']:
|
||||
curr_value = self._config['personality'][name]
|
||||
if curr_value != value:
|
||||
core.log("[ai] ! %s: %s -> %s" % (name, curr_value, value))
|
||||
logging.info("[ai] ! %s: %s -> %s" % (name, curr_value, value))
|
||||
self._config['personality'][name] = value
|
||||
else:
|
||||
core.log("[ai] param %s not in personality configuration!" % name)
|
||||
logging.error("[ai] param %s not in personality configuration!" % name)
|
||||
|
||||
self.run('set wifi.ap.ttl %d' % self._config['personality']['ap_ttl'])
|
||||
self.run('set wifi.sta.ttl %d' % self._config['personality']['sta_ttl'])
|
||||
@ -152,12 +151,12 @@ class AsyncTrainer(object):
|
||||
plugins.on('ai_ready', self)
|
||||
|
||||
def on_ai_best_reward(self, r):
|
||||
core.log("[ai] best reward so far: %s" % r)
|
||||
logging.info("[ai] best reward so far: %s" % r)
|
||||
self._view.on_motivated(r)
|
||||
plugins.on('ai_best_reward', self, r)
|
||||
|
||||
def on_ai_worst_reward(self, r):
|
||||
core.log("[ai] worst reward so far: %s" % r)
|
||||
logging.info("[ai] worst reward so far: %s" % r)
|
||||
self._view.on_demotivated(r)
|
||||
plugins.on('ai_worst_reward', self, r)
|
||||
|
||||
@ -174,12 +173,12 @@ class AsyncTrainer(object):
|
||||
self._model.env.render()
|
||||
# enter in training mode?
|
||||
if random.random() > self._config['ai']['laziness']:
|
||||
core.log("[ai] learning for %d epochs ..." % epochs_per_episode)
|
||||
logging.info("[ai] learning for %d epochs ..." % epochs_per_episode)
|
||||
try:
|
||||
self.set_training(True, epochs_per_episode)
|
||||
self._model.learn(total_timesteps=epochs_per_episode, callback=self.on_ai_training_step)
|
||||
except Exception as e:
|
||||
core.log("[ai] error while training: %s" % e)
|
||||
logging.exception("[ai] error while training")
|
||||
finally:
|
||||
self.set_training(False)
|
||||
obs = self._model.env.reset()
|
||||
|
@ -1,8 +1,7 @@
|
||||
import logging
|
||||
import requests
|
||||
from requests.auth import HTTPBasicAuth
|
||||
|
||||
import core
|
||||
|
||||
|
||||
class Client(object):
|
||||
def __init__(self, hostname='localhost', scheme='http', port=8081, username='user', password='pass'):
|
||||
@ -19,11 +18,11 @@ class Client(object):
|
||||
return r.json()
|
||||
except Exception as e:
|
||||
if r.status_code == 200:
|
||||
core.log("error while decoding json: error='%s' resp='%s'" % (e, r.text))
|
||||
logging.error("error while decoding json: error='%s' resp='%s'" % (e, r.text))
|
||||
else:
|
||||
err = "error %d: %s" % (r.status_code, r.text.strip())
|
||||
if verbose_errors:
|
||||
core.log(err)
|
||||
logging.info(err)
|
||||
raise Exception(err)
|
||||
return r.text
|
||||
|
Binary file not shown.
@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 0.0.1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-10-03 12:22+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"
|
||||
@ -16,217 +16,169 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: voice.py:18
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:22
|
||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||
msgstr "Hi, ich bin ein Pwnagotchi! Starte ..."
|
||||
|
||||
#: voice.py:23
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr "Neuer Tag, neue Jagd, neue Pwns!"
|
||||
|
||||
#: voice.py:24
|
||||
msgid "Hack the Planet!"
|
||||
msgstr "Hack den Planet!"
|
||||
|
||||
#: voice.py:28
|
||||
msgid "AI ready."
|
||||
msgstr "KI bereit."
|
||||
|
||||
#: voice.py:29
|
||||
msgid "The neural network is ready."
|
||||
msgstr "Das neurale Netz ist bereit."
|
||||
|
||||
#: voice.py:37
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr "Hey, Channel {channel} ist frei! Dein AP wir des dir danken."
|
||||
|
||||
#: voice.py:41
|
||||
msgid "I'm bored ..."
|
||||
msgstr "Mir ist langweilig..."
|
||||
|
||||
#: voice.py:42
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr "Lass uns laufen gehen!"
|
||||
|
||||
#: voice.py:45
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr "Das ist der beste Tag meines Lebens."
|
||||
|
||||
#: voice.py:48
|
||||
msgid "Shitty day :/"
|
||||
msgstr "Scheis Tag :/"
|
||||
|
||||
#: voice.py:52
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr "Mir ist sau langweilig..."
|
||||
|
||||
#: voice.py:53
|
||||
msgid "I'm very sad ..."
|
||||
msgstr "Ich bin sehr traurig..."
|
||||
|
||||
#: voice.py:54
|
||||
msgid "I'm sad"
|
||||
msgstr "Ich bin traurig"
|
||||
|
||||
#: voice.py:59
|
||||
msgid "I'm living the life!"
|
||||
msgstr "Ich lebe das Leben!"
|
||||
|
||||
#: voice.py:60
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr "Ich pwne, also bin ich."
|
||||
|
||||
#: voice.py:61
|
||||
msgid "So many networks!!!"
|
||||
msgstr "So viele Netwerke!!!"
|
||||
|
||||
#: voice.py:62
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr "Ich habe sooo viel Spaß!"
|
||||
|
||||
#: voice.py:63
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr "Mein Verbrechen ist das der Neugier ..."
|
||||
|
||||
#: voice.py:67
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you. {name}"
|
||||
msgstr "Hallo {name}, nett Dich kennenzulernen."
|
||||
|
||||
#: voice.py:68
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby! {name}"
|
||||
msgstr "Gerät {name} ist in der nähe!!"
|
||||
|
||||
#: voice.py:72
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
msgstr "Uhm ...tschüß {name}"
|
||||
|
||||
#: voice.py:73
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr "{name} ist weg ..."
|
||||
|
||||
#: voice.py:77
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr "Whoops ...{name} ist weg."
|
||||
|
||||
#: voice.py:78
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr "{name} verpasst!"
|
||||
|
||||
#: voice.py:79
|
||||
msgid "Missed!"
|
||||
msgstr "Verpasst!"
|
||||
|
||||
#: voice.py:83
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr "Niemand will mit mir spielen ..."
|
||||
|
||||
#: voice.py:84
|
||||
msgid "I feel so alone ..."
|
||||
msgstr "Ich fühl michso alleine ..."
|
||||
|
||||
#: voice.py:85
|
||||
msgid "Where's everybody?!"
|
||||
msgstr "Wo sind denn alle?"
|
||||
|
||||
#: voice.py:89
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr "Schlafe für {secs}s"
|
||||
|
||||
#: voice.py:90
|
||||
msgid "Zzzzz"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:91
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:98
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr "Warte für {secs}s ..."
|
||||
|
||||
#: voice.py:100
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr "Schaue mich um ({secs}s)"
|
||||
|
||||
#: voice.py:106
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr "Hey {what}, lass uns Freunde sein!"
|
||||
|
||||
#: voice.py:107
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
msgstr "Verbinde mit {what}"
|
||||
|
||||
#: voice.py:108
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:112
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr "Ich denke, dass {mac} kein WiFi brauch!"
|
||||
|
||||
#: voice.py:113
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
msgstr "Deauthentifiziere {mac}"
|
||||
|
||||
#: voice.py:114
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr "Kicke {mac}!"
|
||||
|
||||
#: voice.py:118
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr "Cool, wir haben {num} neue Handshake{plural}!"
|
||||
|
||||
#: voice.py:121
|
||||
msgid "Ops, something went wrong ... Rebooting ..."
|
||||
msgstr "Ops, da ist etwas schief gelaufen ...Starte neu ..."
|
||||
|
||||
#: voice.py:124
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr "{num} Stationen gekicked\n"
|
||||
|
||||
#: voice.py:125
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr "{num} Freunde gefunden\n"
|
||||
|
||||
#: voice.py:126
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr "{num} Handshakes aufgez.\n"
|
||||
|
||||
#: voice.py:128
|
||||
msgid "Met 1 peer"
|
||||
msgstr "1 Peer getroffen."
|
||||
|
||||
#: voice.py:130
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr "{num} Peers getroffen"
|
||||
|
||||
#: voice.py:135
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
|
||||
@ -236,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"
|
||||
|
Binary file not shown.
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 0.0.1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-10-03 12:22+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"
|
||||
@ -17,225 +17,169 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: voice.py:18
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:22
|
||||
#, fuzzy
|
||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||
msgstr "Γειά, είμαι το Pwnagotchi!Εκκινούμαι ..."
|
||||
msgstr "Γειά, είμαι το Pwnagotchi! Εκκινούμαι ..."
|
||||
|
||||
#: voice.py:23
|
||||
#, fuzzy
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr "Νέα μέρα, νέο κυνήγι,νέα pwns!"
|
||||
msgstr "Νέα μέρα, νέο κυνήγι, νέα pwns!"
|
||||
|
||||
#: voice.py:24
|
||||
msgid "Hack the Planet!"
|
||||
msgstr "Hackαρε τον πλανήτη!"
|
||||
|
||||
#: voice.py:28
|
||||
msgid "AI ready."
|
||||
msgstr "ΤΝ έτοιμη."
|
||||
|
||||
#: voice.py:29
|
||||
#, fuzzy
|
||||
msgid "The neural network is ready."
|
||||
msgstr "Το νευρωνικό δίκτυοείναι έτοιμο."
|
||||
|
||||
#: voice.py:37
|
||||
#, fuzzy, python-brace-format
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr "Ε, το κανάλι {channel} είναιελεύθερο! Το AP σου θαείναι ευγνώμων."
|
||||
|
||||
#: voice.py:41
|
||||
msgid "I'm bored ..."
|
||||
msgstr "Βαριέμαι ..."
|
||||
|
||||
#: voice.py:42
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr "Ας πάμε μια βόλτα!"
|
||||
|
||||
#: voice.py:45
|
||||
#, fuzzy
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr "Είναι η καλύτερημέρα της ζωής μου!"
|
||||
|
||||
#: voice.py:48
|
||||
msgid "Shitty day :/"
|
||||
msgstr "Σκατένια μέρα :/"
|
||||
|
||||
#: voice.py:52
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr "Βαριέμαι πάρα πολύ ..."
|
||||
|
||||
#: voice.py:53
|
||||
msgid "I'm very sad ..."
|
||||
msgstr "Είμαι πολύ λυπημένο ..."
|
||||
|
||||
#: voice.py:54
|
||||
msgid "I'm sad"
|
||||
msgstr "Είμαι λυπημένο"
|
||||
|
||||
#: voice.py:59
|
||||
msgid "I'm living the life!"
|
||||
msgstr "Ζω την ζωή μου!"
|
||||
|
||||
#: voice.py:60
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr "Pwnάρω, άρα υπάρχω."
|
||||
|
||||
#: voice.py:61
|
||||
msgid "So many networks!!!"
|
||||
msgstr "Τόσα πολλά δίκτυα!!!"
|
||||
|
||||
#: voice.py:62
|
||||
#, fuzzy
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr "Περνάω τέλεια!"
|
||||
|
||||
#: voice.py:63
|
||||
#, fuzzy
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr "Η περιέργεια είναιτο μόνο έγκλημά μου ..."
|
||||
|
||||
#: voice.py:67
|
||||
#, fuzzy, python-brace-format
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you. {name}"
|
||||
msgstr "Γειά {name}!Χάρηκα για τη γνωριμία. {name}"
|
||||
|
||||
#: voice.py:68
|
||||
#, fuzzy, python-brace-format
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby! {name}"
|
||||
msgstr "Η μονάδα{name}είναι κοντά! {name}"
|
||||
msgstr "Η μονάδα {name} είναι κοντά! {name}"
|
||||
|
||||
#: voice.py:72
|
||||
#, fuzzy, python-brace-format
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
msgstr "Εμμ ...αντίο{name}"
|
||||
msgstr "Εμμ ...αντίο {name}"
|
||||
|
||||
#: voice.py:73
|
||||
#, fuzzy, python-brace-format
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr "Το {name}έφυγε ..."
|
||||
msgstr "Το {name} έφυγε ..."
|
||||
|
||||
#: voice.py:77
|
||||
#, fuzzy, python-brace-format
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr "Ουπς ... Εξαφανίστηκε το{name}."
|
||||
msgstr "Ουπς ... Εξαφανίστηκε το {name}."
|
||||
|
||||
#: voice.py:78
|
||||
#, fuzzy, python-brace-format
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr "Έχασα το{name}!"
|
||||
msgstr "Έχασα το {name}!"
|
||||
|
||||
#: voice.py:79
|
||||
msgid "Missed!"
|
||||
msgstr "Το έχασα!"
|
||||
|
||||
#: voice.py:83
|
||||
#, fuzzy
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr "Κανείς δε θέλει ναπαίξει μαζί μου ..."
|
||||
|
||||
#: voice.py:84
|
||||
msgid "I feel so alone ..."
|
||||
msgstr "Νιώθω μοναχός μου ..."
|
||||
|
||||
#: voice.py:85
|
||||
msgid "Where's everybody?!"
|
||||
msgstr "Μα, πού πήγαν όλοι;!"
|
||||
|
||||
#: voice.py:89
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr "Ξεκουράζομαι για {secs}s ..."
|
||||
|
||||
#: voice.py:90
|
||||
msgid "Zzzzz"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:91
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:98
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr "Περιμένω για {secs}s ..."
|
||||
|
||||
#: voice.py:100
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr "Ψάχνω τριγύρω ({secs})"
|
||||
|
||||
#: voice.py:106
|
||||
#, fuzzy, python-brace-format
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr "Εε!{what},ας γίνουμε φίλοι!"
|
||||
msgstr "Εε! {what}, ας γίνουμε φίλοι!"
|
||||
|
||||
#: voice.py:107
|
||||
#, fuzzy, python-brace-format
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
msgstr "Συνδέομαι με το{what}"
|
||||
msgstr "Συνδέομαι με το {what}"
|
||||
|
||||
#: voice.py:108
|
||||
#, fuzzy, python-brace-format
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr "Που'σαι ρε τρελέ{what}!"
|
||||
msgstr "Που'σαι ρε τρελέ {what}!"
|
||||
|
||||
#: voice.py:112
|
||||
#, fuzzy, python-brace-format
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr "Μόλις αποφάσισα ότι η{mac}δε χρείαζεται WiFi!"
|
||||
msgstr "Μόλις αποφάσισα ότι η {mac} δε χρείαζεται WiFi!"
|
||||
|
||||
#: voice.py:113
|
||||
#, fuzzy, python-brace-format
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
msgstr "Πετάω έξω την{mac}"
|
||||
msgstr "Πετάω έξω την {mac}"
|
||||
|
||||
#: voice.py:114
|
||||
#, fuzzy, python-brace-format
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr "Μπανάρω την{mac}!"
|
||||
msgstr "Μπανάρω την {mac}!"
|
||||
|
||||
#: voice.py:118
|
||||
#, fuzzy, python-brace-format
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr "Τέλεια δικέ μου, πήραμε {num}νέες χειραψίες!"
|
||||
msgstr "Τέλεια δικέ μου, πήραμε {num} νέες χειραψίες!"
|
||||
|
||||
#: voice.py:121
|
||||
#, fuzzy
|
||||
msgid "Ops, something went wrong ... Rebooting ..."
|
||||
msgstr "Ουπς, κάτιπήγε λάθος ...Επανεκκινούμαι ..."
|
||||
msgstr "Ουπς, κάτιπήγε λάθος ... Επανεκκινούμαι ..."
|
||||
|
||||
#: voice.py:124
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr "Έριξα {num} σταθμούς\n"
|
||||
|
||||
#: voice.py:125
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr "Έκανα {num} νέους φίλους\n"
|
||||
|
||||
#: voice.py:126
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr "Πήρα {num} χειραψίες\n"
|
||||
|
||||
#: voice.py:128
|
||||
msgid "Met 1 peer"
|
||||
msgstr "Γνώρισα 1 φίλο"
|
||||
|
||||
#: voice.py:130
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr "Γνώρισα {num} φίλους"
|
||||
|
||||
#: voice.py:135
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
|
||||
@ -245,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 ""
|
||||
|
Binary file not shown.
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 0.0.1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-10-03 12:22+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"
|
||||
@ -18,220 +18,193 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: voice.py:18
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:22
|
||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||
msgstr ""
|
||||
msgstr "Bonjour, je suis Pwnagotchi! Démarrage ..."
|
||||
|
||||
#: voice.py:23
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr ""
|
||||
msgstr "Nouvelle journée, nouvelle chasse, nouveau pwns!"
|
||||
|
||||
#: voice.py:24
|
||||
msgid "Hack the Planet!"
|
||||
msgstr ""
|
||||
msgstr "Hack la planète!"
|
||||
|
||||
#: voice.py:28
|
||||
msgid "AI ready."
|
||||
msgstr ""
|
||||
msgstr "IA prête."
|
||||
|
||||
#: voice.py:29
|
||||
msgid "The neural network is ready."
|
||||
msgstr ""
|
||||
msgstr "Le réseau neuronal est prêt."
|
||||
|
||||
#: voice.py:37
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr ""
|
||||
msgstr "Hey, le channel {channel} est libre! Ton AP va dis merci."
|
||||
|
||||
#: voice.py:41
|
||||
msgid "I'm bored ..."
|
||||
msgstr ""
|
||||
msgstr "Je m'ennuie ..."
|
||||
|
||||
#: voice.py:42
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr ""
|
||||
msgstr "Allons faire un tour!"
|
||||
|
||||
#: voice.py:45
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr ""
|
||||
msgstr "C'est le meilleur jour de ma vie!"
|
||||
|
||||
#: voice.py:48
|
||||
msgid "Shitty day :/"
|
||||
msgstr ""
|
||||
msgstr "Journée de merde :/"
|
||||
|
||||
#: voice.py:52
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr ""
|
||||
msgstr "Je m'ennuie énormément ..."
|
||||
|
||||
#: voice.py:53
|
||||
msgid "I'm very sad ..."
|
||||
msgstr ""
|
||||
msgstr "Je suis très triste ..."
|
||||
|
||||
#: voice.py:54
|
||||
msgid "I'm sad"
|
||||
msgstr ""
|
||||
msgstr "Je suis triste"
|
||||
|
||||
#: voice.py:59
|
||||
msgid "I'm living the life!"
|
||||
msgstr ""
|
||||
msgstr "Je vis la vie!"
|
||||
|
||||
#: voice.py:60
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr ""
|
||||
msgstr "Je pwn donc je suis."
|
||||
|
||||
#: voice.py:61
|
||||
msgid "So many networks!!!"
|
||||
msgstr ""
|
||||
msgstr "Autant de réseaux!!!"
|
||||
|
||||
#: voice.py:62
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr ""
|
||||
msgstr "Je m'amuse tellement!"
|
||||
|
||||
#: voice.py:63
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr ""
|
||||
msgstr "Mon crime est celui de la curiosité ..."
|
||||
|
||||
#: voice.py:67
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you. {name}"
|
||||
msgstr ""
|
||||
msgstr "Bonjour {name}! Ravis de te rencontrer. {name}"
|
||||
|
||||
#: voice.py:68
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby! {name}"
|
||||
msgstr ""
|
||||
msgstr "L'unité {name} est proche! {name}"
|
||||
|
||||
#: voice.py:72
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
msgstr ""
|
||||
msgstr "Hum ... au revoir {name}"
|
||||
|
||||
#: voice.py:73
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr ""
|
||||
msgstr "{name} est parti ..."
|
||||
|
||||
#: voice.py:77
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr ""
|
||||
msgstr "Oups ... {name} est parti."
|
||||
|
||||
#: voice.py:78
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr ""
|
||||
msgstr "{name} raté!"
|
||||
|
||||
#: voice.py:79
|
||||
msgid "Missed!"
|
||||
msgstr ""
|
||||
msgstr "Raté!"
|
||||
|
||||
#: voice.py:83
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr ""
|
||||
msgstr "Personne ne veut jouer avec moi ..."
|
||||
|
||||
#: voice.py:84
|
||||
msgid "I feel so alone ..."
|
||||
msgstr ""
|
||||
msgstr "Je me sens si seul ..."
|
||||
|
||||
#: voice.py:85
|
||||
msgid "Where's everybody?!"
|
||||
msgstr ""
|
||||
msgstr "Où est tout le monde?!"
|
||||
|
||||
#: voice.py:89
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr ""
|
||||
msgstr "Fais la sieste pendant {secs}s ..."
|
||||
|
||||
#: voice.py:90
|
||||
msgid "Zzzzz"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:91
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:98
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr ""
|
||||
msgstr "Attends pendant {secs}s ..."
|
||||
|
||||
#: voice.py:100
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr ""
|
||||
msgstr "Regarde autour ({secs}s)"
|
||||
|
||||
#: voice.py:106
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr ""
|
||||
msgstr "Hey {what}, soyons amis!"
|
||||
|
||||
#: voice.py:107
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
msgstr ""
|
||||
msgstr "Association à {what}"
|
||||
|
||||
#: voice.py:108
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:112
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr ""
|
||||
msgstr "Décidé à l'instant que {mac} n'a pas besoin de WiFi!"
|
||||
|
||||
#: voice.py:113
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
msgstr ""
|
||||
msgstr "Désauthentification de {mac}"
|
||||
|
||||
#: voice.py:114
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:118
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr ""
|
||||
msgstr "Cool, nous avons {num} nouveaux handshake{plural}!"
|
||||
|
||||
#: voice.py:121
|
||||
msgid "Ops, something went wrong ... Rebooting ..."
|
||||
msgstr ""
|
||||
msgstr "Oups, quelque chose s'est mal passé ... Redémarrage ..."
|
||||
|
||||
#: voice.py:124
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr ""
|
||||
msgstr "{num} stations kick\n"
|
||||
|
||||
#: voice.py:125
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr ""
|
||||
msgstr "Fait {num} nouveaux amis\n"
|
||||
|
||||
#: voice.py:126
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr ""
|
||||
msgstr "Récupéré {num} handshakes\n"
|
||||
|
||||
#: voice.py:128
|
||||
msgid "Met 1 peer"
|
||||
msgstr ""
|
||||
msgstr "1 peer rencontré"
|
||||
|
||||
#: voice.py:130
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr ""
|
||||
msgstr "{num} peers recontrés"
|
||||
|
||||
#: voice.py:135
|
||||
#, 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 ""
|
||||
"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 ""
|
||||
|
Binary file not shown.
@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 0.0.1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-10-03 13:10+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"
|
||||
@ -15,219 +15,169 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: voice.py:18
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:22
|
||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||
msgstr "Ciao! Piacere Pwnagotchi! Caricamento ..."
|
||||
|
||||
#: voice.py:23
|
||||
#, fuzzy
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr "Nuovo giorno...nuovi handshakes!!!"
|
||||
|
||||
#: voice.py:24
|
||||
msgid "Hack the Planet!"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:28
|
||||
msgid "AI ready."
|
||||
msgstr "IA pronta."
|
||||
|
||||
#: voice.py:29
|
||||
msgid "The neural network is ready."
|
||||
msgstr "La rete neurale è pronta."
|
||||
|
||||
#: voice.py:37
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr "Hey, il canale {channel} è libero! Il tuo AP ringrazia."
|
||||
|
||||
#: voice.py:41
|
||||
msgid "I'm bored ..."
|
||||
msgstr "Che noia ..."
|
||||
|
||||
#: voice.py:42
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr ""
|
||||
"Andiamo a fare una passeggiata!"
|
||||
msgstr "Andiamo a fare una passeggiata!"
|
||||
|
||||
#: voice.py:45
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr "Questo è il più bel giorno della mia vita!!!!"
|
||||
|
||||
#: voice.py:48
|
||||
msgid "Shitty day :/"
|
||||
msgstr "Giorno di merda :/"
|
||||
|
||||
#: voice.py:52
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr "Sono estremamente annoiato ..."
|
||||
|
||||
#: voice.py:53
|
||||
msgid "I'm very sad ..."
|
||||
msgstr "Sono molto triste..."
|
||||
|
||||
#: voice.py:54
|
||||
msgid "I'm sad"
|
||||
msgstr "Sono triste"
|
||||
|
||||
#: voice.py:59
|
||||
msgid "I'm living the life!"
|
||||
msgstr "Mi sento vivo!"
|
||||
|
||||
#: voice.py:60
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr "Pwn ergo sum."
|
||||
|
||||
#: voice.py:61
|
||||
msgid "So many networks!!!"
|
||||
msgstr "Qui è pieno di reti!"
|
||||
|
||||
#: voice.py:62
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr "Mi sto divertendo tantissimo!"
|
||||
|
||||
#: voice.py:63
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:67
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you. {name}"
|
||||
msgstr "Ciao {name}! E' un piacere. {name}"
|
||||
|
||||
#: voice.py:68
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby! {name}"
|
||||
msgstr "L'Unità {name} è vicina! {name}"
|
||||
|
||||
#: voice.py:72
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
msgstr "Uhm ... addio {name}, mi mancherai..."
|
||||
|
||||
#: voice.py:73
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr "{name} se n'è andato ..."
|
||||
|
||||
#: voice.py:77
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr "Whoops ...{name} se n'è andato."
|
||||
|
||||
#: voice.py:78
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr "{name} è scomparso..."
|
||||
|
||||
#: voice.py:79
|
||||
msgid "Missed!"
|
||||
msgstr "Ehi! Dove sei andato!?"
|
||||
|
||||
#: voice.py:83
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr "Nessuno vuole giocare con me..."
|
||||
|
||||
#: voice.py:84
|
||||
msgid "I feel so alone ..."
|
||||
msgstr "Mi sento così solo..."
|
||||
|
||||
#: voice.py:85
|
||||
msgid "Where's everybody?!"
|
||||
msgstr "Dove sono tutti?!"
|
||||
|
||||
#: voice.py:89
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr "Schiaccio un pisolino per {secs}s ..."
|
||||
|
||||
#: voice.py:90
|
||||
msgid "Zzzzz"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:91
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:98
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr "Aspetto {secs}s ..."
|
||||
|
||||
#: voice.py:100
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr "Do uno sguardo qui intorno... ({secs}s)"
|
||||
|
||||
#: voice.py:106
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr "Hey {what}! Diventiamo amici!"
|
||||
|
||||
#: voice.py:107
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
msgstr "Collegamento con {what} in corso..."
|
||||
|
||||
#: voice.py:108
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr "Yo {what}!"
|
||||
|
||||
#: voice.py:112
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr "Ho appena deciso che {mac} non necessita di WiFi!"
|
||||
|
||||
#: voice.py:113
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:114
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr "Sto prendendo a calci {mac}!"
|
||||
|
||||
#: voice.py:118
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr "Bene, abbiamo {num} handshake{plural} in più!"
|
||||
|
||||
#: voice.py:121
|
||||
msgid "Ops, something went wrong ... Rebooting ..."
|
||||
msgstr "Ops, qualcosa è andato storto ... Riavvio ..."
|
||||
|
||||
#: voice.py:124
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr "{num} stazioni pestate\n"
|
||||
|
||||
#: voice.py:125
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr "{num} nuovi amici\n"
|
||||
|
||||
#: voice.py:126
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr "{num} handshakes presi\n"
|
||||
|
||||
#: voice.py:128
|
||||
msgid "Met 1 peer"
|
||||
msgstr "1 peer incontrato"
|
||||
|
||||
#: voice.py:130
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr "{num} peers incontrati"
|
||||
|
||||
#: voice.py:135
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
|
||||
@ -237,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"
|
||||
|
Binary file not shown.
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 0.0.1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-10-03 12:44+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"
|
||||
@ -17,225 +17,169 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: voice.py:18
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr "ДреееММмммМммм"
|
||||
|
||||
#: voice.py:22
|
||||
#, fuzzy
|
||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||
msgstr "Здраво, јас сум Pwnagotchi!Почнувам ..."
|
||||
msgstr "Здраво, јас сум Pwnagotchi! Почнувам ..."
|
||||
|
||||
#: voice.py:23
|
||||
#, fuzzy
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr "Нов ден, нов лов,ќе си газиме!"
|
||||
msgstr "Нов ден, нов лов, ќе си газиме!"
|
||||
|
||||
#: voice.py:24
|
||||
msgid "Hack the Planet!"
|
||||
msgstr "Хак д Планет!"
|
||||
|
||||
#: voice.py:28
|
||||
msgid "AI ready."
|
||||
msgstr "AI спремно."
|
||||
|
||||
#: voice.py:29
|
||||
#, fuzzy
|
||||
msgid "The neural network is ready."
|
||||
msgstr "Невронската мрежае спремна."
|
||||
|
||||
#: voice.py:37
|
||||
#, fuzzy, python-brace-format
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr "Еј, каналот {channel} еслободен! APто ќе тикаже фала."
|
||||
|
||||
#: voice.py:41
|
||||
msgid "I'm bored ..."
|
||||
msgstr "Досаднооо ..."
|
||||
|
||||
#: voice.py:42
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr "Ајде да шетнеме!"
|
||||
|
||||
#: voice.py:45
|
||||
#, fuzzy
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr "Ова ми е најдобриот ден во животот!"
|
||||
|
||||
#: voice.py:48
|
||||
msgid "Shitty day :/"
|
||||
msgstr "Срање ден :/"
|
||||
|
||||
#: voice.py:52
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr "Ултра досадно ..."
|
||||
|
||||
#: voice.py:53
|
||||
msgid "I'm very sad ..."
|
||||
msgstr "Многу тажно ..."
|
||||
|
||||
#: voice.py:54
|
||||
msgid "I'm sad"
|
||||
msgstr "Тажно"
|
||||
|
||||
#: voice.py:59
|
||||
msgid "I'm living the life!"
|
||||
msgstr "Ммхх животче!"
|
||||
|
||||
#: voice.py:60
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr "Си газам значи постојам."
|
||||
|
||||
#: voice.py:61
|
||||
msgid "So many networks!!!"
|
||||
msgstr "Мммм колку мрежи!!!"
|
||||
|
||||
#: voice.py:62
|
||||
#, fuzzy
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr "Јухуу забавноо ее!"
|
||||
|
||||
#: voice.py:63
|
||||
#, fuzzy
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr "Виновен сум само заљубопитност ..."
|
||||
|
||||
#: voice.py:67
|
||||
#, fuzzy, python-brace-format
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you. {name}"
|
||||
msgstr "Здраво{name}!Мило ми е. {name}"
|
||||
msgstr "Здраво{name}! Мило ми е. {name}"
|
||||
|
||||
#: voice.py:68
|
||||
#, fuzzy, python-brace-format
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby! {name}"
|
||||
msgstr "Опаа{name}е во близина! {name}"
|
||||
msgstr "Опаа {name} е во близина! {name}"
|
||||
|
||||
#: voice.py:72
|
||||
#, fuzzy, python-brace-format
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
msgstr "Хмм ...чао{name}"
|
||||
msgstr "Хмм ...чао {name}"
|
||||
|
||||
#: voice.py:73
|
||||
#, fuzzy, python-brace-format
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr "{name}го снема ..."
|
||||
msgstr "{name} го снема ..."
|
||||
|
||||
#: voice.py:77
|
||||
#, fuzzy, python-brace-format
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr "Уупс ...{name}го снема."
|
||||
msgstr "Уупс ... {name} го снема."
|
||||
|
||||
#: voice.py:78
|
||||
#, fuzzy, python-brace-format
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr "{name}промаши!"
|
||||
msgstr "{name} промаши!"
|
||||
|
||||
#: voice.py:79
|
||||
msgid "Missed!"
|
||||
msgstr "Промаши!"
|
||||
|
||||
#: voice.py:83
|
||||
#, fuzzy
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr "Никој не сака даси игра со мене ..."
|
||||
|
||||
#: voice.py:84
|
||||
msgid "I feel so alone ..."
|
||||
msgstr "Толку сам ..."
|
||||
|
||||
#: voice.py:85
|
||||
msgid "Where's everybody?!"
|
||||
msgstr "Каде се сите?!"
|
||||
|
||||
#: voice.py:89
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr "Ќе дремнам {secs}с ..."
|
||||
|
||||
#: voice.py:90
|
||||
msgid "Zzzzz"
|
||||
msgstr "Дреммм"
|
||||
|
||||
#: voice.py:91
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr "Дремммм ({secs}с)"
|
||||
|
||||
#: voice.py:98
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr "Чекам {secs}с ..."
|
||||
|
||||
#: voice.py:100
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr "Шарам наоколу ({secs}с)"
|
||||
|
||||
#: voice.py:106
|
||||
#, fuzzy, python-brace-format
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr "Еј{what}ајде да се дружиме!"
|
||||
msgstr "Еј {what} ајде да се дружиме!"
|
||||
|
||||
#: voice.py:107
|
||||
#, fuzzy, python-brace-format
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
msgstr "Се закачувам на{what}"
|
||||
msgstr "Се закачувам на {what}"
|
||||
|
||||
#: voice.py:108
|
||||
#, fuzzy, python-brace-format
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr "Јо{what}!"
|
||||
msgstr "Јо {what}!"
|
||||
|
||||
#: voice.py:112
|
||||
#, fuzzy, python-brace-format
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr "Знаеш што, на{mac}не му треба WiFi!"
|
||||
msgstr "Знаеш што, на {mac} не му треба WiFi!"
|
||||
|
||||
#: voice.py:113
|
||||
#, fuzzy, python-brace-format
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
msgstr "Го деавтентицирам{mac}"
|
||||
msgstr "Го деавтентицирам {mac}"
|
||||
|
||||
#: voice.py:114
|
||||
#, fuzzy, python-brace-format
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr "Кикбан{mac}!"
|
||||
msgstr "Кикбан {mac}!"
|
||||
|
||||
#: voice.py:118
|
||||
#, fuzzy, python-brace-format
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr "Кул, фативме {num}нови ракувања!"
|
||||
msgstr "Кул, фативме {num} нови ракувања!"
|
||||
|
||||
#: voice.py:121
|
||||
#, fuzzy
|
||||
msgid "Ops, something went wrong ... Rebooting ..."
|
||||
msgstr "Упс, нешто не еко што треба ...Рестартирам ..."
|
||||
msgstr "Упс, нешто не еко што треба ... Рестартирам ..."
|
||||
|
||||
#: voice.py:124
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr "Избацив {num} станици\n"
|
||||
|
||||
#: voice.py:125
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr "{num} нови другарчиња\n"
|
||||
|
||||
#: voice.py:126
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr "Фатив {num} ракувања\n"
|
||||
|
||||
#: voice.py:128
|
||||
msgid "Met 1 peer"
|
||||
msgstr "Запознав 1 пријател"
|
||||
|
||||
#: voice.py:130
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr "Запознав {num} пријатели"
|
||||
|
||||
#: voice.py:135
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
|
||||
@ -245,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 ""
|
||||
|
Binary file not shown.
@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 0.0.1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-10-03 12:44+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"
|
||||
@ -16,218 +16,169 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: voice.py:18
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr "ZzzzZZzzzzZzzz"
|
||||
|
||||
#: voice.py:22
|
||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||
msgstr "Hoi, Ik ben Pwnagotchi! Opstarten ..."
|
||||
|
||||
#: voice.py:23
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr "Nieuwe dag, nieuwe jacht, nieuwe pwns!"
|
||||
|
||||
#: voice.py:24
|
||||
msgid "Hack the Planet!"
|
||||
msgstr "Hack de Wereld!"
|
||||
|
||||
#: voice.py:28
|
||||
msgid "AI ready."
|
||||
msgstr "AI is klaar."
|
||||
|
||||
#: voice.py:29
|
||||
msgid "The neural network is ready."
|
||||
msgstr "Neuronen netwerkis klaar voor gebruik."
|
||||
|
||||
#: voice.py:37
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr "Hey, kanaal {channel} is vrij! Je AP zal je bedanken."
|
||||
|
||||
#: voice.py:41
|
||||
msgid "I'm bored ..."
|
||||
msgstr "Ik verveel me ..."
|
||||
|
||||
#: voice.py:42
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr "Laten we een rondje lopen!"
|
||||
|
||||
#: voice.py:45
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr "Dit is de beste dag van mijn leven!"
|
||||
|
||||
#: voice.py:48
|
||||
msgid "Shitty day :/"
|
||||
msgstr "Ruk dag :/"
|
||||
|
||||
#: voice.py:52
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr "Ik verveel me kapot ..."
|
||||
|
||||
#: voice.py:53
|
||||
msgid "I'm very sad ..."
|
||||
msgstr "Ik ben ergverdrietig ..."
|
||||
|
||||
#: voice.py:54
|
||||
msgid "I'm sad"
|
||||
msgstr "Ik ben verdrietig"
|
||||
|
||||
#: voice.py:59
|
||||
msgid "I'm living the life!"
|
||||
msgstr "Beter kan het levenniet worden!"
|
||||
|
||||
#: voice.py:60
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr "Ik pwn daarom besta ik."
|
||||
|
||||
#: voice.py:61
|
||||
msgid "So many networks!!!"
|
||||
msgstr "Zo veel netwerken!!!"
|
||||
|
||||
#: voice.py:62
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr "Dit is zo leuk!"
|
||||
|
||||
#: voice.py:63
|
||||
#, fuzzy
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr "Mijn enige misdrijfis mijn nieuwsgierigheid ..."
|
||||
msgstr "Mijn enige misdrijf is mijn nieuwsgierigheid ..."
|
||||
|
||||
#: voice.py:67
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you. {name}"
|
||||
msgstr "Hallo {name}! Leuk je te ontmoeten. {name}"
|
||||
|
||||
#: voice.py:68
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby! {name}"
|
||||
msgstr "Unit {name} is dichtbij! {name}"
|
||||
|
||||
#: voice.py:72
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
msgstr "Uhm ...tot ziens {name}"
|
||||
|
||||
#: voice.py:73
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr "{name} is weg"
|
||||
|
||||
#: voice.py:77
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr "Whoopsie ...{name} is weg"
|
||||
|
||||
#: voice.py:78
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr "{name} gemist!"
|
||||
|
||||
#: voice.py:79
|
||||
msgid "Missed!"
|
||||
msgstr "Gemist!"
|
||||
|
||||
#: voice.py:83
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr "Niemand wil metmij spelen ..."
|
||||
|
||||
#: voice.py:84
|
||||
msgid "I feel so alone ..."
|
||||
msgstr "Zo alleen ..."
|
||||
|
||||
#: voice.py:85
|
||||
msgid "Where's everybody?!"
|
||||
msgstr "Waar is iedereen?!"
|
||||
|
||||
#: voice.py:89
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr "Dutje doen voor {secs}s ..."
|
||||
|
||||
#: voice.py:90
|
||||
msgid "Zzzzz"
|
||||
msgstr "Zzzzz"
|
||||
|
||||
#: voice.py:91
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:98
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr "Even {secs}s wachten ..."
|
||||
|
||||
#: voice.py:100
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr "Rond kijken ({secs}s)"
|
||||
|
||||
#: voice.py:106
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr "Hey {what}, laten we vriendenworden!"
|
||||
|
||||
#: voice.py:107
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
msgstr "Verbinden met {what}"
|
||||
|
||||
#: voice.py:108
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:112
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr "Ik vind dat {mac} genoeg WiFiheeft gehad!"
|
||||
|
||||
#: voice.py:113
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
msgstr "De-autoriseren {mac}"
|
||||
|
||||
#: voice.py:114
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr "Ik ga {mac} even kicken!"
|
||||
|
||||
#: voice.py:118
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr "Gaaf, we hebben {num} nieuwe handshake{plural}!"
|
||||
|
||||
#: voice.py:121
|
||||
msgid "Ops, something went wrong ... Rebooting ..."
|
||||
msgstr "Oops, iets ging fout ...Rebooting ..."
|
||||
|
||||
#: voice.py:124
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr "{num} stations gekicked\n"
|
||||
|
||||
#: voice.py:125
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr "{num} nieuwe vrienden\n"
|
||||
|
||||
#: voice.py:126
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr "{num} nieuwe handshakes\n"
|
||||
|
||||
#: voice.py:128
|
||||
msgid "Met 1 peer"
|
||||
msgstr "1 peer ontmoet"
|
||||
|
||||
#: voice.py:130
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr "{num} peers ontmoet"
|
||||
|
||||
#: voice.py:135
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
|
||||
@ -237,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 ""
|
||||
|
Binary file not shown.
@ -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"
|
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2019-10-03 13:10+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"
|
||||
@ -17,220 +17,190 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=CHARSET\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
|
||||
#: voice.py:18
|
||||
msgid "ZzzzZZzzzzZzzz"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:22
|
||||
msgid "Hi, I'm Pwnagotchi! Starting ..."
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:23
|
||||
msgid "New day, new hunt, new pwns!"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:24
|
||||
msgid "Hack the Planet!"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:28
|
||||
msgid "AI ready."
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:29
|
||||
msgid "The neural network is ready."
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:37
|
||||
#, python-brace-format
|
||||
msgid "Hey, channel {channel} is free! Your AP will say thanks."
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:41
|
||||
msgid "I'm bored ..."
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:42
|
||||
msgid "Let's go for a walk!"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:45
|
||||
msgid "This is the best day of my life!"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:48
|
||||
msgid "Shitty day :/"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:52
|
||||
msgid "I'm extremely bored ..."
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:53
|
||||
msgid "I'm very sad ..."
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:54
|
||||
msgid "I'm sad"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:59
|
||||
msgid "I'm living the life!"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:60
|
||||
msgid "I pwn therefore I am."
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:61
|
||||
msgid "So many networks!!!"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:62
|
||||
msgid "I'm having so much fun!"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:63
|
||||
msgid "My crime is that of curiosity ..."
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:67
|
||||
#, python-brace-format
|
||||
msgid "Hello {name}! Nice to meet you. {name}"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:68
|
||||
#, python-brace-format
|
||||
msgid "Unit {name} is nearby! {name}"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:72
|
||||
#, python-brace-format
|
||||
msgid "Uhm ... goodbye {name}"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:73
|
||||
#, python-brace-format
|
||||
msgid "{name} is gone ..."
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:77
|
||||
#, python-brace-format
|
||||
msgid "Whoops ... {name} is gone."
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:78
|
||||
#, python-brace-format
|
||||
msgid "{name} missed!"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:79
|
||||
msgid "Missed!"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:83
|
||||
msgid "Nobody wants to play with me ..."
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:84
|
||||
msgid "I feel so alone ..."
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:85
|
||||
msgid "Where's everybody?!"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:89
|
||||
#, python-brace-format
|
||||
msgid "Napping for {secs}s ..."
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:90
|
||||
msgid "Zzzzz"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:91
|
||||
#, python-brace-format
|
||||
msgid "ZzzZzzz ({secs}s)"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:98
|
||||
#, python-brace-format
|
||||
msgid "Waiting for {secs}s ..."
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:100
|
||||
#, python-brace-format
|
||||
msgid "Looking around ({secs}s)"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:106
|
||||
#, python-brace-format
|
||||
msgid "Hey {what} let's be friends!"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:107
|
||||
#, python-brace-format
|
||||
msgid "Associating to {what}"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:108
|
||||
#, python-brace-format
|
||||
msgid "Yo {what}!"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:112
|
||||
#, python-brace-format
|
||||
msgid "Just decided that {mac} needs no WiFi!"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:113
|
||||
#, python-brace-format
|
||||
msgid "Deauthenticating {mac}"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:114
|
||||
#, python-brace-format
|
||||
msgid "Kickbanning {mac}!"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:118
|
||||
#, python-brace-format
|
||||
msgid "Cool, we got {num} new handshake{plural}!"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:121
|
||||
msgid "Ops, something went wrong ... Rebooting ..."
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:124
|
||||
#, python-brace-format
|
||||
msgid "Kicked {num} stations\n"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:125
|
||||
#, python-brace-format
|
||||
msgid "Made {num} new friends\n"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:126
|
||||
#, python-brace-format
|
||||
msgid "Got {num} handshakes\n"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:128
|
||||
msgid "Met 1 peer"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:130
|
||||
#, python-brace-format
|
||||
msgid "Met {num} peers"
|
||||
msgstr ""
|
||||
|
||||
#: voice.py:135
|
||||
#, python-brace-format
|
||||
msgid ""
|
||||
"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
|
||||
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
|
||||
"#pwnlog #pwnlife #hacktheplanet #skynet"
|
||||
msgstr ""
|
||||
|
||||
msgid "hours"
|
||||
msgstr ""
|
||||
|
||||
msgid "minutes"
|
||||
msgstr ""
|
||||
|
||||
msgid "seconds"
|
||||
msgstr ""
|
||||
|
||||
msgid "hour"
|
||||
msgstr ""
|
||||
|
||||
msgid "minute"
|
||||
msgstr ""
|
||||
|
||||
msgid "second"
|
||||
msgstr ""
|
||||
|
@ -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
|
||||
|
||||
@ -37,6 +37,8 @@ class SessionParser(object):
|
||||
self.last_saved_session_id = self.last_session_id
|
||||
|
||||
def _parse_datetime(self, dt):
|
||||
dt = dt.split('.')[0]
|
||||
dt = dt.split(',')[0]
|
||||
dt = datetime.strptime(dt.split('.')[0], '%Y-%m-%d %H:%M:%S')
|
||||
return time.mktime(dt.timetuple())
|
||||
|
||||
@ -123,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 = ''
|
||||
|
@ -2,9 +2,9 @@ import time
|
||||
import json
|
||||
import _thread
|
||||
import threading
|
||||
import logging
|
||||
from scapy.all import Dot11, Dot11FCS, Dot11Elt, RadioTap, sendp, sniff
|
||||
|
||||
import core
|
||||
import pwnagotchi.ui.faces as faces
|
||||
|
||||
import pwnagotchi.mesh.wifi as wifi
|
||||
@ -54,7 +54,6 @@ class Advertiser(object):
|
||||
self._lost_peer_cb = lost_cb
|
||||
|
||||
def on_face_change(self, old, new):
|
||||
# core.log("face change: %s -> %s" % (old, new))
|
||||
self.update({'face': new})
|
||||
|
||||
def start(self):
|
||||
@ -84,12 +83,14 @@ class Advertiser(object):
|
||||
self._stopped.set()
|
||||
|
||||
def _sender(self):
|
||||
core.log("started advertiser thread (period:%s sid:%s) ..." % (str(self._period), self._me.session_id))
|
||||
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:
|
||||
core.log("error: %s" % e)
|
||||
logging.exception("error")
|
||||
time.sleep(self._period)
|
||||
|
||||
def _on_advertisement(self, src_session_id, channel, rssi, adv):
|
||||
@ -97,7 +98,7 @@ class Advertiser(object):
|
||||
with self._peers_lock:
|
||||
if ident not in self._peers:
|
||||
peer = Peer(src_session_id, channel, rssi, adv)
|
||||
core.log("detected unit %s (v%s) on channel %d (%s dBm) [sid:%s pwnd_tot:%d uptime:%d]" % ( \
|
||||
logging.info("detected unit %s (v%s) on channel %d (%s dBm) [sid:%s pwnd_tot:%d uptime:%d]" % ( \
|
||||
peer.full_name(),
|
||||
peer.version(),
|
||||
channel,
|
||||
@ -158,10 +159,10 @@ class Advertiser(object):
|
||||
raise Exception("unknown frame id %d" % dot11elt.ID)
|
||||
|
||||
except Exception as e:
|
||||
core.log("error decoding packet from %s: %s" % (dot11.addr3, e))
|
||||
logging.exception("error decoding packet from %s" % dot11.addr3)
|
||||
|
||||
def _listener(self):
|
||||
# core.log("started advertisements listener ...")
|
||||
# logging.info("started advertisements listener ...")
|
||||
expr = "type mgt subtype beacon and ether src %s" % wifi.SignatureAddress
|
||||
sniff(iface=self._iface, filter=expr, prn=self._on_packet, store=0, stop_filter=lambda x: self._stopped.isSet())
|
||||
|
||||
@ -173,7 +174,7 @@ class Advertiser(object):
|
||||
for ident, peer in self._peers.items():
|
||||
inactive_for = peer.inactive_for()
|
||||
if inactive_for >= Advertiser.MAX_STALE_TIME:
|
||||
core.log("peer %s lost (inactive for %ds)" % (peer.full_name(), inactive_for))
|
||||
logging.info("peer %s lost (inactive for %ds)" % (peer.full_name(), inactive_for))
|
||||
self._lost_peer_cb(peer)
|
||||
stale.append(ident)
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
import time
|
||||
import logging
|
||||
|
||||
import pwnagotchi.mesh.wifi as wifi
|
||||
import pwnagotchi.ui.faces as faces
|
||||
import core
|
||||
|
||||
|
||||
class Peer(object):
|
||||
@ -18,10 +18,10 @@ class Peer(object):
|
||||
|
||||
def update(self, sid, channel, rssi, adv):
|
||||
if self.name() != adv['name']:
|
||||
core.log("peer %s changed name: %s -> %s" % (self.full_name(), self.name(), adv['name']))
|
||||
logging.info("peer %s changed name: %s -> %s" % (self.full_name(), self.name(), adv['name']))
|
||||
|
||||
if self.session_id != sid:
|
||||
core.log("peer %s changed session id: %s -> %s" % (self.full_name(), self.session_id, sid))
|
||||
logging.info("peer %s changed session id: %s -> %s" % (self.full_name(), self.session_id, sid))
|
||||
|
||||
self.presence[channel - 1] += 1
|
||||
self.adv = adv
|
||||
|
@ -1,7 +1,8 @@
|
||||
import _thread
|
||||
import logging
|
||||
|
||||
import core
|
||||
import pwnagotchi, pwnagotchi.plugins as plugins
|
||||
import pwnagotchi
|
||||
import pwnagotchi.plugins as plugins
|
||||
from pwnagotchi.mesh import get_identity
|
||||
|
||||
|
||||
@ -33,7 +34,7 @@ class AsyncAdvertiser(object):
|
||||
self._advertiser.start()
|
||||
self._view.on_state_change('face', self._advertiser.on_face_change)
|
||||
else:
|
||||
core.log("advertising is disabled")
|
||||
logging.warning("advertising is disabled")
|
||||
|
||||
def _on_new_unit(self, peer):
|
||||
self._view.on_new_peer(peer)
|
||||
|
@ -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')
|
||||
|
@ -0,0 +1,56 @@
|
||||
__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
|
||||
|
||||
|
||||
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:
|
||||
display.set('status', 'Backing up ...')
|
||||
display.update()
|
||||
|
||||
for cmd in OPTIONS['commands']:
|
||||
subprocess.call(cmd.format(files=files_to_backup).split(), stdout=open(os.devnull, 'wb'))
|
||||
|
||||
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()
|
@ -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()
|
@ -3,17 +3,25 @@ __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
|
||||
|
||||
from pwnagotchi.ui.components import LabeledValue
|
||||
from pwnagotchi.ui.view import BLACK
|
||||
import pwnagotchi.ui.fonts as fonts
|
||||
import core
|
||||
|
||||
|
||||
# Will be set with the options in config.yml config['main']['plugins'][__name__]
|
||||
OPTIONS = dict()
|
||||
|
||||
# called when the plugin is loaded
|
||||
def on_loaded():
|
||||
core.log("WARNING: plugin %s should be disabled!" % __name__)
|
||||
logging.warning("WARNING: plugin %s should be disabled!" % __name__)
|
||||
|
||||
|
||||
# called in manual mode when there's internet connectivity
|
||||
def on_internet_available(ui, config, log):
|
||||
pass
|
||||
|
||||
|
||||
# called to setup the ui elements
|
||||
@ -39,7 +47,7 @@ def on_display_setup(display):
|
||||
|
||||
# called when everything is ready and the main loop is about to start
|
||||
def on_ready(agent):
|
||||
core.log("unit is ready")
|
||||
logging.info("unit is ready")
|
||||
# you can run custom bettercap commands if you want
|
||||
# agent.run('ble.recon on')
|
||||
# or set a custom state
|
||||
@ -76,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
|
||||
|
||||
|
@ -3,9 +3,8 @@ __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 core
|
||||
import logging
|
||||
import json
|
||||
import os
|
||||
|
||||
@ -15,14 +14,14 @@ running = False
|
||||
|
||||
|
||||
def on_loaded():
|
||||
core.log("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):
|
||||
core.log("enabling GPS bettercap's module for %s" % device)
|
||||
logging.info("enabling gps bettercap's module for %s" % device)
|
||||
try:
|
||||
agent.run('gps off')
|
||||
except:
|
||||
@ -33,7 +32,7 @@ def on_ready(agent):
|
||||
agent.run('gps on')
|
||||
running = True
|
||||
else:
|
||||
core.log("no GPS detected")
|
||||
logging.warning("no GPS detected")
|
||||
|
||||
|
||||
def on_handshake(agent, filename, access_point, client_station):
|
||||
@ -42,6 +41,6 @@ def on_handshake(agent, filename, access_point, client_station):
|
||||
gps = info['gps']
|
||||
gps_filename = filename.replace('.pcap', '.gps.json')
|
||||
|
||||
core.log("saving GPS to %s (%s)" % (gps_filename, gps))
|
||||
logging.info("saving GPS to %s (%s)" % (gps_filename, gps))
|
||||
with open(gps_filename, 'w+t') as fp:
|
||||
json.dump(gps, fp)
|
||||
|
@ -0,0 +1,68 @@
|
||||
# tempmem shows memory infos and cpu temperature
|
||||
#
|
||||
# totalmem usedmem freemem cputemp
|
||||
#
|
||||
__author__ = 'https://github.com/xenDE'
|
||||
__version__ = '1.0.0'
|
||||
__name__ = 'memtemp'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'A plugin that will add a memory and temperature indicator'
|
||||
|
||||
import struct
|
||||
|
||||
from pwnagotchi.ui.components import LabeledValue
|
||||
from pwnagotchi.ui.view import BLACK
|
||||
import pwnagotchi.ui.fonts as fonts
|
||||
|
||||
import time
|
||||
|
||||
|
||||
class MEMTEMP:
|
||||
|
||||
# set the minimum seconds before refresh the values
|
||||
refresh_wait = 30
|
||||
|
||||
refresh_ts_last = time.time() - refresh_wait
|
||||
|
||||
def __init__(self):
|
||||
# only import when the module is loaded and enabled
|
||||
import os
|
||||
|
||||
def get_temp(self):
|
||||
try:
|
||||
temp = os.popen('/opt/vc/bin/vcgencmd measure_temp').readlines()[0].split('=')[1].replace("\n", '').replace("'","")
|
||||
return 'cpu:' + temp
|
||||
except:
|
||||
return 'cpu:0.0C'
|
||||
# cpu:37.4C
|
||||
|
||||
def get_mem_info(self):
|
||||
try:
|
||||
# includes RAM + Swap Memory:
|
||||
# total, used, free = map(int, os.popen('free -t -m').readlines()[-1].split()[1:])
|
||||
# without Swap, only real memory:
|
||||
total, used, free = map(int, os.popen('free -t -m').readlines()[-3].split()[1:4])
|
||||
return "tm:"+str(total)+" um:"+str(used)+" fm:"+str(free)
|
||||
except:
|
||||
return "tm:0 um:0 fm:0"
|
||||
# tm:532 um:82 fm:353
|
||||
|
||||
|
||||
memtemp = None
|
||||
|
||||
|
||||
def on_loaded():
|
||||
global memtemp
|
||||
memtemp = MEMTEMP()
|
||||
|
||||
|
||||
def on_ui_setup(ui):
|
||||
ui.add_element('memtemp', LabeledValue(color=BLACK, label='SYS', value='tm:0 um:0 fm:0 0.0C', position=(0, ui.height()-28),
|
||||
label_font=fonts.Bold, text_font=fonts.Medium))
|
||||
|
||||
|
||||
def on_ui_update(ui):
|
||||
if time.time() > memtemp.refresh_ts_last + memtemp.refresh_wait:
|
||||
ui.set('memtemp', "%s %s" % (memtemp.get_mem_info(), memtemp.get_temp()))
|
||||
memtemp.refresh_ts_last = time.time()
|
||||
|
@ -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}")
|
||||
|
@ -0,0 +1,46 @@
|
||||
__author__ = '33197631+dadav@users.noreply.github.com'
|
||||
__version__ = '1.0.0'
|
||||
__name__ = 'twitter'
|
||||
__license__ = 'GPL3'
|
||||
__description__ = 'This plugin creates tweets about the recent activity of pwnagotchi'
|
||||
|
||||
import logging
|
||||
from pwnagotchi.voice import Voice
|
||||
|
||||
OPTIONS = dict()
|
||||
|
||||
def on_loaded():
|
||||
logging.info("twitter plugin loaded.")
|
||||
|
||||
|
||||
# called in manual mode when there's internet connectivity
|
||||
def on_internet_available(ui, config, log):
|
||||
if log.is_new() and log.handshakes > 0:
|
||||
try:
|
||||
import tweepy
|
||||
except ImportError:
|
||||
logging.error("Couldn't import tweepy")
|
||||
return
|
||||
|
||||
logging.info("detected a new session and internet connectivity!")
|
||||
|
||||
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)
|
||||
|
||||
try:
|
||||
auth = tweepy.OAuthHandler(OPTIONS['consumer_key'], OPTIONS['consumer_secret'])
|
||||
auth.set_access_token(OPTIONS['access_token_key'], OPTIONS['access_token_secret'])
|
||||
api = tweepy.API(auth)
|
||||
|
||||
tweet = Voice(lang=config['main']['lang']).on_log_tweet(log)
|
||||
api.update_with_media(filename=picture, status=tweet)
|
||||
log.save_session_id()
|
||||
|
||||
logging.info("tweeted: %s" % tweet)
|
||||
except Exception as e:
|
||||
logging.exception("error while tweeting")
|
@ -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
|
||||
|
||||
|
@ -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}")
|
@ -3,7 +3,7 @@ from threading import Lock
|
||||
from PIL import Image
|
||||
|
||||
import shutil
|
||||
import core
|
||||
import logging
|
||||
import os
|
||||
import pwnagotchi, pwnagotchi.plugins as plugins
|
||||
|
||||
@ -79,13 +79,12 @@ class Display(View):
|
||||
self._render_cb = None
|
||||
self._display = None
|
||||
self._httpd = None
|
||||
self.canvas = None
|
||||
|
||||
if self._enabled:
|
||||
self._init_display()
|
||||
else:
|
||||
self.on_render(self._on_view_rendered)
|
||||
core.log("display module is disabled")
|
||||
logging.warning("display module is disabled")
|
||||
|
||||
if self._video_enabled:
|
||||
_thread.start_new_thread(self._http_serve, ())
|
||||
@ -93,10 +92,10 @@ class Display(View):
|
||||
def _http_serve(self):
|
||||
if self._video_address is not None:
|
||||
self._httpd = HTTPServer((self._video_address, self._video_port), VideoHandler)
|
||||
core.log("ui available at http://%s:%d/" % (self._video_address, self._video_port))
|
||||
logging.info("ui available at http://%s:%d/" % (self._video_address, self._video_port))
|
||||
self._httpd.serve_forever()
|
||||
else:
|
||||
core.log("could not get ip of usb0, video server not starting")
|
||||
logging.info("could not get ip of usb0, video server not starting")
|
||||
|
||||
def _is_inky(self):
|
||||
return self._display_type in ('inkyphat', 'inky')
|
||||
@ -104,22 +103,25 @@ 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():
|
||||
core.log("initializing inky display")
|
||||
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():
|
||||
core.log("initializing papirus display")
|
||||
logging.info("initializing papirus display")
|
||||
from pwnagotchi.ui.papirus.epd import EPD
|
||||
os.environ['EPD_SIZE'] = '2.0'
|
||||
self._display = EPD()
|
||||
@ -127,10 +129,9 @@ class Display(View):
|
||||
self._render_cb = self._papirus_render
|
||||
|
||||
elif self._is_waveshare1():
|
||||
core.log("initializing waveshare v1 display")
|
||||
logging.info("initializing waveshare v1 display")
|
||||
if self._display_color == 'black':
|
||||
from pwnagotchi.ui.waveshare.v1.epd2in13 import EPD
|
||||
# core.log("display module started")
|
||||
self._display = EPD()
|
||||
self._display.init(self._display.lut_full_update)
|
||||
self._display.Clear(0xFF)
|
||||
@ -144,8 +145,8 @@ class Display(View):
|
||||
self._display.Clear()
|
||||
self._render_cb = self._waveshare_bc_render
|
||||
|
||||
elif self._is_waveshare2():
|
||||
core.log("initializing waveshare v2 display")
|
||||
elif self._is_waveshare_v2():
|
||||
logging.info("initializing waveshare v2 display")
|
||||
from pwnagotchi.ui.waveshare.v2.waveshare import EPD
|
||||
self._display = EPD()
|
||||
self._display.init(self._display.FULL_UPDATE)
|
||||
@ -154,17 +155,23 @@ class Display(View):
|
||||
self._render_cb = self._waveshare_render
|
||||
|
||||
else:
|
||||
core.log("unknown display type %s" % self._display_type)
|
||||
logging.critical("unknown display type %s" % self._display_type)
|
||||
|
||||
plugins.on('display_setup', self._display)
|
||||
|
||||
self.on_render(self._on_view_rendered)
|
||||
|
||||
def image(self):
|
||||
img = None
|
||||
if self.canvas is not None:
|
||||
img = self.canvas if self._rotation == 0 else self.canvas.rotate(-self._rotation)
|
||||
return img
|
||||
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':
|
||||
@ -172,38 +179,40 @@ class Display(View):
|
||||
else:
|
||||
display_colors = 2
|
||||
|
||||
imgbuf = self.canvas.convert('RGB').convert('P', palette=1, colors=display_colors)
|
||||
|
||||
img_buffer = self._canvas.convert('RGB').convert('P', palette=1, colors=display_colors)
|
||||
if self._display_color == 'red':
|
||||
imgbuf.putpalette([
|
||||
img_buffer.putpalette([
|
||||
255, 255, 255, # index 0 is white
|
||||
0, 0, 0, # index 1 is black
|
||||
255, 0, 0 # index 2 is red
|
||||
])
|
||||
elif self._display_color == 'yellow':
|
||||
imgbuf.putpalette([
|
||||
img_buffer.putpalette([
|
||||
255, 255, 255, # index 0 is white
|
||||
0, 0, 0, # index 1 is black
|
||||
255, 255, 0 # index 2 is yellow
|
||||
])
|
||||
else:
|
||||
imgbuf.putpalette([
|
||||
img_buffer.putpalette([
|
||||
255, 255, 255, # index 0 is white
|
||||
0, 0, 0 # index 1 is black
|
||||
])
|
||||
|
||||
self._display.set_image(imgbuf)
|
||||
self._display.show()
|
||||
self._display.set_image(img_buffer)
|
||||
try:
|
||||
self._display.show()
|
||||
except:
|
||||
print("")
|
||||
|
||||
def _papirus_render(self):
|
||||
self._display.display(self.canvas)
|
||||
self._display.display(self._canvas)
|
||||
self._display.partial_update()
|
||||
|
||||
def _waveshare_render(self):
|
||||
buf = self._display.getbuffer(self.canvas)
|
||||
if self._is_waveshare1():
|
||||
buf = self._display.getbuffer(self._canvas)
|
||||
if self._is_waveshare_v1():
|
||||
self._display.display(buf)
|
||||
elif self._is_waveshare2():
|
||||
elif self._is_waveshare_v2():
|
||||
self._display.displayPartial(buf)
|
||||
|
||||
def _waveshare_bc_render(self):
|
||||
@ -216,13 +225,16 @@ class Display(View):
|
||||
# Was included in epd2in13bc.py
|
||||
self._display.displayBlack(buf_black)
|
||||
|
||||
def image(self):
|
||||
img = None
|
||||
if self._canvas is not None:
|
||||
img = self._canvas if self._rotation == 0 else self._canvas.rotate(-self._rotation)
|
||||
return img
|
||||
|
||||
|
||||
def _on_view_rendered(self, img):
|
||||
# core.log("display::_on_view_rendered")
|
||||
VideoHandler.render(img)
|
||||
|
||||
if self._enabled:
|
||||
self.canvas = (img if self._rotation == 0 else img.rotate(self._rotation))
|
||||
self._canvas = (img if self._rotation == 0 else img.rotate(self._rotation))
|
||||
if self._render_cb is not None:
|
||||
self._render_cb()
|
||||
|
@ -1,9 +1,10 @@
|
||||
import _thread
|
||||
from threading import Lock
|
||||
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
|
||||
|
||||
@ -94,8 +95,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),
|
||||
|
||||
@ -123,7 +125,7 @@ class View(object):
|
||||
_thread.start_new_thread(self._refresh_handler, ())
|
||||
self._ignore_changes = ()
|
||||
else:
|
||||
core.log("ui.fps is 0, the display will only update for major changes")
|
||||
logging.warning("ui.fps is 0, the display will only update for major changes")
|
||||
self._ignore_changes = ('uptime', 'name')
|
||||
|
||||
def add_element(self, key, elem):
|
||||
@ -144,7 +146,7 @@ class View(object):
|
||||
|
||||
def _refresh_handler(self):
|
||||
delay = 1.0 / self._config['ui']['fps']
|
||||
# core.log("view refresh handler started with period of %.2fs" % delay)
|
||||
# logging.info("view refresh handler started with period of %.2fs" % delay)
|
||||
|
||||
while True:
|
||||
name = self._state.get('name')
|
||||
@ -174,7 +176,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):
|
||||
@ -322,10 +324,10 @@ class View(object):
|
||||
self.set('status', self._voice.custom(text))
|
||||
self.update()
|
||||
|
||||
def update(self):
|
||||
def update(self, force=False):
|
||||
with self._lock:
|
||||
changes = self._state.changes(ignore=self._ignore_changes)
|
||||
if len(changes):
|
||||
if force or len(changes):
|
||||
self._canvas = Image.new('1', (self._width, self._height), WHITE)
|
||||
drawer = ImageDraw.Draw(self._canvas)
|
||||
|
||||
|
98
sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/utils.py
Normal file
98
sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/utils.py
Normal file
@ -0,0 +1,98 @@
|
||||
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
|
||||
def merge_config(user, default):
|
||||
if isinstance(user, dict) and isinstance(default, dict):
|
||||
for k, v in default.items():
|
||||
if k not in user:
|
||||
user[k] = v
|
||||
else:
|
||||
user[k] = merge_config(user[k], v)
|
||||
return user
|
||||
|
||||
|
||||
def load_config(args):
|
||||
with open(args.config, 'rt') as fp:
|
||||
config = yaml.safe_load(fp)
|
||||
|
||||
if os.path.exists(args.user_config):
|
||||
with open(args.user_config, 'rt') as fp:
|
||||
user_config = yaml.safe_load(fp)
|
||||
config = merge_config(user_config, config)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def setup_logging(args, config):
|
||||
formatter = logging.Formatter("[%(asctime)s] [%(levelname)s] %(message)s")
|
||||
root = logging.getLogger()
|
||||
|
||||
root.setLevel(logging.DEBUG if args.debug else logging.INFO)
|
||||
|
||||
if config['main']['log']:
|
||||
file_handler = logging.FileHandler(config['main']['log'])
|
||||
file_handler.setFormatter(formatter)
|
||||
root.addHandler(file_handler)
|
||||
|
||||
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)
|
@ -1 +0,0 @@
|
||||
version = '1.0.0travistest'
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user