UI: - Theme engine (5 palettes) persisted to NVS; live Theme switcher module - Animated shell: boot splash, CRT scanlines, breathing menu cursor, wipe-in transitions, spinners, progress bars New recon modules: - Bus Dump: SPI-NOR + I2C EEPROM readout to microSD (read-only) - Crypto Lab: entropy, magic-byte file-ID, XOR brute, AES-128 known-key - Exfil: SD browser + base64 dump export over USB serial, manifest writer README updated with the new module table and look-and-feel notes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AAhMHMRAQLQ9hSbBECKNfn
105 lines
3.6 KiB
C++
105 lines
3.6 KiB
C++
#include "../core/module.h"
|
|
#include "../core/ui.h"
|
|
#include <SD.h>
|
|
#include "mbedtls/base64.h"
|
|
|
|
// Exfil / Export — get captured data OFF the Cardputer for analysis on
|
|
// your workstation. Browse the SD card and stream a selected dump out
|
|
// over USB serial as base64 (paste into `base64 -d > file` on the host).
|
|
// Also writes a session manifest of what you pulled. A data-offload
|
|
// helper for your own captures — not a network channel.
|
|
|
|
class Exfil : public Module {
|
|
static constexpr int MAXF = 32;
|
|
char files[MAXF][32]; uint32_t sizes[MAXF]; int nf = 0, sel = 0;
|
|
bool sending = false; File cur; uint32_t sent = 0, total = 0;
|
|
char status[2][40] = {{0},{0}};
|
|
public:
|
|
const char* name() const override { return "Exfil"; }
|
|
const char* blurb() const override { return "export dumps->host"; }
|
|
|
|
void onEnter() override { sending=false; if (!SD.begin()) say("no SD"); else scan(); }
|
|
void onExit() override { if (cur) cur.close(); sending=false; }
|
|
|
|
bool onKey(char c) override {
|
|
if (sending) { if (c==' ') stop(); return true; }
|
|
int m = nf ? nf : 1;
|
|
if (c == ';') { sel = (sel-1+m)%m; return true; }
|
|
if (c == '.') { sel = (sel+1)%m; return true; }
|
|
if (c == 'r') { scan(); return true; }
|
|
if (c == '\r' || c == ' ') { startSend(); return true; }
|
|
if (c == 'x') { writeManifest(); return true; }
|
|
return false;
|
|
}
|
|
|
|
void tick() override {
|
|
if (!sending) return;
|
|
uint8_t in[48]; int n = cur.read(in, sizeof(in));
|
|
if (n <= 0) { Serial.println(); say("done: %s", files[sel]); stop(); return; }
|
|
unsigned char out[80]; size_t olen = 0;
|
|
mbedtls_base64_encode(out, sizeof(out), &olen, in, n);
|
|
Serial.write(out, olen);
|
|
sent += n;
|
|
}
|
|
|
|
void draw() override {
|
|
ui::lineC(0, ui::accent(), "SD export %s", sending ? "STREAMING" : "browse");
|
|
if (sending) {
|
|
ui::bar(1, total ? sent/(float)total : 0, ui::glow(), "b64->usb");
|
|
} else {
|
|
for (int i = 0; i < nf && i < 5; i++)
|
|
ui::lineC(1+i, i==sel ? ui::glow() : ui::fg(), "%c %-16s %luB",
|
|
i==sel?char(0x10):' ', files[i], (unsigned long)sizes[i]);
|
|
if (!nf) ui::line(1, "no files in /dump");
|
|
}
|
|
for (int i = 0; i < 2; i++) ui::line(6+i, "%s", status[i]);
|
|
ui::hintBar("; . pick enter send [x]manifest [`]back");
|
|
}
|
|
|
|
private:
|
|
void say(const char* fmt, ...) {
|
|
strncpy(status[1], status[0], 39);
|
|
va_list ap; va_start(ap,fmt); vsnprintf(status[0],40,fmt,ap); va_end(ap);
|
|
}
|
|
void scan() {
|
|
nf = 0; sel = 0;
|
|
File d = SD.open("/dump");
|
|
if (!d) { say("no /dump dir"); return; }
|
|
File f;
|
|
while ((f = d.openNextFile()) && nf < MAXF) {
|
|
if (!f.isDirectory()) {
|
|
strncpy(files[nf], f.name(), 31);
|
|
sizes[nf] = f.size();
|
|
nf++;
|
|
}
|
|
f.close();
|
|
}
|
|
d.close();
|
|
say("%d file(s)", nf);
|
|
}
|
|
void startSend() {
|
|
if (!nf) { say("nothing to send"); return; }
|
|
char path[48]; snprintf(path, sizeof(path), "/dump/%s", files[sel]);
|
|
cur = SD.open(path, FILE_READ);
|
|
if (!cur) { say("open fail"); return; }
|
|
total = sizes[sel]; sent = 0; sending = true;
|
|
Serial.printf("\n----- BEGIN %s (base64) -----\n", files[sel]);
|
|
say("streaming %s", files[sel]);
|
|
}
|
|
void stop() {
|
|
if (cur) cur.close();
|
|
Serial.printf("\n----- END -----\n");
|
|
sending = false;
|
|
}
|
|
void writeManifest() {
|
|
File m = SD.open("/dump/MANIFEST.txt", FILE_WRITE);
|
|
if (!m) { say("manifest fail"); return; }
|
|
m.printf("Card-Crack export manifest\n");
|
|
for (int i = 0; i < nf; i++) m.printf("%-20s %lu B\n", files[i], (unsigned long)sizes[i]);
|
|
m.close();
|
|
say("wrote MANIFEST.txt");
|
|
}
|
|
};
|
|
|
|
Module* makeExfil() { return new Exfil(); }
|