Politician 1.0.0
WiFi Auditing Library for ESP32
Loading...
Searching...
No Matches
PoliticianStorage.h
Go to the documentation of this file.
1#pragma once
2
3#ifndef ARDUINO
4#error "PoliticianStorage.h requires the Arduino framework. Use ESP-IDF VFS and nvs_flash APIs directly."
5#endif
6
7#include <Arduino.h>
8#include <FS.h>
9#include <Preferences.h>
10#include <string>
11#include "Politician.h"
12#include "PoliticianFormat.h"
13
14namespace politician {
15namespace storage {
16
17/**
18 * @brief Helper for writing HandshakeRecords to a standard PCAPNG file.
19 */
21public:
22 /**
23 * @brief Appends a HandshakeRecord to a file as PCAPNG.
24 * If the file is empty, it automatically writes the global PCAPNG Header Block first.
25 *
26 * @param fs The filesystem (e.g., SD, LittleFS)
27 * @param path The path to the file (e.g., "/captures.pcapng")
28 * @param rec The HandshakeRecord to write
29 * @return true if successful, false if file could not be opened
30 */
31 static bool append(fs::FS &fs, const char* path, const HandshakeRecord& rec) {
32 bool isNew = !fs.exists(path);
33 if (!isNew) {
34 fs::File check = fs.open(path, FILE_READ);
35 if (check) {
36 isNew = (check.size() == 0);
37 check.close();
38 }
39 }
40
41 fs::File file = fs.open(path, FILE_APPEND);
42 if (!file) return false;
43
44 if (isNew) {
45 uint8_t hdr[48];
46 size_t hl = format::writePcapngGlobalHeader(hdr);
47 file.write(hdr, hl);
48 }
49
50 uint8_t buf[512];
51 size_t len = format::writePcapngRecord(rec, buf, sizeof(buf));
52 if (len > 0) {
53 file.write(buf, len);
54 file.flush();
55 }
56 file.close();
57 return true;
58 }
59
60 /**
61 * @brief Appends a raw 802.11 sniffer frame to a PCAPNG file (used for intel gathering).
62 *
63 * @param fs The filesystem (e.g., SD, LittleFS)
64 * @param path The path to the file (e.g., "/intel.pcapng")
65 * @param payload Raw packet data
66 * @param len Packet length
67 * @param rssi Signal strength
68 * @param channel WiFi channel
69 * @param ts_usec Hardware microsecond timestamp
70 * @return true if successful, false otherwise
71 */
72 static bool appendPacket(fs::FS &fs, const char* path, const uint8_t* payload, uint16_t len, int8_t rssi, uint8_t channel, uint32_t ts_usec) {
73 bool isNew = !fs.exists(path);
74 if (!isNew) {
75 fs::File check = fs.open(path, FILE_READ);
76 if (check) {
77 isNew = (check.size() == 0);
78 check.close();
79 }
80 }
81
82 fs::File file = fs.open(path, FILE_APPEND);
83 if (!file) return false;
84
85 if (isNew) {
86 uint8_t hdr[48];
87 size_t hl = format::writePcapngGlobalHeader(hdr);
88 file.write(hdr, hl);
89 }
90
91 uint8_t buf[2500]; // Max 802.11 frame is 2346 bytes
92 size_t wlen = format::writePcapngPacket(payload, len, rssi, channel, ts_usec, buf, sizeof(buf));
93 if (wlen > 0) {
94 file.write(buf, wlen);
95 file.flush();
96 }
97 file.close();
98 return true;
99 }
100};
101
102/**
103 * @brief Helper for writing HandshakeRecords to an HC22000 text file.
104 */
106public:
107 /**
108 * @brief Appends a HandshakeRecord to a file as an HC22000 string.
109 *
110 * @param fs The filesystem (e.g., SD, LittleFS)
111 * @param path The path to the file (e.g., "/captures.22000")
112 * @param rec The HandshakeRecord to write
113 * @return true if successful, false if file could not be opened
114 */
115 static bool append(fs::FS &fs, const char* path, const HandshakeRecord& rec) {
116 fs::File file = fs.open(path, FILE_APPEND);
117 if (!file) return false;
118
119 std::string str = format::toHC22000(rec);
120 if (!str.empty()) {
121 file.println(str.c_str());
122 file.flush();
123 }
124 file.close();
125 return true;
126 }
127};
128
129/**
130 * @brief Helper for writing precise GPS location coordinates to a Wigle.net compatible CSV file.
131 *
132 * Wigle.net has a strict CSV format starting with a specific header:
133 * MAC,SSID,AuthMode,FirstSeen,Channel,RSSI,CurrentLatitude,CurrentLongitude,AltitudeMeters,AccuracyMeters,Type
134 */
136public:
137 /**
138 * @brief Appends a HandshakeRecord's details alongside GPS coordinates to a Wigle CSV.
139 *
140 * @param fs The filesystem (e.g., SD, LittleFS)
141 * @param path The path to the file (e.g., "/wardrive.csv")
142 * @param rec The captured HandshakeRecord
143 * @param lat Current GPS Latitude
144 * @param lon Current GPS Longitude
145 * @param alt (Optional) Current GPS Altitude in meters
146 * @param acc (Optional) GPS Accuracy radius in meters
147 * @return true if successful, false if file could not be opened
148 */
149 static bool append(fs::FS &fs, const char* path, const HandshakeRecord& rec,
150 float lat, float lon, float alt = 0.0, float acc = 10.0,
151 const char* timestamp = nullptr) {
152 fs::File file = _openWithHeader(fs, path);
153 if (!file) return false;
154
155 char line[256];
156 snprintf(line, sizeof(line), "%02X:%02X:%02X:%02X:%02X:%02X,\"%s\",%s,%s,%d,%d,%.6f,%.6f,%.1f,%.1f,WIFI",
157 rec.bssid[0], rec.bssid[1], rec.bssid[2], rec.bssid[3], rec.bssid[4], rec.bssid[5],
158 rec.ssid, _authStr(rec.enc), timestamp ? timestamp : "1970-01-01 00:00:00",
159 rec.channel, rec.rssi, lat, lon, alt, acc);
160
161 file.println(line);
162 file.flush();
163 file.close();
164 return true;
165 }
166
167 /**
168 * @brief Appends any discovered ApRecord alongside GPS coordinates to a Wigle CSV.
169 * Use this with setApFoundCallback() to log all networks, not just captured ones.
170 *
171 * @param fs The filesystem (e.g., SD, LittleFS)
172 * @param path The path to the file (e.g., "/wardrive.csv")
173 * @param ap The discovered ApRecord
174 * @param lat Current GPS Latitude
175 * @param lon Current GPS Longitude
176 * @param alt (Optional) Current GPS Altitude in meters
177 * @param acc (Optional) GPS Accuracy radius in meters
178 * @return true if successful, false if file could not be opened
179 */
180 static bool appendAp(fs::FS &fs, const char* path, const ApRecord& ap,
181 float lat, float lon, float alt = 0.0, float acc = 10.0,
182 const char* timestamp = nullptr) {
183 fs::File file = _openWithHeader(fs, path);
184 if (!file) return false;
185
186 char line[256];
187 snprintf(line, sizeof(line), "%02X:%02X:%02X:%02X:%02X:%02X,\"%s\",%s,%s,%d,%d,%.6f,%.6f,%.1f,%.1f,WIFI",
188 ap.bssid[0], ap.bssid[1], ap.bssid[2], ap.bssid[3], ap.bssid[4], ap.bssid[5],
189 ap.ssid, _authStr(ap.enc), timestamp ? timestamp : "1970-01-01 00:00:00",
190 ap.channel, ap.rssi, lat, lon, alt, acc);
191
192 file.println(line);
193 file.flush();
194 file.close();
195 return true;
196 }
197
198private:
199 static const char* _authStr(uint8_t enc) {
200 switch (enc) {
201 case 1: return "[WEP][ESS]";
202 case 2: return "[WPA-PSK-TKIP][ESS]";
203 case 3: return "[WPA2-PSK-CCMP][ESS]";
204 case 4: return "[WPA2-EAP-CCMP][ESS]";
205 default: return "[ESS]";
206 }
207 }
208
209 static fs::File _openWithHeader(fs::FS &fs, const char* path) {
210 bool isNew = !fs.exists(path);
211 if (!isNew) {
212 fs::File check = fs.open(path, FILE_READ);
213 if (check) { isNew = (check.size() == 0); check.close(); }
214 }
215 fs::File file = fs.open(path, FILE_APPEND);
216 if (!file) return file;
217 if (isNew) {
218 file.println("WigleWifi-1.4,appRelease=1.0,model=Politician,release=1.0,device=ESP32,display=1.0,board=ESP32,brand=Espressif");
219 file.println("MAC,SSID,AuthMode,FirstSeen,Channel,RSSI,CurrentLatitude,CurrentLongitude,AltitudeMeters,AccuracyMeters,Type");
220 }
221 return file;
222 }
223};
224
225/**
226 * @brief Helper for logging harvested 802.1X Enterprise Credentials.
227 * It writes a Clean CSV file containing BSSID, Client, and Plaintext Identity.
228 */
230public:
231 static bool append(fs::FS &fs, const char *path, const EapIdentityRecord &rec) {
232 bool isNew = !fs.exists(path);
233
234 fs::File file = fs.open(path, FILE_APPEND);
235 if (!file) return false;
236
237 if (isNew) {
238 file.println("Enterprise BSSID,Client MAC,Plaintext Identity,Channel,RSSI");
239 }
240
241 char line[160];
242 snprintf(line, sizeof(line), "%02X:%02X:%02X:%02X:%02X:%02X,%02X:%02X:%02X:%02X:%02X:%02X,\"%s\",%d,%d",
243 rec.bssid[0], rec.bssid[1], rec.bssid[2], rec.bssid[3], rec.bssid[4], rec.bssid[5],
244 rec.client[0], rec.client[1], rec.client[2], rec.client[3], rec.client[4], rec.client[5],
245 rec.identity, rec.channel, rec.rssi);
246
247 file.println(line);
248 file.flush();
249 file.close();
250 return true;
251 }
252};
253
254/**
255 * @brief Helper for persistently storing captured BSSIDs in NVS memory.
256 * This ensures that previously captured networks aren't attacked again after a reboot.
257 */
259private:
260 Preferences _prefs;
261 char _ns[16];
262 static const int MAX_STORED = 128;
263 uint8_t _cache[MAX_STORED][6];
264 size_t _count;
265
266public:
267 NvsBssidCache(const char* ns = "wardrive") : _count(0) {
268 if (strlen(ns) > 15)
269 Serial.println("[NvsBssidCache] WARNING: namespace name exceeds 15 chars and will be truncated");
270 strncpy(_ns, ns, sizeof(_ns) - 1);
271 _ns[sizeof(_ns) - 1] = '\0';
272 memset(_cache, 0, sizeof(_cache));
273 }
274
275 /**
276 * @brief Initializes the NVS memory and loads the cached BSSIDs into RAM.
277 */
278 void begin() {
279 _prefs.begin(_ns, false);
280 size_t bytes = _prefs.getBytes("bssids", _cache, sizeof(_cache));
281 _count = bytes / 6;
282 if (_count > MAX_STORED) _count = MAX_STORED; // Safety parameter
283 }
284
285 /**
286 * @brief Feeds the loaded BSSIDs into the Politician engine so it knows to ignore them.
287 * @param engine Reference to your active Politician instance
288 */
290 for (size_t i = 0; i < _count; i++) {
291 engine.markCaptured(_cache[i]);
292 }
293 }
294
295 /**
296 * @brief Adds a newly captured BSSID to the cache and saves it to NVS.
297 * @param bssid The 6-byte BSSID to save.
298 * @return true if added, false if it already exists or the cache is full.
299 */
300 bool add(const uint8_t* bssid) {
301 for (size_t i = 0; i < _count; i++) {
302 if (memcmp(_cache[i], bssid, 6) == 0) return false; // Already cached
303 }
304 if (_count >= MAX_STORED) return false; // Cache full
305
306 memcpy(_cache[_count], bssid, 6);
307 _count++;
308
309 // Save entire updated array to NVS
310 _prefs.putBytes("bssids", _cache, _count * 6);
311 return true;
312 }
313
314 /**
315 * @brief Returns the number of BSSIDs currently stored.
316 */
317 size_t count() const { return _count; }
318
319 /**
320 * @brief Clears the entire cache from NVS.
321 */
322 void clear() {
323 _count = 0;
324 _prefs.remove("bssids");
325 }
326};
327
328} // namespace storage
329} // namespace politician
The core WiFi handshake capturing engine.
Definition Politician.h:91
Helper for logging harvested 802.1X Enterprise Credentials.
static bool append(fs::FS &fs, const char *path, const EapIdentityRecord &rec)
Helper for writing HandshakeRecords to an HC22000 text file.
static bool append(fs::FS &fs, const char *path, const HandshakeRecord &rec)
Appends a HandshakeRecord to a file as an HC22000 string.
Helper for persistently storing captured BSSIDs in NVS memory.
NvsBssidCache(const char *ns="wardrive")
bool add(const uint8_t *bssid)
Adds a newly captured BSSID to the cache and saves it to NVS.
size_t count() const
Returns the number of BSSIDs currently stored.
void loadInto(Politician &engine)
Feeds the loaded BSSIDs into the Politician engine so it knows to ignore them.
void begin()
Initializes the NVS memory and loads the cached BSSIDs into RAM.
void clear()
Clears the entire cache from NVS.
Helper for writing HandshakeRecords to a standard PCAPNG file.
static bool appendPacket(fs::FS &fs, const char *path, const uint8_t *payload, uint16_t len, int8_t rssi, uint8_t channel, uint32_t ts_usec)
Appends a raw 802.11 sniffer frame to a PCAPNG file (used for intel gathering).
static bool append(fs::FS &fs, const char *path, const HandshakeRecord &rec)
Appends a HandshakeRecord to a file as PCAPNG.
Helper for writing precise GPS location coordinates to a Wigle.net compatible CSV file.
static bool append(fs::FS &fs, const char *path, const HandshakeRecord &rec, float lat, float lon, float alt=0.0, float acc=10.0, const char *timestamp=nullptr)
Appends a HandshakeRecord's details alongside GPS coordinates to a Wigle CSV.
static bool appendAp(fs::FS &fs, const char *path, const ApRecord &ap, float lat, float lon, float alt=0.0, float acc=10.0, const char *timestamp=nullptr)
Appends any discovered ApRecord alongside GPS coordinates to a Wigle CSV.
Politician engine
Definition main.cpp:6
size_t writePcapngPacket(const uint8_t *payload, size_t payload_len, int8_t rssi, uint8_t channel, uint64_t ts_usec, uint8_t *buffer, size_t max_len)
Serializes a Raw 802.11 Frame into a PCAPNG Enhanced Packet Block.
std::string toHC22000(const HandshakeRecord &rec)
Converts a captured HandshakeRecord into the Hashcat 22000 (hc22000) text format.
size_t writePcapngRecord(const HandshakeRecord &rec, uint8_t *buffer, size_t max_len)
Serializes a HandshakeRecord into PCAPNG Enhanced Packet Blocks.
size_t writePcapngGlobalHeader(uint8_t *buffer)
Writes a PCAPNG Global Header (SHB + IDB).
Snapshot of a discovered Access Point from the internal cache.
A harvested 802.1X Enterprise plaintext identity, delivered to the IdentityCb callback.
A captured handshake or PMKID record delivered to the EapolCb callback.