Files
nfc-pn532-warlord/firmware/components/nfc_engine/nfc_brute.c
drjones 3be2d3f936 Fix PN532 transport and NFC tag handling.
Tighten PN532 framing and NFC access locking so scan, read, write, and raw operations behave reliably on hardware, and correct Classic versus Type 2 tag classification so common cards hit the right code paths. Refresh the embedded web assets and launcher files to match the corrected firmware behavior.

Made-with: Cursor
2026-04-02 18:32:06 -07:00

249 lines
8.4 KiB
C

#include "nfc_engine/nfc_brute.h"
#include "esp_log.h"
#include "esp_task_wdt.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <string.h>
static const char *TAG = "nfc_brute";
/* Community default keys (subset from public Proxmark3 / MCT-style lists). */
static const uint8_t k_builtin[][6] = {
{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
{0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5}, {0xA5, 0xA4, 0xA3, 0xA2, 0xA1, 0xA0},
{0x89, 0xEC, 0xA9, 0x7F, 0x8C, 0x2A}, {0x5C, 0x8F, 0xF9, 0x99, 0x0D, 0xA2},
{0x75, 0xCC, 0xB5, 0x9C, 0x9B, 0xED}, {0xD0, 0x1A, 0xFE, 0xEB, 0x89, 0x0A},
{0x4B, 0x79, 0x1B, 0xEA, 0x7B, 0xCC}, {0x26, 0x12, 0xC6, 0xDE, 0x84, 0xCA},
{0x70, 0x7B, 0x11, 0xFC, 0x14, 0x81}, {0x03, 0xF9, 0x06, 0x76, 0x46, 0xAE},
{0x23, 0x52, 0xC5, 0xB5, 0x6D, 0x85}, {0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5},
{0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5}, {0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5},
{0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF}, {0x4D, 0x3A, 0x99, 0xC3, 0x51, 0xDD},
{0x1A, 0x98, 0x2C, 0x7E, 0x45, 0x9A}, {0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA},
{0xFB, 0xFB, 0xFB, 0xFB, 0xFB, 0xFB}, {0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7},
{0x5A, 0x1B, 0x85, 0xFC, 0xE2, 0x0A}, {0x71, 0x4C, 0x5C, 0x88, 0x6E, 0x97},
{0x58, 0x7E, 0xE5, 0xF9, 0x35, 0x0F}, {0xA0, 0x47, 0x8C, 0xC3, 0x90, 0x91},
{0x53, 0x3C, 0xB6, 0xC7, 0x23, 0xF6}, {0x8F, 0xD0, 0xA4, 0xF2, 0x56, 0xE9},
{0xE0, 0x00, 0x00, 0x00, 0x00, 0x00}, {0xE7, 0xD6, 0x06, 0x4C, 0x58, 0x60},
{0xB2, 0x7C, 0xCA, 0xB3, 0x0D, 0xBD}, {0xD2, 0xEC, 0xE8, 0xB9, 0x39, 0x5E},
{0x14, 0x94, 0xE8, 0x16, 0x63, 0xD7}, {0x7C, 0x9F, 0xB8, 0x47, 0x42, 0x42},
{0x56, 0x93, 0x69, 0xC5, 0xA0, 0xE5}, {0x63, 0x21, 0x93, 0xBE, 0x1C, 0x3C},
{0x8E, 0x26, 0x5B, 0xE2, 0x45, 0xBF}, {0xF4, 0x6B, 0x6D, 0xC0, 0xD6, 0xC4},
{0x2A, 0xA0, 0x5E, 0xD1, 0x85, 0x6F}, {0xAE, 0x3F, 0xF4, 0xEE, 0xA0, 0xDB},
};
#define NBUILTIN (sizeof(k_builtin) / sizeof(k_builtin[0]))
#define MAX_VARIANTS_PER_KEY 14
#define MAX_TRIES_BEFORE_WDT 48
static bool add_sector_hit(cJSON *hits, uint8_t sector, const uint8_t key[6], const char *key_type)
{
if (!hits || !key || !key_type) {
return false;
}
char hx[16];
for (int i = 0; i < 6; i++) {
snprintf(hx + i * 2, 3, "%02X", key[i]);
}
hx[12] = 0;
cJSON *h = cJSON_CreateObject();
if (!h) {
return false;
}
cJSON_AddNumberToObject(h, "sector", sector);
cJSON_AddStringToObject(h, "keyHex", hx);
cJSON_AddStringToObject(h, "keyType", key_type);
cJSON_AddItemToArray(hits, h);
return true;
}
static int push_variant(const uint8_t base[6], int idx, uint8_t out[6])
{
memcpy(out, base, 6);
switch (idx) {
case 0:
return 0;
case 1:
out[5] ^= 0xFF;
return 0;
case 2:
out[0] ^= 0xFF;
return 0;
case 3:
out[5] ^= 0xAA;
return 0;
case 4:
out[5] ^= 0x55;
return 0;
default: {
int n = idx - 5;
if (n >= 0 && n < 10) {
out[5] = (uint8_t)((out[5] & 0xF0) | (uint8_t)n);
return 0;
}
}
return -1;
}
}
static bool try_key_on_trailer(nfc_tag_info_t *tag, uint8_t trailer, const uint8_t key[6], bool key_b)
{
nfc_mifare_key_t k;
memcpy(k.key, key, 6);
k.key_b = key_b;
return nfc_mifare_authenticate_block(tag, trailer, &k) == ESP_OK;
}
/** Trailer block for MIFARE Classic sector index (0..15 for 1K, 0..39 for 4K). */
static uint8_t classic_trailer_for_sector(const nfc_tag_info_t *tag, uint8_t sec)
{
if (nfc_tag_is_mifare_classic_4k(tag)) {
if (sec <= 31) {
return (uint8_t)(sec * 4 + 3);
}
if (sec <= 39) {
return (uint8_t)(128 + (sec - 32) * 16 + 15);
}
return 0xFF;
}
return (uint8_t)(sec * 4 + 3);
}
cJSON *nfc_mifare_dictionary_attack(nfc_tag_info_t *tag, uint8_t sector_first, uint8_t sector_last,
const uint8_t *extra, size_t extra_n, bool variations,
int *attempts_out)
{
int64_t t0_us = esp_timer_get_time();
int attempts = 0;
cJSON *root = cJSON_CreateObject();
cJSON *hits = cJSON_CreateArray();
if (!root || !hits) {
cJSON_Delete(root);
cJSON_Delete(hits);
return NULL;
}
if (!tag) {
cJSON_Delete(root);
cJSON_Delete(hits);
return NULL;
}
if (!nfc_tag_is_mifare_classic(tag)) {
cJSON_AddStringToObject(root, "error", "not_classic_sak_hint");
cJSON_AddItemToObject(root, "sectorHits", hits);
if (attempts_out) {
*attempts_out = 0;
}
return root;
}
uint8_t sector_max = nfc_tag_is_mifare_classic_4k(tag) ? 39 : 15;
if (sector_first > sector_last) {
uint8_t t = sector_first;
sector_first = sector_last;
sector_last = t;
}
if (sector_first > sector_max) {
sector_first = sector_max;
}
if (sector_last > sector_max) {
sector_last = sector_max;
}
for (int sec_i = sector_first; sec_i <= sector_last; sec_i++) {
uint8_t sec = (uint8_t)sec_i;
uint8_t trailer = classic_trailer_for_sector(tag, sec);
if (trailer == 0xFF) {
continue;
}
bool got = false;
for (size_t bi = 0; bi < NBUILTIN && !got; bi++) {
uint8_t trial[6];
int maxv = variations ? MAX_VARIANTS_PER_KEY : 1;
for (int vi = 0; vi < maxv; vi++) {
if (push_variant(k_builtin[bi], vi, trial) != 0) {
break;
}
attempts++;
if (try_key_on_trailer(tag, trailer, trial, false)) {
if (!add_sector_hit(hits, sec, trial, "A")) {
cJSON_Delete(root);
return NULL;
}
got = true;
break;
}
if (try_key_on_trailer(tag, trailer, trial, true)) {
if (!add_sector_hit(hits, sec, trial, "B")) {
cJSON_Delete(root);
return NULL;
}
got = true;
break;
}
if ((attempts % MAX_TRIES_BEFORE_WDT) == 0) {
esp_task_wdt_reset();
vTaskDelay(pdMS_TO_TICKS(1));
}
}
}
for (size_t ei = 0; ei < extra_n && !got; ei++) {
const uint8_t *ek = extra + ei * 6;
uint8_t trial[6];
int maxv = variations ? MAX_VARIANTS_PER_KEY : 1;
for (int vi = 0; vi < maxv; vi++) {
if (push_variant(ek, vi, trial) != 0) {
break;
}
attempts++;
if (try_key_on_trailer(tag, trailer, trial, false)) {
if (!add_sector_hit(hits, sec, trial, "A")) {
cJSON_Delete(root);
return NULL;
}
got = true;
break;
}
if (try_key_on_trailer(tag, trailer, trial, true)) {
if (!add_sector_hit(hits, sec, trial, "B")) {
cJSON_Delete(root);
return NULL;
}
got = true;
break;
}
if ((attempts % MAX_TRIES_BEFORE_WDT) == 0) {
esp_task_wdt_reset();
vTaskDelay(pdMS_TO_TICKS(1));
}
}
}
if (!got) {
cJSON *h = cJSON_CreateObject();
if (!h) {
cJSON_Delete(root);
return NULL;
}
cJSON_AddNumberToObject(h, "sector", sec);
cJSON_AddBoolToObject(h, "miss", true);
cJSON_AddItemToArray(hits, h);
}
}
cJSON_AddItemToObject(root, "sectorHits", hits);
cJSON_AddNumberToObject(root, "attempts", attempts);
cJSON_AddNumberToObject(root, "durationMs", (double)((esp_timer_get_time() - t0_us) / 1000));
cJSON_AddStringToObject(root, "note",
"Dictionary + bounded variants only — not exhaustive 48-bit keyspace.");
if (attempts_out) {
*attempts_out = attempts;
}
ESP_LOGI(TAG, "dictionary attack attempts=%d", attempts);
return root;
}