#include "../core/module.h" #include "../core/ui.h" #include #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(); }