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