P4wnP1 A.L.O.A.
P4wnP1 A.L.O.A. by MaMe82 is a framework which turns a Rapsberry Pi Zero W into a flexible, low-cost platform for pentesting, red teaming and physical engagements ... or into "A Little Offensive Appliance".
1. Features
Plug&Play USB device emulation
- USB functions:
- USB Ethernet (RNDIS and CDC ECM)
- USB Serial
- USB Mass Storage (Flashdrive or CD-Rom)
- HID Keyboard
- HID Mouse
- runtime reconfiguration of USB stack (no reboot)
- detection of connect/disconnect makes it possible to keep P4wnP1 A.L.O.A powered up (external supply) and trigger action if the emulated USB device is attached to a new host
- no need to deal with different internal ethernet interfaces, as CDC ECM and RNDIS are connected to a virtual bridge
- persistent store and load of configuration templates for USB settings
HIDScript
- replacement for limited DuckyScript
- sophisticated scripting language to automate keyboard and mouse
- up to 8 HIDScript jobs could run in parallel (keep a job up to jiggle the mouse, while others are started on-demand to do arbitrary mouse and keystroke injection seamlessly)
- HIDScript is based on JavaScript, with common libraries available, which allows more complex scripts (function calls,
using
Math
for mouse calculations etc.) - keyboard
- based on UTF-8, so there's no limitation to ASCII characters
- could react on feedback from the hosts real keyboard by reading back LED state changes of NUMLOCK, CAPSLOCK and SCROLLLOCK (if the target OS shares LED state across all connected keyboards, which isn't the case for OSX)
- take branching decisions in HIDScript, based on LED feedback
- mouse
- relative movement (fast, but not precise)
- stepped relative movement (slower, but accurate ... moves mouse in 1 DPI steps)
- absolute positioning on Windows (pixel perfect if target's screen dimensions are known)
- Keyboard and mouse are not only controlled by the same scripting language, both could be used in the same script. This allows combining them in order to achieve goals, which couldn't be achieved using only keyboard or mouse.
Bluetooth
- full interface to Bluez stack (currently no support for remote device discovery/connect)
- allows to run a Bluetooth Network Access Point (NAP)
- customizable Pairing (PIN based legacy mode or SSP)
- High Speed support (uses 802.11 frames to achieve WiFi like transfer rates)
- Runtime reconfiguration of the Bluetooth stack
- Note: PANU is possible, too, but currently not supported (no remote device connection)
- persistent store and load of configuration templates for Bluetooth settings
WiFi
- modified Firmware (build with Nexmon framework)
- allows KARMA (spoof valid answers for Access Points probed by remote devices and allow association)
- broadcast additional Beacons, to emulate multiple SSIDs
- WiFi covert channel
- Note: Nexmon legacy monitor mode is included, but not supported by P4wnP1. Monitor mode is still buggy and likely to crash the firmware if the configuration changes.
- easy Access Point configuration
- easy Station mode configuration (connect to existing AP)
- failover mode (if it is not possible to connect to the target Access Point, bring up an own Access Point)
- runtime reconfiguration of WiFi stack
- persistent store and load of configuration templates for WiFi settings
Networking
- easy ethernet interface configuration for
- bluetooth NAP interface
- USB interface (if RNDIS/CDC ECM is enabled)
- WiFi interface
- supports dedicated DHCP server per interface
- support for DHCP client mode
- manual configuration
- persistent store and load of configuration templates for each interface
Tooling
Not much to say here, P4wnP1 A.L.O.A. is backed by KALI Linux, so everything should be right at your hands (or could be installed using apt)
Configuration and Control via CLI, remotely if needed
- all features mentioned so far, could be configured using a CLI client
- the P4wnP1 core service is a single binary, running as systemd unit which preserves runtime state
- the CLI client interfaces with this service via RPC (gRPC to be specific) to change the state of the core
- as the CLI uses a RPC approach, it could be used for remote configuration, too
- if P4wnP1 is accessed via SSH, the CLI client is there, waiting for your commands (or your tab completion kung fu)
- the CLI is written in Go (as most of the code) and thus compiles for most major platforms and architectures
So if you want to use a a batch file running on a remote Windows host to configure P4wnP1 ... no problem:
- compile the client for windows
- make sure you could connect to P4wnP1 somehow (Bluetooth, WiFi, USB)
- add the
host
parameter to your client commands - ... and use the CLI as you would do with local access.
Configuration and Control via web client
Although it wasn't planned initially, P4wnP1 A.L.O.A. could be configured using a webclient. Even though the client wasn't planned, it evolved to a nice piece of software. In fact it ended up as the main configuration tool for P4wnP1 A.L.O.A. The webclient has capabilities, which couldn't be accessed from the CLI (templates storage, creation of "TriggerActions").
The core features:
- should work on most major mobile and desktop browsers, with consistent look and feel (Quasar Framework)
- uses gRPC via websockets (no RESTful API, no XHR, nearly same approach as CLI)
- Thanks to this interface, the weblient does not rely on a request&reply scheme only, but receives "push events" from
the P4wnP1 core. This means:
- if you (or a script) changes the state of P4wnP1 A.L.O.A. these changes will be immediately reflected into the webclient
- if you have multiple webclients running, changes of the core's state will be reflected from one client to all other clients
- includes a HIDScript editor, with
- syntax highlighting
- auto-complete (
CTRL+SPACE
) - persistent storage & load for HIDScripts
- on-demand execution of HIDScript directly from the browser
- a HIDScript job manager (cancel running jobs, inspect job state and results)
- includes an overview and editor for TriggerActions
- full templating support for all features described so far
- the WebClient is a Single Page Application, once loaded everything runs client side, only gRPC request are exchanged
Automation
The automation approach of the old P4wnP1 version (static bash scripts) couldn't be used anymore.
The automation approach of P4wnP1 A.L.O.A. had to fulfills these requirements:
- easy to use and understand
- usable from a webclient
- be generic and flexible, at the same time
- everything doable with the old "bash script" approach, should still be possible
- able to access all subsystems (USB, WiFi, Bluetooth, Ethernet Interfaces, HIDScript ... )
- modular, with reusable parts
- ability to support (simple) logical tasks without writing additional code
- allow physical computing, by utilizing of the GPIO ports
With introducing of the so called "TriggerActions" and by combining them with the templating system (persistent settings storage for all sub systems) all the requirements could be satisfied. Details on TriggerActions could be find in the WorkFlow section.
2. Workflow part 1 - HIDScript
P4wnP1 A.L.O.A. has no static configuration (or payloads). In fact it has no static workflow at all.
P4wnP1 A.L.O.A. is meant to be as flexible as possible, to allow using it in all possible scenarios (including the ones I couldn't think of while creating P4wnP1 A.L.O.A.).
But there are some basic concepts, I'd like to walk through in this section. As it is hard to explain everything without creating a proper (video) documentation, I visit some some common use cases and examples in order to explain what needs to be explained.
Nevertheless, it is unlikely that I'll have the time to provide a full-fledged documentation. So I encourage everyone to support me with tutorials and ideas, which could be linked back into this README
Now let's start with one of the most basic tasks:
2.1 Run a keystroke injection against a host, which has P4wnP1 attached via USB
The minimum configuration requirements to achieve this goal are:
- The USB sub system is configured to emulate at least a keyboard
- There is a way to access P4wnP1 (remotely), in order to initiate the keystroke injection
The default configuration of P4wnP1's (unmodified image) meets these requirements already:
- the USB settings are initialized to provide keyboard, mouse and ethernet (both, RNDIS and CDC ECM)
- P4wnP1 could already be accessed using one of the following methods:
- WiFi
- the Access Point name should be obvious
- the password is
MaMe82-P4wnP1
- the IP of P4wnP1 is
172.24.0.1
- USB Ethernet
- the IP of P4wnP1 is
172.16.0.1
- the IP of P4wnP1 is
- Bluetooth
- device name
P4wnP1
- PIN
1337
- the IP is
172.26.0.1
- Note: Secure Simple Pairing is OFF in order to force PIN Pairing. This again means, high speed mode is turned off, too. So the bluetooth connection is very slow, which is less of a problem for SSH access, but requesting the webclient could take up to 10 minutes (in contrast to some seconds with high speed enabled).
- device name
- WiFi
- a SSH server is accessible from all the aforementioned IPs
- The SSH user for KALI Linux is
root
, the default password istoor
- The webclient could be reached over all three connections on port 8000 via HTTP
Note: Deploying a HTTPS connection is currently not in scope of the project. So please keep this in mind, if you use the webclient with sensitive data (like WiFi credentials). The whole project isn't built with security in mind (and it is unlikely that this will ever get a requirement). So keep in mind to deploy appropriate measures (f.e. restricting access to webclient with iptables, if the Access Point is configured with Open Authentication; don't keep Bluetooth Discoverability and Connectability enabled without PIN protection etc. etc.)
At this point I assume:
- You have attached P4wnP1 to some target host via USB (the innermost of the Raspberry's micro USB ports is the one to use)
- The USB host has an application running, which is able to receive the keystrokes and has keyboard input focus (f.e. a text editor)
- You are remotely connected to P4wnP1 via SSH (the best way should be WiFi), preferably not from the same host which has the USB connection attached
In order to run the CLI client from the SSH session, issue the following command:
root@kali:~# P4wnP1_cli
The CLI client tool could be used to configure P4wnP1 A.L.O.A.
from the command line. The tool relies on RPC so it could be used
remotely.
Version: v0.1.0-alpha1
Usage:
P4wnP1_cli [command]
Available Commands:
db Database backup and restore
evt Receive P4wnP1 service events
help Help about any command
hid Use keyboard or mouse functionality
led Set or Get LED state of P4wnP1
net Configure Network settings of ethernet interfaces (including USB ethernet if enabled)
system system commands
template Deploy and list templates
trigger Fire a group send action or wait for a group receive trigger
usb USB gadget settings
wifi Configure WiFi (spawn Access Point or join WiFi networks)
Flags:
-h, --help help for P4wnP1_cli
--host string The host with the listening P4wnP1 RPC server (default "localhost")
--port string The port on which the P4wnP1 RPC server is listening (default "50051")
Use "P4wnP1_cli [command] --help" for more information about a command.
The resulting usage help screen shows, that the CLI uses different commands to interact with various subsystems of
P4wnP1 A.L.O.A. Most of these commands have own sub-commands, again. The help for each command or sub-command could be
accessed by appending -h
:
root@kali:~# P4wnP1_cli hid run -h
Run script provided from standard input, commandline parameter or by path to script file on P4wnP1
Usage:
P4wnP1_cli hid run [flags]
Flags:
-c, --commands string HIDScript commands to run, given as string
-h, --help help for run
-r, --server-path string Load HIDScript from given path on P4wnP1 server
-t, --timeout uint32 Interrupt HIDScript after this timeout (seconds)
Global Flags:
--host string The host with the listening P4wnP1 RPC server (default "localhost")
--port string The port on which the P4wnP1 RPC server is listening (default "50051")
Now, in order to type out "Hello world" to the USB host, the following CLI command could be used:
P4wnP1_cli hid run -c 'type("Hello world")'
The result output in the SSH session should look similar to this:
TempFile created: /tmp/HIDscript295065725
Start appending to 'HIDscript295065725' in folder 'TMP'
Result:
null
On the USB host "Hello World" should have been typed to the application with keyboard focus.
If your SSH client runs on the USB host, the "Hello world" is typed somewhere into the result output of the CLI command (it doesn't belong to the output, but has been typed in between).
Goal achieved. We injected keystrokes to the target. Much reading for a simple task, but again, this section is meant to explain basic concepts.
2.2 Moving on to more sophisticated language features of HIDScript
If you managed to run the "Hello world" keystroke injection, this is a good point to explore some additional HIDScript
features. We already know the type
command, here are some more:
Pressing special keys and combinations
The type
command supports pressing return, by encoding a "new line" character into the input string, like this:
P4wnP1_cli hid run -c 'type("line 1\nline 2\nline 3 followed by pressing RETURN three times\n\n\n")'
But what about special keys or key combinations ? The press
command comes to help!
Pressing CTRL+ALT+DELETE
P4wnP1_cli hid run -c 'press("CTRL ALT DELETE")'
Note: all 3 keys have been modifiers in the last example
Press the non-modifier key A:
P4wnP1_cli hid run -c 'press("A")'
Note: The resulting output should be a lowercase 'a', because press
interprets 'A' as key. In contrast, the type
command would try to press a key combination, which results in an uppercase 'A' output character.
Combine a modifier and a non-modifier key, to get an uppercase 'A' as resulting character:
P4wnP1_cli hid run -c 'press("SHIFT A")'
Combine press
and type
.
P4wnP1_cli hid run -c 'type("before caps\n"); press("CAPS"); type("after caps\n"); press("CAPS");'
The last command typed a string, toggled CAPSLOCK, typed another string and toggled CAPS lock again. In result, CAPSLOCK should be in its initial state (toggled two times), but one of the strings is typed uppercase, the other lowercase although both strings have been given in lower case.
Additional notes on key presses with press
:
I don't want to dive into the depth of USB keyboard reports inner workings, but some things are worth mentioning to
pinpoint the limits and possibilities of the press
command (which is based on raw keyboard reports):
- a keyboard report can contain up to 8 modifier keys at once
- the modifier keys are: LEFT_CTRL, RIGHT_CTRL, LEFT_ALT, RIGHT_ALT, LEFT_SHIFT, RIGHT_SHIFT, LEFT_GUI, RIGHT_GUI
- P4wnP1 allows aliases for common modifiers
- CTRL == CONTROL == LEFT_CTRL
- ALT == LEFT_ALT
- SHIFT == LEFT_SHIFT
- WIN == GUI == LEFT_GUI
- in addition to the modifiers, up to eight keys could be added
- normal keys represent characters and special keys
- example of special keys: BACKSPACE, ENTER (== RETURN), F1 .. F12)
/usr/local/P4wnP1/keymaps/common.json
holds a formatted JSON keymap with all possible keys (be careful not to change the file)
- adding multiple keys to the press command, doesn't result in a key sequence, but in pressing all the given keys at the same time
press
releases keys automatically, this means a sequence like "hold ALT, press TAB, press TAB, release ALT" currently isn't possible
Keyboard layout
The HIDScript command to change the keyboard layout is layout(language map string)
.
Here's an example on how to switch keyboard layout, multiple times in a single script:
P4wnP1_cli hid run -c 'layout("us"); type("Typing with EN_US layout\n");layout("de"); type("Typing with German layout supporting special chars üäö\n");'
The output result of the command given above, depends on the layout used by the USB host.
On a host with German keyboard layout the result looks like this:
Tzping with EN?US lazout
Typing with German layout supporting special chars üäö
On a host with US keyboard layout it looks like this:
Typing with EN_US layout
Tzping with German lazout supporting special chars [';
Please note, that the intended output is only achieved, if P4wnP1's keyboard layout aligns with the one of the USB host.
To achieve, this is the obvious aim of the layout
command.
Being able to change the layout in the middle of a running HIDScript, could come in handy: Who knows, maybe you like to brute force the target host's keyboard layout by issuing commands with changing layouts till they achieve the desired effect.
Important: The layout has global effect. This means if multiple HIDScripts are running concurrently and one of the scripts sets a new layout, all other scripts are effected immediately, too.
Typing speed
By default P4wnP1 injects keystrokes as fast as possible. Depending on your goal, this could be a bit too much (think of detecting keystroke injection attacks based on typing speed). HIDScript supports a command to change this behavior.
typingSpeed(delayMillis, jitterMillis)
The first argument is a delay in milliseconds, which is applied before each single keystroke. The second argument is an additional jitter in milliseconds. A random delay between 0 milliseconds and jitter is applied additionally.
Let's try it to type slower:
P4wnP1_cli hid run -c 'typingSpeed(100,0); type("Hello world")'
Instead of a constant delay, try a random jitter:
P4wnP1_cli hid run -c 'typingSpeed(0,500); type("Writing with random jitter up to 500 milliseconds")'
Finally, by combining and tuning both values, we could simulate natural typing speed:
P4wnP1_cli hid run -c 'typingSpeed(100,150); type("Writing with more natural speed")'
Important: The typing speed has global effect. This means if multiple HIDScripts are running concurrently and one of the scripts sets a new typing speed, all other scripts are effected immediately, too.
Wait for LED report
This HIDScript feature needs a bit of explanation.
You may have noticed that (depending on the host OS) the keyboard state modifiers across multiple connected keyboards. For example, if you connect two keyboards to a Windows host, and toggle CAPSLOCK on one of the, the CAPSLOCK LED changes on both of them.
If the state modifier state is shared across multiple keyboards on a given OS, could be tested exactly like this.
In case a USB host supports this kind of state sharing, P4wnP1's HIDScript language could make use out of it.
Imagine the following scenario:
P4wnP1 is connected to a USB host and you want to apply keystroke injection, but you don't want the HIDScript to run the keystrokes immediately. Instead the HIDScript should sit and wait till you hit NUMLOCK, CAPSLOCK or SCROLLLOCK on the host's real keyboard. Why? Maybe you're involved in an engagement, somebody walked in and you don't want that this somebody could see how a ton of characters magically are typed into a console window which suddenly popped up. So you wait till somebody walks out, hit NUMLOCK and ultimately a console window pops up and a ton of character magically are ... yes you got it. It could be done like this:
P4wnP1_cli hid run -c 'waitLED(NUM); type("A ton of characters\n")'
If you tested the command above, you might encounter cases where the keystrokes immediately are issued, even if NUMLOCK wasn't pressed (and the LED didn't toggle), after starting the script.
This is intended behavior and there's a good reason for it. Maybe you have used other keyboard scripting languages, before which target USB devices capable of injecting keystrokes. Most of them have a common problem:
You don't know when to start typing! If you type immediately after the USB device is powered, it is likely that the USB host didn't finished device enumeration and hasn't managed to bring up the keyboard driver. So your keystrokes are lost. To overcome this you could add a delay, before keystroke injection starts. But how long should this delay be? Five seconds, 10 seconds, 30 seconds ? The answer is: it depends! It depends on how fast the host is able to enumerate the device and bring up the driver. In fact you couldn't know how long this takes, without testing against the actual target.
But as we have already learned, Operating Systems like Windows share the LED state across multiple keyboards. This means if the NUMLOCK LED of the host keyboard is ON and you attach a second keyboard, the NUMLOCK LED on this keyboard has to be set to ON, too. If the NUMLOCK LED would have been OFF, anyways, the newly attached keyboard receives the LED state anyways (all LEDs off in this case). The interesting thing about this, is that this "LED update" could only happen, if the keyboard driver of the USB host has finished loading.
Isn't that beautiful, the USB host tells us: "I'm ready to receive keystrokes". There is no need to play around with initial delays.
But here's the problem: We connect P4wnP1 to an USB host. We run a HIDScript starting with waitLED
instead of a delay
and start typing afterwards, but nothing happens. Why? It is likely that we missed the LED state update, because it
arrived before we started the HIDScript at all. Exactly this is the reason, why P4wnP1 preserves all recognized LED
state changes, unless at least one running HIDScript consumes them by calling waitLED
(or waitLEDRepeat
).
It is worth mentioning, that waitLED
returns ONLY if the received LED state differs from P4wnP1's internal state.
This means, even if we listen for a change on any LED with waitLED(ANY)
it still could happen, that we receive an
initial LED state from a USB host after attaching P4wnP1 to it, which doesn't differ from P4wnP1's internal state.
In this case waitLED(ANY)
would block forever (or till a real LED change happens).
This special case could be handled by calling waitLED(ANY_OR_NONE)
, which returns as soon as a new LED state arrive,
even if it doesn't result in a change.
Enough explanation, let's get practical ... before we do so, we have to change the hardware setup a bit:
Attach an external power supply to the second USB port of the Raspberry Pi Zero (the outer one). This assures that P4wnP1 doesn't loose power when detached from the USB host, as it doesn't rely on bus power anymore. The USB port which should be used to connect P4wnP1 to the target USB host is the inner most of the two ports.
Now start the following HIDScript
P4wnP1_cli hid run -c 'while (true) {waitLED(ANY);type("Attached\n");}'
Detach P4wnP1 from the USB host (and make sure it is kept powered on)! Reattach it to the USB host ... Everytime you reattach P4wnP1 to the host, "Attached" should be typed out.
This teaches us 3 things:
waitLED
could be used as initial command in scripts, to start typing as soon as the keyboard driver is readywaitLED
isn't the perfect choice, to pause HID scripts until a LED changing key is pressed on the USB host, as preserved state changes could unblock the command in an unintended way- Providing more complex HIDScript as parameter to the CLI isn't very convenient
We aren't done with the waitLED
command, yet. But before going on lets leave the CLI.
- abort the P4wnP1 CLI with CTRL+C (in case the looping HIDScript is still running)
- open a browser on the host yor have been using for the SSH connection to P4wnP1
- the webclient could be accessed via the same IP as the SSH server, the port is 8000 (for WiFi
http://172.24.0.1:8000
) - navigate to the "HIDScript" tab
- from there you could load and store HIDScripts (we don't do this for now, although
ms_snake.js
is a very good example for the power of LED based triggers)
Replace the script in the editor Window with the following one:
return waitLED(ANY);
After hitting a run button, the right side of the window should show a new running HID job. If you press the little "info" button to the right of the HIDScript job, you could see details, like its state (should be running), the job ID and the VM ID (this is the number of the JavaScript VM running this job. There are 8 of these VMs, so 8 HIDScripts could run in parallel).
Now, if any LED change is emitted from the USB host (by toggling NUM, CAPS or SCROLL) the HIDScript job should end. It still could be found under "Succeeded" jobs.
If you press the little "info" button again, there should be an information about the result value (encoded as JSON), which looks something like this:
{"ERROR":false,"ERRORTEXT":"","TIMEOUT":false,"NUM":true,"CAPS":false,"SCROLL":false,"COMPOSE":false,"KANA":false}
So the waitLED
command returns a JavaScript object looking like this:
{
ERROR: false, // gets true if an error occurred (f.e. HIDScript was aborted, before waitLED could return)
ERRORTEXT: "", // corresponding error string
TIMEOUT: false, // gets true if waitLED timed out (more on this in a minute)
NUM: true, // gets true if NUM LED had changed before waitLED returned
CAPS: false, // gets true if CAPS LED had changed before waitLED returned
SCROLL: false, // gets true if SCROLL LED had changed before waitLED returned
COMPOSE: false, // gets true if COMPOSE LED had changed before waitLED returned (uncommon)
KANA: false // gets true if KANA LED had changed before waitLED returned (uncommon)
}
In my case, NUM
became true. In your case it maybe was CAPS
. It doesn't matter, what does matter is the fact, that
this return value gives the opportunity to take branching decisions in a HIDScript, based on LED state changes issued
from the target USB host.
Let's try an example:
while (true) {
result = waitLED(ANY);
if (result.NUM) {
type("NUM has been toggled\n");
}
if (result.SCROLL) {
type("SCROLL has been toggled\n");
}
if (result.CAPS) {
break; //exit loop
}
}
Assuming the given script is already running, pressing NUM on the USB host should result in typing out "NUM has been toggled", while pressing SCROLL LOCK results in the type text "SCROLL has been toggled". This behavior repeats, until CAPS LOCK is pressed and the resulting LED changes ends the loop.
Puhhh ... a bunch of text on this command, there still some things left.
We provided arguments like NUM
, ANY
or ANY_OR_NONE
commands to waitLED
without much explanation.
In fact waitLED
accepts up to two arguments:
The first argument, as you might have guessed, is a whitelist filter for the LEDs to watch. Valid arguments are:
ANY
(react on a change to any of the LEDs)ANY_OR_NONE
(react on every new LED state, even if there's no change)NUM
(ignore all LED changes, except on the NUM LED)CAPS
(ignore all LED changes, except on the NUM CAPS)SCROLL
(ignore all LED changes, except on the NUM SCROLL)- multiple filters could be combined like this
CAPS | NUM
,NUM | SCROLL
The second argument, we haven't used so far, is a timeout duration in milliseconds. If no LED change occurred during
this timeout, waitLED
returns and has TIMEOUT: true
set in the result object (additionally ERROR
is set to true
and ERRORTEXT
indcates a timeout).
The following command would wait for a change on the NUM LED for up to 5 seconds:
waitLED(NUM,5000)
Even though waitLED
is a very powerful command if used correctly, it doesn't solve the easy task of robustly pausing
a script till a state modifier key is pressed on the target USB host (rember: unintended return, caused by preserved LED
state changes).
This is where waitLEDRepeat
joins the game.
Paste the following script into the editor and try to make the command return. Inspect the HIDScript results afterwards.
return waitLEDRepeat(ANY)
You should quickly notice, that the same LED has to be changed frequently times, in order to make the command return.
It doesn't return if different LEDs change or if the changes happen to slow. The provided argument (ANY
in the
example) serves the same purpose as with waitLED
. It is a whitelist filter. waitLEDRepeat(NUM)
would only return
on changes for the NUM LOCK LED - no matter how fast and often you'd press CAPS, it won't return.
By default, one of the whitelisted LEDs has to change 3 times and the delay between two successive changes mustn't be greater than 800 milliseconds. This behavior could be influenced, by providing additional arguments like in this example:
filter = ANY; // same filters as for waitLED
num_changes = 5; // how often the SAME LED has to change, in order to return from waitLEDRepeat
max_delay = 800; // the maximum duration between two LED changes, which should be taken into acccount (milliseconds)
timeout = 10000; // timeout in milliseconds
waitLEDRepeat(filter, num_changes, max_delay); //wait till a LED frequently changed 5 times, no timeout
waitLEDRepeat(filter, num_changes, max_delay, timeout); //wait till a LED frequently changed 5 times, abort after 10 seconds
So that's how to interact with LED reports from an USB host in HIDScript.
There's one thing left, which should be known: waitLEDRepeat
doesn't differ from waitLED
, when it comes to
consumption of preserved LED state changes. Anyways, it is much harder to trigger it unintended. So waitLEDRepeat
is
the right choice, if the task is to pause HIDScripts till human interaction happens. Of course it could be used for
branching, too.
Up to this point we gained a good bit of knowledge on HIDScript (of course not everything, we haven't even looked into mouse control). Anyways, this text is about P4wnP1 A.L.O.A. workflow and concepts. So we don't look into mouse support right now.
Let's summarize what information we gathered about P4wnP1's workflow and concepts so far:
- we could start actions like keystroke injection from the CLI client, on-demand
- we could use the webclient to achieve the same goal
- with an external powers supply connected, we could change USB hosts and go on working seamlessly
- we could configure the USB stack exactly to our needs ()and change the configuration at runtime)
- we could write multi purpose HIDScripts, with complex logic based on JavaScript (support for functions, loops, branching etc. etc.)
3. Workflow part 2 - Templating and TriggerActions
Before moving on to the other major concepts of P4wnP1 A.L.O.A. let's refine our first goal, which was to "run a keystroke injection against a USB host":
- The new goal is to type "Hello world" into the editor of a Windows USB host (notepad.exe).
- The editor should be opened by P4wnP1 (not manually by the user).
- The editor should automatically be closed, when any of the keyboard LEDs of the USB host is toggled once.
- Everytime P4wnP1 is attached to the USB host, this behavior should repeat (with external power supply, no reboot of P4wnP1)
- The script should only run ones, unless P4wnP1 is re-attached to the USB host, even if successive keyboard LED changes occur.
- Even if P4wnP1 is rebooted, the same behavior should be recoverable without recreating everything from scratch.
Starting notepad, typing "Hellow world" and closing notepad after a LED change could be done with this HIDScript:
// Starting notepad
press("WIN R"); // Windows key + R, to open run dialog
delay(500); // wait 500ms for the dialog to open
type("notepad.exe\n"); // type 'notepad.exe' to the run dialog, append a RETURN press
delay(2000); // wait 2 seconds for notepad to come up
// Type the message
type("Hello world") // Type "Hello world" to notepad
// close notepad after LED change
waitLED(ANY); // wait for a single LED change
press("ALT F4"); // ALT+F4 shortcut to close notepad
//as we changed content, there will be a confirmation dialog before notepad exits
delay(500); // wait for the confirmation dialog
press("RIGHT"); // move focus to next button (don't save) with RIGHT ARROW
press("SPACEBAR"); // confirm dialog with space
The only thing new here is the delay
command, which doesn't need much explanation. It delays execution for the given
amount of milliseconds. The script could be pasted into the webclient HIDScript editor and started with "run" in order
to test it.
It should work as intended, so we are nearly done. In order to be able reuse the script, even after a reboot, we store
it persistently. This could be done by hitting the "store" button in the HIDScript tab of the webclient. After entering
the name tutorial1
and confirming the dialog the script should be stored. We could confirm this, by hitting the
"Load & Replace" button in the webclient, the stored script should be in the list and named tutorial1.js
(the .js
extension is appended automatically, if not already done in the "store" dialog).
Warning: If a name of an existing file is written to the store dialog, the file gets overwritten without further confirmation.
Let's try to start the exact same script via CLI from a SSH session now:
P4wnP1_cli hid run tutorial1.js
So it is possible to start the HIDScript from other applications, supporting shell command or from a bash script.
It would even be possible to start the script remotely from a CLI client compiled for Windows. Assuming the Windows host
is able to reach P4wnP1 A.L.O.A. via WiFi and the IP of P4wnP1 is set to 172.24.0.1
the command would look like this:
P4wnP1_cli.exe --host 172.24.0.1 hid run tutorial1.js
Note: At the time of this writing, I haven't decided yet if P4wnP1 A.L.O.A. ships a CLI binary for each and every possible platform and architecture. But it is likely that precompiled versions for major platforms are provided. If not this shouldn't be a problem. As cross compilation of the Go code takes less than a minute.
The next step is to allow the script to start again, when P4wnP1 is re-attached to the USB host. A approach we already
used, is to wrap everything into a loop and prepend a waitLED(ANY_OR_NONE)
to continue when the USB host signals that
the keyboard driver is ready to receive input. A modified script could look like this:
while (true) {
waitLED(ANY_OR_NONE); // wait till keyboard driver sends the initial LED state
// Starting notepad
press("WIN R"); // Windows key + R, to open run dialog
delay(500); // wait 500ms for the dialog to open
type("notepad.exe\n"); // type 'notepad.exe' to the run dialog, append a RETURN press
delay(2000); // wait 2 seconds for notepad to come up
// Type the message
type("Hello world") // Type "Hello world" to notepad
// close notepad after LED change
waitLED(ANY); // wait for a single LED change
press("ALT F4"); // ALT+F4 shortcut to close notepad
//as we changed content, there will be a confirmation dialog before notepad exits
delay(500); // wait for the confirmation dialog
press("RIGHT"); // move focus to next button (don't save) with RIGHT ARROW
press("SPACEBAR"); // confirm dialog with space
}
Indeed the script would run, every time we attach P4wnP1 to the host, but it isn't very robust, because there's a second
waitLED
involved before notepad is closed. There are several error cases, for example if P4wnP1 is detached before
the "Hello world" is written. The blocking waitLED
would be the one before press("ALT F4")
now and execution would
continue from this point on reattach.
A definitive kill criteria for this approach: The requirement that the script should be run only once isn't met anymore. Hitting NUM LOCK repeatedly would restart the script, again and again.
So how to solve this ?
Let's introduce TriggerActions
The solution to the problem are TriggerActions. As the name implies, the concept is to fire actions, based on predefined triggers.
To get an idea of what I'm talking about, head over to the "TRIGGER ACTIONS" tab on the webclient. Depending on the current setup, there may already exist TriggerActions, which doesn't matter for now.
Hitting the "ADD ONE" button add a new TriggerActions and opens it in edit mode. The TriggerAction is turned off by default and has to be enabled in order to be editable. So we toggle the enable switch.
Now from the pull down menu called "Trigger" the option "USB gadget connected to host" should be selected. The action should have a preset of "write log entry". We leave it like this and hit the "Update" button. The newly added TriggerAction should be visible in the overview now (the one with the highest ID) and show a summary of the selected Trigger and selected Action.
To test how the trigger works, navigate to the "Event Log" tab of the webclient. Make sure you have the webclient open via WiFi (not USB ethernet). Apply external power to P4wnP1, disconnect it from the USB host and connect it again. A log message should be pushed to the client immediately every time P4wnP1 is attached to the USB host.
Repeating this a few times, it becomes pretty obvious that this trigger fires in an early USB enumeration phase. To be precise: When this trigger fires, it is clear that P4wnP1 has been connected to the USB host, but there's no guarantee that the host managed to load all needed drivers. In fact it is very unlikely that the USB keyboard driver is loaded when the trigger fires. We have to keep this in mind.
Before we move on with our task, we do an additional test. Heading back to the "TriggerAction" tab and pressing the
little blue button looking like a pen, on our newly created TriggerAction we end up in edit mode again. Enable the
One shot
switch now. Head back to the "Event Log" and detach and re-attach P4wnP1 from the USB host again. This time
the Trigger fires only once, no matter how often P4wnP1 is re-attached to the USB host afterwards. It is worth
mentioning that a "One shot" TriggerAction isn't deleted after the TriggerFired, instead the TriggerAction is disabled.
Re-enabling allows to reuse the TriggerAction without redefining it. Nothing gets lost until the red "trash" button is
hit on a TriggerAction.
Warning: If the delete button for a TriggerAction is clicked, the TriggerAction is deleted without further confirmation.
At this point let's do the obvious. We edit the created TriggerAction and select "start a HIDScript" instead of "write
log entry" for the action to execute. A new input field called "script name" is shown. Clicking on this input field
brings up a selection dialog for all stored HIDScripts, including our newly created tutorial1.js
.
Before we test if this works, a quick note on the action "write log entry": P4wnP1 A.L.O.A. doesn't keep track of TriggerActions which have already be fired. This means the log entries created by a "write log entry" action are delivered to all listening client, but not stored (for various reasons). The webclient on the other hand stores the log entry until the single page application is reloaded. The same applies to events related to HIDScript jobs. If a HIDScript ends (with success or error), a vent is fired and delivered to the webclient. In fact the webclient has a runtime state, which holds more information than the core service. If the runtime state of the webclient grows to large, one only needs to reload the client to clear "historical" sate information. If the core service would store historical information, it would get out of resources very soon. Thus this concept applies to most sub systems of P4wnP1 A.L.O.A.
Now back to our task. We have a TriggerAction ready, which fires our HIDScript every time P4wnP1 is attached to an USB host. Depending on the USB host, this works more or less reliably. In my test setup it didn't work at all and there's a reason. Let's review the first few lines our HIDScript:
// Starting notepad
press("WIN R"); // Windows key + R, to open run dialog
delay(500); // wait 500ms for the dialog to open
type("notepad.exe\n"); // type 'notepad.exe' to the run dialog, append a RETURN press
... snip ...
Recalling the fact, that the "USB gadget connected" Trigger fires in early USB enumeration phase, when the USB host's
keyboard driver hasn't necessarily loaded, the problem becomes obvious. We have to prepend some kind of delay to the
script to assure the keyboard driver is up. As we already know that it isn't possible to predict the optimal delay, we
go with the waitLED(ANY_OR_NONE)
approach, instead. The new script should look like this:
waitLED(ANY_OR_NONE); //assure keyboard driver is ready
// Starting notepad
press("WIN R"); // Windows key + R, to open run dialog
delay(500); // wait 500ms for the dialog to open
type("notepad.exe\n"); // type 'notepad.exe' to the run dialog, append a RETURN press
delay(2000); // wait 2 seconds for notepad to come up
// Type the message
type("Hello world") // Type "Hello world" to notepad
// close notepad after LED change
waitLEDRepeat(ANY); // wait for a single LED change
press("ALT F4"); // ALT+F4 shortcut to close notepad
//as we changed content, there will be a confirmation dialog before notepad exits
delay(500); // wait for the confirmation dialog
press("RIGHT"); // move focus to next button (don't save) with RIGHT ARROW
press("SPACEBAR"); // confirm dialog with space
Now store the modified script under the exact same name tutorial1
, as already pointed out, the old script gets
overwritten without further confirmation. There's no need to adjust the TriggerAction, because the script name hasn't
changed.
At this point everything should work as intended. Anyways, if P4wnP1 is rebooted or looses power, the HIDScript persists but the TriggerAction is gone. To cope with that, TriggerActions could be stored, too.
The "store" button in the "TriggerAction" tab works exactly like the one in the HIDScript editor. It is worth mentioning that all currently active TriggerActions get stored if the "store" dialog is confirmed (including the disabled ones). Best practice is to delete all TriggerActions which don't belong to the task in current scope before storing (they already should have been stored earlier) and only store a small set of TriggerActions relevant to the current task, using a proper name. There're two options to load back stored TriggerActions:
- "load & replace" clears all active trigger actions and loads only the stored ones
- "load & add" keeps the already active TriggerActions and adds in the stored ones. Thus "load & add" could be used to build a complex TriggerAction set out of smaller sets. The resulting set could then again be stored.
So for now, we should only store our single TriggerAction, which runs the HID script. The name could be tutorial1
again and won't conflict with the HIDScript called tutorial1
.
Confirm successful storing, by hitting the "load&replace" button in the "TriggerAction" tab. The stored TriggerAction
set should be in the list and named tutorial1
.
Warning: The TriggerAction "load" dialogs allow deleting stored TriggerActions by hitting the red "trash" button next to each action. Hitting the button permanently deletes the respective TriggerAction set, without further confirmation
At this point we could safely delete our TriggerAction from the "TriggerActions" tab (!!not from the load dialog!!).
With the TriggerAction deleted, nothing happens if we detach and re-attach P4wnP1 from the USB host.
The stored TriggerAction set persists reboots. Instead of reloading the TriggerAction set from with the webclient, we test the CLI.
Lets take a quick look into the help screen of the template deploy
sub-command:
root@kali:~# P4wnP1_cli template deploy -h
Deploy given gadget settings
Usage:
P4wnP1_cli template deploy [flags]
Flags:
-b, --bluetooth string Deploy Bluetooth template
-f, --full string Deploy full settings template
-h, --help help for deploy
-n, --network string Deploy network settings template
-t, --trigger-actions string Deploy trigger action template
-u, --usb string Deploy USB settings template
-w, --wifi string Deploy WiFi settings templates
Global Flags:
--host string The host with the listening P4wnP1 RPC server (default "localhost")
--port string The port on which the P4wnP1 RPC server is listening (default "50051")
The usage screen shows, that TriggerAction Templates could be deployed with the -t
flag. By running the following
command our TriggerAction should get loaded again and thus the HIDScript should trigger like in our tests, if P4wnP1 is
detached/re-attached from the USB host:
P4wnP1_cli template deploy -t tutorial1