Skip to content

Commit 1579825

Browse files
Bananz0paxcut
andauthored
patterns/mifare: Add MIFARE Classic 1K and 4K patterns and test data (#498)
* patterns/mifare: Add MIFARE Classic 1K and 4K patterns and test data * fixed some of the copilot suggestions from review --------- Co-authored-by: paxcut <53811119+paxcut@users.noreply.github.com>
1 parent 23a97fa commit 1579825

5 files changed

Lines changed: 358 additions & 0 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ Everything will immediately show up in ImHex's Content Store and gets bundled wi
147147
| LZNT1 | | [`patterns/lznt1.hexpat`](patterns/lznt1.hexpat) | LZNT1 compressed data format |
148148
| Mach-O | `application/x-mach-binary` | [`patterns/macho.hexpat`](patterns/macho.hexpat) | Mach-O executable |
149149
| MIDI | `audio/midi` | [`patterns/midi.hexpat`](patterns/midi.hexpat) | MIDI header, event fields provided |
150+
| MIFARE Classic 1K | `application/x-mifare-classic` | [`patterns/mifare/mifare-1k.hexpat`](patterns/mifare/mifare-1k.hexpat) | MIFARE Classic EV1 1K (MF1S50yyX/V1) |
151+
| MIFARE Classic 4K | `application/x-mifare-classic` | [`patterns/mifare/mifare-4k.hexpat`](patterns/mifare/mifare-4k.hexpat) | MIFARE Classic EV1 4K (MF1S70yyX/V1) |
150152
| MiniDump | `application/x-dmp` | [`patterns/minidump.hexpat`](patterns/minidump.hexpat) | Windows MiniDump files |
151153
| MO | | [`patterns/mo.hexpat`](patterns/mo.hexpat) | GNU Machine Object (MO) files containing translations for gettext |
152154
| mp4 | `video/mp4` | [`patterns/mp4.hexpat`](patterns/mp4.hexpat) | MPEG-4 Part 14 digital multimedia container format |

patterns/mifare/mifare-1k.hexpat

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
#pragma author Bananz0
2+
#pragma description MIFARE Classic EV1 1K (MF1S50yyX/V1), ISO/IEC 14443-3 Type A
3+
#pragma MIME application/x-mifare-classic
4+
#pragma endian little
5+
// References:
6+
// NXP MF1S50yyX/V1 Product Data Sheet, Rev. 3.2
7+
// NXP AN10833, MIFARE type identification procedure, Rev. 3.9
8+
//
9+
// Layout: 16 sectors × 4 blocks × 16 bytes = 1024 bytes, absolute blocks 0..63
10+
// Sector trailer: always last block of each sector (blocks 3, 7, 11, ..., 63)
11+
// Load this file for 1K dumps (file size exactly 1024 bytes, SAK = 0x08 or 0x28)
12+
13+
import std.core;
14+
import std.io;
15+
16+
17+
// Sector Trailer
18+
// Byte layout per MF1S50yyX/V1 Rev.3.2 §8.7.1, Figure 10:
19+
// acc0 (+6): ~C2_3 ~C2_2 ~C2_1 ~C2_0 ~C1_3 ~C1_2 ~C1_1 ~C1_0
20+
// acc1 (+7): C1_3 C1_2 C1_1 C1_0 ~C3_3 ~C3_2 ~C3_1 ~C3_0
21+
// acc2 (+8): C3_3 C3_2 C3_1 C3_0 C2_3 C2_2 C2_1 C2_0
22+
// userData (+9): application-defined (MF1S50yyX/V1 §8.6.3)
23+
// Raw u8 fields used to avoid bitfield ordering ambiguity across byte boundaries.
24+
// Delivery default: acc0=0xFF acc1=0x07 acc2=0x80 (MF1S50yyX/V1 §8.6.3)
25+
26+
struct SectorTrailer {
27+
u8 keyA[6];
28+
u8 acc0;
29+
u8 acc1;
30+
u8 acc2;
31+
u8 userData;
32+
u8 keyB[6];
33+
};
34+
35+
36+
// Manufacturer Block (absolute block 0, sector 0 only)
37+
// 4-byte NUID layout (MF1S503yX, single cascade level):
38+
// Bytes 0-3: NUID
39+
// Byte 4: BCC = NUID0^NUID1^NUID2^NUID3 (ISO/IEC 14443-3:2018 §6.5.3,
40+
// stored in block 0 by NXP hardware convention)
41+
// Byte 5: SAK (0x08 = native 1K; MF1S50yyX/V1 Rev.3.2 §9.4, Table 12)
42+
// Bytes 6-7: ATQA, wire order LSByte-first (AN10833 Rev.3.9 §2.3 Note 2)
43+
// 0x0004 = 4-byte NUID 1K (MF1S503yX)
44+
// 0x0044 = 7-byte UID 1K (MF1S500yX)
45+
// Bytes 8-15: Manufacturer data
46+
// Byte 9 = load modulation status (MF1S50yyX/V1 Rev.3.2 §11, Fig.14)
47+
// 0x20 = strong (default), 0x00 = normal
48+
// Note: 7-byte UID variants (MF1S500yX) have no BCC stored in block 0 and use
49+
// bytes 0-6 for UID. This struct models 4-byte NUID only.
50+
51+
struct ManufacturerBlock {
52+
u8 uid[4];
53+
u8 bcc;
54+
u8 sak;
55+
u16 atqa;
56+
u8 manufacturerData[8];
57+
};
58+
59+
60+
// Data Block: 16 bytes (MF1S50yyX/V1 Rev.3.2 §8.6.2)
61+
// Value block mode layout (when access conditions indicate value block,
62+
// MF1S50yyX/V1 Rev.3.2 §8.6.2.1, Figure 8):
63+
// Bytes 0-3: Value (signed 32-bit LE, stored non-inverted)
64+
// Bytes 4-7: ~Value (bit-inverted)
65+
// Bytes 8-11: Value (repeated)
66+
// Byte 12: Addr
67+
// Byte 13: ~Addr
68+
// Byte 14: Addr
69+
// Byte 15: ~Addr
70+
// Value-block mode is determined by access conditions; raw bytes exposed here.
71+
72+
struct DataBlock {
73+
u8 data[16];
74+
};
75+
76+
// Sector 0: manufacturer block + 2 data blocks + trailer (64 bytes)
77+
// Sector 0 data block count: 2 (not 3) because block 0 is the manufacturer block
78+
// (MF1S50yyX/V1 Rev.3.2 §8.6.2: "Sector 0 contains only two data blocks and
79+
// the read-only manufacturer block")
80+
81+
struct Sector0 {
82+
ManufacturerBlock manufacturerBlock;
83+
DataBlock block1;
84+
DataBlock block2;
85+
SectorTrailer trailer;
86+
};
87+
88+
89+
// Sectors 1..15: 3 data blocks + trailer (64 bytes each)
90+
91+
struct Sector {
92+
DataBlock block0;
93+
DataBlock block1;
94+
DataBlock block2;
95+
SectorTrailer trailer;
96+
};
97+
98+
// MIFARE Classic 1K: 16 sectors = 1024 bytes
99+
100+
struct MifareClassic1K {
101+
Sector0 sector0;
102+
Sector sectors[15];
103+
};
104+
105+
// Validation
106+
107+
fn validate_uid_bcc(ref MifareClassic1K card) {
108+
// BCC = XOR of all 4 UID bytes (ISO/IEC 14443-3:2018 §6.5.3)
109+
// Stored at block 0 byte 4 by NXP hardware convention.
110+
u8 computed = card.sector0.manufacturerBlock.uid[0] ^
111+
card.sector0.manufacturerBlock.uid[1] ^
112+
card.sector0.manufacturerBlock.uid[2] ^
113+
card.sector0.manufacturerBlock.uid[3];
114+
if (computed == card.sector0.manufacturerBlock.bcc) {
115+
std::print("BCC OK: 0x{:02X}", computed);
116+
} else {
117+
std::warning(std::format("BCC FAIL: stored=0x{:02X} computed=0x{:02X}",
118+
card.sector0.manufacturerBlock.bcc, computed));
119+
}
120+
};
121+
122+
fn validate_sak(ref MifareClassic1K card) {
123+
// MF1S50yyX/V1 Rev.3.2 §9.4, Table 12: SAK = 0x08
124+
// AN10833 Rev.3.9 Table 5: 0x28 = SmartMX emulation (0x08|0x20), not native silicon
125+
// AN10833 Rev.3.9 §3.1: bit 7 of SAK is a proprietary NXP bit (e.g., Originality Check
126+
// enabled variants) and must be masked out before type identification.
127+
u8 sak = card.sector0.manufacturerBlock.sak;
128+
u8 sak_typ = sak & 0x7F; // strip proprietary bit 7
129+
bool prop = (sak & 0x80) != 0;
130+
if (sak_typ == 0x08) {
131+
std::print("SAK 0x{:02X}: MIFARE Classic 1K, native{}", sak,
132+
prop ? " (bit7 set: NXP proprietary/Originality Check variant)" : "");
133+
} else if (sak_typ == 0x28) {
134+
std::warning(std::format("SAK 0x{:02X}: SmartMX/multi-MIFARE emulation of Classic 1K (AN10833 Rev.3.9, Table 5 Example 1){}",
135+
sak, prop ? " + bit7 set" : ""));
136+
} else {
137+
std::warning(std::format("SAK 0x{:02X} (masked: 0x{:02X}): unexpected for Classic 1K", sak, sak_typ));
138+
}
139+
};
140+
141+
fn validate_access_bits(u8 idx, ref SectorTrailer t) {
142+
// Three nibble-pair complement invariants (MF1S50yyX/V1 Rev.3.2 §8.7.1):
143+
// acc0[3:0] == ~acc1[7:4] (nC1x == ~C1x)
144+
// acc0[7:4] == ~acc2[3:0] (nC2x == ~C2x)
145+
// acc1[3:0] == ~acc2[7:4] (nC3x == ~C3x)
146+
u8 a0 = t.acc0; u8 a1 = t.acc1; u8 a2 = t.acc2;
147+
bool c1 = ((a0 & 0x0F) == ((~a1 >> 4) & 0x0F));
148+
bool c2 = (((a0 >> 4) & 0x0F) == (~a2 & 0x0F));
149+
bool c3 = ((a1 & 0x0F) == ((~a2 >> 4) & 0x0F));
150+
if (c1 && c2 && c3) {
151+
std::print("Sector {:02d}: Access bits OK [{:02X} {:02X} {:02X}]", idx, a0, a1, a2);
152+
} else {
153+
std::warning(std::format("Sector {:02d}: Access bits FAIL [{:02X} {:02X} {:02X}] C1:{} C2:{} C3:{}",
154+
idx, a0, a1, a2, c1, c2, c3));
155+
}
156+
};
157+
158+
// Entry point — unconditional file-scope placement for ImHex visualization
159+
160+
MifareClassic1K card @ 0x00;
161+
162+
validate_uid_bcc(card);
163+
validate_sak(card);
164+
validate_access_bits(0, card.sector0.trailer);
165+
for (u8 i = 1, i < 16, i += 1) {
166+
validate_access_bits(i, card.sectors[i - 1].trailer);
167+
}

patterns/mifare/mifare-4k.hexpat

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
#pragma author Bananz0
2+
#pragma description MIFARE Classic EV1 4K (MF1S70yyX/V1), ISO/IEC 14443-3 Type A
3+
#pragma MIME application/x-mifare-classic
4+
#pragma endian little
5+
6+
// References:
7+
// NXP MF1S70yyX/V1 Product Data Sheet, Rev. 3.2
8+
// NXP AN10833, MIFARE type identification procedure, Rev. 3.9
9+
//
10+
// Layout: sectors 0..31 × 4 blocks × 16 bytes (2048 bytes)
11+
// + sectors 32..39 × 16 blocks × 16 bytes (2048 bytes)
12+
// = 4096 bytes total, absolute blocks 0..255
13+
// Sector trailer: last block of each sector (block 3 for small, block 15 for large)
14+
// Load this file for 4K dumps (file size exactly 4096 bytes, SAK = 0x18 or 0x38)
15+
16+
import std.core;
17+
import std.io;
18+
19+
// Sector Trailer
20+
// Identical encoding for small and large sectors.
21+
// Byte layout per MF1S70yyX/V1 Rev.3.2 §8.7.1, Figure 10:
22+
// acc0 (+6): ~C2_3 ~C2_2 ~C2_1 ~C2_0 ~C1_3 ~C1_2 ~C1_1 ~C1_0
23+
// acc1 (+7): C1_3 C1_2 C1_1 C1_0 ~C3_3 ~C3_2 ~C3_1 ~C3_0
24+
// acc2 (+8): C3_3 C3_2 C3_1 C3_0 C2_3 C2_2 C2_1 C2_0
25+
// For large sectors, Cx_n indexes block groups, not individual blocks:
26+
// C1_0/C2_0/C3_0: blocks 0-4 C1_1/C2_1/C3_1: blocks 5-9
27+
// C1_2/C2_2/C3_2: blocks 10-14 C1_3/C2_3/C3_3: trailer (block 15)
28+
// (MF1S70yyX/V1 Rev.3.2 §8.7.1, Table 6)
29+
// Delivery default: acc0=0xFF acc1=0x07 acc2=0x80 (MF1S70yyX/V1 §8.6.3)
30+
31+
struct SectorTrailer {
32+
u8 keyA[6];
33+
u8 acc0;
34+
u8 acc1;
35+
u8 acc2;
36+
u8 userData;
37+
u8 keyB[6];
38+
};
39+
40+
41+
// Manufacturer Block (absolute block 0, sector 0 only)
42+
// 4-byte NUID layout (MF1S703yX, single cascade level):
43+
// Bytes 0-3: NUID
44+
// Byte 4: BCC = NUID0^NUID1^NUID2^NUID3 (ISO/IEC 14443-3:2018 §6.5.3,
45+
// stored in block 0 by NXP hardware convention)
46+
// Byte 5: SAK (0x18 = native 4K; MF1S70yyX/V1 Rev.3.2 §9.4, Table 12)
47+
// Bytes 6-7: ATQA, wire order LSByte-first (AN10833 Rev.3.9 §2.3 Note 2)
48+
// 0x0002 = 4-byte NUID 4K (MF1S703yX)
49+
// 0x0042 = 7-byte UID 4K (MF1S700yX)
50+
// Bytes 8-15: Manufacturer data
51+
// Byte 9 = load modulation status (MF1S70yyX/V1 Rev.3.2 §11, Fig.14)
52+
// 0x20 = strong (default), 0x00 = normal
53+
// Note: 7-byte UID variants (MF1S700yX) have no BCC in block 0; use bytes 0-6 for UID.
54+
// This struct models 4-byte NUID only.
55+
56+
struct ManufacturerBlock {
57+
u8 uid[4];
58+
u8 bcc;
59+
u8 sak;
60+
u16 atqa;
61+
u8 manufacturerData[8];
62+
};
63+
64+
// Data Block: 16 bytes (MF1S70yyX/V1 Rev.3.2 §8.6.2)
65+
// Value block mode layout (MF1S70yyX/V1 Rev.3.2 §8.6.2.1, Figure 8):
66+
// Bytes 0-3: Value (signed 32-bit LE), 4-7: ~Value, 8-11: Value
67+
// Bytes 12-15: Addr, ~Addr, Addr, ~Addr
68+
69+
struct DataBlock {
70+
u8 data[16];
71+
};
72+
73+
74+
// Sector 0: manufacturer block + 2 data blocks + trailer (64 bytes)
75+
// (MF1S70yyX/V1 Rev.3.2 §8.6.2: "Sector 0 contains only two data blocks
76+
// and the read-only manufacturer block")
77+
78+
struct Sector0 {
79+
ManufacturerBlock manufacturerBlock;
80+
DataBlock block1;
81+
DataBlock block2;
82+
SectorTrailer trailer;
83+
};
84+
85+
// Small Sector (sectors 1..31): 3 data blocks + trailer (64 bytes)
86+
87+
struct SmallSector {
88+
DataBlock block0;
89+
DataBlock block1;
90+
DataBlock block2;
91+
SectorTrailer trailer;
92+
};
93+
94+
// Large Sector (sectors 32..39): 15 data blocks + trailer (256 bytes)
95+
// (MF1S70yyX/V1 Rev.3.2 §8.6: "8 sectors of 16 blocks")
96+
97+
struct LargeSector {
98+
DataBlock block0;
99+
DataBlock block1;
100+
DataBlock block2;
101+
DataBlock block3;
102+
DataBlock block4;
103+
DataBlock block5;
104+
DataBlock block6;
105+
DataBlock block7;
106+
DataBlock block8;
107+
DataBlock block9;
108+
DataBlock block10;
109+
DataBlock block11;
110+
DataBlock block12;
111+
DataBlock block13;
112+
DataBlock block14;
113+
SectorTrailer trailer;
114+
};
115+
116+
// MIFARE Classic 4K: sector0 + 31 small + 8 large = 4096 bytes
117+
// 64 + 31×64 + 8×256 = 64 + 1984 + 2048 = 4096
118+
119+
struct MifareClassic4K {
120+
Sector0 sector0;
121+
SmallSector smallSectors[31];
122+
LargeSector largeSectors[8];
123+
};
124+
125+
// Validation
126+
127+
128+
fn validate_uid_bcc(ref MifareClassic4K card) {
129+
// BCC = XOR of all 4 UID bytes (ISO/IEC 14443-3:2018 §6.5.3)
130+
// Stored at block 0 byte 4 by NXP hardware convention.
131+
u8 computed = card.sector0.manufacturerBlock.uid[0] ^
132+
card.sector0.manufacturerBlock.uid[1] ^
133+
card.sector0.manufacturerBlock.uid[2] ^
134+
card.sector0.manufacturerBlock.uid[3];
135+
if (computed == card.sector0.manufacturerBlock.bcc) {
136+
std::print("BCC OK: 0x{:02X}", computed);
137+
} else {
138+
std::warning(std::format("BCC FAIL: stored=0x{:02X} computed=0x{:02X}",
139+
card.sector0.manufacturerBlock.bcc, computed));
140+
}
141+
};
142+
143+
fn validate_sak(ref MifareClassic4K card) {
144+
// MF1S70yyX/V1 Rev.3.2 §9.4, Table 12: SAK = 0x18
145+
// AN10833 Rev.3.9 Table 5: 0x38 = SmartMX emulation (0x18|0x20), not native silicon
146+
// AN10833 Rev.3.9 §3.1: bit 7 of SAK is a proprietary NXP bit (e.g., Originality Check
147+
// enabled variants) and must be masked out before type identification.
148+
u8 sak = card.sector0.manufacturerBlock.sak;
149+
u8 sak_typ = sak & 0x7F; // strip proprietary bit 7
150+
bool prop = (sak & 0x80) != 0;
151+
if (sak_typ == 0x18) {
152+
std::print("SAK 0x{:02X}: MIFARE Classic 4K, native{}", sak,
153+
prop ? " (bit7 set: NXP proprietary/Originality Check variant)" : "");
154+
} else if (sak_typ == 0x38) {
155+
std::warning(std::format("SAK 0x{:02X}: SmartMX/multi-MIFARE emulation of Classic 4K (AN10833 Rev.3.9, Table 5 Examples 2/3){}",
156+
sak, prop ? " + bit7 set" : ""));
157+
} else {
158+
std::warning(std::format("SAK 0x{:02X} (masked: 0x{:02X}): unexpected for Classic 4K", sak, sak_typ));
159+
}
160+
};
161+
162+
fn validate_access_bits(u8 idx, ref SectorTrailer t) {
163+
// Three nibble-pair complement invariants (MF1S70yyX/V1 Rev.3.2 §8.7.1):
164+
// acc0[3:0] == ~acc1[7:4] acc0[7:4] == ~acc2[3:0] acc1[3:0] == ~acc2[7:4]
165+
u8 a0 = t.acc0; u8 a1 = t.acc1; u8 a2 = t.acc2;
166+
bool c1 = ((a0 & 0x0F) == ((~a1 >> 4) & 0x0F));
167+
bool c2 = (((a0 >> 4) & 0x0F) == (~a2 & 0x0F));
168+
bool c3 = ((a1 & 0x0F) == ((~a2 >> 4) & 0x0F));
169+
if (c1 && c2 && c3) {
170+
std::print("Sector {:02d}: Access bits OK [{:02X} {:02X} {:02X}]", idx, a0, a1, a2);
171+
} else {
172+
std::warning(std::format("Sector {:02d}: Access bits FAIL [{:02X} {:02X} {:02X}] C1:{} C2:{} C3:{}",
173+
idx, a0, a1, a2, c1, c2, c3));
174+
}
175+
};
176+
177+
// Entry point — unconditional file-scope placement for ImHex visualization
178+
179+
MifareClassic4K card @ 0x00;
180+
181+
validate_uid_bcc(card);
182+
validate_sak(card);
183+
validate_access_bits(0, card.sector0.trailer);
184+
for (u8 i = 1, i <= 31, i += 1) {
185+
validate_access_bits(i, card.smallSectors[i - 1].trailer);
186+
}
187+
for (u8 j = 0, j < 8, j += 1) {
188+
validate_access_bits(j + 32, card.largeSectors[j].trailer);
189+
}
1 KB
Binary file not shown.
4 KB
Binary file not shown.

0 commit comments

Comments
 (0)