Building a Solar-Powered Phishing Awareness System with NodeMCU
How I built a low-cost, energy self-sufficient IoT device to educate people about phishing attacks using captive portals, reverse engineering, and solar power
Introduction
The democratization of the Internet has connected most of the world’s population to the network. In Brazil alone, an estimated 96% of internet users accessed cyberspace via mobile phones in 2017. However, socioeconomic conditions often force users to rely on static access points and public Wi-Fi networks—making them prime targets for cybercriminals.
This project was my undergraduate thesis (TCC) at PUCRS, where I built an open-source phishing awareness system that’s:
- Low-cost (mostly upcycled components)
- Portable and weatherproof
- Energy self-sufficient (solar-powered)
- Educational rather than malicious
The goal? Teach people how easy it is to fall for phishing attacks by showing them firsthand—then immediately educating them on how to protect themselves.
GitHub Project: https://github.com/radzki/SunPhish
Project Architecture
The system uses a NodeMCU development board (ESP-12E chip from the ESP8266 family), solar panels, voltage dividers, and a multiplexer. Here’s the high-level architecture:
┌─────────────────┐ ┌──────────────────┐
│ Solar Panels │────▶│ Charge Circuit │
│ (6V/1W x4) │ │ (from e-waste) │
└─────────────────┘ └────────┬─────────┘
│
▼
┌─────────────────┐ ┌──────────────────┐
│ Li-ion Battery │◀───▶│ NodeMCU │
│ (10000mAh) │ │ (ESP-12E) │
└─────────────────┘ └────────┬─────────┘
│
┌────────────────────────┼────────────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌───────────────────┐ ┌──────────────────┐
│ Voltage Div. │ │ Async Web Server │ │ DNS Server │
│ (Solar/Batt) │ │ (Captive Portal) │ │ (All → Gateway) │
└───────────────┘ └───────────────────┘ └──────────────────┘
Why NodeMCU?
The NodeMCU is perfect for this project:
- Full TCP/IP stack support
- Built-in Wi-Fi (2.4GHz)
- GPIO, PWM, I²C, and ADC pins
- Extremely popular in the maker and offensive security communities
- Low cost (~$5 USD)
The main limitation: when operating as an Access Point, it’s limited to 4 simultaneous users (expandable to 8), so we need to disconnect users after they complete the flow.
The Attack Flow
Here’s what happens when someone connects to the malicious network:
- User connects to the Wi-Fi network (e.g., “#NET-ESCURO-WIFI”)
- Captive portal opens automatically (mimicking legitimate portals)
- User selects a social network for “authentication” (Facebook, Instagram, Twitter, Gmail)
- Cloned login page appears - visually identical to the real thing
- User enters credentials and clicks login
- Awareness page displays - explaining what just happened and how to protect themselves
- User is disconnected from the network automatically
Important: The system counts how many users completed the authentication cycle but never stores actual credentials.
Software Deep Dive
Asynchronous Web Server
One key requirement was handling multiple simultaneous connections. The ESPAsyncWebServer library solved this:
#include <ESPAsyncWebServer.h>
#include <ESPAsyncTCP.h>
AsyncWebServer server(80);
void setup() {
// Serve static files from LittleFS
server.serveStatic("/", LittleFS, "/");
// Handle authentication POST
server.on("/login", HTTP_POST, [](AsyncWebServerRequest *request){
// Increment victim counter (don't store credentials!)
victimCount++;
// Add MAC to disconnect queue
addToDisconnectQueue(request->client()->remoteIP());
// Redirect to awareness page
request->redirect("/awareness.html");
});
server.begin();
}
Captive Portal Detection
Different devices check for internet connectivity differently. To ensure the portal opens automatically on all devices, I added routes for common connectivity checks:
// Android
server.on("/generate_204", HTTP_GET, redirectToCaptivePortal);
server.on("/gen_204", HTTP_GET, redirectToCaptivePortal);
// iOS/macOS
server.on("/hotspot-detect.html", HTTP_GET, redirectToCaptivePortal);
server.on("/library/test/success.html", HTTP_GET, redirectToCaptivePortal);
// Windows
server.on("/ncsi.txt", HTTP_GET, redirectToCaptivePortal);
server.on("/connecttest.txt", HTTP_GET, redirectToCaptivePortal);
// Firefox
server.on("/success.txt", HTTP_GET, redirectToCaptivePortal);
Pro tip: Some Samsung phones have Google’s DNS (8.8.8.8) hardcoded. Setting the gateway IP to 8.8.8.8 solved this edge case.
Reverse Engineering with Ghidra
This is where things got interesting. The ESP8266 SDK used to have a function called wifi_send_pkt_freedom() that allowed sending raw 802.11 frames—perfect for deauthentication attacks. Espressif removed it after discovering its abuse.
I needed user disconnection functionality without using an outdated SDK. Two options:
- Use an old SDK version (buggy, fewer features)
- Reverse engineer the firmware to understand what changed
I chose option 2.
The Investigation
Using Ghidra (the NSA’s open-source reverse engineering tool), I analyzed the compiled firmware.elf:

The function ieee80211_freedom_output had new validation checks on the first bytes of the deauth frame. I could patch the binary directly, but that would require re-patching after every compilation—tedious.
Digging deeper into the ELF exports, I found wifi_softap_deauth():
undefined4 wifi_softap_deauth(int param_1) {
int iVar1;
int iVar2;
undefined4 uVar3;
int iVar4;
uint uVar5;
uint uVar6;
iVar1 = g_ic_20_4;
iVar2 = wifi_get_opmode();
if (((iVar2 == 1) || (iVar2 == 0)) || (g_ic[46])) {
uVar3 = 0;
}
// ... deauthentication logic
}
This function wasn’t documented in C/C++ APIs, but searching the NodeMCU Lua firmware revealed wifi.ap.deauth() which calls this exact function. Problem solved—no binary patching needed!
Implementation
// External declaration for the undocumented function
extern "C" {
int wifi_softap_deauth(uint8_t mac[6]);
}
// Ticker that runs every 5 seconds
Ticker disconnectTicker;
void checkDisconnectQueue() {
for (auto& client : disconnectQueue) {
if (millis() - client.timestamp > DISCONNECT_DELAY) {
wifi_softap_deauth(client.mac);
removeFromQueue(client);
}
}
}
void setup() {
disconnectTicker.attach(5, checkDisconnectQueue);
}
Limitation: Modern devices use MAC address randomization. We can’t permanently block someone, but we can still disconnect them after they complete the flow.
Energy Management & Deep Sleep
The ESP8266 can enter deep sleep mode to save power. My implementation:
void checkEnergyLevels() {
float batteryVoltage = readBatteryVoltage();
float panelVoltage = readPanelVoltage();
// If battery is critically low
if (batteryVoltage < CUTOFF_VOLTAGE) {
enterDeepSleep(SLEEP_DURATION);
}
// If it's nighttime (no solar) and battery is getting low
if (panelVoltage < 1.0 && batteryVoltage < WARNING_VOLTAGE) {
enterDeepSleep(UNTIL_MORNING);
}
// Scheduled sleep (default: 8PM to 8AM)
if (currentHour >= SLEEP_MAX_HOUR || currentHour < SLEEP_MIN_HOUR) {
enterDeepSleep(ONE_HOUR);
}
}
Hardware Design
The Upcycling Philosophy
Following the principle of upcycling (reutilização), almost all components came from electronic waste:
- MOSFETs from a discarded video projector
- Optocouplers from old equipment
- Battery charging circuit from a broken power bank
- Diodes and resistors from various e-waste
Only the NodeMCU and prototyping board were purchased new.
Voltage Dividers
The ESP8266’s ADC accepts 0-1V (with the NodeMCU’s onboard divider, up to 3.3V). To measure the 6.4V solar panel and 4.2V battery, I designed custom voltage dividers:
| Source | Max V | R1/R2 | Max Vin | Ratio |
|---|---|---|---|---|
| Solar Panel | 6.4V | 15kΩ/10kΩ | 8.3V | 0.4 |
| Battery | 4.2V | 10kΩ/10kΩ | 6.6V | 0.5 |
The dividers are transistor-switched to avoid constant current drain:
Vin
│
┌┴┐
│ │ R1
└┬┘
├──────────► ADC
┌┴┐
│ │ R2
└┬┘
│
──┴── MOSFET (controlled by GPIO)
│
GND
Temperature Compensation
Diodes, resistors, and MOSFETs are temperature-sensitive. I added an LM35 temperature sensor with a clever trick: two diodes on the GND pin create a virtual ground, adding ~1.2V offset so the ADC can read temperatures below 70°C:
LM35
┌───┴───┐
Vcc ─┤ ├─ Vout ──► ADC
│ │
└───┬───┘
│
─┴─ D1 (1N4148)
│
─┴─ D2 (1N4148)
│
GND
Reducing NodeMCU Power Consumption
The datasheet claims 10µA in deep sleep. My measurements showed 15mA—150,000% higher!
The culprit: the CH340G USB-to-UART chip consumes 12mA constantly. Since USB isn’t needed during normal operation, I desoldered pin 16 (VCC) and added a jumper for when programming is needed.
After this modification: <5mA in deep sleep.
The remaining power draw comes from the AMS1117 linear regulator—notoriously inefficient. Replacing it with a DC-DC converter would help, but time constraints prevented this optimization.
Solar Panel Dimensioning
I empirically tested different panel configurations:
| # Panels | Panel Voltage | Battery Current |
|---|---|---|
| 1 | 4.73V | -20mA (discharging!) |
| 2 | 4.96V | +80mA |
| 3 | 5.10V | +180mA |
| 4 | 5.74V | +280mA |
| 5 | 5.93V | +380mA |
Four panels provide enough current for charging while reducing thermal stress on the charge controller.
Energy Balance Calculation
Daily consumption:
C(t) = Im × t + Imds × (24 - t)
Where:
Im= average current during operation (~85mA)Imds= average current in deep sleep (~5mA)t= hours of operation (12h default: 8AM-8PM)
Daily surplus/deficit:
S(t, ts) = Ipv × ts - C(t)
Where:
Ipv= current from panels to battery (~200mA)ts= hours of useful sunlight (~6h pessimistic)
Result: +108mA daily surplus. The 10000mAh battery can sustain the system for ~70 days of deep sleep without any solar input.
Charge Controller Reverse Engineering
The charge controller came from a discarded power bank. To understand it, I traced every PCB trace with a multimeter in continuity mode:
┌─────────────────────────────────────────────────────────┐
│ │
│ GND ─┬─ IC1 (DW01) ─┬─ R1 ──┬── R4 ──┬── D1 ──┬─ Vout │
│ │ │ 27k │ 10k │ DIODE │ (5V) │
│ └── Q1 ────────┘ │ │ │ │
│ (8205S) │ │ │ │
│ │ │ │ │
│ BAT+ ───────────────────────┼────────┴────────┘ │
│ │ │
│ BAT- ─── R3 ────────────────┤ │
│ 100Ω │ │
│ │ │
│ TD8208 ────────────┴─── L1 ─── D8 │
│ (Boost IC) 2.2µH │
└─────────────────────────────────────────────────────────┘
Key finding: the DW01 provides battery protection, the 8205S dual MOSFET handles switching, and the TD8208 is a boost converter for the 5V output. The circuit accepts the full voltage range from my solar panels.
Web Development
Cloning Login Pages
The fake login pages needed to be convincing. I manually cloned the essential CSS and removed unnecessary features (account creation, password recovery):
The challenge: fitting everything into the NodeMCU’s 1MB filesystem while reserving 2MB for OTA updates.
The Awareness Page
After “authentication,” users see this page explaining:
- What just happened
- That their credentials were NOT stored
- Tips for identifying phishing attacks
- Links to security resources
- A downloadable PDF with more information
The user is automatically disconnected after 60 seconds.
Field Testing Results
Validation
| Test | Expected | Result |
|---|---|---|
| Environment setup | IDE configured | ✅ |
| Captive portal POC | Auto-open portal | ✅ |
| Energy consumption | 140mA TX, 10µA sleep | ⚠️ Partial* |
| Solar panel validation | 170mA @ 6V | ⚠️ Partial** |
| Login pages | 4+ social networks | ✅ |
| User disconnection | Deauth after flow | ✅ |
| Burn-in test | 1 week outdoor | ✅ |
| Field deployment | Users complete flow | ✅ |
*Deep sleep was 15mA due to CH340G; fixed with hardware mod
**Actual output was lower than datasheet; compensated with more panels
Real-World Test
The system was deployed at a private event. Out of ~10 people exposed to the network, 3 completed the full authentication flow—a 30% “success” rate in just one day.
Extrapolating to a larger population with longer exposure time, this demonstrates the very real danger of public Wi-Fi networks.
Conclusion
This project achieved its goals:
- Built a working phishing awareness system
- Powered entirely by solar energy
- Constructed mostly from upcycled components
- Successfully educated users about phishing risks
The 30% “victim rate” in a small sample proves that this threat is real and that user education is critical.
Future Improvements
- Replace the linear regulator with DC-DC converter
- Add current sensing for better energy management
- Integrate LoRaWAN or Sigfox for remote monitoring
- Design a custom PCB to reduce size
- Add more login page templates
Resources
- ESP8266 Arduino Core Documentation
- ESPAsyncWebServer Library
- NodeMCU Firmware (Lua)
- Ghidra - NSA’s Reverse Engineering Tool
- PlatformIO IDE
This project was developed for educational purposes as part of my Computer Engineering degree at PUCRS (Pontifícia Universidade Católica do Rio Grande do Sul), Brazil, in 2025. Always obtain proper authorization before conducting security testing.
GitHub Project: https://github.com/radzki/SunPhish