mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2026-05-12 11:18:11 -07:00
Merge branch 'master' into hf-14b-tearoff
Signed-off-by: Christian Zanon <105173223+xNovyz@users.noreply.github.com>
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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 |||
|
||||
|
||||
@@ -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 (0–255) | 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`.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+480
-63
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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");
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(§or0[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;
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user