]>
Commit | Line | Data |
---|---|---|
1 | //----------------------------------------------------------------------------- | |
2 | // Copyright (C) 2018 Merlok | |
3 | // Copyright (C) 2018 drHatson | |
4 | // | |
5 | // This code is licensed to you under the terms of the GNU GPL, version 2 or, | |
6 | // at your option, any later version. See the LICENSE.txt file for the text of | |
7 | // the license. | |
8 | //----------------------------------------------------------------------------- | |
9 | // iso14443-4 mifare commands | |
10 | //----------------------------------------------------------------------------- | |
11 | ||
12 | #include "mifare4.h" | |
13 | #include <ctype.h> | |
14 | #include <string.h> | |
15 | #include "cmdhf14a.h" | |
16 | #include "util.h" | |
17 | #include "ui.h" | |
18 | #include "crypto/libpcrypto.h" | |
19 | ||
20 | static bool VerboseMode = false; | |
21 | void mfpSetVerboseMode(bool verbose) { | |
22 | VerboseMode = verbose; | |
23 | } | |
24 | ||
25 | typedef struct { | |
26 | uint8_t Code; | |
27 | const char *Description; | |
28 | } PlusErrorsElm; | |
29 | ||
30 | static const PlusErrorsElm PlusErrors[] = { | |
31 | {0xFF, ""}, | |
32 | {0x00, "Transfer cannot be granted within the current authentication."}, | |
33 | {0x06, "Access Conditions not fulfilled. Block does not exist, block is not a value block."}, | |
34 | {0x07, "Too many read or write commands in the session or in the transaction."}, | |
35 | {0x08, "Invalid MAC in command or response"}, | |
36 | {0x09, "Block Number is not valid"}, | |
37 | {0x0a, "Invalid block number, not existing block number"}, | |
38 | {0x0b, "The current command code not available at the current card state."}, | |
39 | {0x0c, "Length error"}, | |
40 | {0x0f, "General Manipulation Error. Failure in the operation of the PICC (cannot write to the data block), etc."}, | |
41 | {0x90, "OK"}, | |
42 | }; | |
43 | int PlusErrorsLen = sizeof(PlusErrors) / sizeof(PlusErrorsElm); | |
44 | ||
45 | const char *mfpGetErrorDescription(uint8_t errorCode) { | |
46 | for (int i = 0; i < PlusErrorsLen; i++) | |
47 | if (errorCode == PlusErrors[i].Code) | |
48 | return PlusErrors[i].Description; | |
49 | ||
50 | return PlusErrors[0].Description; | |
51 | } | |
52 | ||
53 | AccessConditions_t MFAccessConditions[] = { | |
54 | {0x00, "read AB; write AB; increment AB; decrement transfer restore AB"}, | |
55 | {0x01, "read AB; decrement transfer restore AB"}, | |
56 | {0x02, "read AB"}, | |
57 | {0x03, "read B; write B"}, | |
58 | {0x04, "read AB; writeB"}, | |
59 | {0x05, "read B"}, | |
60 | {0x06, "read AB; write B; increment B; decrement transfer restore AB"}, | |
61 | {0x07, "none"} | |
62 | }; | |
63 | ||
64 | AccessConditions_t MFAccessConditionsTrailer[] = { | |
65 | {0x00, "read A by A; read ACCESS by A; read B by A; write B by A"}, | |
66 | {0x01, "write A by A; read ACCESS by A write ACCESS by A; read B by A; write B by A"}, | |
67 | {0x02, "read ACCESS by A; read B by A"}, | |
68 | {0x03, "write A by B; read ACCESS by AB; write ACCESS by B; write B by B"}, | |
69 | {0x04, "write A by B; read ACCESS by AB; write B by B"}, | |
70 | {0x05, "read ACCESS by AB; write ACCESS by B"}, | |
71 | {0x06, "read ACCESS by AB"}, | |
72 | {0x07, "read ACCESS by AB"} | |
73 | }; | |
74 | ||
75 | char *mfGetAccessConditionsDesc(uint8_t blockn, uint8_t *data) { | |
76 | static char StaticNone[] = "none"; | |
77 | ||
78 | uint8_t data1 = ((data[1] >> 4) & 0x0f) >> blockn; | |
79 | uint8_t data2 = ((data[2]) & 0x0f) >> blockn; | |
80 | uint8_t data3 = ((data[2] >> 4) & 0x0f) >> blockn; | |
81 | ||
82 | uint8_t cond = (data1 & 0x01) << 2 | (data2 & 0x01) << 1 | (data3 & 0x01); | |
83 | ||
84 | if (blockn == 3) { | |
85 | for (int i = 0; i < ARRAYLEN(MFAccessConditionsTrailer); i++) | |
86 | if (MFAccessConditionsTrailer[i].cond == cond) { | |
87 | return MFAccessConditionsTrailer[i].description; | |
88 | } | |
89 | } else { | |
90 | for (int i = 0; i < ARRAYLEN(MFAccessConditions); i++) | |
91 | if (MFAccessConditions[i].cond == cond) { | |
92 | return MFAccessConditions[i].description; | |
93 | } | |
94 | }; | |
95 | ||
96 | return StaticNone; | |
97 | }; | |
98 | ||
99 | int CalculateEncIVCommand(mf4Session *session, uint8_t *iv, bool verbose) { | |
100 | memcpy(&iv[0], session->TI, 4); | |
101 | memcpy(&iv[4], &session->R_Ctr, 2); | |
102 | memcpy(&iv[6], &session->W_Ctr, 2); | |
103 | memcpy(&iv[8], &session->R_Ctr, 2); | |
104 | memcpy(&iv[10], &session->W_Ctr, 2); | |
105 | memcpy(&iv[12], &session->R_Ctr, 2); | |
106 | memcpy(&iv[14], &session->W_Ctr, 2); | |
107 | ||
108 | return 0; | |
109 | } | |
110 | ||
111 | int CalculateEncIVResponse(mf4Session *session, uint8_t *iv, bool verbose) { | |
112 | memcpy(&iv[0], &session->R_Ctr, 2); | |
113 | memcpy(&iv[2], &session->W_Ctr, 2); | |
114 | memcpy(&iv[4], &session->R_Ctr, 2); | |
115 | memcpy(&iv[6], &session->W_Ctr, 2); | |
116 | memcpy(&iv[8], &session->R_Ctr, 2); | |
117 | memcpy(&iv[10], &session->W_Ctr, 2); | |
118 | memcpy(&iv[12], session->TI, 4); | |
119 | ||
120 | return 0; | |
121 | } | |
122 | ||
123 | ||
124 | int CalculateMAC(mf4Session *session, MACType_t mtype, uint8_t blockNum, uint8_t blockCount, uint8_t *data, int datalen, uint8_t *mac, bool verbose) { | |
125 | if (!session || !session->Authenticated || !mac || !data || !datalen || datalen < 1) | |
126 | return 1; | |
127 | ||
128 | memset(mac, 0x00, 8); | |
129 | ||
130 | uint16_t ctr = session->R_Ctr; | |
131 | switch (mtype) { | |
132 | case mtypWriteCmd: | |
133 | case mtypWriteResp: | |
134 | ctr = session->W_Ctr; | |
135 | break; | |
136 | case mtypReadCmd: | |
137 | case mtypReadResp: | |
138 | break; | |
139 | } | |
140 | ||
141 | uint8_t macdata[2049] = {data[0], (ctr & 0xFF), (ctr >> 8), 0}; | |
142 | int macdatalen = datalen; | |
143 | memcpy(&macdata[3], session->TI, 4); | |
144 | ||
145 | switch (mtype) { | |
146 | case mtypReadCmd: | |
147 | memcpy(&macdata[7], &data[1], datalen - 1); | |
148 | macdatalen = datalen + 6; | |
149 | break; | |
150 | case mtypReadResp: | |
151 | macdata[7] = blockNum; | |
152 | macdata[8] = 0; | |
153 | macdata[9] = blockCount; | |
154 | memcpy(&macdata[10], &data[1], datalen - 1); | |
155 | macdatalen = datalen + 9; | |
156 | break; | |
157 | case mtypWriteCmd: | |
158 | memcpy(&macdata[7], &data[1], datalen - 1); | |
159 | macdatalen = datalen + 6; | |
160 | break; | |
161 | case mtypWriteResp: | |
162 | macdatalen = 1 + 6; | |
163 | break; | |
164 | } | |
165 | ||
166 | if (verbose) | |
167 | PrintAndLog("MAC data[%d]: %s", macdatalen, sprint_hex(macdata, macdatalen)); | |
168 | ||
169 | return aes_cmac8(NULL, session->Kmac, macdata, mac, macdatalen); | |
170 | } | |
171 | ||
172 | int MifareAuth4(mf4Session *session, uint8_t *keyn, uint8_t *key, bool activateField, bool leaveSignalON, bool verbose) { | |
173 | uint8_t data[257] = {0}; | |
174 | int datalen = 0; | |
175 | ||
176 | uint8_t RndA[17] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00}; | |
177 | uint8_t RndB[17] = {0}; | |
178 | ||
179 | if (session) | |
180 | session->Authenticated = false; | |
181 | ||
182 | uint8_t cmd1[] = {0x70, keyn[1], keyn[0], 0x00}; | |
183 | int res = ExchangeRAW14a(cmd1, sizeof(cmd1), activateField, true, data, sizeof(data), &datalen); | |
184 | if (res) { | |
185 | PrintAndLogEx(ERR, "Exchande raw error: %d", res); | |
186 | DropField(); | |
187 | return 2; | |
188 | } | |
189 | ||
190 | if (verbose) | |
191 | PrintAndLogEx(INFO, "<phase1: %s", sprint_hex(data, datalen)); | |
192 | ||
193 | if (datalen < 1) { | |
194 | PrintAndLogEx(ERR, "Card response wrong length: %d", datalen); | |
195 | DropField(); | |
196 | return 3; | |
197 | } | |
198 | ||
199 | if (data[0] != 0x90) { | |
200 | PrintAndLogEx(ERR, "Card response error: %02x", data[2]); | |
201 | DropField(); | |
202 | return 3; | |
203 | } | |
204 | ||
205 | if (datalen != 19) { // code 1b + 16b + crc 2b | |
206 | PrintAndLogEx(ERR, "Card response must be 19 bytes long instead of: %d", datalen); | |
207 | DropField(); | |
208 | return 3; | |
209 | } | |
210 | ||
211 | aes_decode(NULL, key, &data[1], RndB, 16); | |
212 | RndB[16] = RndB[0]; | |
213 | if (verbose) | |
214 | PrintAndLogEx(INFO, "RndB: %s", sprint_hex(RndB, 16)); | |
215 | ||
216 | uint8_t cmd2[33] = {0}; | |
217 | cmd2[0] = 0x72; | |
218 | ||
219 | uint8_t raw[32] = {0}; | |
220 | memmove(raw, RndA, 16); | |
221 | memmove(&raw[16], &RndB[1], 16); | |
222 | ||
223 | aes_encode(NULL, key, raw, &cmd2[1], 32); | |
224 | if (verbose) | |
225 | PrintAndLogEx(INFO, ">phase2: %s", sprint_hex(cmd2, 33)); | |
226 | ||
227 | res = ExchangeRAW14a(cmd2, sizeof(cmd2), false, true, data, sizeof(data), &datalen); | |
228 | if (res) { | |
229 | PrintAndLogEx(ERR, "Exchande raw error: %d", res); | |
230 | DropField(); | |
231 | return 4; | |
232 | } | |
233 | ||
234 | if (verbose) | |
235 | PrintAndLogEx(INFO, "<phase2: %s", sprint_hex(data, datalen)); | |
236 | ||
237 | aes_decode(NULL, key, &data[1], raw, 32); | |
238 | ||
239 | if (verbose) { | |
240 | PrintAndLogEx(INFO, "res: %s", sprint_hex(raw, 32)); | |
241 | PrintAndLogEx(INFO, "RndA`: %s", sprint_hex(&raw[4], 16)); | |
242 | } | |
243 | ||
244 | if (memcmp(&raw[4], &RndA[1], 16)) { | |
245 | PrintAndLogEx(ERR, "\nAuthentication FAILED. rnd not equal"); | |
246 | if (verbose) { | |
247 | PrintAndLogEx(ERR, "RndA reader: %s", sprint_hex(&RndA[1], 16)); | |
248 | PrintAndLogEx(ERR, "RndA card: %s", sprint_hex(&raw[4], 16)); | |
249 | } | |
250 | DropField(); | |
251 | return 5; | |
252 | } | |
253 | ||
254 | if (verbose) { | |
255 | PrintAndLogEx(INFO, " TI: %s", sprint_hex(raw, 4)); | |
256 | PrintAndLogEx(INFO, "pic: %s", sprint_hex(&raw[20], 6)); | |
257 | PrintAndLogEx(INFO, "pcd: %s", sprint_hex(&raw[26], 6)); | |
258 | } | |
259 | ||
260 | uint8_t kenc[16] = {0}; | |
261 | memcpy(&kenc[0], &RndA[11], 5); | |
262 | memcpy(&kenc[5], &RndB[11], 5); | |
263 | for (int i = 0; i < 5; i++) | |
264 | kenc[10 + i] = RndA[4 + i] ^ RndB[4 + i]; | |
265 | kenc[15] = 0x11; | |
266 | ||
267 | aes_encode(NULL, key, kenc, kenc, 16); | |
268 | if (verbose) { | |
269 | PrintAndLogEx(INFO, "kenc: %s", sprint_hex(kenc, 16)); | |
270 | } | |
271 | ||
272 | uint8_t kmac[16] = {0}; | |
273 | memcpy(&kmac[0], &RndA[7], 5); | |
274 | memcpy(&kmac[5], &RndB[7], 5); | |
275 | for(int i = 0; i < 5; i++) | |
276 | kmac[10 + i] = RndA[0 + i] ^ RndB[0 + i]; | |
277 | kmac[15] = 0x22; | |
278 | ||
279 | aes_encode(NULL, key, kmac, kmac, 16); | |
280 | if (verbose) { | |
281 | PrintAndLog("kmac: %s", sprint_hex(kmac, 16)); | |
282 | } | |
283 | ||
284 | if (!leaveSignalON) | |
285 | DropField(); | |
286 | ||
287 | if (verbose) | |
288 | PrintAndLog(""); | |
289 | ||
290 | if (session) { | |
291 | session->Authenticated = true; | |
292 | session->R_Ctr = 0; | |
293 | session->W_Ctr = 0; | |
294 | session->KeyNum = keyn[1] + (keyn[0] << 8); | |
295 | memmove(session->RndA, RndA, 16); | |
296 | memmove(session->RndB, RndB, 16); | |
297 | memmove(session->Key, key, 16); | |
298 | memmove(session->TI, raw, 4); | |
299 | memmove(session->PICCap2, &raw[20], 6); | |
300 | memmove(session->PCDCap2, &raw[26], 6); | |
301 | memmove(session->Kenc, kenc, 16); | |
302 | memmove(session->Kmac, kmac, 16); | |
303 | } | |
304 | ||
305 | if (verbose) | |
306 | PrintAndLogEx(INFO, "Authentication OK"); | |
307 | ||
308 | return 0; | |
309 | } | |
310 | ||
311 | int intExchangeRAW14aPlus(uint8_t *datain, int datainlen, bool activateField, bool leaveSignalON, uint8_t *dataout, int maxdataoutlen, int *dataoutlen) { | |
312 | if (VerboseMode) | |
313 | PrintAndLogEx(INFO, ">>> %s", sprint_hex(datain, datainlen)); | |
314 | ||
315 | int res = ExchangeRAW14a(datain, datainlen, activateField, leaveSignalON, dataout, maxdataoutlen, dataoutlen); | |
316 | ||
317 | if (VerboseMode) | |
318 | PrintAndLogEx(INFO, "<<< %s", sprint_hex(dataout, *dataoutlen)); | |
319 | ||
320 | return res; | |
321 | } | |
322 | ||
323 | int MFPWritePerso(uint8_t *keyNum, uint8_t *key, bool activateField, bool leaveSignalON, uint8_t *dataout, int maxdataoutlen, int *dataoutlen) { | |
324 | uint8_t rcmd[3 + 16] = {0xa8, keyNum[1], keyNum[0], 0x00}; | |
325 | memmove(&rcmd[3], key, 16); | |
326 | ||
327 | return intExchangeRAW14aPlus(rcmd, sizeof(rcmd), activateField, leaveSignalON, dataout, maxdataoutlen, dataoutlen); | |
328 | } | |
329 | ||
330 | int MFPCommitPerso(bool activateField, bool leaveSignalON, uint8_t *dataout, int maxdataoutlen, int *dataoutlen) { | |
331 | uint8_t rcmd[1] = {0xaa}; | |
332 | ||
333 | return intExchangeRAW14aPlus(rcmd, sizeof(rcmd), activateField, leaveSignalON, dataout, maxdataoutlen, dataoutlen); | |
334 | } | |
335 | ||
336 | int MFPReadBlock(mf4Session *session, bool plain, uint8_t blockNum, uint8_t blockCount, bool activateField, bool leaveSignalON, uint8_t *dataout, int maxdataoutlen, int *dataoutlen, uint8_t *mac) { | |
337 | uint8_t rcmd[4 + 8] = {(plain ? (0x37) : (0x33)), blockNum, 0x00, blockCount}; | |
338 | if (!plain && session) | |
339 | CalculateMAC(session, mtypReadCmd, blockNum, blockCount, rcmd, 4, &rcmd[4], VerboseMode); | |
340 | ||
341 | int res = intExchangeRAW14aPlus(rcmd, plain ? 4 : sizeof(rcmd), activateField, leaveSignalON, dataout, maxdataoutlen, dataoutlen); | |
342 | if (res) | |
343 | return res; | |
344 | ||
345 | if (session) | |
346 | session->R_Ctr++; | |
347 | ||
348 | if (session && mac && *dataoutlen > 11) | |
349 | CalculateMAC(session, mtypReadResp, blockNum, blockCount, dataout, *dataoutlen - 8 - 2, mac, VerboseMode); | |
350 | ||
351 | return 0; | |
352 | } | |
353 | ||
354 | int MFPWriteBlock(mf4Session *session, uint8_t blockNum, uint8_t *data, bool activateField, bool leaveSignalON, uint8_t *dataout, int maxdataoutlen, int *dataoutlen, uint8_t *mac) { | |
355 | uint8_t rcmd[1 + 2 + 16 + 8] = {0xA3, blockNum, 0x00}; | |
356 | memmove(&rcmd[3], data, 16); | |
357 | if (session) | |
358 | CalculateMAC(session, mtypWriteCmd, blockNum, 1, rcmd, 19, &rcmd[19], VerboseMode); | |
359 | ||
360 | int res = intExchangeRAW14aPlus(rcmd, sizeof(rcmd), activateField, leaveSignalON, dataout, maxdataoutlen, dataoutlen); | |
361 | if (res) | |
362 | return res; | |
363 | ||
364 | if (session) | |
365 | session->W_Ctr++; | |
366 | ||
367 | if (session && mac && *dataoutlen > 3) | |
368 | CalculateMAC(session, mtypWriteResp, blockNum, 1, dataout, *dataoutlen, mac, VerboseMode); | |
369 | ||
370 | return 0; | |
371 | } | |
372 | ||
373 | int mfpReadSector(uint8_t sectorNo, uint8_t keyType, uint8_t *key, uint8_t *dataout, bool verbose) { | |
374 | uint8_t keyn[2] = {0}; | |
375 | bool plain = false; | |
376 | ||
377 | uint16_t uKeyNum = 0x4000 + sectorNo * 2 + (keyType ? 1 : 0); | |
378 | keyn[0] = uKeyNum >> 8; | |
379 | keyn[1] = uKeyNum & 0xff; | |
380 | if (verbose) | |
381 | PrintAndLogEx(INFO, "--sector[%d]:%02x key:%04x", mfNumBlocksPerSector(sectorNo), sectorNo, uKeyNum); | |
382 | ||
383 | mf4Session session; | |
384 | int res = MifareAuth4(&session, keyn, key, true, true, verbose); | |
385 | if (res) { | |
386 | PrintAndLogEx(ERR, "Sector %d authentication error: %d", sectorNo, res); | |
387 | return res; | |
388 | } | |
389 | ||
390 | uint8_t data[250] = {0}; | |
391 | int datalen = 0; | |
392 | uint8_t mac[8] = {0}; | |
393 | uint8_t firstBlockNo = mfFirstBlockOfSector(sectorNo); | |
394 | for (int n = firstBlockNo; n < firstBlockNo + mfNumBlocksPerSector(sectorNo); n++) { | |
395 | res = MFPReadBlock(&session, plain, n & 0xff, 1, false, true, data, sizeof(data), &datalen, mac); | |
396 | if (res) { | |
397 | PrintAndLogEx(ERR, "Sector %d read error: %d", sectorNo, res); | |
398 | DropField(); | |
399 | return res; | |
400 | } | |
401 | ||
402 | if (datalen && data[0] != 0x90) { | |
403 | PrintAndLogEx(ERR, "Sector %d card read error: %02x %s", sectorNo, data[0], mfpGetErrorDescription(data[0])); | |
404 | DropField(); | |
405 | return 5; | |
406 | } | |
407 | if (datalen != 1 + 16 + 8 + 2) { | |
408 | PrintAndLogEx(ERR, "Sector %d error returned data length:%d", sectorNo, datalen); | |
409 | DropField(); | |
410 | return 6; | |
411 | } | |
412 | ||
413 | memcpy(&dataout[(n - firstBlockNo) * 16], &data[1], 16); | |
414 | ||
415 | if (verbose) | |
416 | PrintAndLogEx(INFO, "data[%03d]: %s", n, sprint_hex(&data[1], 16)); | |
417 | ||
418 | if (memcmp(&data[1 + 16], mac, 8)) { | |
419 | PrintAndLogEx(WARNING, "WARNING: mac on block %d not equal...", n); | |
420 | PrintAndLogEx(WARNING, "MAC card: %s", sprint_hex(&data[1 + 16], 8)); | |
421 | PrintAndLogEx(WARNING, "MAC reader: %s", sprint_hex(mac, 8)); | |
422 | ||
423 | if (!verbose) | |
424 | return 7; | |
425 | } else { | |
426 | if (verbose) | |
427 | PrintAndLogEx(INFO, "MAC: %s", sprint_hex(&data[1 + 16], 8)); | |
428 | } | |
429 | } | |
430 | DropField(); | |
431 | ||
432 | return 0; | |
433 | } | |
434 | ||
435 | // Mifare Memory Structure: up to 32 Sectors with 4 blocks each (1k and 2k cards), | |
436 | // plus evtl. 8 sectors with 16 blocks each (4k cards) | |
437 | uint8_t mfNumBlocksPerSector(uint8_t sectorNo) { | |
438 | if (sectorNo < 32) | |
439 | return 4; | |
440 | else | |
441 | return 16; | |
442 | } | |
443 | ||
444 | uint8_t mfFirstBlockOfSector(uint8_t sectorNo) { | |
445 | if (sectorNo < 32) | |
446 | return sectorNo * 4; | |
447 | else | |
448 | return 32 * 4 + (sectorNo - 32) * 16; | |
449 | } | |
450 | ||
451 | uint8_t mfSectorTrailer(uint8_t blockNo) { | |
452 | if (blockNo < 32 * 4) { | |
453 | return (blockNo | 0x03); | |
454 | } else { | |
455 | return (blockNo | 0x0f); | |
456 | } | |
457 | } | |
458 | ||
459 | bool mfIsSectorTrailer(uint8_t blockNo) { | |
460 | return (blockNo == mfSectorTrailer(blockNo)); | |
461 | } | |
462 | ||
463 | uint8_t mfSectorNum(uint8_t blockNo) { | |
464 | if (blockNo < 32 * 4) | |
465 | return blockNo / 4; | |
466 | else | |
467 | return 32 + (blockNo - 32 * 4) / 16; | |
468 | ||
469 | } |