Merge branch 'master' into hf-14b-tearoff

Signed-off-by: Christian Zanon <105173223+xNovyz@users.noreply.github.com>
This commit is contained in:
Christian Zanon
2026-03-13 11:39:06 +01:00
committed by GitHub
65 changed files with 5885 additions and 560 deletions
+3
View File
@@ -5,6 +5,9 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac
## [unreleased][unreleased]
- Added `hf 14b tearoff` - interactive ST25TB/SRx monotonic counter tear-off attack (@xNovyz)
- Fixed missing `WDT_HIT()` in `Get14443bAnswerFromTag()` DMA polling loop causing hardware watchdog reboot on SSC clock stall (@xNovyz)
- Changed `wiegand encode` / `wiegand decode` - added support for encoding and decoding the new 96-bit ASN.1 encoded format, `--bin` encoding, verbose PACS encoding output, and explicit rejection of raw/binary decodes above 96 bits (@cindersocket)
- Added Mifare Classic support to `hf gallagher` command (@pingu2211)
- Added `hf felica discnodes` command (@kormax)
- Added `hf mfp dump` command (@apply-science)
- Added `hf felica seacinfo` command (@kormax)
- Added `hf mfdes bruteisofid` and `hf mfdes selectisofid` commands (@kormax)
+2 -1
View File
@@ -38,7 +38,7 @@ The Proxmark3 is the swiss-army tool of RFID, allowing for interactions with the
| [Linux - Important notes on ModemManager](/doc/md/Installation_Instructions/ModemManager-Must-Be-Discarded.md) | [Validating Proxmark3 Client Functionality](/doc/md/Use_of_Proxmark/1_Validation.md)|
| [macOS - Homebrew & Upgrading HomeBrew Tap Formula](/doc/md/Installation_Instructions/macOS-Homebrew-Installation-Instructions.md) | [First Use and Verification](/doc/md/Use_of_Proxmark/2_Configuration-and-Verification.md)|
| [macOS - MacPorts](/doc/md/Installation_Instructions/macOS-MacPorts-Installation-Instructions.md) | [Commands & Features](/doc/md/Use_of_Proxmark/3_Commands-and-Features.md)|
| [macOS - Setup and Build](/doc/md/Installation_Instructions/macOS-Compile-From-Source-Instructions.md) ||
| [macOS - Setup and Build](/doc/md/Installation_Instructions/macOS-Compile-From-Source-Instructions.md) |[Standalone Modes](/doc/standalone/)|
| [Windows - Setup and Build](/doc/md/Installation_Instructions/Windows-Installation-Instructions.md) ||
| [Termux / Android - Setup and Build](/doc/termux_notes.md) ||
| [iOS - Setup and Build](/doc/md/Installation_Instructions/iOS-Installation-Instructions.md)
@@ -49,6 +49,7 @@ The Proxmark3 is the swiss-army tool of RFID, allowing for interactions with the
## Notes / helpful documents
| Notes |||
+63
View File
@@ -5,6 +5,9 @@
# Table of Contents
- [Standalone Modes](#standalone-modes)
- [Table of Contents](#table-of-contents)
- [What are standalone modes?](#what-are-standalone-modes)
- [Individual mode documentation](#individual-mode-documentation)
- [Developing Standalone Modes](#developing-standalone-modes)
- [Implementing a standalone mode](#implementing-a-standalone-mode)
- [Naming your standalone mode](#naming-your-standalone-mode)
- [Update MAKEFILE.HAL](#update-makefilehal)
@@ -14,7 +17,67 @@
- [Submitting your code](#submitting-your-code)
Standalone modes run directly on the Proxmark3 device without a connected host computer.
See [Developing Standalone Modes](#developing-standalone-modes) for how to build your own.
> Only one (1) mode can be compiled into the firmware at a time (except via [DANKARMULTI](../../doc/standalone/dankarmulti.md)).
## Individual Mode Documentation
### LF (Low Frequency — 125 kHz) Standalone Modes
| Mode ID | Document | Description | Hardware |
|---------|----------|-------------|----------|
| LF_SAMYRUN | [SamyRun](../../doc/standalone/lf_samyrun.md) | HID26 read/clone/simulate (Samy Kamkar) | Generic |
| LF_EM4100EMUL | [EM4100 Emulator](../../doc/standalone/lf_em4100emul.md) | Simulate predefined EM4100 tag IDs | Generic |
| LF_EM4100RSWB | [EM4100 RSWB](../../doc/standalone/lf_em4100rswb.md) | Read/simulate/write/brute EM4100 (4 slots) | RDV4 (flash) |
| LF_EM4100RSWW | [EM4100 RSWW](../../doc/standalone/lf_em4100rsww.md) | Read/simulate/write/wipe/validate EM4100 | RDV4 (flash) |
| LF_EM4100RWC | [EM4100 RWC](../../doc/standalone/lf_em4100rwc.md) | Read/simulate/clone EM4100 (16 slots) | RDV4 (flash) |
| LF_HIDBRUTE | [HID Corporate Brute](../../doc/standalone/lf_hidbrute.md) | HID Corporate 1000 card number bruteforce | Generic |
| LF_HIDFCBRUTE | [HID FC Brute](../../doc/standalone/lf_hidfcbrute.md) | HID facility code bruteforce (0255) | RDV4 (flash) |
| LF_ICEHID | [IceHID Collector](../../doc/standalone/lf_icehid.md) | Multi-format LF credential collector to flash | RDV4 (flash) |
| LF_MULTIHID | [MultiHID](../../doc/standalone/lf_multihid.md) | HID 26-bit multi-card simulator | Generic |
| LF_NEDAP_SIM | [Nedap Simulator](../../doc/standalone/lf_nedap_sim.md) | Nedap RFID simple tag simulator | Generic |
| LF_NEXID | [NexID Collector](../../doc/standalone/lf_nexid.md) | Nexwatch credential collector to flash | RDV4 (flash) |
| LF_PROXBRUTE | [ProxBrute](../../doc/standalone/lf_proxbrute.md) | HID ProxII card number bruteforce | Generic |
| LF_PROX2BRUTE | [Prox2Brute](../../doc/standalone/lf_prox2brute.md) | HID ProxII bruteforce v2 (faster, configurable) | Generic |
| LF_THAREXDE | [Tharexde EM4x50](../../doc/standalone/lf_tharexde.md) | EM4x50 simulate/read/collect | RDV4 (flash) |
| LF_SKELETON | [Skeleton Template](../../doc/standalone/lf_skeleton.md) | Development template for new LF modes | Generic |
### HF (High Frequency — 13.56 MHz) Standalone Modes
| Mode ID | Document | Description | Hardware |
|---------|----------|-------------|----------|
| HF_14ASNIFF | [14A Sniffer](../../doc/standalone/hf_14asniff.md) | ISO14443A passive sniffer to flash | RDV4 (flash) |
| HF_14BSNIFF | [14B Sniffer](../../doc/standalone/hf_14bsniff.md) | ISO14443B passive sniffer to flash | RDV4 (flash) |
| HF_15SNIFF | [15693 Sniffer](../../doc/standalone/hf_15sniff.md) | ISO15693 sniffer to flash | RDV4 (flash) |
| HF_15SIM | [15693 Simulator](../../doc/standalone/hf_15sim.md) | ISO15693 dump and simulate | RDV4 (flash) |
| HF_AVEFUL | [Aveful UL Reader](../../doc/standalone/hf_aveful.md) | MIFARE Ultralight read and emulate | Generic |
| HF_BOG | [BogitoRun Auth Sniffer](../../doc/standalone/hf_bog.md) | 14A sniff with ULC/ULEV1/NTAG auth capture | RDV4 (flash) |
| HF_CARDHOPPER | [CardHopper Relay](../../doc/standalone/hf_cardhopper.md) | Long-range 14A relay over serial/IP | RDV4 (BT) |
| HF_COLIN | [VIGIKPWN](../../doc/standalone/hf_colin.md) | MIFARE Classic ultra-fast sniff/sim/clone | RDV4 (flash) |
| HF_CRAFTBYTE | [CraftByte UID Stealer](../../doc/standalone/hf_craftbyte.md) | Scan and emulate ISO14443A UIDs | Generic |
| HF_DOEGOX_AUTH0 | [UL-C/UL-AES Unlocker](../../doc/standalone/hf_doegox_auth0.md) | Unlock password-protected Ultralight tags | Generic |
| HF_EMVPNG | [EMV Visa Reader/Emulator](../../doc/standalone/hf_emvpng.md) | Read Visa EMV cards and emulate transactions | RDV4 (flash) |
| HF_ICECLASS | [IceClass iCLASS](../../doc/standalone/hf_iceclass.md) | iCLASS multi-mode: sim/dump/attack/config | RDV4 (flash) |
| HF_LEGIC | [Legic Prime Reader](../../doc/standalone/hf_legic.md) | Read and simulate Legic Prime tags | Generic |
| HF_LEGICSIM | [Legic Prime Simulator](../../doc/standalone/hf_legicsim.md) | Simulate Legic Prime dumps from flash (15 slots) | RDV4 (flash) |
| HF_MATTYRUN | [MattyRun MFC Clone](../../doc/standalone/hf_mattyrun.md) | MIFARE Classic key check, dump, and emulate | Generic |
| HF_MFCSIM | [MFC Simulator](../../doc/standalone/hf_mfcsim.md) | Simulate MIFARE Classic 1K from flash (15 slots) | RDV4 (flash) |
| HF_MSDSAL | [MSD Visa Reader](../../doc/standalone/hf_msdsal.md) | Read and emulate Visa MSD cards | Generic |
| HF_REBLAY | [Reblay BT Relay](../../doc/standalone/hf_reblay.md) | ISO14443A relay over Bluetooth | RDV4 (BT) |
| HF_ST25_TEAROFF | [ST25TB Tear-off](../../doc/standalone/hf_st25_tearoff.md) | ST25TB store/restore with counter tear-off | RDV4 (flash) |
| HF_TCPRST | [IKEA Rothult](../../doc/standalone/hf_tcprst.md) | IKEA Rothult ST25TA master key dump/emulation | Generic |
| HF_TMUDFORD | [ISO15693 UID Emulator](../../doc/standalone/hf_tmudford.md) | Read and emulate ISO15693 UIDs | Generic |
| HF_UNISNIFF | [Universal Sniffer](../../doc/standalone/hf_unisniff.md) | Multi-protocol sniffer (14A/14B/15/iCLASS) | RDV4 (flash) |
| HF_YOUNG | [Young MFC Sniff/Sim](../../doc/standalone/hf_young.md) | MIFARE sniff/simulation with 2-bank storage | Generic |
### Multi-Mode Loader
| Mode ID | Document | Description |
|---------|----------|-------------|
| DANKARMULTI | [Dankarmulti Loader](../../doc/standalone/dankarmulti.md) | Combine multiple standalone modes into one firmware image |
# Developing Standalone Modes
This contains functionality for different StandAlone modes. The fullimage will be built given the correct compiler flags used. Build targets for these files are contained in `Makefile.inc` and `Makefile.hal`
If you want to implement a new standalone mode, you need to implement the methods provided in `standalone.h`.
+1
View File
@@ -359,6 +359,7 @@ set (TARGET_SOURCES
${PM3_ROOT}/client/src/mifare/desfirecore.c
${PM3_ROOT}/client/src/mifare/desfiretest.c
${PM3_ROOT}/client/src/mifare/gallaghercore.c
${PM3_ROOT}/client/src/mifare/gallaghertest.c
${PM3_ROOT}/client/src/uart/ringbuffer.c
${PM3_ROOT}/client/src/uart/uart_common.c
${PM3_ROOT}/client/src/uart/uart_posix.c
+1
View File
@@ -795,6 +795,7 @@ SRCS = mifare/aiddesfire.c \
mifare/desfiresecurechan.c \
mifare/desfiretest.c \
mifare/gallaghercore.c \
mifare/gallaghertest.c \
mifare/mad.c \
mifare/mfkey.c \
mifare/mifare4.c \
+7
View File
@@ -3252,3 +3252,10 @@ E058DC0E2C34
# Yerevan Transport
26ABFC3C24FB
#
# Vanderbilt Industries, ACT Line
# "ACTact" in ASCII -- appears to hold creds in Sector 1, Block 4
# Note: "Acces"+N pattern keys (416363657300-4163636573FF) can be generated
# dynamically using: hf mf keygen -k 6
414354616374
4D61071B7254
34016FAC127D
+1
View File
@@ -279,6 +279,7 @@ set (TARGET_SOURCES
${PM3_ROOT}/client/src/mifare/desfirecore.c
${PM3_ROOT}/client/src/mifare/desfiretest.c
${PM3_ROOT}/client/src/mifare/gallaghercore.c
${PM3_ROOT}/client/src/mifare/gallaghertest.c
${PM3_ROOT}/client/src/uart/ringbuffer.c
${PM3_ROOT}/client/src/uart/uart_common.c
${PM3_ROOT}/client/src/uart/uart_posix.c
+16 -8
View File
@@ -1535,6 +1535,14 @@
"Description": "",
"Type": "EMV"
},
{
"AID": "A000000476416E64726F6964484345",
"Vendor": "Google",
"Country": "N/A",
"Name": "Android HCE",
"Description": "Available on AOSP-based android devices with active HCE",
"Type": ""
},
{
"AID": "A000000476A010",
"Vendor": "Google",
@@ -2275,8 +2283,8 @@
"AID": "A0000008580102",
"Vendor": "Apple",
"Country": "",
"Name": "Apple Home Key Framework",
"Description": "Home Key configuration applet. Used for attestation exchange",
"Name": "Apple Home Key Step Up",
"Description": "Used for reading the attestation certificate",
"Type": ""
},
{
@@ -2291,8 +2299,8 @@
"AID": "A0000008580202",
"Vendor": "Apple",
"Country": "",
"Name": "Apple Access Key Framework",
"Description": "Access Key configuration applet. Used for attestation exchange",
"Name": "Apple Access Key Step Up",
"Description": "Used for reading the attestation certificate",
"Type": ""
},
{
@@ -2307,16 +2315,16 @@
"AID": "A000000909ACCE5502",
"Vendor": "Connectivity Standards Alliance (CSA)",
"Country": "",
"Name": "Aliro Framework",
"Description": "Used during key provisioning, configuration, attestation exchange",
"Name": "Aliro Step Up",
"Description": "Used to retrieve 'access documents' in case a reader needs to verify the validity of a credential",
"Type": ""
},
{
"AID": "A000000909ACCE5501",
"Vendor": "Connectivity Standards Alliance (CSA)",
"Country": "",
"Name": "Aliro",
"Description": "",
"Name": "Aliro Expedited",
"Description": "Acts as the primary credential holder applet",
"Type": "access"
},
{
+1 -1
View File
@@ -242,7 +242,7 @@ static const hintAIDList_t hintAIDList[] = {
{ "\x41\x44\x20\x46\x31", 5, "CIPURSE", "hf cipurse" },
{ "\xA0\x00\x00\x09\x09\xAC\xCE\x55\x01", 9, "Aliro", "hf aliro" },
{ "\xd2\x76\x00\x00\x85\x01\x00", 7, "desfire", "hf mfdes" },
{ "\x4F\x53\x45\x2E\x56\x41\x53\x2E\x30\x31", 10, "Apple VAS", "hf vas"},
{ "\x4F\x53\x45\x2E\x56\x41\x53\x2E\x30\x31", 10, "OSE.VAS", "hf vas"},
};
// iso14a apdu input frame length
+26 -10
View File
@@ -881,7 +881,7 @@ static int NxpSysInfo(uint8_t *uid) {
static int StCheckSig(uint8_t *uid) {
// request to be sent to device/card
uint8_t approxlen = 2 + 8 + 1 + 2;
uint16_t approxlen = 2 + ISO15693_UID_LENGTH + 1 + 2;
iso15_raw_cmd_t *packet = (iso15_raw_cmd_t *)calloc(1, sizeof(iso15_raw_cmd_t) + approxlen);
if (packet == NULL) {
PrintAndLogEx(WARNING, "Failed to allocate memory");
@@ -891,19 +891,27 @@ static int StCheckSig(uint8_t *uid) {
// ISO15693 Protocol params
packet->raw[packet->rawlen++] = arg_get_raw_flag(ISO15693_UID_LENGTH, false, false, false);
packet->raw[packet->rawlen++] = ISO15693_READBLOCK;
// add UID (scan, uid)
memcpy(packet->raw + packet->rawlen, uid, ISO15693_UID_LENGTH);
packet->rawlen += ISO15693_UID_LENGTH;
packet->flags = (ISO15_CONNECT | ISO15_READ_RESPONSE | ISO15_NO_DISCONNECT);
uint16_t blkoff = packet->rawlen;
char signature_hex[65] = {0};
for (int j = 0; j < 17; j++) {
for (int i = 0; i < 17; i++) {
// reset rawlength counter
packet->rawlen = blkoff;
// block no
packet->raw[packet->rawlen++] = 0x3F + j;
// crc
packet->raw[packet->rawlen++] = 0x3F + i;
// add crc
AddCrc15(packet->raw, packet->rawlen);
packet->rawlen += 2;
clearCommandBuffer();
SendCommandNG(CMD_HF_ISO15693_COMMAND, (uint8_t *)packet, ISO15_RAW_LEN(packet->rawlen));
PacketResponseNG resp;
@@ -913,27 +921,35 @@ static int StCheckSig(uint8_t *uid) {
DropField();
return PM3_ETIMEOUT;
}
ISO15_ERROR_HANDLING_RESPONSE
uint8_t *d = resp.data.asBytes;
ISO15_ERROR_HANDLING_CARD_RESPONSE(d, resp.length)
if (j == 0) {
if (i == 0) {
if (memcmp(d + 1, "K04S", 4) != 0) {
// No signature
free(packet);
return PM3_ESOFT;
}
} else {
memcpy(signature_hex + ((j - 1) * 4), d + 1, 4);
memcpy(signature_hex + ((i - 1) * 4), d + 1, 4);
}
packet->flags = (ISO15_READ_RESPONSE | ISO15_NO_DISCONNECT);
}
free(packet);
DropField();
uint8_t signature[16];
size_t signature_len;
size_t signature_len = 0;
hexstr_to_byte_array(signature_hex, signature, &signature_len);
uint8_t uid_swap[ISO15693_UID_LENGTH];
reverse_array_copy(uid, ISO15693_UID_LENGTH, uid_swap);
int index = originality_check_verify_ex(uid_swap, ISO15693_UID_LENGTH, signature, signature_len, PK_ST25TV, false, true);
PrintAndLogEx(NORMAL, "");
return originality_check_print(signature, signature_len, index);
@@ -1715,7 +1731,7 @@ static int CmdHF15WriteDsfid(const char *Cmd) {
}
// request to be sent to device/card
uint8_t approxlen = 2 + 8 + 1 + 2;
uint16_t approxlen = 2 + ISO15693_UID_LENGTH + 1 + 2;
iso15_raw_cmd_t *packet = (iso15_raw_cmd_t *)calloc(1, sizeof(iso15_raw_cmd_t) + approxlen);
if (packet == NULL) {
PrintAndLogEx(WARNING, "Failed to allocate memory");
@@ -1835,7 +1851,7 @@ static int CmdHF15Dump(const char *Cmd) {
}
// request to be sent to device/card
uint8_t approxlen = 2 + 8 + 1 + 2;
uint16_t approxlen = 2 + ISO15693_UID_LENGTH + 1 + 2;
iso15_raw_cmd_t *packet = (iso15_raw_cmd_t *)calloc(1, sizeof(iso15_raw_cmd_t) + approxlen);
if (packet == NULL) {
PrintAndLogEx(WARNING, "Failed to allocate memory");
@@ -2380,7 +2396,7 @@ static int CmdHF15Readblock(const char *Cmd) {
}
// request to be sent to device/card
uint8_t approxlen = 2 + 8 + 1 + 2;
uint16_t approxlen = 2 + ISO15693_UID_LENGTH + 1 + 2;
iso15_raw_cmd_t *packet = (iso15_raw_cmd_t *)calloc(1, sizeof(iso15_raw_cmd_t) + approxlen);
if (packet == NULL) {
PrintAndLogEx(WARNING, "Failed to allocate memory");
+1455 -416
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -37,8 +37,8 @@ int CmdHFGallagher(const char *cmd);
* @param keyOut Buffer to copy the diversified key into (must be 16 bytes).
* @return PM3_SUCCESS if successful, PM3_EINVARG if an argument is invalid.
*/
int hfgal_diversify_key(uint8_t *site_key, uint8_t *uid, uint8_t uid_len,
uint8_t key_num, uint32_t aid, uint8_t *key_output);
int hfgal_diversify_desfire_key(uint8_t *site_key, uint8_t *uid, uint8_t uid_len,
uint8_t key_num, uint32_t aid, uint8_t *key_output);
// The response code when an invalid key is used for authentication
// Returned in /client/src/mifare/desfirecore.c, line 1185 (if DesfireExchangeEx fails)
-2
View File
@@ -4621,8 +4621,6 @@ static int iclass_recover(uint8_t key[8], uint32_t index_start, uint32_t loop, u
while (repeat == true) {
uint32_t payload_size = sizeof(iclass_recover_req_t);
uint8_t aa2_standard_key[PICOPASS_BLOCK_SIZE] = {0};
memcpy(aa2_standard_key, iClass_Key_Table[1], PICOPASS_BLOCK_SIZE);
iclass_recover_req_t *payload = calloc(1, payload_size);
payload->req.use_raw = true;
payload->req.use_elite = false;
+6
View File
@@ -2037,6 +2037,12 @@ void annotateFelica(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize) {
case FELICA_WRTSEC_ACK:
snprintf(exp, size, "WRITE ACK");
break;
case FELICA_GET_NODE_LIST_REQ:
snprintf(exp, size, "REQUEST CODE LIST");
break;
case FELICA_GET_NODE_LIST_ACK:
snprintf(exp, size, "REQUEST CODE LIST ACK");
break;
case FELICA_REQSRV2_REQ:
snprintf(exp, size, "REQUEST SERVICE v2");
break;
+154 -41
View File
@@ -27,6 +27,7 @@
#include "protocols.h"
#include "parity.h" // oddparity
#include "cmdhflist.h" // annotations
#include "commonutil.h" // ARRAYLEN
#include "wiegand_formats.h"
#include "wiegand_formatutils.h"
#include "util.h"
@@ -34,45 +35,104 @@
static int CmdHelp(const char *Cmd);
#define PACS_EXTRA_LONG_FORMAT 18 // 144 bits
#define PACS_LONG_FORMAT 12 // 96 bits
#define PACS_LONG_FORMAT 13 // 96 bits + 1 byte pad
#define PACS_FORMAT 6 // 44 bits
static int wiegand_new_pacs(uint8_t *padded_pacs, uint8_t plen) {
#define PACS_MAX_WIEGAND_BITS 96
#define WIEGAND_MAX_ENCODED_BITS (PACS_MAX_WIEGAND_BITS + 8)
uint8_t d[PACS_EXTRA_LONG_FORMAT] = {0};
memcpy(d, padded_pacs, plen);
uint8_t pad = d[0];
char *binstr = (char *)calloc((PACS_EXTRA_LONG_FORMAT * 8) + 1, sizeof(uint8_t));
if (binstr == NULL) {
PrintAndLogEx(WARNING, "Failed to allocate memory");
return PM3_EMALLOC;
static void wiegand_packed_to_binstr(const wiegand_message_t *packed, char *binstr) {
for (uint8_t i = 0; i < packed->Length; i++) {
binstr[i] = get_bit_by_position((wiegand_message_t *)packed, i) ? '1' : '0';
}
binstr[packed->Length] = '\0';
}
uint8_t n = plen - 1;
static int wiegand_print_new_pacs_verbose(const wiegand_message_t *packed, const uint8_t *pacs, size_t pacs_len) {
char binstr[PACS_MAX_WIEGAND_BITS + 1] = {0};
char rawbin[WIEGAND_MAX_ENCODED_BITS + 1] = {0};
uint8_t raw[(WIEGAND_MAX_ENCODED_BITS + 7) / 8] = {0};
size_t raw_len = 0;
bytes_2_binstr(binstr, d + 1, n);
wiegand_packed_to_binstr(packed, binstr);
rawbin[0] = '1';
memcpy(rawbin + 1, binstr, packed->Length);
binstr_2_bytes(raw, &raw_len, rawbin);
bytes_2_binstr(rawbin, raw, raw_len);
binstr[strlen(binstr) - pad] = '\0';
PrintAndLogEx(INFO, "----------------------- " _CYAN_("PACS Encoding") " ------------------------");
PrintAndLogEx(SUCCESS, "New PACS......... " _GREEN_("0x %s"), sprint_hex_inrow(pacs, pacs_len));
PrintAndLogEx(INFO, "With Sentinel.... " _GREEN_("0b %s") " (%zu-bit)", rawbin, strlen(rawbin));
PrintAndLogEx(SUCCESS, "Wiegand --raw.... " _YELLOW_("0x %s"), sprint_hex_inrow(raw, raw_len));
PrintAndLogEx(INFO, "Without Sentinel. " _GREEN_("0b %s") " (%zu-bit)", binstr, strlen(binstr));
return PM3_SUCCESS;
}
size_t tlen = 0;
uint8_t tmp[16] = {0};
binstr_2_bytes(tmp, &tlen, binstr);
PrintAndLogEx(SUCCESS, "Wiegand raw.... " _YELLOW_("%s"), sprint_hex_inrow(tmp, tlen));
static int wiegand_encode_new_pacs(const wiegand_message_t *packed, bool verbose) {
uint32_t top = 0, mid = 0, bot = 0;
if (binstring_to_u96(&top, &mid, &bot, binstr) != strlen(binstr)) {
PrintAndLogEx(ERR, "Binary string contains none <0|1> chars");
free(binstr);
if (packed->Length == 0) {
PrintAndLogEx(ERR, "Empty Wiegand input");
return PM3_EINVARG;
}
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(INFO, "------------------------- " _CYAN_("SIO - Wiegand") " ---------------------------");
decode_wiegand(top, mid, bot, strlen(binstr));
free(binstr);
if (packed->Length > PACS_MAX_WIEGAND_BITS) {
PrintAndLogEx(ERR, "New PACS encoding supports up to %u Wiegand bits", PACS_MAX_WIEGAND_BITS);
return PM3_EINVARG;
}
uint8_t padded_bits = (uint8_t)(((packed->Length + 7) / 8) * 8);
uint8_t pad = padded_bits - packed->Length;
char binstr[PACS_MAX_WIEGAND_BITS + 1] = {0};
wiegand_packed_to_binstr(packed, binstr);
memset(binstr + packed->Length, '0', pad);
binstr[padded_bits] = '\0';
size_t pacs_len = 0;
uint8_t pacs[PACS_LONG_FORMAT] = {0};
binstr_2_bytes(pacs + 1, &pacs_len, binstr);
pacs[0] = pad;
PrintAndLogEx(SUCCESS, "New PACS......... " _GREEN_("0x %s"), sprint_hex_inrow(pacs, pacs_len + 1));
if (verbose) {
PrintAndLogEx(NORMAL, "");
return wiegand_print_new_pacs_verbose(packed, pacs, pacs_len + 1);
}
return PM3_SUCCESS;
}
static int wiegand_new_pacs(const uint8_t *padded_pacs, uint8_t plen) {
return HIDDumpPACSBits(padded_pacs, plen, false);
}
static int wiegand_print_raw_from_bin(const uint8_t *binarr, int blen) {
uint8_t out[(WIEGAND_MAX_ENCODED_BITS + 7) / 8] = {0};
char binstr[WIEGAND_MAX_ENCODED_BITS + 1] = {0};
binstr[0] = '1';
for (int i = 0; i < blen; i++) {
binstr[i + 1] = binarr[i] ? '1' : '0';
}
size_t out_len = 0;
binstr_2_bytes(out, &out_len, binstr);
PrintAndLogEx(SUCCESS, "Wiegand raw.... " _YELLOW_("%s"), sprint_hex_inrow(out, out_len));
return PM3_SUCCESS;
}
static int wiegand_encode_new_pacs_from_bin(const uint8_t *binarr, int blen, bool verbose) {
wiegand_message_t packed;
memset(&packed, 0, sizeof(packed));
packed.Length = blen;
for (int i = 0; i < blen; i++) {
if (set_bit_by_position(&packed, binarr[i], i) == false) {
PrintAndLogEx(ERR, "Binary string must be less than or equal to %u bits", PACS_MAX_WIEGAND_BITS);
return PM3_EINVARG;
}
}
return wiegand_encode_new_pacs(&packed, verbose);
}
int CmdWiegandList(const char *Cmd) {
CLIParserContext *ctx;
@@ -98,17 +158,22 @@ int CmdWiegandEncode(const char *Cmd) {
CLIParserInit(&ctx, "wiegand encode",
"Encode wiegand formatted number to raw hex",
"wiegand encode --fc 101 --cn 1337 -> show all formats\n"
"wiegand encode -w H10301 --fc 101 --cn 1337 -> H10301 format "
"wiegand encode -w H10301 --fc 101 --cn 1337 -> H10301 format\n"
"wiegand encode --bin 1 -> raw wiegand hex with sentinel\n"
"wiegand encode -w H10301 --fc 123 --cn 4567 --new -> new ASN.1 encoded format"
);
void *argtable[] = {
arg_param_begin,
arg_str0("b", "bin", "<bin>", "binary string to be encoded"),
arg_u64_0(NULL, "fc", "<dec>", "facility number"),
arg_u64_1(NULL, "cn", "<dec>", "card number"),
arg_u64_0(NULL, "cn", "<dec>", "card number"),
arg_u64_0(NULL, "issue", "<dec>", "issue level"),
arg_u64_0(NULL, "oem", "<dec>", "OEM code"),
arg_str0("w", "wiegand", "<format>", "see `wiegand list` for available formats"),
arg_lit0("n", "new", "encode to new ASN.1 encoded format"),
arg_lit0(NULL, "pre", "add HID ProxII preamble to wiegand output"),
arg_lit0("v", "verbose", "verbose output"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
@@ -116,17 +181,44 @@ int CmdWiegandEncode(const char *Cmd) {
wiegand_card_t data;
memset(&data, 0, sizeof(wiegand_card_t));
data.FacilityCode = arg_get_u32_def(ctx, 1, 0);
data.CardNumber = arg_get_u64_def(ctx, 2, 0);
data.IssueLevel = arg_get_u32_def(ctx, 3, 0);
data.OEM = arg_get_u32_def(ctx, 4, 0);
uint8_t binarr[PACS_MAX_WIEGAND_BITS] = {0};
int blen = 0;
int res = CLIParamBinToBuf(arg_get_str(ctx, 1), binarr, ARRAYLEN(binarr), &blen);
data.FacilityCode = arg_get_u32_def(ctx, 2, 0);
data.CardNumber = arg_get_u64_def(ctx, 3, 0);
data.IssueLevel = arg_get_u32_def(ctx, 4, 0);
data.OEM = arg_get_u32_def(ctx, 5, 0);
int len = 0;
char format[16] = {0};
CLIParamStrToBuf(arg_get_str(ctx, 5), (uint8_t *)format, sizeof(format), &len);
bool preamble = arg_get_lit(ctx, 6);
CLIParamStrToBuf(arg_get_str(ctx, 6), (uint8_t *)format, sizeof(format), &len);
bool new_pacs = arg_get_lit(ctx, 7);
bool preamble = arg_get_lit(ctx, 8);
bool verbose = arg_get_lit(ctx, 9);
CLIParserFree(ctx);
if (res) {
PrintAndLogEx(FAILED, "Error parsing binary string");
return PM3_EINVARG;
}
if (new_pacs && preamble) {
PrintAndLogEx(ERR, "`--new` and `--pre` can't be combined");
return PM3_EINVARG;
}
if (blen && (len || data.FacilityCode || data.CardNumber || data.IssueLevel || data.OEM || preamble)) {
PrintAndLogEx(ERR, "`--bin` can't be combined with format, field, or preamble options");
return PM3_EINVARG;
}
if (blen == 0 && len == 0 && data.CardNumber == 0 && data.FacilityCode == 0 && data.IssueLevel == 0 && data.OEM == 0) {
PrintAndLogEx(ERR, "Must provide either card data, a specific format, or `--bin`");
return PM3_EINVARG;
}
int idx = -1;
if (len) {
idx = HIDFindCardFormat(format);
@@ -136,13 +228,26 @@ int CmdWiegandEncode(const char *Cmd) {
}
}
if (idx != -1) {
if (new_pacs && idx == -1 && blen == 0) {
PrintAndLogEx(ERR, "`--new` requires either `--bin` or a specific wiegand format");
return PM3_EINVARG;
}
if (blen) {
if (new_pacs) {
return wiegand_encode_new_pacs_from_bin(binarr, blen, verbose);
}
return wiegand_print_raw_from_bin(binarr, blen);
} else if (idx != -1) {
wiegand_message_t packed;
memset(&packed, 0, sizeof(wiegand_message_t));
if (HIDPack(idx, &data, &packed, preamble) == false) {
PrintAndLogEx(WARNING, "The card data could not be encoded in the selected format.");
return PM3_ESOFT;
}
if (new_pacs) {
return wiegand_encode_new_pacs(&packed, verbose);
}
print_wiegand_code(&packed);
} else {
// try all formats and print only the ones that work.
@@ -157,14 +262,14 @@ int CmdWiegandDecode(const char *Cmd) {
CLIParserInit(&ctx, "wiegand decode",
"Decode raw hex or binary to wiegand format",
"wiegand decode --raw 2006F623AE\n"
"wiegand decode --new 06BD88EB80 -> 4..8 bytes, new padded format "
"wiegand decode --new 06BD88EB80 -> 4..13 bytes, new ASN.1 encoded format "
);
void *argtable[] = {
arg_param_begin,
arg_str0("r", "raw", "<hex>", "raw hex to be decoded"),
arg_str0("b", "bin", "<bin>", "binary string to be decoded"),
arg_str0("n", "new", "<hex>", "new padded pacs as raw hex to be decoded"),
arg_str0("n", "new", "<hex>", "new ASN.1 encoded data as raw hex to be decoded"),
arg_lit0("f", "force", "skip preabmle checking, brute force all possible lengths for raw hex input"),
arg_param_end
};
@@ -174,11 +279,11 @@ int CmdWiegandDecode(const char *Cmd) {
CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)hex, sizeof(hex), &hlen);
int blen = 0;
uint8_t binarr[100] = {0x00};
uint8_t binarr[WIEGAND_MAX_ENCODED_BITS] = {0x00};
int res = CLIParamBinToBuf(arg_get_str(ctx, 2), binarr, sizeof(binarr), &blen);
int plen = 0;
uint8_t phex[8] = {0};
uint8_t phex[PACS_LONG_FORMAT] = {0};
res = CLIParamHexToBuf(arg_get_str(ctx, 3), phex, sizeof(phex), &plen);
bool no_preamble = arg_get_lit(ctx, 4);
@@ -193,6 +298,10 @@ int CmdWiegandDecode(const char *Cmd) {
uint32_t top = 0, mid = 0, bot = 0;
if (hlen) {
if ((hlen * 4) > PACS_MAX_WIEGAND_BITS) {
PrintAndLogEx(ERR, "Raw hex decode supports up to %u Wiegand bits", PACS_MAX_WIEGAND_BITS);
return PM3_EINVARG;
}
res = hexstring_to_u96(&top, &mid, &bot, hex);
if (res != hlen) {
PrintAndLogEx(ERR, "Hex string contains none hex chars");
@@ -200,11 +309,15 @@ int CmdWiegandDecode(const char *Cmd) {
}
if (no_preamble) {
// pass hex input length as is and brute force all possible lengths
// Pass the input hex length through so decode_wiegand() brute-forces
// the possible bit lengths instead of assuming a preamble-encoded value.
blen = -hlen;
}
} else if (blen) {
if (blen > PACS_MAX_WIEGAND_BITS) {
PrintAndLogEx(ERR, "Binary decode supports up to %u Wiegand bits", PACS_MAX_WIEGAND_BITS);
return PM3_EINVARG;
}
int n = binarray_to_u96(&top, &mid, &bot, binarr, blen);
if (n != blen) {
PrintAndLogEx(ERR, "Binary string contains none <0|1> chars");
+181
View File
@@ -16,8 +16,10 @@
// Common functionality for low/high-frequency GALLAGHER tag encoding & decoding.
//-----------------------------------------------------------------------------
#include "gallaghercore.h"
#include "aes.h"
#include "common.h"
#include "ui.h"
#include <string.h>
static void scramble(uint8_t *arr, uint8_t len) {
const uint8_t lut[] = {
@@ -69,6 +71,51 @@ static void descramble(uint8_t *arr, uint8_t len) {
}
}
int gallagher_diversify_classic_key(uint8_t *site_key, uint8_t *csn, size_t csn_len, uint8_t *key_output) {
memcpy(key_output, site_key, 16);
for (int i = 0; i < csn_len; i++) {
key_output[i] ^= csn[i];
}
return PM3_SUCCESS;
}
int gallagher_construct_credential(GallagherCredentials_t *creds, uint8_t region, uint16_t facility, uint32_t card, uint8_t issue, bool mes, uint8_t *csn, size_t csn_len, uint8_t *site_key) {
creds->region_code = region;
creds->facility_code = facility;
creds->card_number = card;
creds->issue_level = issue;
creds->mes = mes;
memcpy(creds->csn, csn, csn_len);
memcpy(creds->site_key, site_key, 16);
return PM3_SUCCESS;
}
int gallagher_parse_cad(uint8_t *cad, uint8_t region, uint16_t facility) {
// CAD entries are 3.5 bytes each (28 bits: 4-bit RC, 16-bit FC, 8-bit sector).
// Packed in pairs of 7 bytes starting at byte 4, up to 6 pairs (12 entries).
for (int pair = 0; pair < 6; pair++) {
int base = 4 + pair * 7;
// Even entry (first 3.5 bytes of pair)
uint8_t rc = (cad[base] >> 4) & 0x0F;
uint16_t fc = ((cad[base] & 0x0F) << 12) | (cad[base + 1] << 4) | ((cad[base + 2] >> 4) & 0x0F);
uint8_t sector = ((cad[base + 2] & 0x0F) << 4) | ((cad[base + 3] >> 4) & 0x0F);
if (rc == region && fc == facility) {
return sector;
}
// Odd entry (last 3.5 bytes of pair)
rc = cad[base + 3] & 0x0F;
fc = (cad[base + 4] << 8) | cad[base + 5];
sector = cad[base + 6];
if (rc == region && fc == facility) {
return sector;
}
}
return -1;
}
void gallagher_decode_creds(uint8_t *eight_bytes, GallagherCredentials_t *creds) {
uint8_t *arr = eight_bytes;
@@ -107,6 +154,122 @@ void gallagher_encode_creds(uint8_t *eight_bytes, GallagherCredentials_t *creds)
scramble(eight_bytes, 8);
}
int gallagher_encode_mes(uint8_t *sixteen_bytes, GallagherCredentials_t *creds) {
if (creds->csn_len > 4) {
PrintAndLogEx(ERR, "Credential could not be encoded into a Mifare Enhanced Encryption block. only 4 byte UUID's are supported");
return PM3_ENOTIMPL;
}
// unknown parameters from the research these might be for UUID's longer than 4 bytes?
uint8_t UB = 0x00;
uint8_t UC = 0x00;
uint8_t UD = 0x00;
uint8_t UE = 0x00;
uint8_t PO = 0x00; // Pin offset
uint8_t UX = 0x00;
uint16_t R = 0x0748;
uint8_t mes[16];
uint8_t diversified_site_key[16];
mes[0] = 0x01;
mes[1] = (creds->card_number & 0xFF0000) >> 16;
mes[2] = (creds->card_number & 0x00FF00) >> 8;
mes[3] = creds->card_number & 0x0000FF;
mes[4] = (creds->facility_code & 0xFF00) >> 8;
mes[5] = creds->facility_code & 0x00FF;
mes[6] = ((creds->region_code & 0x0F) << 4) | (creds->issue_level & 0x0F);
mes[7] = (PO & 0x0F) | ((UX & 0x0F) << 4);
mes[8] = (UB & 0x0F) | ((UC & 0x0F) << 4);
mes[9] = (UD & 0x0F) | ((UE & 0x0F) << 4);
mes[10] = creds->csn[0];
mes[11] = creds->csn[1];
mes[12] = creds->csn[2];
mes[13] = creds->csn[3];
mes[14] = (R & 0xFF00) >> 8;
mes[15] = R & 0x00FF;
PrintAndLogEx(DEBUG, "MES before encryption %s", sprint_hex_ascii(mes, 16));
gallagher_diversify_classic_key(creds->site_key, creds->csn, creds->csn_len, diversified_site_key);
mbedtls_aes_context actx;
mbedtls_aes_init(&actx);
if (mbedtls_aes_setkey_enc(&actx, diversified_site_key, 128) != 0) {
return PM3_ENOKEY;
}
if (mbedtls_aes_crypt_ecb(&actx, MBEDTLS_AES_ENCRYPT, mes, sixteen_bytes) != 0) {
return PM3_ENOKEY;
}
mbedtls_aes_free(&actx);
PrintAndLogEx(DEBUG, "MES after encryption %s", sprint_hex_ascii(sixteen_bytes, 16));
return PM3_SUCCESS;
}
int gallagher_decode_mes(uint8_t *block, GallagherCredentials_t *creds) {
// unknown parameters from the research these might be for UUID's longer than 4 bytes?
// uint8_t UB = 0x00;
// uint8_t UC = 0x00;
// uint8_t UD = 0x00;
// uint8_t UE = 0x00;
// uint8_t PO = 0x00;
// uint8_t UX = 0x00;
uint16_t R = 0x0748;
uint8_t mes[16];
uint8_t diversified_site_key[16];
gallagher_diversify_classic_key(creds->site_key, creds->csn, creds->csn_len, diversified_site_key);
if (creds->csn_len > 4) {
PrintAndLogEx(WARNING, "UUID length is > 4, this may not be a valid gallagher credential?");
}
// AES decrypt 16 bytes
mbedtls_aes_context actx;
mbedtls_aes_init(&actx);
if (mbedtls_aes_setkey_dec(&actx, diversified_site_key, 128) != 0) {
return PM3_ENOKEY;
}
if (mbedtls_aes_crypt_ecb(&actx, MBEDTLS_AES_DECRYPT, block, mes) != 0) {
return PM3_ENOKEY;
}
mbedtls_aes_free(&actx);
PrintAndLogEx(DEBUG, "MES after decryption %s", sprint_hex_ascii(mes, 16));
if (mes[0] != 0x01) {
PrintAndLogEx(ERR, "MES block is not valid");
return PM3_EWRONGANSWER;
}
creds->card_number = mes[1] << 16 | mes[2] << 8 | mes[3];
creds->facility_code = mes[4] << 8 | mes[5];
creds->region_code = (mes[6] & 0xF0) >> 4;
creds->issue_level = mes[6] & 0x0F;
// PO = mes[7] & 0x0F;
// UX = (mes[7] & 0xF0) >> 4;
// UB = mes[8] & 0x0F;
// UC = (mes[8] & 0xF0) >> 4;
// UD = mes[9] & 0x0F;
// UE = (mes[9] & 0xF0) >> 4;
// csn is already verified by key diversification
// csn[0] = mes[10];
// csn[1] = mes[11];
// csn[2] = mes[12];
// csn[3] = mes[13];
R = mes[14] << 8 | mes[15];
if (R != 0x0748) {
PrintAndLogEx(WARNING, "R value is different from 0x0748, this hasn't been seen in the wild \n https://github.com/megabug/gallagher-research/blob/master/formats/mes.md");
}
return PM3_SUCCESS;
}
bool gallagher_is_valid_creds_struct(GallagherCredentials_t *creds) {
return gallagher_is_valid_creds(creds->region_code, creds->facility_code, creds->card_number, creds->issue_level);
}
bool gallagher_is_valid_creds(uint64_t region_code, uint64_t facility_code, uint64_t card_number, uint64_t issue_level) {
bool is_valid = true;
@@ -129,3 +292,21 @@ bool gallagher_is_valid_creds(uint64_t region_code, uint64_t facility_code, uint
}
return is_valid;
}
void print_gallagher_creds(GallagherCredentials_t *creds) {
if (!gallagher_is_valid_creds_struct(creds)) {
PrintAndLogEx(ERR, "Invalid Gallagher credential");
return;
}
PrintAndLogEx(SUCCESS, "Gallagher - region: " _GREEN_("%c") " ( " _GREEN_("%u") " )"
", facility: " _GREEN_("%u")
", card number: " _GREEN_("%u")
", issue level: " _GREEN_("%u"),
'A' + creds->region_code,
creds->region_code,
creds->facility_code,
creds->card_number,
creds->issue_level
);
}
+19
View File
@@ -17,6 +17,7 @@
#define MIFARE_GALLAGHERCORE_H__
#include "common.h"
#include "crypto/libpcrypto.h"
#include <stdint.h>
typedef struct {
@@ -24,12 +25,30 @@ typedef struct {
uint16_t facility_code;
uint32_t card_number;
uint8_t issue_level;
bool mes;
uint8_t csn[10];
size_t csn_len;
uint8_t site_key[16];
} GallagherCredentials_t;
int gallagher_diversify_classic_key(uint8_t *site_key, uint8_t *csn, size_t csn_len, uint8_t *key_output);
int gallagher_parse_cad(uint8_t *cad, uint8_t region, uint16_t facility);
void gallagher_encode_creds(uint8_t *eight_bytes, GallagherCredentials_t *creds);
void gallagher_decode_creds(uint8_t *eight_bytes, GallagherCredentials_t *creds);
int gallagher_construct_credential(GallagherCredentials_t *creds, uint8_t region, uint16_t facility, uint32_t card, uint8_t issue, bool mes, uint8_t *csn, size_t csn_len, uint8_t *site_key);
int gallagher_encode_mes(uint8_t *sector, GallagherCredentials_t *creds);
int gallagher_decode_mes(uint8_t *sector, GallagherCredentials_t *creds);
bool gallagher_is_valid_creds(uint64_t region_code, uint64_t facility_code, uint64_t card_number, uint64_t issue_level);
bool gallagher_is_valid_creds_struct(GallagherCredentials_t *creds);
void print_gallagher_creds(GallagherCredentials_t *creds);
#endif
+196
View File
@@ -0,0 +1,196 @@
#include "gallaghertest.h"
#include <unistd.h>
#include <string.h> // memcpy memset
#include "ui.h"
#include "crc.h"
#include "mifare/gallaghercore.h"
static bool creds_match(GallagherCredentials_t *a, GallagherCredentials_t *b) {
return a->region_code == b->region_code &&
a->facility_code == b->facility_code &&
a->card_number == b->card_number &&
a->issue_level == b->issue_level;
}
static bool test_CAD(void) {
// Example CAD sector from https://github.com/megabug/gallagher-research/blob/master/formats/card-specific/mifare-classic.md
uint8_t cad[] = {0x1B, 0x58, 0x00, 0x01, 0xC1, 0x33, 0x70, 0xFD, 0x13, 0x38, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x77, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// Entry 0: RC=0xC, FC=0x1337, Sector=0x0F
int result = gallagher_parse_cad(cad, 0xC, 0x1337);
if (result != 0x0F) {
PrintAndLogEx(INFO, "Gallagher CAD test 1 failed: expected sector 0x0F, got 0x%02X", result);
return false;
}
// Entry 1: RC=0xD, FC=0x1338, Sector=0x0D
result = gallagher_parse_cad(cad, 0xD, 0x1338);
if (result != 0x0D) {
PrintAndLogEx(INFO, "Gallagher CAD test 2 failed: expected sector 0x0D, got 0x%02X", result);
return false;
}
// Non-existent entry should return -1
result = gallagher_parse_cad(cad, 0xA, 0x1234);
if (result != -1) {
PrintAndLogEx(INFO, "Gallagher CAD test 3 failed: expected -1, got %d", result);
return false;
}
return true;
}
static bool test_creds(void) {
GallagherCredentials_t creds1 = {
.region_code = 0x0,
.facility_code = 0x0,
.card_number = 0x0,
.issue_level = 0x0,
};
GallagherCredentials_t creds2 = {
.region_code = 0x1,
.facility_code = 0x2,
.card_number = 0x20,
.issue_level = 0x1,
};
GallagherCredentials_t cred_result = {0};
uint8_t bytes_result[8] = {0};
gallagher_encode_creds(bytes_result, &creds1);
gallagher_decode_creds(bytes_result, &cred_result);
if (!creds_match(&cred_result, &creds1)) {
PrintAndLogEx(INFO, "Gallagher encode/decode roundtrip test 1 failed");
return false;
}
gallagher_encode_creds(bytes_result, &creds2);
gallagher_decode_creds(bytes_result, &cred_result);
if (!creds_match(&cred_result, &creds2)) {
PrintAndLogEx(INFO, "Gallagher encode/decode roundtrip test 2 failed");
return false;
}
return true;
}
// Test decode/encode against known real-world data from the documentation
static bool test_known_vector_creds(void) {
// From doc: 0xA3B4B0C151B0A31B decodes to RC=12, FC=4919(0x1337), CN=61453(0xF00D), IL=1
uint8_t known_bytes[] = {0xA3, 0xB4, 0xB0, 0xC1, 0x51, 0xB0, 0xA3, 0x1B};
GallagherCredentials_t expected = {
.region_code = 12,
.facility_code = 4919,
.card_number = 61453,
.issue_level = 1,
};
// Test decode
GallagherCredentials_t result = {0};
gallagher_decode_creds(known_bytes, &result);
if (!creds_match(&expected, &result)) {
PrintAndLogEx(INFO, "Known vector decode failed: RC=%d FC=%d CN=%d IL=%d",
result.region_code, result.facility_code, result.card_number, result.issue_level);
return false;
}
// Test encode roundtrip
uint8_t encoded[8] = {0};
gallagher_encode_creds(encoded, &expected);
if (memcmp(encoded, known_bytes, 8) != 0) {
PrintAndLogEx(INFO, "Known vector encode failed");
return false;
}
// Verify bitwise inverse (block 0 format: 8-byte creds + 8-byte inverse)
uint8_t known_block0[] = {0xA3, 0xB4, 0xB0, 0xC1, 0x51, 0xB0, 0xA3, 0x1B,
0x5C, 0x4B, 0x4F, 0x3E, 0xAE, 0x4F, 0x5C, 0xE4
};
for (int i = 0; i < 8; i++) {
if ((uint8_t)(known_block0[i] ^ 0xFF) != known_block0[i + 8]) {
PrintAndLogEx(INFO, "Bitwise inverse check failed at byte %d", i);
return false;
}
}
return true;
}
// Test MAD CRC against known sector 0 data from the documentation
static bool test_mad_crc(void) {
// Full sector 0 from documentation (blocks 0-2, excluding trailer)
uint8_t sector0[64] = {
// Block 0 (manufacturer)
0xE3, 0x51, 0x54, 0x3C, 0xDA, 0x08, 0x04, 0x00, 0x01, 0x6F, 0x01, 0x6D, 0x45, 0x68, 0xF8, 0x1D,
// Block 1 (MAD: CRC, info byte, AIDs 1-7)
0xBD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Block 2 (MAD: AIDs 8-15)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x48, 0x11, 0x48, 0x12, 0x48,
// Block 3 (sector trailer - not part of CRC)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x77, 0x88, 0xC1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
// MAD v1 CRC: computed over sector0[17..47] (info byte + 15 AID pairs = 31 bytes)
uint8_t expected_crc = sector0[16]; // 0xBD
uint8_t computed_crc = CRC8Mad(&sector0[16 + 1], 15 + 16);
if (computed_crc != expected_crc) {
PrintAndLogEx(INFO, "MAD CRC test failed: expected 0x%02X, got 0x%02X", expected_crc, computed_crc);
return false;
}
return true;
}
static bool test_MES(void) {
uint8_t csn[] = {0x3C, 0x54, 0x51, 0xE3};
uint8_t csn_len = 4;
uint8_t site_key[] = {0x13, 0x37, 0xD0, 0x0D, 0x13, 0x37, 0xD0, 0x0D, 0x13, 0x37, 0xD0, 0x0D, 0x13, 0x37, 0xD0, 0x0D};
GallagherCredentials_t known_cred;
gallagher_construct_credential(&known_cred, 12, 0x1337, 0xF00D, 1, true, csn, csn_len, site_key);
GallagherCredentials_t result_creds = {0};
gallagher_construct_credential(&result_creds, 0, 0, 0, 0, true, csn, csn_len, site_key);
uint8_t sector_result[16] = {0};
uint8_t known_sector[16] = {0x4F, 0x36, 0xB7, 0x4E, 0xFF, 0xCD, 0x76, 0xEF, 0xED, 0xA5, 0x74, 0x58, 0xC8, 0xB4, 0xE3, 0x04};
// Test encode
gallagher_encode_mes(sector_result, &known_cred);
if (memcmp(sector_result, known_sector, 16) != 0) {
PrintAndLogEx(INFO, "Gallagher MES encode test failed");
PrintAndLogEx(INFO, "Expected: %s", sprint_hex_ascii(known_sector, 16));
PrintAndLogEx(INFO, "Got: %s", sprint_hex_ascii(sector_result, 16));
return false;
}
// Test decode
if (gallagher_decode_mes(known_sector, &result_creds) != PM3_SUCCESS) {
PrintAndLogEx(INFO, "Gallagher MES decode test failed");
return false;
}
if (!creds_match(&known_cred, &result_creds)) {
PrintAndLogEx(INFO, "Gallagher MES decoded different creds than expected");
return false;
}
return true;
}
bool GallagherTest(bool verbose) {
bool result = true;
result &= test_CAD();
result &= test_creds();
result &= test_known_vector_creds();
result &= test_mad_crc();
result &= test_MES();
return result;
}
+27
View File
@@ -0,0 +1,27 @@
//-----------------------------------------------------------------------------
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// See LICENSE.txt for the text of the license.
//-----------------------------------------------------------------------------
// tests for desfire
//-----------------------------------------------------------------------------
#ifndef __GALLAGHERTEST_H__
#define __GALLAGHERTEST_H__
#include <stdbool.h>
#include "common.h"
bool GallagherTest(bool verbose);
#endif /* __GALLAGHERTEST_H__ */

Some files were not shown because too many files have changed in this diff Show More