Files
esp8266_deauther/esp8266_deauther/cli.cpp
2021-01-18 13:54:23 +01:00

1702 lines
68 KiB
C++

/*
Copyright (c) 2020 Stefan Kremser (@Spacehuhn)
This software is licensed under the MIT License. See the license file for details.
Source: github.com/spacehuhn/esp8266_deauther
*/
#include "cli.h"
#include "src/SimpleCLI-1.1.1/SimpleCLI.h"
#if SIMPLECLI_VERSION_MAJOR == 1 && SIMPLECLI_VERSION_MINOR < 1
#error "Please update SimpleCLI library"
#endif // if SIMPLECLI_VERSION_MAJOR == 1 && SIMPLECLI_VERSION_MINOR < 1
#include "debug.h"
#include "scan.h"
#include "strh.h"
#include "StringList.h"
#include "mac.h"
#include "vendor.h"
#include "attack.h"
#include "alias.h"
#include "config.h"
#include "result_filter.h"
#include "ap.h"
// ram usage
extern "C" {
#include "user_interface.h"
}
#define CLI_READ_RES(_TEXT)\
debuglnF(_TEXT);\
res = String();\
while (!read(res));\
if (res == String(F("exit")) || res == String(F("stop"))) {\
debuglnF("Ok byeee");\
return;\
}
#define CLI_READ_RES_DEFAULT(_TEXT, _DEFAULT)\
debuglnF(_TEXT);\
debugF("[default=");\
debug(_DEFAULT);\
debuglnF("]");\
res = String();\
while (!read(res, _DEFAULT));\
if ((res == String(F("exit"))) || (res == String(F("stop")))) {\
debuglnF("Ok byeee");\
return;\
}
#define CLI_READ_UNTIL(_TEXT, _STOP)\
do {\
CLI_READ_RES(_TEXT);\
}\
while (!_STOP);
#define CLI_READ_DEFAULT_UNTIL(_TEXT, _DEFAULT, _STOP)\
do {\
CLI_READ_RES_DEFAULT(_TEXT, _DEFAULT);\
}\
while (!_STOP);
void rssi_meter_cb(int8_t rssi) {
debugln(rssi);
}
namespace cli {
// ===== PRIVATE ===== //
SimpleCLI cli { 64, 64 }; // !< Instance of SimpleCLI library
unsigned long timer = 0; // !< Timestamp to wakeup, if sleep command was used
#ifdef ENABLE_HISTORY
StringList history(HISTORY_SIZE); // !< Command history
#endif // ifdef ENABLE_HISTORY
void parse_mac(const String& str, uint8_t* mac) {
// if in alias list, copy
if (!alias::resolve(str, mac)) {
if (str == "random") {
vendor::getRandomMac(mac);
} else if (str == "broadcast") {
memcpy(mac, mac::BROADCAST, 6);
} else {
mac::fromStr(str.c_str(), mac);
}
}
}
unsigned long parse_time(const String& str, unsigned long defaultMultiplicator) {
StringList list(str, "+");
list.begin();
unsigned long value { 0 };
while (list.available()) {
String str { list.iterate() };
long str_value { str.toInt() };
if (str_value > 0) {
if (str.endsWith("ms")) str_value *= 1;
else if (str.endsWith("s") || str.endsWith("sec")) str_value *= 1000;
else if (str.endsWith("m") || str.endsWith("min")) str_value *= 60*1000;
else if (str.endsWith("h")) str_value *= 60*60*1000;
else str_value *= defaultMultiplicator;
value += str_value;
}
}
return value;
}
SortedStringList parse_int_list(const String& str) {
StringList values { str };
SortedStringList list;
values.begin();
while (values.available()) {
String h = values.iterate();
int hyphen = h.indexOf("-", 1);
if ((hyphen >= 0) && (hyphen < h.length()-1)) {
int start = h.substring(0, hyphen).toInt();
int end = h.substring(hyphen+1).toInt();
if (start < 0) start = 0;
for (int i = start; i<=end; ++i) {
list.push(String(i));
}
} else if ((h.charAt(0) >= '0') && (h.charAt(0) <= '9')) {
list.push(String(h.toInt()));
}
}
return list;
}
uint16_t parse_channels(const String& ch_str) {
if (ch_str == "all") return 0x3FFF;
SortedStringList ch_list = parse_int_list(ch_str);
ch_list.begin();
uint16_t channels = 0;
while (ch_list.available()) {
int ch = ch_list.iterate().toInt();
if ((ch >= 1) && (ch <= 14)) {
channels |= 1 << (ch-1);
}
}
return channels;
}
// ===== PUBLIC ===== //
void begin() {
debug_init();
debuglnF("\r\nSTARTED \\o/\r\n");
cli.setOnError([](cmd_error* e) {
CommandError cmdError(e); // Create wrapper object
debugF("ERROR: ");
debug(cmdError.toString());
if (cmdError.hasCommand()) {
debugln();
debugF("Did you mean:");
debug(cmdError.getCommand().toString());
}
debugln();
});
Command cmd_welcome = cli.addCommand("welcome", [](cmd* c) {
debuglnF(VERSION);
debuglnF("\r\n"
"[ ================== DISCLAIMER ================== ]\r\n"
" This is a tool.\r\n"
" It's neither good nor bad.\r\n"
" Use it to study and test.\r\n"
" Never use it to create harm or damage!\r\n"
"\r\n"
" The continuation of this project counts on you!\r\n"
"[ ================================================ ]\r\n");
debuglnF("Type \"help\" to see all commands.\r\n"
"Type \"start\" to go through the functionalities step by step.");
});
cmd_welcome.setDescription(" Print welcome screen including version and disclaimer");
cmd_welcome.run();
Command cmd_help = cli.addCommand("help", [](cmd* c) {
Command cmd(c);
Argument cmdName = cmd.getArg("command");
bool description = !cmd.getArg("short").isSet();
if (cmdName.isSet()) {
String cmdNameStr = cmdName.getValue();
Command tmp = cli.getCommand(cmdNameStr);
if (tmp) {
debugln(tmp.toString(description));
} else {
debug(cmdNameStr);
debuglnF(" not found :(");
}
} else {
debug(cli.toString(description));
}
});
cmd_help.addPosArg("cmd,command", "");
cmd_help.addFlagArg("s/hort");
cmd_help.setDescription(" Print the list of commands that you see right now");
Command cmd_start = cli.addCommand("start", [](cmd* c) {
Command command { c };
String res { command.getArg("cmd").getValue() };
String cmd;
debuglnF("Good morning friend!");
debugln();
{ // Command
while (!(res == "scan" || res == "beacon" || res == "deauth" || res == "probe" || res == "alias" || res == "results")) {
CLI_READ_RES("What can I do for you today?\r\n"
" scan: Search for WiFi networks and clients\r\n"
" ---------\r\n"
" beacon: Send WiFi network advertisement beacons (spam network scanners)\r\n"
" deauth: Disrupt WiFi connections\r\n"
" probe: Send WiFi network requests (spam client scanners)\r\n"
" ---------\r\n"
" alias: Give MAC addresses an alias\r\n"
" results: Display and filter scan results\r\n"
"Remember that you can always escape by typing 'stop'");
}
cmd += res;
}
if (res == "scan") {
{ // Scan mode
CLI_READ_DEFAULT_UNTIL("What do you wish to scan for?\r\n"
" ap: Access points (WiFi networks)\r\n"
" st: Stations (WiFi client devices)\r\n"
" ap+st: Access points and stations",
"ap+st",
(res == "ap" || res == "st" || res == "ap+st"));
if (cmd != "ap+st") cmd += " -m " + res;
}
// Scan time and channel(s)
if (res != "ap") {
{ // Scan time
CLI_READ_DEFAULT_UNTIL("Scan for how long?\r\n"
" >1: Station scan time",
"20",
(res.toInt() > 0));
if (res != "20") cmd += " -t " + res;
}
{ // Scan on channel(s)
CLI_READ_RES_DEFAULT("Scan on which channel(s)?\r\n"
" 1-14: WiFi channel(s) to search on (for example: 1,6,11)",
"all");
if ((res != "all") && (res != "1-14")) cmd += " -ch " + res;
}
{ // Channel scan time
CLI_READ_DEFAULT_UNTIL("Stay on each channel for how long?\r\n"
" >1: Channel time in milliseconds",
"284",
(res.toInt() > 0));
if (res != "284") cmd += " -ct " + res;
}
}
{ // Retain scan results
CLI_READ_DEFAULT_UNTIL("Keep previous scan results?\r\n"
" y: Yes\r\n"
" n: No",
"n",
(res == String('y') || res == String('n')));
if (res == String('y')) cmd += " -r";
}
} else if (res == "beacon") {
{ // SSIDs
CLI_READ_RES("Which network names do you wish to advertise?\r\n"
" for example: \"network A\",\"network B\"");
cmd += " -ssid " + strh::escape(res);
}
{ // BSSID
CLI_READ_DEFAULT_UNTIL("What is the transmitter or sender address? (BSSID)\r\n"
" MAC address: for example '00:20:91:aa:bb:5c\r\n"
" random: generate random MAC address",
"random",
(res.length() == 17 || res == "random"));
if (res != "random") cmd += " -from " + res;
}
{ // Receiver
CLI_READ_DEFAULT_UNTIL("What is the receiver address?\r\n"
" MAC address: for example 00:20:91:aa:bb:5cc\r\n"
" broadcast: send to everyone",
"broadcast",
(res.length() == 17 || res == "broadcast"));
if (res != "broadcast") cmd += " -to " + res;
}
{ // Encryption
CLI_READ_DEFAULT_UNTIL("What encryption should it use?\r\n"
" open: no encryption, an open network without a password\r\n"
" wpa2: WPA2 protected network",
"open",
(res == "open" || res == "wpa2"));
if (res != "open") cmd += " -enc " + res;
}
{ // Channels
CLI_READ_DEFAULT_UNTIL("Which channel(s) should be used?\r\n"
" 1-14: WiFi channel to send packets on",
"1",
(res.toInt() >= 1 && res.toInt() <= 14));
if (res != "1") cmd += " -ch " + res;
}
{ // Time
CLI_READ_DEFAULT_UNTIL("How long should the attack last?\r\n"
" 0: Infinite\r\n"
" >0: Stop after x seconds",
"300",
(res.toInt() >= 0));
if (res != "300") cmd += " -t " + res;
}
{ // Scan
CLI_READ_DEFAULT_UNTIL("Scan for authentication requests?\r\n"
" y: Yes\r\n"
" n: No",
"n",
(res == String('y') || res == String('n')));
if (res == String('y')) cmd += " -auth";
}
} else if (res == "deauth") {
{ // Target
CLI_READ_UNTIL("What do you want to deauthenticate?\r\n"
" ap: a network\r\n"
" st: a client device\r\n"
" mac: enter MAC addresses manually",
(res == "ap" || res == "st" || res == "mac"));
cmd += " -"+res;
if (res == "ap") {
if (scan::getAccessPoints().size() == 0) {
debuglnF("ERROR: No access points in scan results.\r\n"
"Type 'scan -m ap' to search for access points");
return;
}
result_filter_t filter;
filter.channels = 0x3FFF;
filter.aps = true;
filter.sts = false;
scan::print(&filter);
CLI_READ_RES("Select access point(s) to attack\r\n"
" >=0: ID(s) to select for the attack");
cmd += " "+res;
} else if (res == "st") {
if (scan::getStations().size() == 0) {
debuglnF("ERROR: No stations in scan results.\r\n"
"Type 'scan -m st' to search for stations");
return;
}
result_filter_t filter;
filter.channels = 0x3FFF;
filter.aps = false;
filter.sts = true;
scan::print(&filter);
CLI_READ_RES("Select station(s) to attack\r\n"
" >=0: ID(s) to select for the attack");
cmd += " "+res;
} else if (res == "mac") {
CLI_READ_RES("Target(s) to attack\r\n"
" Sender-Receiver-Channel for example:'aa:bb:cc:dd:ee:ff-00:11:22:33:44:55-7'");
cmd += " "+res;
}
}
{ // Noob filer
CLI_READ_UNTIL("Do you own or have permission to attack the selected devices?\r\n"
" yes\r\n"
" no",
(res == "yes" || res == "no"));
if (res == "no") {
debuglnF("Then you should not attack them.\r\n"
"It could get you in serious trouble.\r\n"
"Please use this tool respectfully!");
return;
}
}
{ // Time
CLI_READ_DEFAULT_UNTIL("How long should the attack last?\r\n"
" 0: Infinite\r\n"
" >0: Stop after x seconds",
"300",
(res.toInt() >= 0));
if (res != "300") cmd += " -t " + res;
}
{ // Number of packets
CLI_READ_DEFAULT_UNTIL("How many packets shall be sent?\r\n"
" 0: Infinite\r\n"
" >0: Send x packets",
"0",
(res.toInt() >= 0));
if (res != "0") cmd += " -n " + res;
}
{ // Packet rate
CLI_READ_DEFAULT_UNTIL("At which speed/rate?\r\n"
" >0 : Packets per second",
"20",
(res.toInt() > 0));
if (res != "20") cmd += " -r " + res;
}
{ // Mode
CLI_READ_DEFAULT_UNTIL("What kind of packets shall be sent?\r\n"
" deauth: Deauthentication\r\n"
" disassoc: Disassociation\r\n"
" deauth+disassoc: Both",
"deauth+disassoc",
(res == "deauth" || res == "disassoc" || res == "deauth+disassoc"));
if (res != "deauth+disassoc") cmd += " -m " + res;
}
} else if (res == "probe") {
{ // SSIDs
CLI_READ_RES("Which network names do you wish to request for?\r\n"
" for example: \"network A\",\"network B\"");
cmd += " -ssid " + strh::escape(res);
}
{ // Receiver
CLI_READ_DEFAULT_UNTIL("What is the receiver address?\r\n"
" MAC address: for example 00:20:91:aa:bb:5c\r\n"
" broadcast: send to everyone",
"broadcast",
(res.length() == 17 || res == "broadcast"));
if (res != "broadcast") cmd += " -to " + res;
}
{ // Channel
CLI_READ_DEFAULT_UNTIL("Which channel(s) should be used?\r\n"
" 1-14: WiFi channel to send packets on",
"1",
(res.toInt() >= 1 && res.toInt() <= 14));
if (res != String(1)) cmd += " -ch " + res;
}
{ // Time
CLI_READ_DEFAULT_UNTIL("How long should the attack last ?\r\n"
" 0: Infinite\r\n"
" >0: Stop after x seconds",
"300",
(res.toInt() >= 0));
if (res != String(300)) cmd += " -t " + res;
}
} else if (res == "alias") {
{ // Mode
CLI_READ_DEFAULT_UNTIL("Do you want to set a new alias, remove one or see the entire list ?\r\n"
" list: Print list of existing MAC address aliases\r\n"
" add: Add new MAC address alias to list\r\n"
" remove: Remove an exisiting MAC address alias\r\n"
" clear: Remove all saved aliases",
"list",
(res == "list" || res == "add" || res == "remove" || res == "clear"));
if (res != "list") cmd += " -mode " + res;
}
// Add
if (res == "add") {
CLI_READ_DEFAULT_UNTIL("Selector:\r\n"
" mac: Enter MAC address manually\r\n"
" ap: Use the BSSID of an access point\r\n"
" st: Use the MAC of a station",
"mac",
(res == "mac" || res == "ap" || res == "st"));
{ // MAC
if (res == "mac") {
CLI_READ_UNTIL("Enter a valid MAC address:",
(mac::valid(res.c_str(), res.length())));
cmd += " -mac " + res.substring(0, 17);
}
else if (res == "ap") {
if (scan::getAccessPoints().size() == 0) {
debuglnF("ERROR: No access points in scan results.\r\n"
"Type 'scan -m ap' to search for access points");
return;
}
result_filter_t filter;
filter.channels = 0x3FFF;
filter.aps = false;
filter.sts = true;
scan::print(&filter);
CLI_READ_UNTIL("Enter access point ID:",
(res.toInt() >= 0));
cmd += " -ap " + res;
}
else if (res == "st") {
if (scan::getStations().size() == 0) {
debuglnF("ERROR: No stations in scan results.\r\n"
"Type 'scan -m st' to search for stations");
return;
}
result_filter_t filter;
filter.channels = 0x3FFF;
filter.aps = false;
filter.sts = true;
scan::print(&filter);
CLI_READ_UNTIL("Enter station ID:",
(res.toInt() >= 0));
cmd += " -st " + res;
}
}
{ // Name
CLI_READ_RES("Alias name:");
if (res.length() > 0) cmd += " -name " + strh::escape(res);
}
}
// Remove
else if (res == "remove") {
alias::print();
CLI_READ_RES("Enter ID, name or MAC address of alias you want to remove:");
cmd += " " + res;
}
} else if (res == "results") {
{ // Type
CLI_READ_DEFAULT_UNTIL("Filter type of results:\r\n"
" ap: Access points(WiFi networks)\r\n"
" st: Stations(WiFi client devices)\r\n"
" ap+st: Access points and stations",
"ap+st",
(res == "ap" || res == "st" || res == "ap+st"));
if (res != "ap+st") cmd += " -t " + res;
if ((res == "ap") && (scan::getAccessPoints().size() == 0)) {
debuglnF("ERROR: No access points in scan results.\r\n"
"Type 'scan -m ap' to search for access points");
return;
} else if ((res == "st") && (scan::getStations().size() == 0)) {
debuglnF("ERROR: No stations in scan results.\r\n"
"Type 'scan -m st' to search for stations");
return;
} else if ((res == "ap+st") && (scan::getAccessPoints().size() == 0) && (scan::getStations().size() == 0)) {
debuglnF("ERROR: No access points or stations in scan results.\r\n"
"Type 'scan' to search for access points");
return;
}
}
{ // Channel(s)
CLI_READ_RES_DEFAULT("Filter channel(s):\r\n"
" 1-14: WiFi channel(s) to search on(for example: 1, 6, 11)",
"all");
if ((res != "all ") && (res != " 1-14 ")) cmd += " -ch " + res;
}
{ // SSIDs
CLI_READ_RES_DEFAULT("Filter for SSIDs(network names):\r\n"
" for example: \"network A\",\"network B\"",
"");
if (res.length() > 0) cmd += " -ssid " + strh::escape(res);
}
{ // BSSID
CLI_READ_RES_DEFAULT("Filter for BSSID (MAC address of access point):\r\n"
" for example 00:20:91:aa:bb:5cc",
"");
if (res.length() > 0) cmd += " -bssid " + res;
}
{ // Vendor
CLI_READ_RES_DEFAULT("Filter for vendor name:\r\n"
" for example \"Apple\",\"Intel\"",
"");
if (res.length() > 0) cmd += " -vendor " + strh::escape(res);
}
}
// Result
debuglnF("> Exiting start command");
debugln();
debugF("# ");
debugln(cmd);
debugln();
cli::parse(cmd.c_str());
});
cmd_start.addPosArg("cmd", "");
cmd_start.setDescription(" Start a guided tour through the functions of this device");
Command cmd_scan = cli.addCommand("scan", [](cmd* c) {
Command cmd(c);
bool ap;
bool st;
ap_scan_settings_t scan_settings;
{ // Station scan time
String time_str = cmd.getArg("t").getValue();
scan_settings.st_settings.timeout = parse_time(time_str, 1000);
}
{ // Channels
String ch_str = cmd.getArg("ch").getValue();
scan_settings.channels = scan_settings.st_settings.channels = parse_channels(ch_str);
}
{ // Channel scan time
String time_str = cmd.getArg("ct").getValue();
scan_settings.st_settings.ch_time = parse_time(time_str, 1);
}
{ // Retain results
scan_settings.retain = scan_settings.st_settings.retain = cmd.getArg("r").isSet();
}
{ // Mode
String mode = cmd.getArg("m").getValue();
ap = false;
st = false;
if (mode == "ap+st") {
ap = true;
st = true;
} else if (mode == "ap") {
ap = true;
} else if (mode == "st") {
st = true;
}
}
scan_settings.st = st;
if (ap) scan::startAP(scan_settings);
else if (st) scan::startST(scan_settings.st_settings);
});
cmd_scan.addPosArg("m/ode", "ap+st");
cmd_scan.addArg("t/ime", "20s");
cmd_scan.addArg("ch/annel", "all");
cmd_scan.addArg("ct/ime", "284");
cmd_scan.addFlagArg("r/etain");
cmd_scan.setDescription(
" Scan for WiFi devices\r\n"
" -m: scan mode [ap,st,ap+st] (default=ap+st)\r\n"
" -t: station scan time (default=20s)\r\n"
" -ch: 2.4 GHz channels for station scan [1-14] (default=all)\r\n"
" -ct: channel scan time in milliseconds (default=284)\r\n"
" -r: keep previous scan results"
);
Command cmd_auth = cli.addCommand("auth", [](cmd* c) {
Command cmd(c);
auth_scan_settings_t auth_settings;
{ // Channels
String ch_str = cmd.getArg("ch").getValue();
auth_settings.channels = parse_channels(ch_str);
}
{ // Read Access Point MACs
String ap_str = cmd.getArg("ap").getValue();
SortedStringList list = parse_int_list(ap_str);
if (list.size() > 0) {
auth_settings.channels = 0;
MacArr ap_bssids { list.size() };
list.begin();
while (list.available()) {
int id = list.iterate().toInt();
AccessPoint* ap = scan::getAccessPoints().get(id);
if (ap) {
ap_bssids.add(ap->getBSSID());
auth_settings.channels |= 1 << (ap->getChannel()-1);
}
}
auth_settings.bssids += ap_bssids;
}
}
{ // Read BSSIDs
String bssid_str { cmd.getArg("bssid").getValue() };
auth_settings.bssids += MacArr { bssid_str };
}
{ // Timeout
String time_str = cmd.getArg("t").getValue();
auth_settings.timeout = parse_time(time_str, 1000);
}
{ // Channel scan time
String time_str = cmd.getArg("ct").getValue();
auth_settings.ch_time = parse_time(time_str, 1);
}
{ // Save
auth_settings.save = cmd.getArg("save").isSet();
}
scan::startAuth(auth_settings);
});
cmd_auth.addPosArg("bssid", "");
cmd_auth.addArg("ap", "");
cmd_auth.addArg("t/ime", "0");
cmd_auth.addArg("ch/annel", "all");
cmd_auth.addArg("ct/ime", "284");
cmd_auth.addFlagArg("save");
cmd_auth.setDescription(" Authentication scan\r\n"
" -bssid: filter by BSSID(s)\r\n"
" -ap: filter by access point ID(s)\r\n"
" -ch: 2.4 GHz channels for auth. scan [1-14] (default=all)\r\n"
" -ct: channel scan time in milliseconds (default=284)\r\n"
" -t: scan timeout (default=none)\r\n"
" -save: save recorded probe requests");
Command cmd_rssi = cli.addCommand("rssi", [](cmd* c) {
Command cmd(c);
rssi_scan_settings_t rssi_settings;
rssi_settings.channels = 0;
{ // MACs
String bssid_str { cmd.getArg("mac").getValue() };
rssi_settings.macs += MacArr { bssid_str };
}
{ // Read Access Point MACs
String ap_str { cmd.getArg("ap").getValue() };
SortedStringList list { ap_str };
AccessPointList& aps = scan::getAccessPoints();
MacArr ap_bssids { list.size() };
list.begin();
while (list.available()) {
String value { list.iterate() };
SortedStringList ids { parse_int_list(value) };
uint8_t mac[6];
AccessPoint* ap;
if (alias::resolve(value, mac)) { // MAC address (or alias)
ap = aps.search(mac);
if (!ap) {
debugF("WARNING: Access Point ");
debug(value);
debuglnF(" not found");
} else {
ap_bssids.add(ap->getBSSID());
rssi_settings.channels |= 1 << (ap->getChannel()-1);
}
} else if (ids.size() > 0) { // ID(s)
int id;
ids.begin();
while (ids.available()) {
id = ids.iterate().toInt();
ap = aps.get(id);
if (!ap) {
debugF("WARNING: Access Point ");
debug(id);
debuglnF(" not found");
} else {
ap_bssids.add(ap->getBSSID());
rssi_settings.channels |= 1 << (ap->getChannel()-1);
}
}
} else { // SSID
aps.begin();
while (aps.available()) {
ap = aps.iterate();
if (ap && (String(ap->getSSID()) == value)) {
ap_bssids.add(ap->getBSSID());
rssi_settings.channels |= 1 << (ap->getChannel()-1);
}
}
}
}
rssi_settings.macs += ap_bssids;
}
{ // Read Station MACs
String st_str { cmd.getArg("st").getValue() };
SortedStringList list { st_str };
StationList& sts = scan::getStations();
MacArr st_macs { list.size() };
list.begin();
while (list.available()) {
String value { list.iterate() };
SortedStringList ids { parse_int_list(value) };
uint8_t mac[6];
Station* st;
if (alias::resolve(value, mac)) { // MAC address (or alias)
st = sts.search(mac);
if (!st) {
debugF("WARNING: Station ");
debug(value);
debuglnF(" not found");
} else {
st_macs.add(st->getMAC());
if (st->getAccessPoint()) rssi_settings.channels |= 1 << (st->getAccessPoint()->getChannel()-1);
}
} else if (ids.size() > 0) { // ID(s)
int id;
ids.begin();
while (ids.available()) {
id = ids.iterate().toInt();
st = sts.get(id);
if (!st) {
debugF("WARNING: Station ");
debug(id);
debuglnF(" not found");
} else {
if (st->getAccessPoint()) rssi_settings.channels |= 1 << (st->getAccessPoint()->getChannel()-1);
}
}
}
}
rssi_settings.macs += st_macs;
}
{ // Channels
Argument ch_arg = cmd.getArg("ch");
if ((rssi_settings.channels == 0) || ch_arg.isSet()) {
String ch_str = ch_arg.getValue();
rssi_settings.channels = parse_channels(ch_str);
}
}
{ // Channel scan time
String time_str = cmd.getArg("ct").getValue();
rssi_settings.ch_time = parse_time(time_str, 1);
}
{ // Update Time
String time_str = cmd.getArg("ut").getValue();
rssi_settings.update_time = parse_time(time_str, 1000);
}
scan::startRSSI(rssi_settings);
});
cmd_rssi.addPosArg("mac", "");
cmd_rssi.addArg("ap", "");
cmd_rssi.addArg("st/ation", "");
cmd_rssi.addArg("ch/annel", "all");
cmd_rssi.addArg("ct/ime", "120");
cmd_rssi.addArg("ut,u/pdate/time", "1s");
cmd_rssi.setDescription(" Signal Strength scan\r\n"
" -mac: filter by MAC(s)\r\n"
" -ap: filter by AP(s)\r\n"
" -st: filter by Station(s)\r\n"
" -ch: 2.4 GHz channel(s) for scan [1-14] (default=all)\r\n"
" -ct: channel scan time in milliseconds (default=120)\r\n"
" -ut: update time (default=1s)");
Command cmd_results = cli.addCommand("results", [](cmd* c) {
Command cmd(c);
String mode = cmd.getArg("t").getValue();
String ch_str = cmd.getArg("ch").getValue();
String ssid_str = cmd.getArg("ssid").getValue();
String bssid_str = cmd.getArg("bssid").getValue();
String vendor_str = cmd.getArg("vendor").getValue();
uint16_t channels = parse_channels(ch_str);
StringList vendors { vendor_str };
result_filter_t filter;
filter.channels = channels;
filter.ssids.parse(ssid_str);
filter.bssids.parse(bssid_str);
filter.vendors.parse(vendor_str);
filter.aps = (mode == "ap") || (mode == "ap+st");
filter.sts = (mode == "st") || (mode == "ap+st");
scan::print(&filter);
});
cmd_results.addPosArg("t/ype", "ap+st");
cmd_results.addArg("ch/annel/s", "all");
cmd_results.addArg("ssid/s", "");
cmd_results.addArg("bssid/s", "");
cmd_results.addArg("vendor/s", "");
cmd_results.setDescription(
" Print list of scan results [access points (networks) and stations (clients)]\r\n"
" -type: type of results [ap,st,ap+st] (default=ap+st)\r\n"
" -ch: filter by channel(s)\r\n"
" -ssid: filter by SSID(s)\r\n"
" -bssid: filter by BSSID(s)\r\n"
" -vendor: filter by vendor name(s)");
Command cmd_beacon = cli.addCommand("beacon", [](cmd* c) {
Command cmd(c);
bool scan;
beacon_attack_settings_t beacon_settings;
{ // SSIDs
String ssids { cmd.getArg("ssid").getValue() };
beacon_settings.ssids.parse(ssids);
}
{ // BSSID
String bssid_str { cmd.getArg("from").getValue() };
parse_mac(bssid_str, beacon_settings.bssid);
}
{ // Receiver
String receiver_str { cmd.getArg("to").getValue() };
parse_mac(receiver_str, beacon_settings.receiver);
}
{ // Encryption
String enc_str { cmd.getArg("enc").getValue() };
if (enc_str == "wpa2") beacon_settings.enc = ENCRYPTION_WPA2;
else beacon_settings.enc = ENCRYPTION_OPEN;
}
{ // Channels
String ch_str { cmd.getArg("ch").getValue() };
beacon_settings.channels = parse_channels(ch_str);
}
{ // Packet rate
String pkt_rate_str { cmd.getArg("r").getValue() };
beacon_settings.pkt_rate = pkt_rate_str.toInt();
}
{ // Time
String time_str { cmd.getArg("t").getValue() };
beacon_settings.timeout = parse_time(time_str, 1000);
}
{ // Monitor connection tries
beacon_settings.scan = cmd.getArg("m").isSet();
}
{ // Save
beacon_settings.save_scan = cmd.getArg("save").isSet();
}
attack::startBeacon(beacon_settings);
});
cmd_beacon.addPosArg("ssid/s");
cmd_beacon.addArg("bssid,from", "random");
cmd_beacon.addArg("receiver,to", "broadcast");
cmd_beacon.addPosArg("enc/ryption", "open");
cmd_beacon.addArg("ch/annel", "1");
cmd_beacon.addArg("r/ate", "10");
cmd_beacon.addFlagArg("auth,m/on/itor");
cmd_beacon.addFlagArg("save");
cmd_beacon.addArg("t/ime/out", "5min");
cmd_beacon.setDescription(
" Send WiFi network advertisement beacons\r\n"
" -ssid: network names (SSIDs) for example: \"test A\",\"test B\"\r\n"
" -from: BSSID or sender MAC address (default=random)\r\n"
" -to: receiver MAC address (default=broadcast)\r\n"
" -enc: encryption [open,wpa2] (default=open)\r\n"
" -ch: 2.4 GHz channel(s) [1-14] (default=1)\r\n"
" -r: packets per second per SSID (default=10)\r\n"
" -m: scan for authentications\r\n"
" -save: save probe requests from auth. scan\r\n"
" -t: attack timeout (default=5min)"
);
Command cmd_deauth = cli.addCommand("deauth", [](cmd* c) {
Command cmd(c);
deauth_attack_settings_t deauth_settings;
{ // Read Access Point MACs
String ap_str { cmd.getArg("ap").getValue() };
SortedStringList list { ap_str };
AccessPointList& aps = scan::getAccessPoints();
TargetArr ap_targets { aps.size() };
list.begin();
while (list.available()) {
String value { list.iterate() };
SortedStringList ids { parse_int_list(value) };
uint8_t mac[6];
AccessPoint* ap;
if (alias::resolve(value, mac)) { // MAC address (or alias)
ap = aps.search(mac);
ap_targets.add(ap);
} else if (ids.size() > 0) { // ID(s)
int id;
ids.begin();
while (ids.available()) {
id = ids.iterate().toInt();
ap = aps.get(id);
ap_targets.add(ap);
}
} else { // SSID
aps.begin();
while (aps.available()) {
ap = aps.iterate();
if (String(ap->getSSID()) == value) {
ap_targets.add(ap);
}
}
}
}
deauth_settings.targets += ap_targets;
}
{ // Read Station MACs
String st_str { cmd.getArg("st").getValue() };
SortedStringList list { st_str };
StationList& sts = scan::getStations();
TargetArr st_targets { sts.size() };
list.begin();
while (list.available()) {
String value { list.iterate() };
SortedStringList ids { parse_int_list(value) };
uint8_t mac[6];
Station* st;
if (alias::resolve(value, mac)) { // MAC address (or alias)
st = sts.search(mac);
if (!st_targets.add(st)) {
debugF("WARNING: Station ");
debug(value);
debuglnF(" not found, not connected to an AP or already in list.");
}
} else if (ids.size() > 0) { // ID(s)
int id;
ids.begin();
while (ids.available()) {
id = ids.iterate().toInt();
st = sts.get(id);
if (!st_targets.add(st)) {
debugF("WARNING: Station ");
debug(id);
debuglnF(" not found, not connected to an AP or already in list.");
}
}
}
}
deauth_settings.targets += st_targets;
}
{ // Read custom MACs
String mac_str { cmd.getArg("mac").getValue() };
StringList list { mac_str };
TargetArr manual_targets { list.size() };
list.begin();
while (list.available()) {
String target = list.iterate();
StringList target_data(target, "-");
if (target_data.size() != 3) continue;
String sender_mac_str { target_data.iterate() };
String receiver_mac_str { target_data.iterate() };
String ch_str { target_data.iterate() };
uint8_t sender[6];
uint8_t receiver[6];
uint16_t channels = parse_channels(ch_str);
parse_mac(sender_mac_str, sender);
parse_mac(receiver_mac_str, receiver);
manual_targets.add(sender, receiver, channels);
}
deauth_settings.targets += manual_targets;
}
{ // Time
String time_str { cmd.getArg("t").getValue() };
deauth_settings.timeout = parse_time(time_str, 1000);
}
{ // Number
deauth_settings.max_pkts = cmd.getArg("n").getValue().toInt();
}
{ // Rate
deauth_settings.pkt_rate = cmd.getArg("r").getValue().toInt();
}
{ // Mode
String mode { cmd.getArg("m").getValue() };
if (mode == "deauth+disassoc") {
deauth_settings.deauth = true;
deauth_settings.disassoc = true;
} else if (mode == "deauth") {
deauth_settings.deauth = true;
deauth_settings.disassoc = false;
} else if (mode == "disassoc") {
deauth_settings.deauth = false;
deauth_settings.disassoc = true;
}
}
attack::startDeauth(deauth_settings);
});
cmd_deauth.addArg("ap", "");
cmd_deauth.addArg("st/ation", "");
cmd_deauth.addArg("mac,manual", "");
cmd_deauth.addArg("t/ime/out", "5min");
cmd_deauth.addArg("n/um/ber", "0");
cmd_deauth.addArg("r/ate", "20");
cmd_deauth.addArg("m/ode", "deauth+disassoc");
cmd_deauth.setDescription(
" Deauthenticate (disconnect) selected WiFi connections\r\n"
" -ap: access point IDs to attack\r\n"
" -st: station IDs to attack\r\n"
" -mac: manual target selection [Sender-Receiver-Channel] for example:'aa:bb:cc:dd:ee:ff-00:11:22:33:44:55-7'\r\n"
" -t: attack timeout (default=5min)\r\n"
" -n: packet limit [>1] (default=0)\r\n"
" -r: packets per second (default=20)\r\n"
" -m: packet types [deauth,disassoc,deauth+disassoc] (default=deauth+disassoc)"
);
Command cmd_probe = cli.addCommand("probe", [](cmd* c) {
Command cmd(c);
probe_attack_settings_t probe_settings;
{ // SSIDs
String ssids { cmd.getArg("ssid").getValue() };
probe_settings.ssids.parse(ssids);
}
{ // Sender
String sender { cmd.getArg("from").getValue() };
parse_mac(sender, probe_settings.sender);
}
{ // Receiver
String receiver_str { cmd.getArg("to").getValue() };
parse_mac(receiver_str, probe_settings.receiver);
}
{ // Channel
String ch_str { cmd.getArg("ch").getValue() };
probe_settings.channels = parse_channels(ch_str);
}
{ // Packet rate
probe_settings.pkt_rate = cmd.getArg("r").getValue().toInt();
}
{ // Time
String time_str { cmd.getArg("t").getValue() };
probe_settings.timeout = parse_time(time_str, 1000);
}
attack::startProbe(probe_settings);
});
cmd_probe.addPosArg("ssid/s");
cmd_probe.addArg("sender,from", "random");
cmd_probe.addArg("receiver,to", "broadcast");
cmd_probe.addArg("ch/annel/s", "1");
cmd_probe.addArg("r/ate", "10");
cmd_probe.addArg("t/ime/out", "5min");
cmd_probe.setDescription(
" Send probe requests for WiFi networks\r\n"
" -ssid: network names (SSIDs) for example: \"test A\",\"test B\"\r\n"
" -from: sender MAC address (default=random)\r\n"
" -to: receiver MAC address (default=broadcast)\r\n"
" -ch: 2.4 GHz channel(s) [1-14] (default=1)\r\n"
" -r: packets per second per SSID (default=10)\r\n"
" -t: attack timeout (default=5min)"
);
Command cmd_alias = cli.addCommand("alias", [](cmd* c) {
Command cmd(c);
Argument arg_mac { cmd.getArg("mac") };
Argument arg_ap { cmd.getArg("ap") };
Argument arg_st { cmd.getArg("st") };
String mode { cmd.getArg("mode").getValue() };
String name { cmd.getArg("name").getValue() };
String mac_str;
if (arg_mac.isSet()) {
mac_str = arg_mac.getValue();
} else if (arg_ap.isSet()) {
int id { arg_ap.getValue().toInt() };
AccessPoint* ap { scan::getAccessPoints().get(id) };
if (ap) mac_str = ap->getBSSIDString();
} else if (arg_st.isSet()) {
int id = arg_st.getValue().toInt();
Station* st { scan::getStations().get(id) };
if (st) mac_str = st->getMACString();
}
if (mode == "list") {
alias::print();
return;
}
else if (mode == "add") {
// No valid mac? Try switching arg values!
if (!mac::valid(mac_str.c_str(), mac_str.length())) {
String tmp = name;
name = mac_str;
mac_str = tmp;
}
uint8_t mac[6] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
parse_mac(mac_str, mac);
if (alias::add(mac, name)) {
debugF("Alias \"");
debug(name);
debugF("\" for ");
debug(strh::mac(mac));
debuglnF(" saved");
} else {
debuglnF("Something went wrong :(");
debuglnF("Name or address already in list or invalid");
}
return;
}
else if (mode == "remove") {
uint8_t mac[6] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
parse_mac(name, mac);
if (alias::remove(mac)) {
debugF("Removed alias ");
debugln(strh::mac(mac));
} else if (alias::remove(name)) {
debugF("Removed alias ");
debugln(name);
} else if (alias::remove(name.toInt())) {
debugF("Removed alias ");
debugln(mac_str);
}
return;
}
else if (mode == "clear") {
alias::clear();
debuglnF("Cleared alias list");
}
else {
debugF("Unknown mode \"");
debug(mode);
debuglnF("\".");
debuglnF("Try \"alias list\", \"alias add [...]\", \"alias remove [...]\" or \"alias clear\".");
}
});
cmd_alias.addPosArg("m/ode", "list");
cmd_alias.addPosArg("name", "");
cmd_alias.addPosArg("mac", "");
cmd_alias.addArg("ap", "");
cmd_alias.addArg("st", "");
cmd_alias.setDescription(
" Manage alias for MAC address\r\n"
" -mode: list,add,remove or clear (default=list)\r\n"
" -name: alias name\r\n"
" -mac: MAC address\r\n"
" -ap: access point ID\r\n"
" -st: station ID");
Command cmd_clear = cli.addCommand("clear", [](cmd* c) {
for (uint8_t i = 0; i<100; ++i) {
debugln();
}
});
cmd_clear.setDescription(" Clear serial output (by spamming line breaks :P)");
Command cmd_ram = cli.addCommand("ram", [](cmd* c) {
debugF("Size: ");
debug(81920);
debuglnF(" byte");
debugF("Used: ");
debug(81920 - system_get_free_heap_size());
debugF(" byte (");
debug(100 - (system_get_free_heap_size() / (81920 / 100)));
debuglnF("%)");
debugF("Free: ");
debug(system_get_free_heap_size());
debugF(" byte (");
debug(system_get_free_heap_size() / (81920 / 100));
debuglnF("%)");
});
cmd_ram.setDescription(" Print memory usage");
Command cmd_stop = cli.addCommand("stop", [](cmd* c) {
Command cmd(c);
String mode_str { cmd.getArg("mode").getValue() };
if (mode_str == "all") {
ap::stop();
attack::stop();
scan::stop();
} else {
SortedStringList mode_list { mode_str };
mode_list.begin();
while (mode_list.available()) {
String mode { mode_list.iterate() };
if (mode == "scan") {
scan::stopAP();
scan::stopST();
} else if (mode == "auth") {
scan::stopAuth();
} else if (mode == "rssi") {
scan::stopRSSI();
} else if (mode == "attack") {
attack::stop();
} else if (mode == "beacon") {
attack::stopBeacon();
} else if (mode == "deauth") {
attack::stopDeauth();
} else if (mode == "probe") {
attack::stopProbe();
} else if (mode == "ap") {
ap::stop();
}
}
}
});
cmd_stop.addPosArg("mode", "all");
cmd_stop.setDescription(" Stop scans or attacks\r\n"
" -mode: all,scan,auth,rssi,attack,beacon,deauth,probe,ap (default=all)");
#ifdef ENABLE_HISTORY
Command cmd_history = cli.addCommand("history", [](cmd* c) {
debuglnF("Command history:");
history.begin();
while (history.available()) {
debugF(" ");
debugln(history.iterate());
}
});
cmd_history.setDescription(" Print previous 10 commands");
#endif // ifdef ENABLE_HISTORY
Command cmd_reboot = cli.addCommand("restart,reboot", [](cmd* c) {
ESP.restart();
});
cmd_reboot.setDescription(" Restarts/Reboots the ESP8266");
Command cmd_chicken = cli.addCommand("chicken", [](cmd* c) {
debugF(" ` - : /////////:-.\r\n"
" ./++so:` `` `.:/++/.\r\n"
" `/+o+.+o:.s:-++//s` `:++-\r\n"
" `/+oo+//d- oh/ s- :o/`\r\n"
" .++.o+` `h- .d. h`://+` .o+\r\n"
" .o+` +/ +o y- +d+. .y .s-\r\n"
" +o` h d` `/ .h- `h ++\r\n"
" .s- d - .` +/ /o\r\n"
" :o` y. -y /+\r\n"
" /+ :o ` -h` s:\r\n"
" /o y..://///////:` /o/o. `h\r\n"
" -s +o:`` `-++::/+- `o/ o:\r\n"
" y. :o `:::.` `oo` -s\r\n"
" -s h` .++:---/+/+/:::++. `h\r\n"
" +/ h :o` ` `/s ` .s- d\r\n"
" o: .s h` /h- o:/h- -s `h\r\n"
" +/ +/ h` `` s- ` +/ -s\r\n"
" .y `h` -s- `+y-.`.:+/ +:\r\n"
" o: o: `/+/:/+ss:.-:y/.` `h`\r\n"
" .:-` `y- ++ `so::-://+y. +/\r\n"
" :o.-/+: :+//:` `s: `+/ -h//:::---:/o- -y\r\n"
" :o `:o:h. `-+/` -d+.:o- .y.``...-/y/++` `y.\r\n"
" +/ `:hs -o- o/:/yo:-` +y++s//+/. `s.\r\n"
" /o` `oo` `/+` .-:y/-`+:+so+/:-` s- y: -s.\r\n"
" ++//+y: -+ .o: ``-:/+:-.`.:+/:hs+`++:/o/:.` `h .y` /o`\r\n"
"`h` `./ys- :o- .--:////:-.` `-/o/::.`/sh-:os/:.` .y oo`+/ -o:\r\n"
" :o- `-o+. `/+o/:-..` `.:+++o/``/:-oo++/:.so+://` `:+/`\r\n"
" `/+:` .. `++` `.-/+/:-/sy.`+o:+y/-. .-/+-`\r\n"
" `-+/- .- `.:/o+:-:.```-:oy/:://:-`\r\n"
" .:+/. `.-:/+/::s/-..`\r\n"
" .++. `.-h.\r\n"
" .o/ +/\r\n"
" :o. :o\r\n"
" .o: -s\r\n"
" /o` :+\r\n"
" -o- o:\r\n"
" `o/ h`\r\n"
" :o. -s\r\n"
" .o: y.\r\n"
" /o. /+\r\n"
" .+/` -s\r\n"
" -+/. .s`\r\n"
" ./+/.` -s`\r\n"
" .:/+:.` /o`\r\n"
" .:/o/.` .o:\r\n"
" o/:/+/.` .++`\r\n"
" -s `:/+:` `:+/`\r\n"
" ++` -+o-` `-++-\r\n"
" :s/::/+//::+/---/+/:`\r\n"
" +/s:` `-h-s-\r\n"
" +/s- `y y.\r\n"
" +/y. `y h`\r\n"
" //s:` `y d\r\n"
" +/-:/++/-` `y h-`\r\n"
" y:hs-ysosss..y --/+++/-`\r\n"
" ds:`s:o+`-:`o:oos./h++osoo`\r\n"
" :: o+++ h:y `o+.s:`.::\r\n"
" -+- -/` :s.++\r\n"
" `/+-\r\n");
});
cmd_chicken.setDescription(" Needs more chicken!");
Command cmd_vendor = cli.addCommand("vendor", [](cmd* c) {
Command cmd(c);
String input { cmd.getArg("mac").getValue() };
bool exact { cmd.getArg("e").isSet() };
uint8_t mac[3];
debuglnF("MAC Vendor");
debuglnF("=================");
if (alias::resolve(input, mac, 3)) {
debug(strh::mac(mac, 3));
debug(' ');
debugln(vendor::getName(mac));
} else {
vendor::getMAC(input, !exact, [](const uint8_t* mac, const char* name) {
debug(strh::mac(mac, 3));
debug(' ');
debugln(name);
});
}
debuglnF("=================");
});
cmd_vendor.addPosArg("mac");
cmd_vendor.addFlagArg("e/xact");
cmd_vendor.setDescription(" Vendor (manufacturer) lookup\r\n"
" -mac: MAC address(es)\r\n"
" -e: list only exact matchess");
Command cmd_wait = cli.addCommand("wait", [](cmd* c) {
debugln("> Paused CLI");
cli.pause();
});
cmd_wait.setDescription(" Wait until scan or attack has finished");
Command cmd_sleep = cli.addCommand("sleep", [](cmd* c) {
Command cmd(c);
timer = millis() + parse_time(cmd.getArg("t").getValue(), 1);
cli.pause();
});
cmd_sleep.addPosArg("t/ime", "");
cmd_sleep.setDescription(" Sleep for specified amount of time\r\n"
" -t: time to sleep");
Command cmd_ap = cli.addCommand("ap", [](cmd* c) {
Command cmd(c);
String ssid { cmd.getArg("s").getValue() };
String pswd { cmd.getArg("p").getValue() };
bool hidden { cmd.getArg("h").isSet() };
uint8_t channel { (uint8_t)cmd.getArg("ch").getValue().toInt() };
uint8_t bssid[6];
String bssid_str { cmd.getArg("bssid").getValue() };
parse_mac(bssid_str, bssid);
ap::start(ssid, pswd, hidden, channel, bssid);
});
cmd_ap.addPosArg("s/sid");
cmd_ap.addPosArg("p/assword", "");
cmd_ap.addFlagArg("hidden");
cmd_ap.addArg("ch/annel", "1");
cmd_ap.addArg("b/ssid", "random");
cmd_ap.setDescription(" Start access point\r\n"
" -s: SSID network name\r\n"
" -p: Password with at least 8 characters\r\n"
" -h: Hidden network\r\n"
" -ch: Channel (default=1)\r\n"
" -b: BSSID MAC address (default=random)");
Command cmd_demo = cli.addCommand("demo", [](cmd* c) {
Command cmd(c);
String mode { cmd.getArg("m").getValue() };
if (mode == "on") {
strh::hide_mac(true);
debugln("Demo Mode: ACTIVATED");
} else {
strh::hide_mac(false);
debugln("Demo Mode: DEACTIVATED");
}
});
cmd_demo.addPosArg("m/ode", "on");
cmd_demo.setDescription(" Demo mode\r\n"
" on: turn on demo mode\r\n"
" off: turn off demo mode");
}
void parse(const char* input) {
cli.parse(input);
}
bool available() {
return debug_available();
}
bool read(String& dest, const char* _default) {
bool printed = false;
while (true) {
// Wait until input is available
if (!debug_available()) {
yield();
continue;
}
// Read next character
int c = debug_read();
// Printable ASCII characters
if ((c >= ' ') && (c <= '~')) {
// Print prefix before the first char
if (!printed) {
debugF("\r\n# ");
printed = true;
}
// Print char and add it to the string
debug(char(c));
dest += char(c);
}
// If end of line, check next char and break the loop
else if ((c == '\r') || (c == '\n')) {
delay(1);
c = debug_peek();
if ((c == '\r') || (c == '\n')) debug_read();
break;
}
// Backspace key
else if ((c == 127) || (c == 8)) {
int len = dest.length();
if (len > 0) {
dest.remove(len-1);
debugF("\r\n# ");
debug(dest);
printed = true;
}
}
}
// No input? Use default value if available.
if (!printed && _default) {
dest += String(_default);
debugF("\r\n# ");
debug(_default);
printed = true;
}
// Add linebreak and print empty line after input
if (printed) {
debugln();
debugln();
return true;
}
return false;
}
bool read_exit() {
if (debug_available()) {
String input;
read(input);
return input == "stop" || input == "exit";
}
return false;
}
void update() {
if (cli.paused() && (((timer == 0) && !scan::active() && !attack::active()) || ((timer > 0) && (millis() > timer)))) {
debugln("> Resumed CLI");
cli.unpause();
timer = 0;
}
if (debug_available()) {
// String input = debug_read();
String input;
read(input);
if (input.length() > 0) {
cli::parse(input.c_str());
#ifdef ENABLE_HISTORY
if (history.full()) {
history.begin();
history.remove();
}
history.push(input);
#endif // ifdef ENABLE_HISTORY
}
}
}
}