Files
nfc-pn532-warlord/firmware/components/nfc_engine/nfc_brute.c
drjones 63db10d400 fw: robust HTTP body reads, CORS preflight, LED off at boot; update docs
- app_net.c: replace bare httpd_req_recv with recv_body_capped/alloc
  helpers (TCP-safe, full-body reads); add OPTIONS/* CORS preflight
  handler; bump WS broadcast buffer to 2048; add CORS Allow-Methods
- CMakeLists (net_service): add http_parser dep for HTTP_OPTIONS
- nfc_engine/pn532_core: add nfc_access_lock/unlock mutex, board-RGB
  quiet helper, UL type detection, general-status improvements
- pn532_transport: minor cleanup
- main.c: call board_rgb_led_quiet() at boot to kill onboard LED
- sdkconfig.defaults: add board RGB Kconfig defaults
- README, docs/LIMITATIONS, docs/PINOUT: expand and correct

Made-with: Cursor
2026-04-07 21:23:04 -07:00

254 lines
8.6 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(hits);
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(hits);
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(hits);
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(hits);
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(hits);
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;
}