]>
Commit | Line | Data |
---|---|---|
1 | //----------------------------------------------------------------------------- | |
2 | // Copyright (C) 2018 Merlok | |
3 | // | |
4 | // This code is licensed to you under the terms of the GNU GPL, version 2 or, | |
5 | // at your option, any later version. See the LICENSE.txt file for the text of | |
6 | // the license. | |
7 | //----------------------------------------------------------------------------- | |
8 | // High frequency MIFARE Plus commands | |
9 | //----------------------------------------------------------------------------- | |
10 | ||
11 | #include "cmdhfmfp.h" | |
12 | ||
13 | #include <inttypes.h> | |
14 | #include <string.h> | |
15 | #include <stdio.h> | |
16 | #include <stdlib.h> | |
17 | #include <ctype.h> | |
18 | #include "comms.h" | |
19 | #include "cmdmain.h" | |
20 | #include "util.h" | |
21 | #include "ui.h" | |
22 | #include "cmdhf14a.h" | |
23 | #include "mifare.h" | |
24 | #include "mifare4.h" | |
25 | #include "cliparser/cliparser.h" | |
26 | #include "polarssl/libpcrypto.h" | |
27 | ||
28 | static const uint8_t DefaultKey[16] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; | |
29 | ||
30 | typedef struct { | |
31 | uint8_t Code; | |
32 | const char *Description; | |
33 | } PlusErrorsElm; | |
34 | ||
35 | static const PlusErrorsElm PlusErrors[] = { | |
36 | {0xFF, ""}, | |
37 | {0x00, "Unknown error"}, | |
38 | {0x09, "Invalid block number"}, | |
39 | {0x0b, "Command code error"}, | |
40 | {0x0c, "Length error"}, | |
41 | {0x90, "OK"}, | |
42 | }; | |
43 | int PlusErrorsLen = sizeof(PlusErrors) / sizeof(PlusErrorsElm); | |
44 | ||
45 | const char * GetErrorDescription(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 | static int CmdHelp(const char *Cmd); | |
54 | ||
55 | static bool VerboseMode = false; | |
56 | void SetVerboseMode(bool verbose) { | |
57 | VerboseMode = verbose; | |
58 | } | |
59 | ||
60 | int intExchangeRAW14aPlus(uint8_t *datain, int datainlen, bool activateField, bool leaveSignalON, uint8_t *dataout, int maxdataoutlen, int *dataoutlen) { | |
61 | if(VerboseMode) | |
62 | PrintAndLog(">>> %s", sprint_hex(datain, datainlen)); | |
63 | ||
64 | int res = ExchangeRAW14a(datain, datainlen, activateField, leaveSignalON, dataout, maxdataoutlen, dataoutlen); | |
65 | ||
66 | if(VerboseMode) | |
67 | PrintAndLog("<<< %s", sprint_hex(dataout, *dataoutlen)); | |
68 | ||
69 | return res; | |
70 | } | |
71 | ||
72 | int MFPWritePerso(uint8_t *keyNum, uint8_t *key, bool activateField, bool leaveSignalON, uint8_t *dataout, int maxdataoutlen, int *dataoutlen) { | |
73 | uint8_t rcmd[3 + 16] = {0xa8, keyNum[1], keyNum[0], 0x00}; | |
74 | memmove(&rcmd[3], key, 16); | |
75 | ||
76 | return intExchangeRAW14aPlus(rcmd, sizeof(rcmd), activateField, leaveSignalON, dataout, maxdataoutlen, dataoutlen); | |
77 | } | |
78 | ||
79 | int MFPCommitPerso(bool activateField, bool leaveSignalON, uint8_t *dataout, int maxdataoutlen, int *dataoutlen) { | |
80 | uint8_t rcmd[1] = {0xaa}; | |
81 | ||
82 | return intExchangeRAW14aPlus(rcmd, sizeof(rcmd), activateField, leaveSignalON, dataout, maxdataoutlen, dataoutlen); | |
83 | } | |
84 | ||
85 | int CmdHFMFPInfo(const char *cmd) { | |
86 | ||
87 | if (cmd && strlen(cmd) > 0) | |
88 | PrintAndLog("WARNING: command don't have any parameters.\n"); | |
89 | ||
90 | // info about 14a part | |
91 | CmdHF14AInfo(""); | |
92 | ||
93 | // Mifare Plus info | |
94 | UsbCommand c = {CMD_READER_ISO_14443a, {ISO14A_CONNECT | ISO14A_NO_DISCONNECT, 0, 0}}; | |
95 | SendCommand(&c); | |
96 | ||
97 | UsbCommand resp; | |
98 | WaitForResponse(CMD_ACK,&resp); | |
99 | ||
100 | iso14a_card_select_t card; | |
101 | memcpy(&card, (iso14a_card_select_t *)resp.d.asBytes, sizeof(iso14a_card_select_t)); | |
102 | ||
103 | uint64_t select_status = resp.arg[0]; // 0: couldn't read, 1: OK, with ATS, 2: OK, no ATS, 3: proprietary Anticollision | |
104 | ||
105 | if (select_status == 1 || select_status == 2) { | |
106 | PrintAndLog("----------------------------------------------"); | |
107 | PrintAndLog("Mifare Plus info:"); | |
108 | ||
109 | // MIFARE Type Identification Procedure | |
110 | // https://www.nxp.com/docs/en/application-note/AN10833.pdf | |
111 | uint16_t ATQA = card.atqa[0] + (card.atqa[1] << 8); | |
112 | if (ATQA == 0x0004) PrintAndLog("ATQA: Mifare Plus 2k 4bUID"); | |
113 | if (ATQA == 0x0002) PrintAndLog("ATQA: Mifare Plus 4k 4bUID"); | |
114 | if (ATQA == 0x0044) PrintAndLog("ATQA: Mifare Plus 2k 7bUID"); | |
115 | if (ATQA == 0x0042) PrintAndLog("ATQA: Mifare Plus 4k 7bUID"); | |
116 | ||
117 | uint8_t SLmode = 0xff; | |
118 | if (card.sak == 0x08) { | |
119 | PrintAndLog("SAK: Mifare Plus 2k 7bUID"); | |
120 | if (select_status == 2) SLmode = 1; | |
121 | } | |
122 | if (card.sak == 0x18) { | |
123 | PrintAndLog("SAK: Mifare Plus 4k 7bUID"); | |
124 | if (select_status == 2) SLmode = 1; | |
125 | } | |
126 | if (card.sak == 0x10) { | |
127 | PrintAndLog("SAK: Mifare Plus 2k"); | |
128 | if (select_status == 2) SLmode = 2; | |
129 | } | |
130 | if (card.sak == 0x11) { | |
131 | PrintAndLog("SAK: Mifare Plus 4k"); | |
132 | if (select_status == 2) SLmode = 2; | |
133 | } | |
134 | if (card.sak == 0x20) { | |
135 | PrintAndLog("SAK: Mifare Plus SL0/SL3 or Mifare desfire"); | |
136 | if (card.ats_len > 0) { | |
137 | SLmode = 3; | |
138 | ||
139 | // check SL0 | |
140 | uint8_t data[250] = {0}; | |
141 | int datalen = 0; | |
142 | // https://github.com/Proxmark/proxmark3/blob/master/client/scripts/mifarePlus.lua#L161 | |
143 | uint8_t cmd[3 + 16] = {0xa8, 0x90, 0x90, 0x00}; | |
144 | int res = ExchangeRAW14a(cmd, sizeof(cmd), false, false, data, sizeof(data), &datalen); | |
145 | if (!res && datalen > 1 && data[0] == 0x09) { | |
146 | SLmode = 0; | |
147 | } | |
148 | } | |
149 | } | |
150 | ||
151 | if (SLmode != 0xff) | |
152 | PrintAndLog("Mifare Plus SL mode: SL%d", SLmode); | |
153 | else | |
154 | PrintAndLog("Mifare Plus SL mode: unknown("); | |
155 | } else { | |
156 | PrintAndLog("Mifare Plus info not available."); | |
157 | } | |
158 | ||
159 | DropField(); | |
160 | ||
161 | return 0; | |
162 | } | |
163 | ||
164 | int CmdHFMFPWritePerso(const char *cmd) { | |
165 | uint8_t keyNum[64] = {0}; | |
166 | int keyNumLen = 0; | |
167 | uint8_t key[64] = {0}; | |
168 | int keyLen = 0; | |
169 | ||
170 | CLIParserInit("hf mfp wrp", | |
171 | "Executes Write Perso command. Can be used in SL0 mode only.", | |
172 | "Usage:\n\thf mfp wrp 4000 000102030405060708090a0b0c0d0e0f -> write key (00..0f) to key number 4000 \n" | |
173 | "\thf mfp wrp 4000 -> write default key(0xff..0xff) to key number 4000"); | |
174 | ||
175 | void* argtable[] = { | |
176 | arg_param_begin, | |
177 | arg_lit0("vV", "verbose", "show internal data."), | |
178 | arg_str1(NULL, NULL, "<HEX key number (2b)>", NULL), | |
179 | arg_strx0(NULL, NULL, "<HEX key (16b)>", NULL), | |
180 | arg_param_end | |
181 | }; | |
182 | CLIExecWithReturn(cmd, argtable, true); | |
183 | ||
184 | bool verbose = arg_get_lit(1); | |
185 | CLIGetHexWithReturn(2, keyNum, &keyNumLen); | |
186 | CLIGetHexWithReturn(3, key, &keyLen); | |
187 | CLIParserFree(); | |
188 | ||
189 | SetVerboseMode(verbose); | |
190 | ||
191 | if (!keyLen) { | |
192 | memmove(key, DefaultKey, 16); | |
193 | keyLen = 16; | |
194 | } | |
195 | ||
196 | if (keyNumLen != 2) { | |
197 | PrintAndLog("Key number length must be 2 bytes instead of: %d", keyNumLen); | |
198 | return 1; | |
199 | } | |
200 | if (keyLen != 16) { | |
201 | PrintAndLog("Key length must be 16 bytes instead of: %d", keyLen); | |
202 | return 1; | |
203 | } | |
204 | ||
205 | uint8_t data[250] = {0}; | |
206 | int datalen = 0; | |
207 | ||
208 | int res = MFPWritePerso(keyNum, key, true, false, data, sizeof(data), &datalen); | |
209 | if (res) { | |
210 | PrintAndLog("Exchange error: %d", res); | |
211 | return res; | |
212 | } | |
213 | ||
214 | if (datalen != 3) { | |
215 | PrintAndLog("Command must return 3 bytes instead of: %d", datalen); | |
216 | return 1; | |
217 | } | |
218 | ||
219 | if (data[0] != 0x90) { | |
220 | PrintAndLog("Command error: %02x %s", data[0], GetErrorDescription(data[0])); | |
221 | return 1; | |
222 | } | |
223 | PrintAndLog("Write OK."); | |
224 | ||
225 | return 0; | |
226 | } | |
227 | ||
228 | uint16_t CardAddresses[] = {0x9000, 0x9001, 0x9002, 0x9003, 0x9004, 0xA000, 0xA001, 0xA080, 0xA081, 0xC000, 0xC001}; | |
229 | ||
230 | int CmdHFMFPInitPerso(const char *cmd) { | |
231 | int res; | |
232 | uint8_t key[256] = {0}; | |
233 | int keyLen = 0; | |
234 | uint8_t keyNum[2] = {0}; | |
235 | uint8_t data[250] = {0}; | |
236 | int datalen = 0; | |
237 | ||
238 | CLIParserInit("hf mfp initp", | |
239 | "Executes Write Perso command for all card's keys. Can be used in SL0 mode only.", | |
240 | "Usage:\n\thf mfp initp 000102030405060708090a0b0c0d0e0f -> fill all the keys with key (00..0f)\n" | |
241 | "\thf mfp initp -vv -> fill all the keys with default key(0xff..0xff) and show all the data exchange"); | |
242 | ||
243 | void* argtable[] = { | |
244 | arg_param_begin, | |
245 | arg_litn("vV", "verbose", 0, 2, "show internal data."), | |
246 | arg_strx0(NULL, NULL, "<HEX key (16b)>", NULL), | |
247 | arg_param_end | |
248 | }; | |
249 | CLIExecWithReturn(cmd, argtable, true); | |
250 | ||
251 | bool verbose = arg_get_lit(1); | |
252 | bool verbose2 = arg_get_lit(1) > 1; | |
253 | CLIGetHexWithReturn(2, key, &keyLen); | |
254 | CLIParserFree(); | |
255 | ||
256 | if (keyLen && keyLen != 16) { | |
257 | PrintAndLog("Key length must be 16 bytes instead of: %d", keyLen); | |
258 | return 1; | |
259 | } | |
260 | ||
261 | if (!keyLen) | |
262 | memmove(key, DefaultKey, 16); | |
263 | ||
264 | SetVerboseMode(verbose2); | |
265 | for (uint16_t sn = 0x4000; sn < 0x4050; sn++) { | |
266 | keyNum[0] = sn >> 8; | |
267 | keyNum[1] = sn & 0xff; | |
268 | res = MFPWritePerso(keyNum, key, (sn == 0x4000), true, data, sizeof(data), &datalen); | |
269 | if (!res && (datalen == 3) && data[0] == 0x09) { | |
270 | PrintAndLog("2k card detected."); | |
271 | break; | |
272 | } | |
273 | if (res || (datalen != 3) || data[0] != 0x90) { | |
274 | PrintAndLog("Write error on address %04x", sn); | |
275 | break; | |
276 | } | |
277 | } | |
278 | ||
279 | SetVerboseMode(verbose); | |
280 | for (int i = 0; i < sizeof(CardAddresses) / 2; i++) { | |
281 | keyNum[0] = CardAddresses[i] >> 8; | |
282 | keyNum[1] = CardAddresses[i] & 0xff; | |
283 | res = MFPWritePerso(keyNum, key, false, true, data, sizeof(data), &datalen); | |
284 | if (!res && (datalen == 3) && data[0] == 0x09) { | |
285 | PrintAndLog("Skipped[%04x]...", CardAddresses[i]); | |
286 | } else { | |
287 | if (res || (datalen != 3) || data[0] != 0x90) { | |
288 | PrintAndLog("Write error on address %04x", CardAddresses[i]); | |
289 | break; | |
290 | } | |
291 | } | |
292 | } | |
293 | ||
294 | DropField(); | |
295 | ||
296 | if (res) | |
297 | return res; | |
298 | ||
299 | PrintAndLog("Done."); | |
300 | ||
301 | return 0; | |
302 | } | |
303 | ||
304 | int CmdHFMFPCommitPerso(const char *cmd) { | |
305 | CLIParserInit("hf mfp commitp", | |
306 | "Executes Commit Perso command. Can be used in SL0 mode only.", | |
307 | "Usage:\n\thf mfp commitp -> \n"); | |
308 | ||
309 | void* argtable[] = { | |
310 | arg_param_begin, | |
311 | arg_lit0("vV", "verbose", "show internal data."), | |
312 | arg_int0(NULL, NULL, "SL mode", NULL), | |
313 | arg_param_end | |
314 | }; | |
315 | CLIExecWithReturn(cmd, argtable, true); | |
316 | ||
317 | bool verbose = arg_get_lit(1); | |
318 | CLIParserFree(); | |
319 | ||
320 | SetVerboseMode(verbose); | |
321 | ||
322 | uint8_t data[250] = {0}; | |
323 | int datalen = 0; | |
324 | ||
325 | int res = MFPCommitPerso(true, false, data, sizeof(data), &datalen); | |
326 | if (res) { | |
327 | PrintAndLog("Exchange error: %d", res); | |
328 | return res; | |
329 | } | |
330 | ||
331 | if (datalen != 3) { | |
332 | PrintAndLog("Command must return 3 bytes instead of: %d", datalen); | |
333 | return 1; | |
334 | } | |
335 | ||
336 | if (data[0] != 0x90) { | |
337 | PrintAndLog("Command error: %02x %s", data[0], GetErrorDescription(data[0])); | |
338 | return 1; | |
339 | } | |
340 | PrintAndLog("Switch level OK."); | |
341 | ||
342 | return 0; | |
343 | } | |
344 | ||
345 | int CmdHFMFPAuth(const char *cmd) { | |
346 | uint8_t keyn[250] = {0}; | |
347 | int keynlen = 0; | |
348 | uint8_t key[250] = {0}; | |
349 | int keylen = 0; | |
350 | ||
351 | CLIParserInit("hf mfp auth", | |
352 | "Executes AES authentication command in ISO14443-4", | |
353 | "Usage:\n\thf mfp auth 4000 000102030405060708090a0b0c0d0e0f -> executes authentication\n" | |
354 | "\thf mfp auth 9003 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF -v -> executes authentication and shows all the system data\n"); | |
355 | ||
356 | void* argtable[] = { | |
357 | arg_param_begin, | |
358 | arg_lit0("vV", "verbose", "show internal data."), | |
359 | arg_str1(NULL, NULL, "<Key Num (HEX 2 bytes)>", NULL), | |
360 | arg_str1(NULL, NULL, "<Key Value (HEX 16 bytes)>", NULL), | |
361 | arg_param_end | |
362 | }; | |
363 | CLIExecWithReturn(cmd, argtable, true); | |
364 | ||
365 | bool verbose = arg_get_lit(1); | |
366 | CLIGetHexWithReturn(2, keyn, &keynlen); | |
367 | CLIGetHexWithReturn(3, key, &keylen); | |
368 | CLIParserFree(); | |
369 | ||
370 | if (keynlen != 2) { | |
371 | PrintAndLog("ERROR: <Key Num> must be 2 bytes long instead of: %d", keynlen); | |
372 | return 1; | |
373 | } | |
374 | ||
375 | if (keylen != 16) { | |
376 | PrintAndLog("ERROR: <Key Value> must be 16 bytes long instead of: %d", keylen); | |
377 | return 1; | |
378 | } | |
379 | ||
380 | return MifareAuth4(NULL, keyn, key, true, false, verbose); | |
381 | } | |
382 | ||
383 | int CmdHFMFPRdbl(const char *cmd) { | |
384 | //mf4Session session | |
385 | //int res = MifareAuth4(&session, keyn, key, true, false, verbose); | |
386 | //res = Read(); | |
387 | ||
388 | return 0; | |
389 | } | |
390 | ||
391 | int CmdHFMFPRdsc(const char *cmd) { | |
392 | ||
393 | return 0; | |
394 | } | |
395 | ||
396 | int CmdHFMFPWrbl(const char *cmd) { | |
397 | ||
398 | return 0; | |
399 | } | |
400 | ||
401 | static command_t CommandTable[] = | |
402 | { | |
403 | {"help", CmdHelp, 1, "This help"}, | |
404 | {"info", CmdHFMFPInfo, 0, "Info about Mifare Plus tag"}, | |
405 | {"wrp", CmdHFMFPWritePerso, 0, "Write Perso command"}, | |
406 | {"initp", CmdHFMFPInitPerso, 0, "Fills all the card's keys"}, | |
407 | {"commitp", CmdHFMFPCommitPerso, 0, "Move card to SL1 or SL3 mode"}, | |
408 | {"auth", CmdHFMFPAuth, 0, "Authentication in iso1443-4"}, | |
409 | // {"rdbl", CmdHFMFPRdbl, 0, "Read blocks"}, | |
410 | // {"rdsc", CmdHFMFPRdsc, 0, "Read sectors"}, | |
411 | // {"wrbl", CmdHFMFPWrbl, 0, "Write blocks"}, | |
412 | {NULL, NULL, 0, NULL} | |
413 | }; | |
414 | ||
415 | int CmdHFMFP(const char *Cmd) { | |
416 | (void)WaitForResponseTimeout(CMD_ACK,NULL,100); | |
417 | CmdsParse(CommandTable, Cmd); | |
418 | return 0; | |
419 | } | |
420 | ||
421 | int CmdHelp(const char *Cmd) { | |
422 | CmdsHelp(CommandTable); | |
423 | return 0; | |
424 | } |