Includes ESP-IDF NFC stack (deep capture, 4K Classic geometry, UL write API, open SoftAP), React dashboard with live tag diagnostics, and docs. README updated for APIs and lab Wi-Fi defaults. Made-with: Cursor
114 lines
3.8 KiB
TypeScript
114 lines
3.8 KiB
TypeScript
import { useState } from "react";
|
||
import { apiUrl } from "../api";
|
||
import { useToast } from "../toast";
|
||
|
||
export default function Brute() {
|
||
const toast = useToast();
|
||
const [reader, setReader] = useState<"classic1k" | "classic4k">("classic1k");
|
||
const [variations, setVariations] = useState(true);
|
||
const [extraKeys, setExtraKeys] = useState(
|
||
"FFFFFFFFFFFF\nA0A1A2A3A4A5\nD3F7D3F7D3F7",
|
||
);
|
||
const [busy, setBusy] = useState(false);
|
||
const [result, setResult] = useState<string>("");
|
||
|
||
const run = async () => {
|
||
setBusy(true);
|
||
setResult("");
|
||
try {
|
||
const keysHex = extraKeys
|
||
.split(/\r?\n/)
|
||
.map((l) => l.replace(/\s/g, "").toUpperCase())
|
||
.filter((l) => l.length === 12);
|
||
const body = {
|
||
readerType: reader,
|
||
variations,
|
||
keysHex,
|
||
};
|
||
const r = await fetch(apiUrl("/api/mifare/dictionary-attack"), {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify(body),
|
||
});
|
||
const text = await r.text();
|
||
if (!r.ok) {
|
||
throw new Error(text);
|
||
}
|
||
try {
|
||
const j = JSON.parse(text) as object;
|
||
setResult(JSON.stringify(j, null, 2));
|
||
} catch {
|
||
setResult(text);
|
||
}
|
||
toast("Dictionary pass finished");
|
||
} catch (e) {
|
||
toast(String(e), "err");
|
||
} finally {
|
||
setBusy(false);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className="glass space-y-6 p-6">
|
||
<h1 className="font-display text-2xl font-bold">Dictionary attack</h1>
|
||
<p className="text-sm text-slate-400">
|
||
Select card family, optionally enable <strong>bounded variations</strong> (XOR / low-nibble tweaks per
|
||
key). Firmware tries a <strong>built-in community list</strong> (Proxmark3 / MCT-style defaults) plus
|
||
your lines below. This is <strong>not</strong> a full 2⁴⁸ exhaustive search — only keys you and the
|
||
community already know. Hold a <strong>MIFARE Classic</strong> on the coil.{" "}
|
||
<a
|
||
className="text-bubble-mint underline"
|
||
href="https://github.com/RfidResearchGroup/proxmark3/blob/master/client/dictionaries/mfc_default_keys.dic"
|
||
target="_blank"
|
||
rel="noreferrer"
|
||
>
|
||
More keys online
|
||
</a>
|
||
.
|
||
</p>
|
||
|
||
<label className="block text-sm">
|
||
Reader / map
|
||
<select
|
||
value={reader}
|
||
onChange={(e) => setReader(e.target.value as "classic1k" | "classic4k")}
|
||
className="mt-1 w-full rounded-2xl border border-white/10 bg-black/25 px-4 py-2"
|
||
>
|
||
<option value="classic1k">MIFARE Classic 1K (sectors 0–15)</option>
|
||
<option value="classic4k">MIFARE Classic 4K (sectors 0–39, proper 4/16-block geometry)</option>
|
||
</select>
|
||
</label>
|
||
|
||
<label className="flex items-center gap-2 text-sm">
|
||
<input type="checkbox" checked={variations} onChange={(e) => setVariations(e.target.checked)} />
|
||
Variations (extra tries per key — slower, wider net)
|
||
</label>
|
||
|
||
<label className="block text-sm">
|
||
Extra keys (one 12-hex key per line, merged after built-in list)
|
||
<textarea
|
||
value={extraKeys}
|
||
onChange={(e) => setExtraKeys(e.target.value)}
|
||
rows={6}
|
||
className="mt-1 w-full rounded-2xl border border-white/10 bg-black/25 p-3 font-mono text-xs"
|
||
/>
|
||
</label>
|
||
|
||
<button
|
||
type="button"
|
||
disabled={busy}
|
||
onClick={run}
|
||
className="rounded-2xl bg-gradient-to-r from-bubble-rose to-orange-500 px-6 py-3 font-bold text-white disabled:opacity-50"
|
||
>
|
||
{busy ? "Running…" : "Run dictionary attack"}
|
||
</button>
|
||
|
||
{result && (
|
||
<pre className="max-h-96 overflow-auto rounded-2xl border border-white/10 bg-black/40 p-4 text-xs text-bubble-mint">
|
||
{result}
|
||
</pre>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|