Add theme engine, animated UI, and Bus Dump / Crypto / Exfil modules
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
This commit is contained in:
104
src/modules/exfil.cpp
Normal file
104
src/modules/exfil.cpp
Normal file
@@ -0,0 +1,104 @@
|
||||
#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(); }
|
||||
Reference in New Issue
Block a user