]> git.zerfleddert.de Git - proxmark3-svn/blob - client/cmdhficlass.c
FIX: @marshmellow42 's ST detection fix.
[proxmark3-svn] / client / cmdhficlass.c
1 //-----------------------------------------------------------------------------
2 // Copyright (C) 2010 iZsh <izsh at fail0verflow.com>, Hagen Fritsch
3 // Copyright (C) 2011 Gerhard de Koning Gans
4 // Copyright (C) 2014 Midnitesnake & Andy Davies & Martin Holst Swende
5 //
6 // This code is licensed to you under the terms of the GNU GPL, version 2 or,
7 // at your option, any later version. See the LICENSE.txt file for the text of
8 // the license.
9 //-----------------------------------------------------------------------------
10 // High frequency iClass commands
11 //-----------------------------------------------------------------------------
12
13 #include "cmdhficlass.h"
14
15 static int CmdHelp(const char *Cmd);
16
17 #define NUM_CSNS 15
18 #define ICLASS_KEYS_MAX 8
19 static uint8_t iClass_Key_Table[ICLASS_KEYS_MAX][8] = {
20 { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 },
21 { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 },
22 { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 },
23 { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 },
24 { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 },
25 { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 },
26 { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 },
27 { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }
28 };
29
30 typedef struct iclass_block {
31 uint8_t d[8];
32 } iclass_block_t;
33
34 int usage_hf_iclass_sim(void) {
35 PrintAndLog("Usage: hf iclass sim <option> [CSN]");
36 PrintAndLog(" options");
37 PrintAndLog(" 0 <CSN> simulate the given CSN");
38 PrintAndLog(" 1 simulate default CSN");
39 PrintAndLog(" 2 Reader-attack, gather reader responses to extract elite key");
40 PrintAndLog(" 3 Full simulation using emulator memory (see 'hf iclass eload')");
41 PrintAndLog(" example: hf iclass sim 0 031FEC8AF7FF12E0");
42 PrintAndLog(" example: hf iclass sim 2");
43 PrintAndLog(" example: hf iclass eload 'tagdump.bin'");
44 PrintAndLog(" hf iclass sim 3");
45 return 0;
46 }
47 int usage_hf_iclass_eload(void) {
48 PrintAndLog("Loads iclass tag-dump into emulator memory on device");
49 PrintAndLog("Usage: hf iclass eload f <filename>");
50 PrintAndLog("");
51 PrintAndLog("Example: hf iclass eload f iclass_tagdump-aa162d30f8ff12f1.bin");
52 return 0;
53 }
54 int usage_hf_iclass_decrypt(void) {
55 PrintAndLog("Usage: hf iclass decrypt f <tagdump>");
56 PrintAndLog("");
57 PrintAndLog("OBS! In order to use this function, the file 'iclass_decryptionkey.bin' must reside");
58 PrintAndLog("in the working directory. The file should be 16 bytes binary data");
59 PrintAndLog("");
60 PrintAndLog("example: hf iclass decrypt f tagdump_12312342343.bin");
61 PrintAndLog("");
62 PrintAndLog("OBS! This is pretty stupid implementation, it tries to decrypt every block after block 6. ");
63 PrintAndLog("Correct behaviour would be to decrypt only the application areas where the key is valid,");
64 PrintAndLog("which is defined by the configuration block.");
65 return 1;
66 }
67 int usage_hf_iclass_encrypt(void) {
68 PrintAndLog("Usage: hf iclass encrypt <BlockData>");
69 PrintAndLog("");
70 PrintAndLog("OBS! In order to use this function, the file 'iclass_decryptionkey.bin' must reside");
71 PrintAndLog("in the working directory. The file should be 16 bytes binary data");
72 PrintAndLog("");
73 PrintAndLog("example: hf iclass encrypt 0102030405060708");
74 PrintAndLog("");
75 return 0;
76 }
77 int usage_hf_iclass_dump(void) {
78 PrintAndLog("Usage: hf iclass dump f <fileName> k <Key> c <CreditKey> e|r\n");
79 PrintAndLog("Options:");
80 PrintAndLog(" f <filename> : specify a filename to save dump to");
81 PrintAndLog(" k <Key> : *Access Key as 16 hex symbols or 1 hex to select key from memory");
82 PrintAndLog(" c <CreditKey>: Credit Key as 16 hex symbols or 1 hex to select key from memory");
83 PrintAndLog(" e : If 'e' is specified, the key is interpreted as the 16 byte");
84 PrintAndLog(" Custom Key (KCus), which can be obtained via reader-attack");
85 PrintAndLog(" See 'hf iclass sim 2'. This key should be on iclass-format");
86 PrintAndLog(" r : If 'r' is specified, the key is interpreted as raw block 3/4");
87 PrintAndLog(" NOTE: * = required");
88 PrintAndLog("Samples:");
89 PrintAndLog(" hf iclass dump k 001122334455667B");
90 PrintAndLog(" hf iclass dump k AAAAAAAAAAAAAAAA c 001122334455667B");
91 PrintAndLog(" hf iclass dump k AAAAAAAAAAAAAAAA e");
92 return 0;
93 }
94 int usage_hf_iclass_clone(void) {
95 PrintAndLog("Usage: hf iclass clone f <tagfile.bin> b <first block> l <last block> k <KEY> c e|r");
96 PrintAndLog("Options:");
97 PrintAndLog(" f <filename>: specify a filename to clone from");
98 PrintAndLog(" b <Block> : The first block to clone as 2 hex symbols");
99 PrintAndLog(" l <Last Blk>: Set the Data to write as 16 hex symbols");
100 PrintAndLog(" k <Key> : Access Key as 16 hex symbols or 1 hex to select key from memory");
101 PrintAndLog(" c : If 'c' is specified, the key set is assumed to be the credit key\n");
102 PrintAndLog(" e : If 'e' is specified, elite computations applied to key");
103 PrintAndLog(" r : If 'r' is specified, no computations applied to key");
104 PrintAndLog("Samples:");
105 PrintAndLog(" hf iclass clone f iclass_tagdump-121345.bin b 06 l 1A k 1122334455667788 e");
106 PrintAndLog(" hf iclass clone f iclass_tagdump-121345.bin b 05 l 19 k 0");
107 PrintAndLog(" hf iclass clone f iclass_tagdump-121345.bin b 06 l 19 k 0 e");
108 return -1;
109 }
110 int usage_hf_iclass_writeblock(void) {
111 PrintAndLog("Options:");
112 PrintAndLog(" b <Block> : The block number as 2 hex symbols");
113 PrintAndLog(" d <data> : Set the Data to write as 16 hex symbols");
114 PrintAndLog(" k <Key> : Access Key as 16 hex symbols or 1 hex to select key from memory");
115 PrintAndLog(" c : If 'c' is specified, the key set is assumed to be the credit key\n");
116 PrintAndLog(" e : If 'e' is specified, elite computations applied to key");
117 PrintAndLog(" r : If 'r' is specified, no computations applied to key");
118 PrintAndLog("Samples:");
119 PrintAndLog(" hf iclass writeblk b 0A d AAAAAAAAAAAAAAAA k 001122334455667B");
120 PrintAndLog(" hf iclass writeblk b 1B d AAAAAAAAAAAAAAAA k 001122334455667B c");
121 PrintAndLog(" hf iclass writeblk b 0A d AAAAAAAAAAAAAAAA n 0");
122 return 0;
123 }
124 int usage_hf_iclass_readblock(void) {
125 PrintAndLog("Usage: hf iclass readblk b <Block> k <Key> c e|r\n");
126 PrintAndLog("Options:");
127 PrintAndLog(" b <Block> : The block number as 2 hex symbols");
128 PrintAndLog(" k <Key> : Access Key as 16 hex symbols or 1 hex to select key from memory");
129 PrintAndLog(" c : If 'c' is specified, the key set is assumed to be the credit key\n");
130 PrintAndLog(" e : If 'e' is specified, elite computations applied to key");
131 PrintAndLog(" r : If 'r' is specified, no computations applied to key");
132 PrintAndLog("Samples:");
133 PrintAndLog(" hf iclass readblk b 06 k 0011223344556677");
134 PrintAndLog(" hf iclass readblk b 1B k 0011223344556677 c");
135 PrintAndLog(" hf iclass readblk b 0A k 0");
136 return 0;
137 }
138 int usage_hf_iclass_readtagfile() {
139 PrintAndLog("Usage: hf iclass readtagfile <filename> [startblock] [endblock]");
140 return 1;
141 }
142 int usage_hf_iclass_calc_newkey(void) {
143 PrintAndLog("HELP : Manage iClass Keys in client memory:\n");
144 PrintAndLog("Usage: hf iclass calc_newkey o <Old key> n <New key> s [csn] e");
145 PrintAndLog(" Options:");
146 PrintAndLog(" o <oldkey> : *specify a key as 16 hex symbols or a key number as 1 symbol");
147 PrintAndLog(" n <newkey> : *specify a key as 16 hex symbols or a key number as 1 symbol");
148 PrintAndLog(" s <csn> : specify a card Serial number to diversify the key (if omitted will attempt to read a csn)");
149 PrintAndLog(" e : specify new key as elite calc");
150 PrintAndLog(" ee : specify old and new key as elite calc");
151 PrintAndLog("Samples:");
152 PrintAndLog(" e key to e key given csn : hf iclass calcnewkey o 1122334455667788 n 2233445566778899 s deadbeafdeadbeaf ee");
153 PrintAndLog(" std key to e key read csn: hf iclass calcnewkey o 1122334455667788 n 2233445566778899 e");
154 PrintAndLog(" std to std read csn : hf iclass calcnewkey o 1122334455667788 n 2233445566778899");
155 PrintAndLog("NOTE: * = required\n");
156 return 1;
157 }
158 int usage_hf_iclass_managekeys(void) {
159 PrintAndLog("HELP : Manage iClass Keys in client memory:\n");
160 PrintAndLog("Usage: hf iclass managekeys n [keynbr] k [key] f [filename] s l p\n");
161 PrintAndLog(" Options:");
162 PrintAndLog(" n <keynbr> : specify the keyNbr to set in memory");
163 PrintAndLog(" k <key> : set a key in memory");
164 PrintAndLog(" f <filename>: specify a filename to use with load or save operations");
165 PrintAndLog(" s : save keys in memory to file specified by filename");
166 PrintAndLog(" l : load keys to memory from file specified by filename");
167 PrintAndLog(" p : print keys loaded into memory\n");
168 PrintAndLog("Samples:");
169 PrintAndLog(" set key : hf iclass managekeys n 0 k 1122334455667788");
170 PrintAndLog(" save key file: hf iclass managekeys f mykeys.bin s");
171 PrintAndLog(" load key file: hf iclass managekeys f mykeys.bin l");
172 PrintAndLog(" print keys : hf iclass managekeys p\n");
173 return 0;
174 }
175 int usage_hf_iclass_reader(void) {
176 PrintAndLog("HELP : Act as a Iclass reader:\n");
177 PrintAndLog("Usage: hf iclass reader [h] [1]\n");
178 PrintAndLog("Options:");
179 PrintAndLog(" h This help text");
180 PrintAndLog(" 1 read only 1 tag");
181 PrintAndLog("Samples:");
182 PrintAndLog(" hf iclass reader 1");
183 return 0;
184 }
185 int usage_hf_iclass_replay(void){
186 PrintAndLog("HELP: Replay a collected mac message");
187 PrintAndLog("Usage: hf iclass replay [h] <mac>");
188 PrintAndLog("Options:");
189 PrintAndLog(" h This help text");
190 PrintAndLog(" <mac> Mac bytes to replay (8 hexsymbols)");
191 PrintAndLog("Samples:");
192 PrintAndLog(" hf iclass replay 00112233");
193 return 0;
194 }
195 int usage_hf_iclass_snoop(void){
196 PrintAndLog("HELP: Snoops the communication between reader and tag");
197 PrintAndLog("Usage: hf iclass snoop [h]");
198 PrintAndLog("Samples:");
199 PrintAndLog(" hf iclass snoop");
200 return 0;
201 }
202 int xorbits_8(uint8_t val) {
203 uint8_t res = val ^ (val >> 1); //1st pass
204 res = res ^ (res >> 1); // 2nd pass
205 res = res ^ (res >> 2); // 3rd pass
206 res = res ^ (res >> 4); // 4th pass
207 return res & 1;
208 }
209
210 int CmdHFiClassList(const char *Cmd) {
211 //PrintAndLog("Deprecated command, use 'hf list iclass' instead");
212 CmdHFList("iclass");
213 return 0;
214 }
215
216 int CmdHFiClassSnoop(const char *Cmd) {
217 char cmdp = param_getchar(Cmd, 0);
218 if (cmdp == 'h' || cmdp == 'H') return usage_hf_iclass_snoop();
219
220 UsbCommand c = {CMD_SNOOP_ICLASS};
221 SendCommand(&c);
222 return 0;
223 }
224
225 int CmdHFiClassSim(const char *Cmd) {
226 uint8_t simType = 0;
227 uint8_t CSN[8] = {0, 0, 0, 0, 0, 0, 0, 0};
228
229 if (strlen(Cmd)<1) return usage_hf_iclass_sim();
230
231 simType = param_get8ex(Cmd, 0, 0, 10);
232
233 if(simType == 0)
234 {
235 if (param_gethex(Cmd, 1, CSN, 16)) {
236 PrintAndLog("A CSN should consist of 16 HEX symbols");
237 return usage_hf_iclass_sim();
238 }
239
240 PrintAndLog("--simtype:%02x csn:%s", simType, sprint_hex(CSN, 8));
241 }
242
243 if(simType > 3)
244 {
245 PrintAndLog("Undefined simptype %d", simType);
246 return usage_hf_iclass_sim();
247 }
248
249 uint8_t numberOfCSNs=0;
250 if(simType == 2)
251 {
252 UsbCommand c = {CMD_SIMULATE_TAG_ICLASS, {simType,NUM_CSNS}};
253 UsbCommand resp = {0};
254
255 uint8_t csns[8*NUM_CSNS] = {
256 0x00, 0x0B, 0x0F, 0xFF, 0xF7, 0xFF, 0x12, 0xE0,
257 0x00, 0x04, 0x0E, 0x08, 0xF7, 0xFF, 0x12, 0xE0,
258 0x00, 0x09, 0x0D, 0x05, 0xF7, 0xFF, 0x12, 0xE0,
259 0x00, 0x0A, 0x0C, 0x06, 0xF7, 0xFF, 0x12, 0xE0,
260 0x00, 0x0F, 0x0B, 0x03, 0xF7, 0xFF, 0x12, 0xE0,
261 0x00, 0x08, 0x0A, 0x0C, 0xF7, 0xFF, 0x12, 0xE0,
262 0x00, 0x0D, 0x09, 0x09, 0xF7, 0xFF, 0x12, 0xE0,
263 0x00, 0x0E, 0x08, 0x0A, 0xF7, 0xFF, 0x12, 0xE0,
264 0x00, 0x03, 0x07, 0x17, 0xF7, 0xFF, 0x12, 0xE0,
265 0x00, 0x3C, 0x06, 0xE0, 0xF7, 0xFF, 0x12, 0xE0,
266 0x00, 0x01, 0x05, 0x1D, 0xF7, 0xFF, 0x12, 0xE0,
267 0x00, 0x02, 0x04, 0x1E, 0xF7, 0xFF, 0x12, 0xE0,
268 0x00, 0x07, 0x03, 0x1B, 0xF7, 0xFF, 0x12, 0xE0,
269 0x00, 0x00, 0x02, 0x24, 0xF7, 0xFF, 0x12, 0xE0,
270 0x00, 0x05, 0x01, 0x21, 0xF7, 0xFF, 0x12, 0xE0 };
271
272 memcpy(c.d.asBytes, csns, 8*NUM_CSNS);
273 clearCommandBuffer();
274 SendCommand(&c);
275 if (!WaitForResponseTimeout(CMD_ACK, &resp, -1)) {
276 PrintAndLog("Command timed out");
277 return 0;
278 }
279
280 uint8_t num_mac_responses = resp.arg[1];
281 PrintAndLog("Mac responses: %d MACs obtained (should be %d)", num_mac_responses,NUM_CSNS);
282
283 size_t datalen = NUM_CSNS*24;
284 /*
285 * Now, time to dump to file. We'll use this format:
286 * <8-byte CSN><8-byte CC><4 byte NR><4 byte MAC>....
287 * So, it should wind up as
288 * 8 * 24 bytes.
289 *
290 * The returndata from the pm3 is on the following format
291 * <4 byte NR><4 byte MAC>
292 * CC are all zeroes, CSN is the same as was sent in
293 **/
294 void* dump = malloc(datalen);
295 memset(dump,0,datalen);//<-- Need zeroes for the CC-field
296 uint8_t i = 0;
297 for(i = 0 ; i < NUM_CSNS ; i++) {
298 memcpy(dump+i*24, csns+i*8, 8); //CSN
299 //8 zero bytes here...
300 //Then comes NR_MAC (eight bytes from the response)
301 memcpy(dump+i*24+16, resp.d.asBytes+i*8, 8);
302 }
303 /** Now, save to dumpfile **/
304 saveFile("iclass_mac_attack", "bin", dump, datalen);
305 free(dump);
306 } else {
307 UsbCommand c = {CMD_SIMULATE_TAG_ICLASS, {simType,numberOfCSNs}};
308 memcpy(c.d.asBytes, CSN, 8);
309 clearCommandBuffer();
310 SendCommand(&c);
311 }
312 return 0;
313 }
314
315 int HFiClassReader(const char *Cmd, bool loop, bool verbose) {
316 bool tagFound = false;
317 UsbCommand c = {CMD_READER_ICLASS, {FLAG_ICLASS_READER_CSN |
318 FLAG_ICLASS_READER_CONF | FLAG_ICLASS_READER_AA}};
319 // loop in client not device - else on windows have a communication error
320 c.arg[0] |= FLAG_ICLASS_READER_ONLY_ONCE | FLAG_ICLASS_READER_ONE_TRY;
321 UsbCommand resp;
322 while(!ukbhit()){
323 clearCommandBuffer();
324 SendCommand(&c);
325 if (WaitForResponseTimeout(CMD_ACK,&resp, 4500)) {
326 uint8_t readStatus = resp.arg[0] & 0xff;
327 uint8_t *data = resp.d.asBytes;
328
329 if (verbose) PrintAndLog("Readstatus:%02x", readStatus);
330 if( readStatus == 0){
331 //Aborted
332 if (verbose) PrintAndLog("Quitting...");
333 return 0;
334 }
335 if( readStatus & FLAG_ICLASS_READER_CSN){
336 PrintAndLog("CSN: %s",sprint_hex(data,8));
337 tagFound = true;
338 }
339 if( readStatus & FLAG_ICLASS_READER_CC) PrintAndLog("CC: %s", sprint_hex(data+16, 8));
340 if( readStatus & FLAG_ICLASS_READER_CONF) printIclassDumpInfo(data);
341 if (tagFound && !loop) return 1;
342 } else {
343 if (verbose) PrintAndLog("Command execute timeout");
344 }
345 if (!loop) break;
346 }
347 return 0;
348 }
349
350 int CmdHFiClassReader(const char *Cmd) {
351 char cmdp = param_getchar(Cmd, 0);
352 if (cmdp == 'h' || cmdp == 'H') return usage_hf_iclass_reader();
353 bool findone = (cmdp == '1') ? FALSE : TRUE;
354 return HFiClassReader(Cmd, findone, true);
355 }
356
357 int CmdHFiClassReader_Replay(const char *Cmd) {
358 uint8_t readerType = 0;
359 uint8_t MAC[4]={0x00, 0x00, 0x00, 0x00};
360
361 if (strlen(Cmd)<1) return usage_hf_iclass_replay();
362
363 if (param_gethex(Cmd, 0, MAC, 8)) {
364 PrintAndLog("MAC must include 8 HEX symbols");
365 return 1;
366 }
367
368 UsbCommand c = {CMD_READER_ICLASS_REPLAY, {readerType}};
369 memcpy(c.d.asBytes, MAC, 4);
370 clearCommandBuffer();
371 SendCommand(&c);
372 return 0;
373 }
374
375 int iclassEmlSetMem(uint8_t *data, int blockNum, int blocksCount) {
376 UsbCommand c = {CMD_MIFARE_EML_MEMSET, {blockNum, blocksCount, 0}};
377 memcpy(c.d.asBytes, data, blocksCount * 16);
378 clearCommandBuffer();
379 SendCommand(&c);
380 return 0;
381 }
382
383 int CmdHFiClassELoad(const char *Cmd) {
384
385 char opt = param_getchar(Cmd, 0);
386 if (strlen(Cmd)<1 || opt == 'h' || opt == 'H') return usage_hf_iclass_eload();
387
388 //File handling and reading
389 FILE *f;
390 char filename[FILE_PATH_SIZE];
391 if(opt == 'f' && param_getstr(Cmd, 1, filename) > 0) {
392 f = fopen(filename, "rb");
393 } else {
394 return usage_hf_iclass_eload();
395 }
396
397 if(!f) {
398 PrintAndLog("Failed to read from file '%s'", filename);
399 return 1;
400 }
401
402 fseek(f, 0, SEEK_END);
403 long fsize = ftell(f);
404 fseek(f, 0, SEEK_SET);
405
406 if (fsize < 0) {
407 prnlog("Error, when getting filesize");
408 fclose(f);
409 return 1;
410 }
411
412 uint8_t *dump = malloc(fsize);
413
414 size_t bytes_read = fread(dump, 1, fsize, f);
415 fclose(f);
416
417 printIclassDumpInfo(dump);
418 //Validate
419
420 if (bytes_read < fsize) {
421 prnlog("Error, could only read %d bytes (should be %d)",bytes_read, fsize );
422 free(dump);
423 return 1;
424 }
425 //Send to device
426 uint32_t bytes_sent = 0;
427 uint32_t bytes_remaining = bytes_read;
428
429 while(bytes_remaining > 0){
430 uint32_t bytes_in_packet = MIN(USB_CMD_DATA_SIZE, bytes_remaining);
431 UsbCommand c = {CMD_ICLASS_EML_MEMSET, {bytes_sent,bytes_in_packet,0}};
432 memcpy(c.d.asBytes, dump, bytes_in_packet);
433 clearCommandBuffer();
434 SendCommand(&c);
435 bytes_remaining -= bytes_in_packet;
436 bytes_sent += bytes_in_packet;
437 }
438 free(dump);
439 PrintAndLog("Sent %d bytes of data to device emulator memory", bytes_sent);
440 return 0;
441 }
442
443 static int readKeyfile(const char *filename, size_t len, uint8_t* buffer) {
444 FILE *f = fopen(filename, "rb");
445 if(!f) {
446 PrintAndLog("Failed to read from file '%s'", filename);
447 return 1;
448 }
449 fseek(f, 0, SEEK_END);
450 long fsize = ftell(f);
451 fseek(f, 0, SEEK_SET);
452 size_t bytes_read = fread(buffer, 1, len, f);
453 fclose(f);
454
455 if(fsize != len) {
456 PrintAndLog("Warning, file size is %d, expected %d", fsize, len);
457 return 1;
458 }
459
460 if(bytes_read != len) {
461 PrintAndLog("Warning, could only read %d bytes, expected %d" ,bytes_read, len);
462 return 1;
463 }
464 return 0;
465 }
466
467 int CmdHFiClassDecrypt(const char *Cmd) {
468
469 char opt = param_getchar(Cmd, 0);
470 if (strlen(Cmd)<1 || opt == 'h' || opt == 'H') return usage_hf_iclass_decrypt();
471
472 uint8_t key[16] = { 0 };
473 if(readKeyfile("iclass_decryptionkey.bin", 16, key)) return usage_hf_iclass_decrypt();
474
475 PrintAndLog("Decryption key loaded from file [ok]");
476
477 //Open the tagdump-file
478 FILE *f;
479 char filename[FILE_PATH_SIZE];
480 if(opt == 'f' && param_getstr(Cmd, 1, filename) > 0) {
481 f = fopen(filename, "rb");
482 if (!f) {
483 PrintAndLog("Could not find file %s", filename);
484 return 1;
485 }
486 } else {
487 return usage_hf_iclass_decrypt();
488 }
489
490 fseek(f, 0, SEEK_END);
491 long fsize = ftell(f);
492 fseek(f, 0, SEEK_SET);
493
494 if ( fsize < 0 ) {
495 PrintAndLog("Error, when getting filesize");
496 fclose(f);
497 return 2;
498 }
499
500 uint8_t *decrypted = malloc(fsize);
501
502 size_t bytes_read = fread(decrypted, 1, fsize, f);
503 fclose(f);
504 if ( bytes_read == 0) {
505 PrintAndLog("File reading error");
506 free(decrypted);
507 return 3;
508 }
509
510 picopass_hdr *hdr = (picopass_hdr *)decrypted;
511
512 uint8_t mem = hdr->conf.mem_config;
513 uint8_t chip = hdr->conf.chip_config;
514 uint8_t applimit = hdr->conf.app_limit;
515 uint8_t kb = 2;
516 uint8_t app_areas = 2;
517 uint8_t max_blk = 31;
518 getMemConfig(mem, chip, &max_blk, &app_areas, &kb);
519
520 //Use the first block (CSN) for filename
521 char outfilename[FILE_PATH_SIZE] = {0};
522 snprintf(outfilename, FILE_PATH_SIZE, "iclass_tagdump-%02x%02x%02x%02x%02x%02x%02x%02x-decrypted",
523 hdr->csn[0],hdr->csn[1],hdr->csn[2],hdr->csn[3],
524 hdr->csn[4],hdr->csn[5],hdr->csn[6],hdr->csn[7]);
525
526 // tripledes
527 des3_context ctx = { DES_DECRYPT ,{ 0 } };
528 des3_set2key_dec( &ctx, key);
529
530 uint8_t enc_dump[8] = {0};
531 uint8_t empty[8] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
532 for(uint16_t blocknum=0; blocknum < applimit; ++blocknum) {
533
534 uint8_t idx = blocknum*8;
535 memcpy(enc_dump, decrypted + idx, 8);
536
537 // block 7 or higher, and not empty 0xFF
538 if(blocknum > 6 && memcmp(enc_dump, empty, 8) != 0 ) {
539 des3_crypt_ecb(&ctx, enc_dump, decrypted + idx );
540 }
541 //printvar("decrypted block", decrypted + idx, 8);
542 }
543
544 saveFile(outfilename, "bin", decrypted, fsize);
545 free(decrypted);
546
547 printIclassDumpContents(decrypted, 1, (fsize/8), fsize);
548 return 0;
549 }
550
551 static int iClassEncryptBlkData(uint8_t *blkData) {
552 uint8_t key[16] = { 0 };
553 if(readKeyfile("iclass_decryptionkey.bin", 16, key)) {
554 usage_hf_iclass_encrypt();
555 return 1;
556 }
557 PrintAndLog("Decryption file found... ");
558 uint8_t encryptedData[16];
559 uint8_t *encrypted = encryptedData;
560 des3_context ctx = { DES_DECRYPT ,{ 0 } };
561 des3_set2key_enc( &ctx, key);
562
563 des3_crypt_ecb(&ctx, blkData,encrypted);
564 //printvar("decrypted block", decrypted, 8);
565 memcpy(blkData,encrypted,8);
566
567 return 1;
568 }
569
570 int CmdHFiClassEncryptBlk(const char *Cmd) {
571 uint8_t blkData[8] = {0};
572 char opt = param_getchar(Cmd, 0);
573 if (strlen(Cmd)<1 || opt == 'h' || opt == 'H') return usage_hf_iclass_encrypt();
574
575 //get the bytes to encrypt
576 if (param_gethex(Cmd, 0, blkData, 16)) {
577 PrintAndLog("BlockData must include 16 HEX symbols");
578 return 0;
579 }
580 if (!iClassEncryptBlkData(blkData)) return 0;
581
582 printvar("encrypted block", blkData, 8);
583 return 1;
584 }
585
586 void Calc_wb_mac(uint8_t blockno, uint8_t *data, uint8_t *div_key, uint8_t MAC[4]) {
587 uint8_t WB[9];
588 WB[0] = blockno;
589 memcpy(WB + 1,data,8);
590 doMAC_N(WB,sizeof(WB),div_key,MAC);
591 //printf("Cal wb mac block [%02x][%02x%02x%02x%02x%02x%02x%02x%02x] : MAC [%02x%02x%02x%02x]",WB[0],WB[1],WB[2],WB[3],WB[4],WB[5],WB[6],WB[7],WB[8],MAC[0],MAC[1],MAC[2],MAC[3]);
592 }
593
594 static bool select_only(uint8_t *CSN, uint8_t *CCNR, bool use_credit_key, bool verbose) {
595 UsbCommand resp;
596
597 UsbCommand c = {CMD_READER_ICLASS, {0}};
598 c.arg[0] = FLAG_ICLASS_READER_ONLY_ONCE | FLAG_ICLASS_READER_CC | FLAG_ICLASS_READER_ONE_TRY;
599 if (use_credit_key)
600 c.arg[0] |= FLAG_ICLASS_READER_CEDITKEY;
601
602 clearCommandBuffer();
603 SendCommand(&c);
604 if (!WaitForResponseTimeout(CMD_ACK,&resp,4500)) {
605 PrintAndLog("Command execute timeout");
606 return false;
607 }
608
609 uint8_t isOK = resp.arg[0] & 0xff;
610 uint8_t *data = resp.d.asBytes;
611
612 memcpy(CSN,data,8);
613
614 if (CCNR!=NULL)
615 memcpy(CCNR,data+16,8);
616
617 if(isOK > 0) {
618 if (verbose) PrintAndLog("CSN: %s",sprint_hex(CSN,8));
619 }
620
621 if(isOK <= 1){
622 PrintAndLog("Failed to obtain CC! Aborting");
623 return false;
624 }
625 return true;
626 }
627
628 static bool select_and_auth(uint8_t *KEY, uint8_t *MAC, uint8_t *div_key, bool use_credit_key, bool elite, bool rawkey, bool verbose) {
629 uint8_t CSN[8]={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
630 uint8_t CCNR[12]={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
631
632 if (!select_only(CSN, CCNR, use_credit_key, verbose))
633 return false;
634
635 //get div_key
636 if(rawkey)
637 memcpy(div_key, KEY, 8);
638 else
639 HFiClassCalcDivKey(CSN, KEY, div_key, elite);
640
641 PrintAndLog("Authing with %s: %02x%02x%02x%02x%02x%02x%02x%02x", rawkey ? "raw key" : "diversified key", div_key[0],div_key[1],div_key[2],div_key[3],div_key[4],div_key[5],div_key[6],div_key[7]);
642
643 doMAC(CCNR, div_key, MAC);
644 UsbCommand resp;
645 UsbCommand d = {CMD_ICLASS_AUTHENTICATION, {0}};
646 memcpy(d.d.asBytes, MAC, 4);
647 clearCommandBuffer();
648 SendCommand(&d);
649 if (!WaitForResponseTimeout(CMD_ACK,&resp,4500)) {
650 PrintAndLog("Auth Command execute timeout");
651 return false;
652 }
653 uint8_t isOK = resp.arg[0] & 0xff;
654 if (!isOK) {
655 PrintAndLog("Authentication error");
656 return false;
657 }
658 return true;
659 }
660
661 int CmdHFiClassReader_Dump(const char *Cmd) {
662
663 uint8_t MAC[4] = {0x00,0x00,0x00,0x00};
664 uint8_t div_key[8] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
665 uint8_t c_div_key[8] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
666 uint8_t blockno = 0;
667 uint8_t numblks = 0;
668 uint8_t maxBlk = 31;
669 uint8_t app_areas = 1;
670 uint8_t kb = 2;
671 uint8_t KEY[8] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
672 uint8_t CreditKEY[8] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
673 uint8_t keyNbr = 0;
674 uint8_t dataLen = 0;
675 uint8_t fileNameLen = 0;
676 char filename[FILE_PATH_SIZE]={0};
677 char tempStr[50] = {0};
678 bool have_debit_key = false;
679 bool have_credit_key = false;
680 bool use_credit_key = false;
681 bool elite = false;
682 bool rawkey = false;
683 bool errors = false;
684 uint8_t cmdp = 0;
685
686 while(param_getchar(Cmd, cmdp) != 0x00)
687 {
688 switch(param_getchar(Cmd, cmdp))
689 {
690 case 'h':
691 case 'H':
692 return usage_hf_iclass_dump();
693 case 'c':
694 case 'C':
695 have_credit_key = true;
696 dataLen = param_getstr(Cmd, cmdp+1, tempStr);
697 if (dataLen == 16) {
698 errors = param_gethex(tempStr, 0, CreditKEY, dataLen);
699 } else if (dataLen == 1) {
700 keyNbr = param_get8(Cmd, cmdp+1);
701 if (keyNbr < ICLASS_KEYS_MAX) {
702 memcpy(CreditKEY, iClass_Key_Table[keyNbr], 8);
703 } else {
704 PrintAndLog("\nERROR: Credit KeyNbr is invalid\n");
705 errors = true;
706 }
707 } else {
708 PrintAndLog("\nERROR: Credit Key is incorrect length\n");
709 errors = true;
710 }
711 cmdp += 2;
712 break;
713 case 'e':
714 case 'E':
715 elite = true;
716 cmdp++;
717 break;
718 case 'f':
719 case 'F':
720 fileNameLen = param_getstr(Cmd, cmdp+1, filename);
721 if (fileNameLen < 1) {
722 PrintAndLog("No filename found after f");
723 errors = true;
724 }
725 cmdp += 2;
726 break;
727 case 'k':
728 case 'K':
729 have_debit_key = true;
730 dataLen = param_getstr(Cmd, cmdp+1, tempStr);
731 if (dataLen == 16) {
732 errors = param_gethex(tempStr, 0, KEY, dataLen);
733 } else if (dataLen == 1) {
734 keyNbr = param_get8(Cmd, cmdp+1);
735 if (keyNbr < ICLASS_KEYS_MAX) {
736 memcpy(KEY, iClass_Key_Table[keyNbr], 8);
737 } else {
738 PrintAndLog("\nERROR: Credit KeyNbr is invalid\n");
739 errors = true;
740 }
741 } else {
742 PrintAndLog("\nERROR: Credit Key is incorrect length\n");
743 errors = true;
744 }
745 cmdp += 2;
746 break;
747 case 'r':
748 case 'R':
749 rawkey = true;
750 cmdp++;
751 break;
752 default:
753 PrintAndLog("Unknown parameter '%c'\n", param_getchar(Cmd, cmdp));
754 errors = true;
755 break;
756 }
757 if(errors) return usage_hf_iclass_dump();
758 }
759
760 if (cmdp < 2) return usage_hf_iclass_dump();
761 // if no debit key given try credit key on AA1 (not for iclass but for some picopass this will work)
762 if (!have_debit_key && have_credit_key) use_credit_key = true;
763
764 //get config and first 3 blocks
765 UsbCommand c = {CMD_READER_ICLASS, {FLAG_ICLASS_READER_CSN |
766 FLAG_ICLASS_READER_CONF | FLAG_ICLASS_READER_ONLY_ONCE | FLAG_ICLASS_READER_ONE_TRY}};
767 UsbCommand resp;
768 uint8_t tag_data[255*8];
769
770 clearCommandBuffer();
771 SendCommand(&c);
772 if (!WaitForResponseTimeout(CMD_ACK, &resp, 4500)) {
773 PrintAndLog("Command execute timeout");
774 ul_switch_off_field();
775 return 0;
776 }
777 uint8_t readStatus = resp.arg[0] & 0xff;
778 uint8_t * data = resp.d.asBytes;
779
780 if(readStatus == 0){
781 PrintAndLog("No tag found...");
782 ul_switch_off_field();
783 return 0;
784 }
785
786 if( readStatus & (FLAG_ICLASS_READER_CSN | FLAG_ICLASS_READER_CONF | FLAG_ICLASS_READER_CC)){
787 memcpy(tag_data, data, 8*3);
788 blockno += 2; // 2 to force re-read of block 2 later. (seems to respond differently..)
789 numblks = data[8];
790 getMemConfig(data[13], data[12], &maxBlk, &app_areas, &kb);
791 // large memory - not able to dump pages currently
792 if (numblks > maxBlk) numblks = maxBlk;
793 }
794
795 ul_switch_off_field();
796 // authenticate debit key and get div_key - later store in dump block 3
797 if (!select_and_auth(KEY, MAC, div_key, use_credit_key, elite, rawkey, false)){
798 //try twice - for some reason it sometimes fails the first time...
799 if (!select_and_auth(KEY, MAC, div_key, use_credit_key, elite, rawkey, false)){
800 ul_switch_off_field();
801 return 0;
802 }
803 }
804
805 // begin dump
806 UsbCommand w = {CMD_ICLASS_DUMP, {blockno, numblks-blockno+1}};
807 clearCommandBuffer();
808 SendCommand(&w);
809 if (!WaitForResponseTimeout(CMD_ACK, &resp, 4500)) {
810 PrintAndLog("Command execute time-out 1");
811 ul_switch_off_field();
812 return 1;
813 }
814 uint32_t blocksRead = resp.arg[1];
815 uint8_t isOK = resp.arg[0] & 0xff;
816 if (!isOK && !blocksRead) {
817 PrintAndLog("Read Block Failed");
818 ul_switch_off_field();
819 return 0;
820 }
821 uint32_t startindex = resp.arg[2];
822 if (blocksRead*8 > sizeof(tag_data)-(blockno*8)) {
823 PrintAndLog("Data exceeded Buffer size!");
824 blocksRead = (sizeof(tag_data)/8) - blockno;
825 }
826 // response ok - now get bigbuf content of the dump
827 GetFromBigBuf(tag_data+(blockno*8), blocksRead*8, startindex);
828 WaitForResponse(CMD_ACK,NULL);
829 size_t gotBytes = blocksRead*8 + blockno*8;
830
831 // try AA2
832 if (have_credit_key) {
833 //turn off hf field before authenticating with different key
834 ul_switch_off_field();
835 memset(MAC,0,4);
836 // AA2 authenticate credit key and git c_div_key - later store in dump block 4
837 if (!select_and_auth(CreditKEY, MAC, c_div_key, true, false, false, false)){
838 //try twice - for some reason it sometimes fails the first time...
839 if (!select_and_auth(CreditKEY, MAC, c_div_key, true, false, false, false)){
840 ul_switch_off_field();
841 return 0;
842 }
843 }
844 // do we still need to read more block? (aa2 enabled?)
845 if (maxBlk > blockno+numblks+1) {
846 // setup dump and start
847 w.arg[0] = blockno + blocksRead;
848 w.arg[1] = maxBlk - (blockno + blocksRead);
849 clearCommandBuffer();
850 SendCommand(&w);
851 if (!WaitForResponseTimeout(CMD_ACK, &resp, 4500)) {
852 PrintAndLog("Command execute timeout 2");
853 ul_switch_off_field();
854 return 0;
855 }
856 uint8_t isOK = resp.arg[0] & 0xff;
857 blocksRead = resp.arg[1];
858 if (!isOK && !blocksRead) {
859 PrintAndLog("Read Block Failed 2");
860 ul_switch_off_field();
861 return 0;
862 }
863
864 startindex = resp.arg[2];
865 if (blocksRead*8 > sizeof(tag_data)-gotBytes) {
866 PrintAndLog("Data exceeded Buffer size!");
867 blocksRead = (sizeof(tag_data) - gotBytes)/8;
868 }
869 // get dumped data from bigbuf
870 GetFromBigBuf(tag_data+gotBytes, blocksRead*8, startindex);
871 WaitForResponse(CMD_ACK,NULL);
872
873 gotBytes += blocksRead*8;
874 } else { //field is still on - turn it off...
875 ul_switch_off_field();
876 }
877 }
878
879 // add diversified keys to dump
880 if (have_debit_key) memcpy(tag_data+(3*8),div_key,8);
881 if (have_credit_key) memcpy(tag_data+(4*8),c_div_key,8);
882
883 printf("Num of bytes: %zu\n", gotBytes);
884
885 // print the dump
886 printf("------+--+-------------------------+\n");
887 printf("CSN |00| %s|\n", sprint_hex(tag_data, 8));
888 //printIclassDumpContents(tag_data, 1, (gotBytes/8)-1, gotBytes-8);
889 printIclassDumpContents(tag_data, 1, (gotBytes/8), gotBytes);
890
891 if (filename[0] == 0){
892 snprintf(filename, FILE_PATH_SIZE,"iclass_tagdump-%02x%02x%02x%02x%02x%02x%02x%02x",
893 tag_data[0],tag_data[1],tag_data[2],tag_data[3],
894 tag_data[4],tag_data[5],tag_data[6],tag_data[7]);
895 }
896
897 // save the dump to .bin file
898 PrintAndLog("Saving dump file - %d blocks read", gotBytes/8);
899 saveFile(filename, "bin", tag_data, gotBytes);
900 return 1;
901 }
902
903 static int WriteBlock(uint8_t blockno, uint8_t *bldata, uint8_t *KEY, bool use_credit_key, bool elite, bool rawkey, bool verbose) {
904 uint8_t MAC[4]={0x00,0x00,0x00,0x00};
905 uint8_t div_key[8]={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
906 if (!select_and_auth(KEY, MAC, div_key, use_credit_key, elite, rawkey, verbose))
907 return 0;
908
909 UsbCommand resp;
910
911 Calc_wb_mac(blockno,bldata,div_key,MAC);
912 UsbCommand w = {CMD_ICLASS_WRITEBLOCK, {blockno}};
913 memcpy(w.d.asBytes, bldata, 8);
914 memcpy(w.d.asBytes + 8, MAC, 4);
915
916 clearCommandBuffer();
917 SendCommand(&w);
918 if (!WaitForResponseTimeout(CMD_ACK,&resp,4500)) {
919 PrintAndLog("Write Command execute timeout");
920 return 0;
921 }
922 uint8_t isOK = resp.arg[0] & 0xff;
923 if (!isOK) {
924 PrintAndLog("Write Block Failed");
925 return 0;
926 }
927 PrintAndLog("Write Block Successful");
928 return 1;
929 }
930
931 int CmdHFiClass_WriteBlock(const char *Cmd) {
932 uint8_t blockno=0;
933 uint8_t bldata[8]={0,0,0,0,0,0,0,0};
934 uint8_t KEY[8]={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
935 uint8_t keyNbr = 0;
936 uint8_t dataLen = 0;
937 char tempStr[50] = {0};
938 bool use_credit_key = false;
939 bool elite = false;
940 bool rawkey= false;
941 bool errors = false;
942 uint8_t cmdp = 0;
943 while(param_getchar(Cmd, cmdp) != 0x00)
944 {
945 switch(param_getchar(Cmd, cmdp))
946 {
947 case 'h':
948 case 'H':
949 return usage_hf_iclass_writeblock();
950 case 'b':
951 case 'B':
952 if (param_gethex(Cmd, cmdp+1, &blockno, 2)) {
953 PrintAndLog("Block No must include 2 HEX symbols\n");
954 errors = true;
955 }
956 cmdp += 2;
957 break;
958 case 'c':
959 case 'C':
960 use_credit_key = true;
961 cmdp++;
962 break;
963 case 'd':
964 case 'D':
965 if (param_gethex(Cmd, cmdp+1, bldata, 16))
966 {
967 PrintAndLog("KEY must include 16 HEX symbols\n");
968 errors = true;
969 }
970 cmdp += 2;
971 break;
972 case 'e':
973 case 'E':
974 elite = true;
975 cmdp++;
976 break;
977 case 'k':
978 case 'K':
979 dataLen = param_getstr(Cmd, cmdp+1, tempStr);
980 if (dataLen == 16) {
981 errors = param_gethex(tempStr, 0, KEY, dataLen);
982 } else if (dataLen == 1) {
983 keyNbr = param_get8(Cmd, cmdp+1);
984 if (keyNbr < ICLASS_KEYS_MAX) {
985 memcpy(KEY, iClass_Key_Table[keyNbr], 8);
986 } else {
987 PrintAndLog("\nERROR: Credit KeyNbr is invalid\n");
988 errors = true;
989 }
990 } else {
991 PrintAndLog("\nERROR: Credit Key is incorrect length\n");
992 errors = true;
993 }
994 cmdp += 2;
995 break;
996 case 'r':
997 case 'R':
998 rawkey = true;
999 cmdp++;
1000 break;
1001 default:
1002 PrintAndLog("Unknown parameter '%c'\n", param_getchar(Cmd, cmdp));
1003 errors = true;
1004 break;
1005 }
1006 if(errors) return usage_hf_iclass_writeblock();
1007 }
1008
1009 if (cmdp < 6) return usage_hf_iclass_writeblock();
1010 int ans = WriteBlock(blockno, bldata, KEY, use_credit_key, elite, rawkey, true);
1011 ul_switch_off_field();
1012 return ans;
1013 }
1014
1015 int CmdHFiClassCloneTag(const char *Cmd) {
1016 char filename[FILE_PATH_SIZE] = { 0x00 };
1017 char tempStr[50]={0};
1018 uint8_t KEY[8]={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
1019 uint8_t keyNbr = 0;
1020 uint8_t fileNameLen = 0;
1021 uint8_t startblock = 0;
1022 uint8_t endblock = 0;
1023 uint8_t dataLen = 0;
1024 bool use_credit_key = false;
1025 bool elite = false;
1026 bool rawkey = false;
1027 bool errors = false;
1028 uint8_t cmdp = 0;
1029 while(param_getchar(Cmd, cmdp) != 0x00)
1030 {
1031 switch(param_getchar(Cmd, cmdp))
1032 {
1033 case 'h':
1034 case 'H':
1035 return usage_hf_iclass_clone();
1036 case 'b':
1037 case 'B':
1038 if (param_gethex(Cmd, cmdp+1, &startblock, 2)) {
1039 PrintAndLog("Start Block No must include 2 HEX symbols\n");
1040 errors = true;
1041 }
1042 cmdp += 2;
1043 break;
1044 case 'c':
1045 case 'C':
1046 use_credit_key = true;
1047 cmdp++;
1048 break;
1049 case 'e':
1050 case 'E':
1051 elite = true;
1052 cmdp++;
1053 break;
1054 case 'f':
1055 case 'F':
1056 fileNameLen = param_getstr(Cmd, cmdp+1, filename);
1057 if (fileNameLen < 1) {
1058 PrintAndLog("No filename found after f");
1059 errors = true;
1060 }
1061 cmdp += 2;
1062 break;
1063 case 'k':
1064 case 'K':
1065 dataLen = param_getstr(Cmd, cmdp+1, tempStr);
1066 if (dataLen == 16) {
1067 errors = param_gethex(tempStr, 0, KEY, dataLen);
1068 } else if (dataLen == 1) {
1069 keyNbr = param_get8(Cmd, cmdp+1);
1070 if (keyNbr < ICLASS_KEYS_MAX) {
1071 memcpy(KEY, iClass_Key_Table[keyNbr], 8);
1072 } else {
1073 PrintAndLog("\nERROR: Credit KeyNbr is invalid\n");
1074 errors = true;
1075 }
1076 } else {
1077 PrintAndLog("\nERROR: Credit Key is incorrect length\n");
1078 errors = true;
1079 }
1080 cmdp += 2;
1081 break;
1082 case 'l':
1083 case 'L':
1084 if (param_gethex(Cmd, cmdp+1, &endblock, 2)) {
1085 PrintAndLog("Start Block No must include 2 HEX symbols\n");
1086 errors = true;
1087 }
1088 cmdp += 2;
1089 break;
1090 case 'r':
1091 case 'R':
1092 rawkey = true;
1093 cmdp++;
1094 break;
1095 default:
1096 PrintAndLog("Unknown parameter '%c'\n", param_getchar(Cmd, cmdp));
1097 errors = true;
1098 break;
1099 }
1100 if(errors) return usage_hf_iclass_clone();
1101 }
1102
1103 if (cmdp < 8) return usage_hf_iclass_clone();
1104
1105 FILE *f;
1106
1107 iclass_block_t tag_data[USB_CMD_DATA_SIZE/12];
1108
1109 if ((endblock-startblock+1)*12 > USB_CMD_DATA_SIZE) {
1110 PrintAndLog("Trying to write too many blocks at once. Max: %d", USB_CMD_DATA_SIZE/8);
1111 }
1112 // file handling and reading
1113 f = fopen(filename,"rb");
1114 if(!f) {
1115 PrintAndLog("Failed to read from file '%s'", filename);
1116 return 1;
1117 }
1118
1119 if (startblock<5) {
1120 PrintAndLog("You cannot write key blocks this way. yet... make your start block > 4");
1121 fclose(f);
1122 return 0;
1123 }
1124 // now read data from the file from block 6 --- 19
1125 // ok we will use this struct [data 8 bytes][MAC 4 bytes] for each block calculate all mac number for each data
1126 // then copy to usbcommand->asbytes; the max is 32 - 6 = 24 block 12 bytes each block 288 bytes then we can only accept to clone 21 blocks at the time,
1127 // else we have to create a share memory
1128 int i;
1129 fseek(f,startblock*8,SEEK_SET);
1130 size_t bytes_read = fread(tag_data,sizeof(iclass_block_t),endblock - startblock + 1,f);
1131 if ( bytes_read == 0){
1132 PrintAndLog("File reading error.");
1133 fclose(f);
1134 return 2;
1135 }
1136
1137 uint8_t MAC[4]={0x00,0x00,0x00,0x00};
1138 uint8_t div_key[8]={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
1139
1140 if (!select_and_auth(KEY, MAC, div_key, use_credit_key, elite, rawkey, true))
1141 return 0;
1142
1143 UsbCommand w = {CMD_ICLASS_CLONE,{startblock,endblock}};
1144 uint8_t *ptr;
1145 // calculate all mac for every the block we will write
1146 for (i = startblock; i <= endblock; i++){
1147 Calc_wb_mac(i,tag_data[i - startblock].d,div_key,MAC);
1148 // usb command d start pointer = d + (i - 6) * 12
1149 // memcpy(pointer,tag_data[i - 6],8) 8 bytes
1150 // memcpy(pointer + 8,mac,sizoof(mac) 4 bytes;
1151 // next one
1152 ptr = w.d.asBytes + (i - startblock) * 12;
1153 memcpy(ptr, &(tag_data[i - startblock].d[0]), 8);
1154 memcpy(ptr + 8,MAC, 4);
1155 }
1156 uint8_t p[12];
1157 for (i = 0; i <= endblock - startblock;i++){
1158 memcpy(p,w.d.asBytes + (i * 12),12);
1159 printf("Block |%02x|",i + startblock);
1160 printf(" %02x%02x%02x%02x%02x%02x%02x%02x |",p[0],p[1],p[2],p[3],p[4],p[5],p[6],p[7]);
1161 printf(" MAC |%02x%02x%02x%02x|\n",p[8],p[9],p[10],p[11]);
1162 }
1163 UsbCommand resp;
1164 clearCommandBuffer();
1165 SendCommand(&w);
1166 if (!WaitForResponseTimeout(CMD_ACK,&resp,4500)) {
1167 PrintAndLog("Command execute timeout");
1168 return 0;
1169 }
1170 return 1;
1171 }
1172
1173 static int ReadBlock(uint8_t *KEY, uint8_t blockno, uint8_t keyType, bool elite, bool rawkey, bool verbose) {
1174 uint8_t MAC[4]={0x00,0x00,0x00,0x00};
1175 uint8_t div_key[8]={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
1176
1177 if (!select_and_auth(KEY, MAC, div_key, (keyType==0x18), elite, rawkey, verbose))
1178 return 0;
1179
1180 UsbCommand resp;
1181 UsbCommand w = {CMD_ICLASS_READBLOCK, {blockno}};
1182 clearCommandBuffer();
1183 SendCommand(&w);
1184 if (!WaitForResponseTimeout(CMD_ACK,&resp,4500)) {
1185 PrintAndLog("Command execute timeout");
1186 return 0;
1187 }
1188
1189 uint8_t isOK = resp.arg[0] & 0xff;
1190 if (!isOK) {
1191 PrintAndLog("Read Block Failed");
1192 return 0;
1193 }
1194 //data read is stored in: resp.d.asBytes[0-15]
1195 if (verbose) PrintAndLog("Block %02X: %s\n",blockno, sprint_hex(resp.d.asBytes,8));
1196 return 1;
1197 }
1198
1199 int CmdHFiClass_ReadBlock(const char *Cmd) {
1200 uint8_t blockno=0;
1201 uint8_t keyType = 0x88; //debit key
1202 uint8_t KEY[8]={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
1203 uint8_t keyNbr = 0;
1204 uint8_t dataLen = 0;
1205 char tempStr[50] = {0};
1206 bool elite = false;
1207 bool rawkey = false;
1208 bool errors = false;
1209 uint8_t cmdp = 0;
1210 while(param_getchar(Cmd, cmdp) != 0x00)
1211 {
1212 switch(param_getchar(Cmd, cmdp))
1213 {
1214 case 'h':
1215 case 'H':
1216 return usage_hf_iclass_readblock();
1217 case 'b':
1218 case 'B':
1219 if (param_gethex(Cmd, cmdp+1, &blockno, 2)) {
1220 PrintAndLog("Block No must include 2 HEX symbols\n");
1221 errors = true;
1222 }
1223 cmdp += 2;
1224 break;
1225 case 'c':
1226 case 'C':
1227 keyType = 0x18;
1228 cmdp++;
1229 break;
1230 case 'e':
1231 case 'E':
1232 elite = true;
1233 cmdp++;
1234 break;
1235 case 'k':
1236 case 'K':
1237 dataLen = param_getstr(Cmd, cmdp+1, tempStr);
1238 if (dataLen == 16) {
1239 errors = param_gethex(tempStr, 0, KEY, dataLen);
1240 } else if (dataLen == 1) {
1241 keyNbr = param_get8(Cmd, cmdp+1);
1242 if (keyNbr < ICLASS_KEYS_MAX) {
1243 memcpy(KEY, iClass_Key_Table[keyNbr], 8);
1244 } else {
1245 PrintAndLog("\nERROR: Credit KeyNbr is invalid\n");
1246 errors = true;
1247 }
1248 } else {
1249 PrintAndLog("\nERROR: Credit Key is incorrect length\n");
1250 errors = true;
1251 }
1252 cmdp += 2;
1253 break;
1254 case 'r':
1255 case 'R':
1256 rawkey = true;
1257 cmdp++;
1258 break;
1259 default:
1260 PrintAndLog("Unknown parameter '%c'\n", param_getchar(Cmd, cmdp));
1261 errors = true;
1262 break;
1263 }
1264 if(errors) return usage_hf_iclass_readblock();
1265 }
1266
1267 if (cmdp < 4) return usage_hf_iclass_readblock();
1268
1269 return ReadBlock(KEY, blockno, keyType, elite, rawkey, true);
1270 }
1271
1272 int CmdHFiClass_loclass(const char *Cmd) {
1273 char opt = param_getchar(Cmd, 0);
1274
1275 if (strlen(Cmd)<1 || opt == 'h') {
1276 PrintAndLog("Usage: hf iclass loclass [options]");
1277 PrintAndLog("Options:");
1278 PrintAndLog("h Show this help");
1279 PrintAndLog("t Perform self-test");
1280 PrintAndLog("f <filename> Bruteforce iclass dumpfile");
1281 PrintAndLog(" An iclass dumpfile is assumed to consist of an arbitrary number of");
1282 PrintAndLog(" malicious CSNs, and their protocol responses");
1283 PrintAndLog(" The binary format of the file is expected to be as follows: ");
1284 PrintAndLog(" <8 byte CSN><8 byte CC><4 byte NR><4 byte MAC>");
1285 PrintAndLog(" <8 byte CSN><8 byte CC><4 byte NR><4 byte MAC>");
1286 PrintAndLog(" <8 byte CSN><8 byte CC><4 byte NR><4 byte MAC>");
1287 PrintAndLog(" ... totalling N*24 bytes");
1288 return 0;
1289 }
1290 char fileName[255] = {0};
1291 if(opt == 'f') {
1292 if(param_getstr(Cmd, 1, fileName) > 0) {
1293 return bruteforceFileNoKeys(fileName);
1294 } else {
1295 PrintAndLog("You must specify a filename");
1296 // no return?
1297 }
1298 }
1299 else if(opt == 't') {
1300 int errors = testCipherUtils();
1301 errors += testMAC();
1302 errors += doKeyTests(0);
1303 errors += testElite();
1304 if(errors) prnlog("OBS! There were errors!!!");
1305 return errors;
1306 }
1307 return 0;
1308 }
1309
1310 void printIclassDumpContents(uint8_t *iclass_dump, uint8_t startblock, uint8_t endblock, size_t filesize) {
1311 uint8_t mem_config;
1312 memcpy(&mem_config, iclass_dump + 13,1);
1313 uint8_t maxmemcount;
1314
1315 uint8_t filemaxblock = filesize / 8;
1316
1317 if (mem_config & 0x80)
1318 maxmemcount = 255;
1319 else
1320 maxmemcount = 31;
1321
1322 if (startblock == 0)
1323 startblock = 6;
1324
1325 if ((endblock > maxmemcount) || (endblock == 0))
1326 endblock = maxmemcount;
1327
1328 // remember endblock needs to relate to zero-index arrays.
1329 if (endblock > filemaxblock-1)
1330 endblock = filemaxblock-1;
1331
1332 //PrintAndLog ("startblock: %d, endblock: %d, filesize: %d, maxmemcount: %d, filemaxblock: %d",startblock, endblock,filesize, maxmemcount, filemaxblock);
1333
1334 int i = startblock;
1335 printf("------+--+-------------------------+\n");
1336 while (i <= endblock){
1337 uint8_t *blk = iclass_dump + (i * 8);
1338 printf("Block |%02X| %s\n", i, sprint_hex_ascii(blk, 8) );
1339 i++;
1340 }
1341 printf("------+--+-------------------------+\n");
1342 }
1343
1344 int CmdHFiClassReadTagFile(const char *Cmd) {
1345 int startblock = 0;
1346 int endblock = 0;
1347 char tempnum[5];
1348 FILE *f;
1349 char filename[FILE_PATH_SIZE];
1350 if (param_getstr(Cmd, 0, filename) < 1)
1351 return usage_hf_iclass_readtagfile();
1352
1353 if (param_getstr(Cmd,1,(char *)&tempnum) < 1)
1354 startblock = 0;
1355 else
1356 sscanf(tempnum,"%d",&startblock);
1357
1358 if (param_getstr(Cmd,2,(char *)&tempnum) < 1)
1359 endblock = 0;
1360 else
1361 sscanf(tempnum,"%d",&endblock);
1362
1363 // file handling and reading
1364 f = fopen(filename,"rb");
1365 if(!f) {
1366 PrintAndLog("Failed to read from file '%s'", filename);
1367 return 1;
1368 }
1369 fseek(f, 0, SEEK_END);
1370 long fsize = ftell(f);
1371 fseek(f, 0, SEEK_SET);
1372
1373 if ( fsize < 0 ) {
1374 PrintAndLog("Error, when getting filesize");
1375 fclose(f);
1376 return 1;
1377 }
1378
1379 uint8_t *dump = malloc(fsize);
1380 size_t bytes_read = fread(dump, 1, fsize, f);
1381 fclose(f);
1382
1383 uint8_t *csn = dump;
1384 printf("------+--+-------------------------+\n");
1385 printf("CSN |00| %s|\n", sprint_hex(csn, 8) );
1386 printIclassDumpContents(dump, startblock, endblock, bytes_read);
1387 free(dump);
1388 return 0;
1389 }
1390
1391 void HFiClassCalcDivKey(uint8_t *CSN, uint8_t *KEY, uint8_t *div_key, bool elite){
1392 uint8_t keytable[128] = {0};
1393 uint8_t key_index[8] = {0};
1394 if (elite) {
1395 uint8_t key_sel[8] = { 0 };
1396 uint8_t key_sel_p[8] = { 0 };
1397 hash2(KEY, keytable);
1398 hash1(CSN, key_index);
1399 for(uint8_t i = 0; i < 8 ; i++)
1400 key_sel[i] = keytable[key_index[i]] & 0xFF;
1401
1402 //Permute from iclass format to standard format
1403 permutekey_rev(key_sel, key_sel_p);
1404 diversifyKey(CSN, key_sel_p, div_key);
1405 } else {
1406 diversifyKey(CSN, KEY, div_key);
1407 }
1408 }
1409
1410 //when told CSN, oldkey, newkey, if new key is elite (elite), and if old key was elite (oldElite)
1411 //calculate and return xor_div_key (ready for a key write command)
1412 //print all div_keys if verbose
1413 static void HFiClassCalcNewKey(uint8_t *CSN, uint8_t *OLDKEY, uint8_t *NEWKEY, uint8_t *xor_div_key, bool elite, bool oldElite, bool verbose){
1414 uint8_t old_div_key[8] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
1415 uint8_t new_div_key[8] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
1416 //get old div key
1417 HFiClassCalcDivKey(CSN, OLDKEY, old_div_key, oldElite);
1418 //get new div key
1419 HFiClassCalcDivKey(CSN, NEWKEY, new_div_key, elite);
1420
1421 for (uint8_t i = 0; i < sizeof(old_div_key); i++){
1422 xor_div_key[i] = old_div_key[i] ^ new_div_key[i];
1423 }
1424 if (verbose) {
1425 printf("Old div key : %s\n",sprint_hex(old_div_key,8));
1426 printf("New div key : %s\n",sprint_hex(new_div_key,8));
1427 printf("Xor div key : %s\n",sprint_hex(xor_div_key,8));
1428 }
1429 }
1430
1431 int CmdHFiClassCalcNewKey(const char *Cmd) {
1432 uint8_t OLDKEY[8] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
1433 uint8_t NEWKEY[8] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
1434 uint8_t xor_div_key[8] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
1435 uint8_t CSN[8] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
1436 uint8_t CCNR[12] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
1437 uint8_t keyNbr = 0;
1438 uint8_t dataLen = 0;
1439 char tempStr[50] = {0};
1440 bool givenCSN = false;
1441 bool oldElite = false;
1442 bool elite = false;
1443 bool errors = false;
1444 uint8_t cmdp = 0;
1445 while(param_getchar(Cmd, cmdp) != 0x00)
1446 {
1447 switch(param_getchar(Cmd, cmdp))
1448 {
1449 case 'h':
1450 case 'H':
1451 return usage_hf_iclass_calc_newkey();
1452 case 'e':
1453 case 'E':
1454 dataLen = param_getstr(Cmd, cmdp, tempStr);
1455 if (dataLen==2)
1456 oldElite = true;
1457 elite = true;
1458 cmdp++;
1459 break;
1460 case 'n':
1461 case 'N':
1462 dataLen = param_getstr(Cmd, cmdp+1, tempStr);
1463 if (dataLen == 16) {
1464 errors = param_gethex(tempStr, 0, NEWKEY, dataLen);
1465 } else if (dataLen == 1) {
1466 keyNbr = param_get8(Cmd, cmdp+1);
1467 if (keyNbr < ICLASS_KEYS_MAX) {
1468 memcpy(NEWKEY, iClass_Key_Table[keyNbr], 8);
1469 } else {
1470 PrintAndLog("\nERROR: NewKey Nbr is invalid\n");
1471 errors = true;
1472 }
1473 } else {
1474 PrintAndLog("\nERROR: NewKey is incorrect length\n");
1475 errors = true;
1476 }
1477 cmdp += 2;
1478 break;
1479 case 'o':
1480 case 'O':
1481 dataLen = param_getstr(Cmd, cmdp+1, tempStr);
1482 if (dataLen == 16) {
1483 errors = param_gethex(tempStr, 0, OLDKEY, dataLen);
1484 } else if (dataLen == 1) {
1485 keyNbr = param_get8(Cmd, cmdp+1);
1486 if (keyNbr < ICLASS_KEYS_MAX) {
1487 memcpy(OLDKEY, iClass_Key_Table[keyNbr], 8);
1488 } else {
1489 PrintAndLog("\nERROR: Credit KeyNbr is invalid\n");
1490 errors = true;
1491 }
1492 } else {
1493 PrintAndLog("\nERROR: Credit Key is incorrect length\n");
1494 errors = true;
1495 }
1496 cmdp += 2;
1497 break;
1498 case 's':
1499 case 'S':
1500 givenCSN = true;
1501 if (param_gethex(Cmd, cmdp+1, CSN, 16))
1502 return usage_hf_iclass_calc_newkey();
1503 cmdp += 2;
1504 break;
1505 default:
1506 PrintAndLog("Unknown parameter '%c'\n", param_getchar(Cmd, cmdp));
1507 errors = true;
1508 break;
1509 }
1510 if(errors) return usage_hf_iclass_calc_newkey();
1511 }
1512
1513 if (cmdp < 4) return usage_hf_iclass_calc_newkey();
1514
1515 if (!givenCSN)
1516 if (!select_only(CSN, CCNR, false, true))
1517 return 0;
1518
1519 HFiClassCalcNewKey(CSN, OLDKEY, NEWKEY, xor_div_key, elite, oldElite, true);
1520 return 0;
1521 }
1522
1523 static int loadKeys(char *filename) {
1524 FILE *f;
1525 f = fopen(filename,"rb");
1526 if(!f) {
1527 PrintAndLog("Failed to read from file '%s'", filename);
1528 return 0;
1529 }
1530 fseek(f, 0, SEEK_END);
1531 long fsize = ftell(f);
1532 fseek(f, 0, SEEK_SET);
1533
1534 if ( fsize < 0 ) {
1535 PrintAndLog("Error, when getting filesize");
1536 fclose(f);
1537 return 1;
1538 }
1539
1540 uint8_t *dump = malloc(fsize);
1541
1542 size_t bytes_read = fread(dump, 1, fsize, f);
1543 fclose(f);
1544 if (bytes_read > ICLASS_KEYS_MAX * 8){
1545 PrintAndLog("File is too long to load - bytes: %u", bytes_read);
1546 free(dump);
1547 return 0;
1548 }
1549 uint8_t i = 0;
1550 for (; i < bytes_read/8; i++)
1551 memcpy(iClass_Key_Table[i],dump+(i*8),8);
1552
1553 free(dump);
1554 PrintAndLog("%u keys loaded", i);
1555 return 1;
1556 }
1557
1558 static int saveKeys(char *filename) {
1559 FILE *f;
1560 f = fopen(filename,"wb");
1561 if (!f) {
1562 printf("error opening file %s\n",filename);
1563 return 0;
1564 }
1565 for (uint8_t i = 0; i < ICLASS_KEYS_MAX; i++){
1566 if (fwrite(iClass_Key_Table[i],8,1,f) != 1){
1567 PrintAndLog("save key failed to write to file: %s", filename);
1568 break;
1569 }
1570 }
1571 fclose(f);
1572 return 0;
1573 }
1574
1575 static int printKeys(void) {
1576 PrintAndLog("");
1577 for (uint8_t i = 0; i < ICLASS_KEYS_MAX; i++)
1578 PrintAndLog("%u: %s", i, sprint_hex(iClass_Key_Table[i],8));
1579 PrintAndLog("");
1580 return 0;
1581 }
1582
1583 int CmdHFiClassManageKeys(const char *Cmd) {
1584 uint8_t keyNbr = 0;
1585 uint8_t dataLen = 0;
1586 uint8_t KEY[8] = {0};
1587 char filename[FILE_PATH_SIZE];
1588 uint8_t fileNameLen = 0;
1589 bool errors = false;
1590 uint8_t operation = 0;
1591 char tempStr[20];
1592 uint8_t cmdp = 0;
1593
1594 while(param_getchar(Cmd, cmdp) != 0x00)
1595 {
1596 switch(param_getchar(Cmd, cmdp))
1597 {
1598 case 'h':
1599 case 'H':
1600 return usage_hf_iclass_managekeys();
1601 case 'f':
1602 case 'F':
1603 fileNameLen = param_getstr(Cmd, cmdp+1, filename);
1604 if (fileNameLen < 1) {
1605 PrintAndLog("No filename found after f");
1606 errors = true;
1607 }
1608 cmdp += 2;
1609 break;
1610 case 'n':
1611 case 'N':
1612 keyNbr = param_get8(Cmd, cmdp+1);
1613 if (keyNbr == 0) {
1614 PrintAndLog("Wrong block number");
1615 errors = true;
1616 }
1617 cmdp += 2;
1618 break;
1619 case 'k':
1620 case 'K':
1621 operation += 3; //set key
1622 dataLen = param_getstr(Cmd, cmdp+1, tempStr);
1623 if (dataLen == 16) { //ul-c or ev1/ntag key length
1624 errors = param_gethex(tempStr, 0, KEY, dataLen);
1625 } else {
1626 PrintAndLog("\nERROR: Key is incorrect length\n");
1627 errors = true;
1628 }
1629 cmdp += 2;
1630 break;
1631 case 'p':
1632 case 'P':
1633 operation += 4; //print keys in memory
1634 cmdp++;
1635 break;
1636 case 'l':
1637 case 'L':
1638 operation += 5; //load keys from file
1639 cmdp++;
1640 break;
1641 case 's':
1642 case 'S':
1643 operation += 6; //save keys to file
1644 cmdp++;
1645 break;
1646 default:
1647 PrintAndLog("Unknown parameter '%c'\n", param_getchar(Cmd, cmdp));
1648 errors = true;
1649 break;
1650 }
1651 if(errors) return usage_hf_iclass_managekeys();
1652 }
1653 if (operation == 0){
1654 PrintAndLog("no operation specified (load, save, or print)\n");
1655 return usage_hf_iclass_managekeys();
1656 }
1657 if (operation > 6){
1658 PrintAndLog("Too many operations specified\n");
1659 return usage_hf_iclass_managekeys();
1660 }
1661 if (operation > 4 && fileNameLen == 0){
1662 PrintAndLog("You must enter a filename when loading or saving\n");
1663 return usage_hf_iclass_managekeys();
1664 }
1665
1666 switch (operation){
1667 case 3: memcpy(iClass_Key_Table[keyNbr], KEY, 8); return 1;
1668 case 4: return printKeys();
1669 case 5: return loadKeys(filename);
1670 case 6: return saveKeys(filename);
1671 break;
1672 }
1673 return 0;
1674 }
1675
1676 static command_t CommandTable[] = {
1677 {"help", CmdHelp, 1, "This help"},
1678 {"calcnewkey", CmdHFiClassCalcNewKey, 1, "[options..] Calc Diversified keys (blocks 3 & 4) to write new keys"},
1679 {"clone", CmdHFiClassCloneTag, 0, "[options..] Authenticate and Clone from iClass bin file"},
1680 {"decrypt", CmdHFiClassDecrypt, 1, "[f <fname>] Decrypt tagdump" },
1681 {"dump", CmdHFiClassReader_Dump, 0, "[options..] Authenticate and Dump iClass tag's AA1"},
1682 {"eload", CmdHFiClassELoad, 0, "[f <fname>] (experimental) Load data into iClass emulator memory"},
1683 {"encryptblk", CmdHFiClassEncryptBlk, 1, "<BlockData> Encrypt given block data"},
1684 {"list", CmdHFiClassList, 0, " (Deprecated) List iClass history"},
1685 {"loclass", CmdHFiClass_loclass, 1, "[options..] Use loclass to perform bruteforce of reader attack dump"},
1686 {"managekeys", CmdHFiClassManageKeys, 1, "[options..] Manage the keys to use with iClass"},
1687 {"readblk", CmdHFiClass_ReadBlock, 0, "[options..] Authenticate and Read iClass block"},
1688 {"reader", CmdHFiClassReader, 0, "Read an iClass tag"},
1689 {"readtagfile", CmdHFiClassReadTagFile, 1, "[options..] Display Content from tagfile"},
1690 {"replay", CmdHFiClassReader_Replay, 0, "<mac> Read an iClass tag via Reply Attack"},
1691 {"sim", CmdHFiClassSim, 0, "[options..] Simulate iClass tag"},
1692 {"snoop", CmdHFiClassSnoop, 0, " Eavesdrop iClass communication"},
1693 {"writeblk", CmdHFiClass_WriteBlock, 0, "[options..] Authenticate and Write iClass block"},
1694 {NULL, NULL, 0, NULL}
1695 };
1696
1697 int CmdHFiClass(const char *Cmd) {
1698 clearCommandBuffer();
1699 CmdsParse(CommandTable, Cmd);
1700 return 0;
1701 }
1702
1703 int CmdHelp(const char *Cmd) {
1704 CmdsHelp(CommandTable);
1705 return 0;
1706 }
Impressum, Datenschutz