| 1 | //----------------------------------------------------------------------------- |
| 2 | // Copyright (C) 2018 iceman |
| 3 | // |
| 4 | // This code is licensed to you under the terms of the GNU GPL, version 2 or, |
| 5 | // at your option, any later version. See the LICENSE.txt file for the text of |
| 6 | // the license. |
| 7 | //----------------------------------------------------------------------------- |
| 8 | // Proxmark3 RDV40 Smartcard module commands |
| 9 | //----------------------------------------------------------------------------- |
| 10 | #include "cmdsmartcard.h" |
| 11 | |
| 12 | #include <ctype.h> |
| 13 | |
| 14 | #include "ui.h" |
| 15 | #include "cmdparser.h" |
| 16 | #include "proxmark3.h" |
| 17 | #include "util.h" |
| 18 | #include "smartcard.h" |
| 19 | #include "comms.h" |
| 20 | #include "protocols.h" |
| 21 | #include "cmdhw.h" |
| 22 | #include "cmdhflist.h" |
| 23 | #include "emv/apduinfo.h" // APDUcode description |
| 24 | #include "emv/emvcore.h" // decodeTVL |
| 25 | #include "crypto/libpcrypto.h" // sha512hash |
| 26 | #include "emv/dump.h" // dump_buffer |
| 27 | #include "pcsc.h" |
| 28 | |
| 29 | #define SC_UPGRADE_FILES_DIRECTORY "sc_upgrade_firmware/" |
| 30 | |
| 31 | static bool UseAlternativeSmartcardReader = false; // default: use PM3 RDV40 Smartcard Slot (if available) |
| 32 | |
| 33 | static int CmdHelp(const char *Cmd); |
| 34 | |
| 35 | static int usage_sm_raw(void) { |
| 36 | PrintAndLogEx(NORMAL, "Usage: sc raw [h|r|c] d <0A 0B 0C ... hex>"); |
| 37 | PrintAndLogEx(NORMAL, " h : this help"); |
| 38 | PrintAndLogEx(NORMAL, " r : do not read response"); |
| 39 | PrintAndLogEx(NORMAL, " a : active smartcard without select (reset sc module)"); |
| 40 | PrintAndLogEx(NORMAL, " s : active smartcard with select (get ATR)"); |
| 41 | PrintAndLogEx(NORMAL, " t : executes TLV decoder if it possible"); |
| 42 | PrintAndLogEx(NORMAL, " 0 : use protocol T=0"); |
| 43 | PrintAndLogEx(NORMAL, " d <bytes> : bytes to send"); |
| 44 | PrintAndLogEx(NORMAL, ""); |
| 45 | PrintAndLogEx(NORMAL, "Examples:"); |
| 46 | PrintAndLogEx(NORMAL, " sc raw s 0 d 00a404000e315041592e5359532e4444463031 - `1PAY.SYS.DDF01` PSE directory with get ATR"); |
| 47 | return 0; |
| 48 | } |
| 49 | |
| 50 | static int usage_sm_select(void) { |
| 51 | PrintAndLogEx(NORMAL, "Usage: sc select [h|<reader name>] "); |
| 52 | PrintAndLogEx(NORMAL, " h : this help"); |
| 53 | PrintAndLogEx(NORMAL, " <reader name> : a card reader's name, wildcards allowed, leave empty to pick from available readers"); |
| 54 | PrintAndLogEx(NORMAL, ""); |
| 55 | PrintAndLogEx(NORMAL, "Examples:"); |
| 56 | PrintAndLogEx(NORMAL, " sc select : list available card readers and pick"); |
| 57 | PrintAndLogEx(NORMAL, " sc select Gemalto* : select a connected Gemalto card reader" ); |
| 58 | return 0; |
| 59 | } |
| 60 | |
| 61 | static int usage_sm_reader(void) { |
| 62 | PrintAndLogEx(NORMAL, "Usage: sc reader [h|s]"); |
| 63 | PrintAndLogEx(NORMAL, " h : this help"); |
| 64 | PrintAndLogEx(NORMAL, " s : silent (no messages)"); |
| 65 | PrintAndLogEx(NORMAL, ""); |
| 66 | PrintAndLogEx(NORMAL, "Examples:"); |
| 67 | PrintAndLogEx(NORMAL, " sc reader"); |
| 68 | return 0; |
| 69 | } |
| 70 | |
| 71 | static int usage_sm_info(void) { |
| 72 | PrintAndLogEx(NORMAL, "Usage: s info [h|s]"); |
| 73 | PrintAndLogEx(NORMAL, " h : this help"); |
| 74 | PrintAndLogEx(NORMAL, " s : silent (no messages)"); |
| 75 | PrintAndLogEx(NORMAL, ""); |
| 76 | PrintAndLogEx(NORMAL, "Examples:"); |
| 77 | PrintAndLogEx(NORMAL, " sc info"); |
| 78 | return 0; |
| 79 | } |
| 80 | |
| 81 | static int usage_sm_upgrade(void) { |
| 82 | PrintAndLogEx(NORMAL, "Upgrade RDV4.0 Smartcard Socket Firmware"); |
| 83 | PrintAndLogEx(NORMAL, "Usage: sc upgrade f <file name>"); |
| 84 | PrintAndLogEx(NORMAL, " h : this help"); |
| 85 | PrintAndLogEx(NORMAL, " f <filename> : firmware file name"); |
| 86 | PrintAndLogEx(NORMAL, ""); |
| 87 | PrintAndLogEx(NORMAL, "Examples:"); |
| 88 | PrintAndLogEx(NORMAL, " sc upgrade f SIM010.BIN"); |
| 89 | return 0; |
| 90 | } |
| 91 | |
| 92 | static int usage_sm_setclock(void) { |
| 93 | PrintAndLogEx(NORMAL, "Usage: sc setclock [h] c <clockspeed>"); |
| 94 | PrintAndLogEx(NORMAL, " h : this help"); |
| 95 | PrintAndLogEx(NORMAL, " c <> : clockspeed (0 = 16mhz, 1=8mhz, 2=4mhz) "); |
| 96 | PrintAndLogEx(NORMAL, ""); |
| 97 | PrintAndLogEx(NORMAL, "Examples:"); |
| 98 | PrintAndLogEx(NORMAL, " sc setclock c 2"); |
| 99 | return 0; |
| 100 | } |
| 101 | |
| 102 | static int usage_sm_brute(void) { |
| 103 | PrintAndLogEx(NORMAL, "Tries to bruteforce SFI, "); |
| 104 | PrintAndLogEx(NORMAL, "Usage: sc brute [h]"); |
| 105 | PrintAndLogEx(NORMAL, " h : this help"); |
| 106 | PrintAndLogEx(NORMAL, ""); |
| 107 | PrintAndLogEx(NORMAL, "Examples:"); |
| 108 | PrintAndLogEx(NORMAL, " sc brute"); |
| 109 | return 0; |
| 110 | } |
| 111 | |
| 112 | uint8_t GetATRTA1(uint8_t *atr, size_t atrlen) { |
| 113 | if (atrlen > 2) { |
| 114 | uint8_t T0 = atr[1]; |
| 115 | if (T0 & 0x10) |
| 116 | return atr[2]; |
| 117 | } |
| 118 | |
| 119 | return 0x11; // default value is 0x11, corresponding to fmax=5 MHz, Fi=372, Di=1. |
| 120 | } |
| 121 | |
| 122 | int DiArray[] = { |
| 123 | 0, // b0000 RFU |
| 124 | 1, // b0001 |
| 125 | 2, |
| 126 | 4, |
| 127 | 8, |
| 128 | 16, |
| 129 | 32, // b0110 |
| 130 | 64, // b0111. This was RFU in ISO/IEC 7816-3:1997 and former. Some card readers or drivers may erroneously reject cards using this value |
| 131 | 12, |
| 132 | 20, |
| 133 | 0, // b1010 RFU |
| 134 | 0, |
| 135 | 0, // ... |
| 136 | 0, |
| 137 | 0, |
| 138 | 0 // b1111 RFU |
| 139 | }; |
| 140 | |
| 141 | int FiArray[] = { |
| 142 | 372, // b0000 Historical note: in ISO/IEC 7816-3:1989, this was assigned to cards with internal clock |
| 143 | 372, // b0001 |
| 144 | 558, // b0010 |
| 145 | 744, // b0011 |
| 146 | 1116, // b0100 |
| 147 | 1488, // b0101 |
| 148 | 1860, // b0110 |
| 149 | 0, // b0111 RFU |
| 150 | 0, // b1000 RFU |
| 151 | 512, // b1001 |
| 152 | 768, // b1010 |
| 153 | 1024, // b1011 |
| 154 | 1536, // b1100 |
| 155 | 2048, // b1101 |
| 156 | 0, // b1110 RFU |
| 157 | 0 // b1111 RFU |
| 158 | }; |
| 159 | |
| 160 | float FArray[] = { |
| 161 | 4, // b0000 Historical note: in ISO/IEC 7816-3:1989, this was assigned to cards with internal clock |
| 162 | 5, // b0001 |
| 163 | 6, // b0010 |
| 164 | 8, // b0011 |
| 165 | 12, // b0100 |
| 166 | 16, // b0101 |
| 167 | 20, // b0110 |
| 168 | 0, // b0111 RFU |
| 169 | 0, // b1000 RFU |
| 170 | 5, // b1001 |
| 171 | 7.5, // b1010 |
| 172 | 10, // b1011 |
| 173 | 15, // b1100 |
| 174 | 20, // b1101 |
| 175 | 0, // b1110 RFU |
| 176 | 0 // b1111 RFU |
| 177 | }; |
| 178 | |
| 179 | static int GetATRDi(uint8_t *atr, size_t atrlen) { |
| 180 | uint8_t TA1 = GetATRTA1(atr, atrlen); |
| 181 | |
| 182 | return DiArray[TA1 & 0x0f]; // The 4 low-order bits of TA1 (4th MSbit to 1st LSbit) encode Di |
| 183 | } |
| 184 | |
| 185 | static int GetATRFi(uint8_t *atr, size_t atrlen) { |
| 186 | uint8_t TA1 = GetATRTA1(atr, atrlen); |
| 187 | |
| 188 | return FiArray[TA1 >> 4]; // The 4 high-order bits of TA1 (8th MSbit to 5th LSbit) encode fmax and Fi |
| 189 | } |
| 190 | |
| 191 | static float GetATRF(uint8_t *atr, size_t atrlen) { |
| 192 | uint8_t TA1 = GetATRTA1(atr, atrlen); |
| 193 | |
| 194 | return FArray[TA1 >> 4]; // The 4 high-order bits of TA1 (8th MSbit to 5th LSbit) encode fmax and Fi |
| 195 | } |
| 196 | |
| 197 | static int PrintATR(uint8_t *atr, size_t atrlen) { |
| 198 | |
| 199 | uint8_t T0 = atr[1]; |
| 200 | uint8_t K = T0 & 0x0F; |
| 201 | uint8_t TD1 = 0, T1len = 0, TD1len = 0, TDilen = 0; |
| 202 | bool protocol_T0_present = true; |
| 203 | bool protocol_T15_present = false; |
| 204 | |
| 205 | if (T0 & 0x10) { |
| 206 | PrintAndLog("\t- TA1 (Maximum clock frequency, proposed bit duration) [ 0x%02x ]", atr[2 + T1len]); |
| 207 | T1len++; |
| 208 | } |
| 209 | |
| 210 | if (T0 & 0x20) { |
| 211 | PrintAndLog("\t- TB1 (Deprecated: VPP requirements) [ 0x%02x ]", atr[2 + T1len]); |
| 212 | T1len++; |
| 213 | } |
| 214 | |
| 215 | if (T0 & 0x40) { |
| 216 | PrintAndLog("\t- TC1 (Extra delay between bytes required by card) [ 0x%02x ]", atr[2 + T1len]); |
| 217 | T1len++; |
| 218 | } |
| 219 | |
| 220 | if (T0 & 0x80) { |
| 221 | TD1 = atr[2 + T1len]; |
| 222 | PrintAndLog("\t- TD1 (First offered transmission protocol, presence of TA2..TD2) [ 0x%02x ] Protocol T%d", TD1, TD1 & 0x0f); |
| 223 | protocol_T0_present = false; |
| 224 | if ((TD1 & 0x0f) == 0) { |
| 225 | protocol_T0_present = true; |
| 226 | } |
| 227 | if ((TD1 & 0x0f) == 15) { |
| 228 | protocol_T15_present = true; |
| 229 | } |
| 230 | |
| 231 | T1len++; |
| 232 | |
| 233 | if (TD1 & 0x10) { |
| 234 | PrintAndLog("\t- TA2 (Specific protocol and parameters to be used after the ATR) [ 0x%02x ]", atr[2 + T1len + TD1len]); |
| 235 | TD1len++; |
| 236 | } |
| 237 | if (TD1 & 0x20) { |
| 238 | PrintAndLog("\t- TB2 (Deprecated: VPP precise voltage requirement) [ 0x%02x ]", atr[2 + T1len + TD1len]); |
| 239 | TD1len++; |
| 240 | } |
| 241 | if (TD1 & 0x40) { |
| 242 | PrintAndLog("\t- TC2 (Maximum waiting time for protocol T=0) [ 0x%02x ]", atr[2 + T1len + TD1len]); |
| 243 | TD1len++; |
| 244 | } |
| 245 | if (TD1 & 0x80) { |
| 246 | uint8_t TDi = atr[2 + T1len + TD1len]; |
| 247 | PrintAndLog("\t- TD2 (A supported protocol or more global parameters, presence of TA3..TD3) [ 0x%02x ] Protocol T%d", TDi, TDi & 0x0f); |
| 248 | if ((TDi & 0x0f) == 0) { |
| 249 | protocol_T0_present = true; |
| 250 | } |
| 251 | if ((TDi & 0x0f) == 15) { |
| 252 | protocol_T15_present = true; |
| 253 | } |
| 254 | TD1len++; |
| 255 | |
| 256 | bool nextCycle = true; |
| 257 | uint8_t vi = 3; |
| 258 | while (nextCycle) { |
| 259 | nextCycle = false; |
| 260 | if (TDi & 0x10) { |
| 261 | PrintAndLog("\t- TA%d: 0x%02x", vi, atr[2 + T1len + TD1len + TDilen]); |
| 262 | TDilen++; |
| 263 | } |
| 264 | if (TDi & 0x20) { |
| 265 | PrintAndLog("\t- TB%d: 0x%02x", vi, atr[2 + T1len + TD1len + TDilen]); |
| 266 | TDilen++; |
| 267 | } |
| 268 | if (TDi & 0x40) { |
| 269 | PrintAndLog("\t- TC%d: 0x%02x", vi, atr[2 + T1len + TD1len + TDilen]); |
| 270 | TDilen++; |
| 271 | } |
| 272 | if (TDi & 0x80) { |
| 273 | TDi = atr[2 + T1len + TD1len + TDilen]; |
| 274 | PrintAndLog("\t- TD%d [ 0x%02x ] Protocol T%d", vi, TDi, TDi & 0x0f); |
| 275 | TDilen++; |
| 276 | |
| 277 | nextCycle = true; |
| 278 | vi++; |
| 279 | } |
| 280 | } |
| 281 | } |
| 282 | } |
| 283 | |
| 284 | if (!protocol_T0_present || protocol_T15_present) { // there is CRC Check Byte TCK |
| 285 | uint8_t vxor = 0; |
| 286 | for (int i = 1; i < atrlen; i++) |
| 287 | vxor ^= atr[i]; |
| 288 | |
| 289 | if (vxor) |
| 290 | PrintAndLogEx(WARNING, "Check sum error. Must be 0 got 0x%02X", vxor); |
| 291 | else |
| 292 | PrintAndLogEx(INFO, "Check sum OK."); |
| 293 | } |
| 294 | |
| 295 | if (atr[0] != 0x3b) |
| 296 | PrintAndLogEx(WARNING, "Not a direct convention [ 0x%02x ]", atr[0]); |
| 297 | |
| 298 | uint8_t calen = 2 + T1len + TD1len + TDilen + K; |
| 299 | |
| 300 | if (atrlen != calen && atrlen != calen + 1) // may be CRC |
| 301 | PrintAndLogEx(ERR, "ATR length error. len: %d, T1len: %d, TD1len: %d, TDilen: %d, K: %d", atrlen, T1len, TD1len, TDilen, K); |
| 302 | |
| 303 | if (K > 0) |
| 304 | PrintAndLogEx(INFO, "\nHistorical bytes | len %02d | format %02x", K, atr[2 + T1len + TD1len + TDilen]); |
| 305 | |
| 306 | if (K > 1) { |
| 307 | PrintAndLogEx(INFO, "\tHistorical bytes"); |
| 308 | dump_buffer(&atr[2 + T1len + TD1len + TDilen], K, NULL, 1); |
| 309 | } |
| 310 | |
| 311 | return 0; |
| 312 | } |
| 313 | |
| 314 | static bool smart_getATR(smart_card_atr_t *card) |
| 315 | { |
| 316 | if (UseAlternativeSmartcardReader) { |
| 317 | return pcscGetATR(card); |
| 318 | } else { |
| 319 | UsbCommand c = {CMD_SMART_ATR, {0, 0, 0}}; |
| 320 | SendCommand(&c); |
| 321 | |
| 322 | UsbCommand resp; |
| 323 | if ( !WaitForResponseTimeout(CMD_ACK, &resp, 2500) ) { |
| 324 | return false; |
| 325 | } |
| 326 | |
| 327 | if (resp.arg[0] & 0xff) { |
| 328 | return resp.arg[0] & 0xFF; |
| 329 | } |
| 330 | |
| 331 | memcpy(card, (smart_card_atr_t *)resp.d.asBytes, sizeof(smart_card_atr_t)); |
| 332 | |
| 333 | return true; |
| 334 | } |
| 335 | } |
| 336 | |
| 337 | static bool smart_select(bool silent) { |
| 338 | |
| 339 | smart_card_atr_t card; |
| 340 | if (!smart_getATR(&card)) { |
| 341 | if (!silent) PrintAndLogEx(WARNING, "smart card select failed"); |
| 342 | return false; |
| 343 | } |
| 344 | |
| 345 | if (!silent) { |
| 346 | PrintAndLogEx(INFO, "ISO7816-3 ATR : %s", sprint_hex(card.atr, card.atr_len)); |
| 347 | } |
| 348 | |
| 349 | return true; |
| 350 | } |
| 351 | |
| 352 | |
| 353 | static void smart_transmit(uint8_t *data, uint32_t data_len, uint32_t flags, uint8_t *response, int *response_len, uint32_t max_response_len) |
| 354 | { |
| 355 | // PrintAndLogEx(SUCCESS, "C-TPDU>>>> %s", sprint_hex(data, data_len)); |
| 356 | if (UseAlternativeSmartcardReader) { |
| 357 | *response_len = max_response_len; |
| 358 | pcscTransmit(data, data_len, flags, response, response_len); |
| 359 | } else { |
| 360 | UsbCommand c = {CMD_SMART_RAW, {flags, data_len, 0}}; |
| 361 | memcpy(c.d.asBytes, data, data_len); |
| 362 | SendCommand(&c); |
| 363 | |
| 364 | if (!WaitForResponseTimeout(CMD_ACK, &c, 2500)) { |
| 365 | PrintAndLogEx(WARNING, "smart card response timeout"); |
| 366 | *response_len = -1; |
| 367 | return; |
| 368 | } |
| 369 | |
| 370 | *response_len = c.arg[0]; |
| 371 | if (*response_len > 0) { |
| 372 | memcpy(response, c.d.asBytes, *response_len); |
| 373 | } |
| 374 | } |
| 375 | |
| 376 | if (*response_len <= 0) { |
| 377 | PrintAndLogEx(WARNING, "smart card response failed"); |
| 378 | *response_len = -2; |
| 379 | return; |
| 380 | } |
| 381 | |
| 382 | if (*response_len < 2) { |
| 383 | // PrintAndLogEx(SUCCESS, "R-TPDU %02X | ", response[0]); |
| 384 | return; |
| 385 | } |
| 386 | |
| 387 | // PrintAndLogEx(SUCCESS, "R-TPDU<<<< %s", sprint_hex(response, *response_len)); |
| 388 | // PrintAndLogEx(SUCCESS, "R-TPDU SW %02X%02X | %s", response[*response_len-2], response[*response_len-1], GetAPDUCodeDescription(response[*response_len-2], response[*response_len-1])); |
| 389 | } |
| 390 | |
| 391 | |
| 392 | static int CmdSmartSelect(const char *Cmd) |
| 393 | { |
| 394 | const char *readername; |
| 395 | |
| 396 | if (tolower(param_getchar(Cmd, 0)) == 'h') { |
| 397 | return usage_sm_select(); |
| 398 | } |
| 399 | |
| 400 | if (!PM3hasSmartcardSlot() && !pcscCheckForCardReaders()) { |
| 401 | PrintAndLogEx(WARNING, "No Smartcard Readers available"); |
| 402 | UseAlternativeSmartcardReader = false; |
| 403 | return 1; |
| 404 | } |
| 405 | |
| 406 | int bg, en; |
| 407 | if (param_getptr(Cmd, &bg, &en, 0)) { |
| 408 | UseAlternativeSmartcardReader = pcscSelectAlternativeCardReader(NULL); |
| 409 | } else { |
| 410 | readername = Cmd + bg; |
| 411 | UseAlternativeSmartcardReader = pcscSelectAlternativeCardReader(readername); |
| 412 | } |
| 413 | |
| 414 | return 0; |
| 415 | } |
| 416 | |
| 417 | |
| 418 | static int CmdSmartRaw(const char *Cmd) { |
| 419 | |
| 420 | int hexlen = 0; |
| 421 | bool active = false; |
| 422 | bool active_select = false; |
| 423 | bool useT0 = false; |
| 424 | uint8_t cmdp = 0; |
| 425 | bool errors = false, reply = true, decodeTLV = false, breakloop = false; |
| 426 | uint8_t data[ISO7816_MAX_FRAME_SIZE] = {0x00}; |
| 427 | |
| 428 | while (param_getchar(Cmd, cmdp) != 0x00 && !errors) { |
| 429 | switch (tolower(param_getchar(Cmd, cmdp))) { |
| 430 | case 'h': return usage_sm_raw(); |
| 431 | case 'r': |
| 432 | reply = false; |
| 433 | cmdp++; |
| 434 | break; |
| 435 | case 'a': |
| 436 | active = true; |
| 437 | cmdp++; |
| 438 | break; |
| 439 | case 's': |
| 440 | active_select = true; |
| 441 | cmdp++; |
| 442 | break; |
| 443 | case 't': |
| 444 | decodeTLV = true; |
| 445 | cmdp++; |
| 446 | break; |
| 447 | case '0': |
| 448 | useT0 = true; |
| 449 | cmdp++; |
| 450 | break; |
| 451 | case 'd': { |
| 452 | switch (param_gethex_to_eol(Cmd, cmdp+1, data, sizeof(data), &hexlen)) { |
| 453 | case 1: |
| 454 | PrintAndLogEx(WARNING, "Invalid HEX value."); |
| 455 | return 1; |
| 456 | case 2: |
| 457 | PrintAndLogEx(WARNING, "Too many bytes. Max %d bytes", sizeof(data)); |
| 458 | return 1; |
| 459 | case 3: |
| 460 | PrintAndLogEx(WARNING, "Hex must have even number of digits."); |
| 461 | return 1; |
| 462 | } |
| 463 | cmdp++; |
| 464 | breakloop = true; |
| 465 | break; |
| 466 | } |
| 467 | default: |
| 468 | PrintAndLogEx(WARNING, "Unknown parameter '%c'", param_getchar(Cmd, cmdp)); |
| 469 | errors = true; |
| 470 | break; |
| 471 | } |
| 472 | |
| 473 | if ( breakloop ) |
| 474 | break; |
| 475 | } |
| 476 | |
| 477 | //Validations |
| 478 | if (errors || cmdp == 0 ) return usage_sm_raw(); |
| 479 | |
| 480 | uint32_t flags = 0; |
| 481 | uint32_t protocol = 0; |
| 482 | if (active || active_select) { |
| 483 | flags |= SC_CONNECT; |
| 484 | if (active_select) |
| 485 | flags |= SC_SELECT; |
| 486 | } |
| 487 | if (hexlen > 0) { |
| 488 | if (useT0) |
| 489 | protocol = SC_RAW_T0; |
| 490 | else |
| 491 | protocol = SC_RAW; |
| 492 | } |
| 493 | |
| 494 | int response_len = 0; |
| 495 | uint8_t *response = NULL; |
| 496 | if (reply) { |
| 497 | response = calloc(ISO7816_MAX_FRAME_SIZE, sizeof(uint8_t)); |
| 498 | if ( !response ) |
| 499 | return 1; |
| 500 | } |
| 501 | |
| 502 | smart_transmit(data, hexlen, flags|protocol, response, &response_len, ISO7816_MAX_FRAME_SIZE); |
| 503 | |
| 504 | // reading response from smart card |
| 505 | if ( reply ) { |
| 506 | if ( response_len < 0 ) { |
| 507 | free(response); |
| 508 | return 2; |
| 509 | } |
| 510 | |
| 511 | if ( response[0] == 0x6C ) { |
| 512 | data[4] = response[1]; |
| 513 | smart_transmit(data, hexlen, protocol, response, &response_len, ISO7816_MAX_FRAME_SIZE); |
| 514 | data[4] = 0; |
| 515 | } |
| 516 | |
| 517 | if (decodeTLV && response_len > 4) |
| 518 | TLVPrintFromBuffer(response, response_len-2); |
| 519 | |
| 520 | free(response); |
| 521 | } |
| 522 | return 0; |
| 523 | } |
| 524 | |
| 525 | |
| 526 | int ExchangeAPDUSC(uint8_t *APDU, int APDUlen, bool activateCard, bool leaveSignalON, uint8_t *response, int maxresponselen, int *responselen) |
| 527 | { |
| 528 | uint8_t TPDU[ISO7816_MAX_FRAME_SIZE]; |
| 529 | |
| 530 | *responselen = 0; |
| 531 | |
| 532 | if (activateCard) |
| 533 | smart_select(false); |
| 534 | |
| 535 | uint32_t flags = SC_RAW_T0; |
| 536 | if (activateCard) { |
| 537 | flags |= SC_SELECT | SC_CONNECT; |
| 538 | } |
| 539 | |
| 540 | if (APDUlen == 4) { // Case 1 |
| 541 | memcpy(TPDU, APDU, 4); |
| 542 | TPDU[4] = 0x00; |
| 543 | smart_transmit(TPDU, 5, flags, response, responselen, maxresponselen); |
| 544 | } else if (APDUlen == 5) { // Case 2 Short |
| 545 | smart_transmit(APDU, 5, flags, response, responselen, maxresponselen); |
| 546 | if (response[0] == 0x6C) { // wrong Le |
| 547 | uint16_t Le = APDU[4] ? APDU[4] : 256; |
| 548 | uint8_t La = response[1]; |
| 549 | memcpy(TPDU, APDU, 5); |
| 550 | TPDU[4] = La; |
| 551 | smart_transmit(TPDU, 5, SC_RAW_T0, response, responselen, maxresponselen); |
| 552 | if (Le < La && *responselen >= 0) { |
| 553 | response[Le] = response[*responselen-2]; |
| 554 | response[Le+1] = response[*responselen-1]; |
| 555 | *responselen = Le + 2; |
| 556 | } |
| 557 | } |
| 558 | } else if (APDU[4] != 0 && APDUlen == 5 + APDU[4]) { // Case 3 Short |
| 559 | smart_transmit(APDU, APDUlen, flags, response, responselen, maxresponselen); |
| 560 | } else if (APDU[4] != 0 && APDUlen == 5 + APDU[4] + 1) { // Case 4 Short |
| 561 | smart_transmit(APDU, APDUlen-1, flags, response, responselen, maxresponselen); |
| 562 | if (response[0] == 0x90 && response[1] == 0x00) { |
| 563 | uint8_t Le = APDU[APDUlen-1]; |
| 564 | uint8_t get_response[5] = {0x00, ISO7816_GET_RESPONSE, 0x00, 0x00, Le}; |
| 565 | return ExchangeAPDUSC(get_response, 5, false, leaveSignalON, response, maxresponselen, responselen); |
| 566 | } |
| 567 | } else { // Long Cases not yet implemented |
| 568 | PrintAndLogEx(ERR, "Long APDUs not yet implemented"); |
| 569 | *responselen = -3; |
| 570 | } |
| 571 | |
| 572 | if (*responselen < 0 ) { |
| 573 | return 1; |
| 574 | } else { |
| 575 | return 0; |
| 576 | } |
| 577 | } |
| 578 | |
| 579 | |
| 580 | static int CmdSmartUpgrade(const char *Cmd) { |
| 581 | |
| 582 | PrintAndLogEx(NORMAL, ""); |
| 583 | PrintAndLogEx(WARNING, "WARNING - RDV4.0 Smartcard Socket Firmware upgrade."); |
| 584 | PrintAndLogEx(WARNING, "A dangerous command, do wrong and you will brick the smart card socket"); |
| 585 | PrintAndLogEx(NORMAL, ""); |
| 586 | |
| 587 | FILE *f; |
| 588 | char filename[FILE_PATH_SIZE] = {0}; |
| 589 | uint8_t cmdp = 0; |
| 590 | bool errors = false; |
| 591 | |
| 592 | while (param_getchar(Cmd, cmdp) != 0x00 && !errors) { |
| 593 | switch (tolower(param_getchar(Cmd, cmdp))) { |
| 594 | case 'f': |
| 595 | //File handling and reading |
| 596 | if ( param_getstr(Cmd, cmdp+1, filename, FILE_PATH_SIZE) >= FILE_PATH_SIZE ) { |
| 597 | PrintAndLogEx(FAILED, "Filename too long"); |
| 598 | errors = true; |
| 599 | break; |
| 600 | } |
| 601 | cmdp += 2; |
| 602 | break; |
| 603 | case 'h': |
| 604 | return usage_sm_upgrade(); |
| 605 | default: |
| 606 | PrintAndLogEx(WARNING, "Unknown parameter '%c'", param_getchar(Cmd, cmdp)); |
| 607 | errors = true; |
| 608 | break; |
| 609 | } |
| 610 | } |
| 611 | |
| 612 | //Validations |
| 613 | if (errors || cmdp == 0 ) return usage_sm_upgrade(); |
| 614 | |
| 615 | if (strchr(filename, '\\') || strchr(filename, '/')) { |
| 616 | PrintAndLogEx(FAILED, "Filename must not contain \\ or /. Firmware file will be found in client/sc_upgrade_firmware directory."); |
| 617 | return 1; |
| 618 | } |
| 619 | |
| 620 | char sc_upgrade_file_path[strlen(get_my_executable_directory()) + strlen(SC_UPGRADE_FILES_DIRECTORY) + strlen(filename) + 1]; |
| 621 | strcpy(sc_upgrade_file_path, get_my_executable_directory()); |
| 622 | strcat(sc_upgrade_file_path, SC_UPGRADE_FILES_DIRECTORY); |
| 623 | strcat(sc_upgrade_file_path, filename); |
| 624 | if (strlen(sc_upgrade_file_path) >= FILE_PATH_SIZE ) { |
| 625 | PrintAndLogEx(FAILED, "Filename too long"); |
| 626 | return 1; |
| 627 | } |
| 628 | |
| 629 | char sha512filename[FILE_PATH_SIZE] = {'\0'}; |
| 630 | char *bin_extension = filename; |
| 631 | char *dot_position = NULL; |
| 632 | while ((dot_position = strchr(bin_extension, '.')) != NULL) { |
| 633 | bin_extension = dot_position + 1; |
| 634 | } |
| 635 | if (!strcmp(bin_extension, "BIN") |
| 636 | #ifdef _WIN32 |
| 637 | || !strcmp(bin_extension, "bin") |
| 638 | #endif |
| 639 | ) { |
| 640 | memcpy(sha512filename, filename, strlen(filename) - strlen("bin")); |
| 641 | strcat(sha512filename, "sha512.txt"); |
| 642 | } else { |
| 643 | PrintAndLogEx(FAILED, "Filename extension of Firmware Upgrade File must be .BIN"); |
| 644 | return 1; |
| 645 | } |
| 646 | |
| 647 | PrintAndLogEx(INFO, "Checking integrity using SHA512 File %s ...", sha512filename); |
| 648 | char sc_upgrade_sha512file_path[strlen(get_my_executable_directory()) + strlen(SC_UPGRADE_FILES_DIRECTORY) + strlen(sha512filename) + 1]; |
| 649 | strcpy(sc_upgrade_sha512file_path, get_my_executable_directory()); |
| 650 | strcat(sc_upgrade_sha512file_path, SC_UPGRADE_FILES_DIRECTORY); |
| 651 | strcat(sc_upgrade_sha512file_path, sha512filename); |
| 652 | if (strlen(sc_upgrade_sha512file_path) >= FILE_PATH_SIZE ) { |
| 653 | PrintAndLogEx(FAILED, "Filename too long"); |
| 654 | return 1; |
| 655 | } |
| 656 | |
| 657 | // load firmware file |
| 658 | f = fopen(sc_upgrade_file_path, "rb"); |
| 659 | if ( !f ){ |
| 660 | PrintAndLogEx(FAILED, "Firmware file not found or locked."); |
| 661 | return 1; |
| 662 | } |
| 663 | |
| 664 | // get filesize in order to malloc memory |
| 665 | fseek(f, 0, SEEK_END); |
| 666 | size_t fsize = ftell(f); |
| 667 | fseek(f, 0, SEEK_SET); |
| 668 | |
| 669 | if (fsize < 0) { |
| 670 | PrintAndLogEx(FAILED, "Could not determine size of firmware file"); |
| 671 | fclose(f); |
| 672 | return 1; |
| 673 | } |
| 674 | |
| 675 | uint8_t *dump = calloc(fsize, sizeof(uint8_t)); |
| 676 | if (!dump) { |
| 677 | PrintAndLogEx(FAILED, "Could not allocate memory for firmware"); |
| 678 | fclose(f); |
| 679 | return 1; |
| 680 | } |
| 681 | |
| 682 | size_t firmware_size = fread(dump, 1, fsize, f); |
| 683 | if (f) |
| 684 | fclose(f); |
| 685 | |
| 686 | // load sha512 file |
| 687 | f = fopen(sc_upgrade_sha512file_path, "rb"); |
| 688 | if ( !f ){ |
| 689 | PrintAndLogEx(FAILED, "SHA-512 file not found or locked."); |
| 690 | return 1; |
| 691 | } |
| 692 | |
| 693 | // get filesize in order to malloc memory |
| 694 | fseek(f, 0, SEEK_END); |
| 695 | fsize = ftell(f); |
| 696 | fseek(f, 0, SEEK_SET); |
| 697 | |
| 698 | if (fsize < 0) { |
| 699 | PrintAndLogEx(FAILED, "Could not determine size of SHA-512 file"); |
| 700 | fclose(f); |
| 701 | return 1; |
| 702 | } |
| 703 | |
| 704 | if (fsize < 128) { |
| 705 | PrintAndLogEx(FAILED, "SHA-512 file too short"); |
| 706 | fclose(f); |
| 707 | return 1; |
| 708 | } |
| 709 | |
| 710 | char hashstring[129]; |
| 711 | size_t bytes_read = fread(hashstring, 1, 128, f); |
| 712 | hashstring[128] = '\0'; |
| 713 | |
| 714 | if (f) |
| 715 | fclose(f); |
| 716 | |
| 717 | uint8_t hash1[64]; |
| 718 | if (bytes_read != 128 || param_gethex(hashstring, 0, hash1, 128)) { |
| 719 | PrintAndLogEx(FAILED, "Couldn't read SHA-512 file"); |
| 720 | return 1; |
| 721 | } |
| 722 | |
| 723 | uint8_t hash2[64]; |
| 724 | if (sha512hash(dump, firmware_size, hash2)) { |
| 725 | PrintAndLogEx(FAILED, "Couldn't calculate SHA-512 of Firmware"); |
| 726 | return 1; |
| 727 | } |
| 728 | |
| 729 | if (memcmp(hash1, hash2, 64)) { |
| 730 | PrintAndLogEx(FAILED, "Couldn't verify integrity of Firmware file (wrong SHA-512)"); |
| 731 | return 1; |
| 732 | } |
| 733 | |
| 734 | PrintAndLogEx(SUCCESS, "RDV4.0 Smartcard Socket Firmware uploading to PM3"); |
| 735 | |
| 736 | //Send to device |
| 737 | uint32_t index = 0; |
| 738 | uint32_t bytes_sent = 0; |
| 739 | uint32_t bytes_remaining = firmware_size; |
| 740 | |
| 741 | while (bytes_remaining > 0){ |
| 742 | uint32_t bytes_in_packet = MIN(USB_CMD_DATA_SIZE, bytes_remaining); |
| 743 | UsbCommand c = {CMD_SMART_UPLOAD, {index + bytes_sent, bytes_in_packet, 0}}; |
| 744 | |
| 745 | // Fill usb bytes with 0xFF |
| 746 | memset(c.d.asBytes, 0xFF, USB_CMD_DATA_SIZE); |
| 747 | memcpy(c.d.asBytes, dump + bytes_sent, bytes_in_packet); |
| 748 | clearCommandBuffer(); |
| 749 | SendCommand(&c); |
| 750 | if ( !WaitForResponseTimeout(CMD_ACK, NULL, 2000) ) { |
| 751 | PrintAndLogEx(WARNING, "timeout while waiting for reply."); |
| 752 | free(dump); |
| 753 | return 1; |
| 754 | } |
| 755 | |
| 756 | bytes_remaining -= bytes_in_packet; |
| 757 | bytes_sent += bytes_in_packet; |
| 758 | printf("."); fflush(stdout); |
| 759 | } |
| 760 | free(dump); |
| 761 | printf("\n"); |
| 762 | PrintAndLogEx(SUCCESS, "RDV4.0 Smartcard Socket Firmware updating, don\'t turn off your PM3!"); |
| 763 | |
| 764 | // trigger the firmware upgrade |
| 765 | UsbCommand c = {CMD_SMART_UPGRADE, {firmware_size, 0, 0}}; |
| 766 | clearCommandBuffer(); |
| 767 | SendCommand(&c); |
| 768 | UsbCommand resp; |
| 769 | if ( !WaitForResponseTimeout(CMD_ACK, &resp, 2500) ) { |
| 770 | PrintAndLogEx(WARNING, "timeout while waiting for reply."); |
| 771 | return 1; |
| 772 | } |
| 773 | if ( (resp.arg[0] & 0xFF ) ) |
| 774 | PrintAndLogEx(SUCCESS, "RDV4.0 Smartcard Socket Firmware upgraded successful"); |
| 775 | else |
| 776 | PrintAndLogEx(FAILED, "RDV4.0 Smartcard Socket Firmware Upgrade failed"); |
| 777 | return 0; |
| 778 | } |
| 779 | |
| 780 | |
| 781 | static int CmdSmartInfo(const char *Cmd){ |
| 782 | uint8_t cmdp = 0; |
| 783 | bool errors = false, silent = false; |
| 784 | |
| 785 | while (param_getchar(Cmd, cmdp) != 0x00 && !errors) { |
| 786 | switch (tolower(param_getchar(Cmd, cmdp))) { |
| 787 | case 'h': return usage_sm_info(); |
| 788 | case 's': |
| 789 | silent = true; |
| 790 | break; |
| 791 | default: |
| 792 | PrintAndLogEx(WARNING, "Unknown parameter '%c'", param_getchar(Cmd, cmdp)); |
| 793 | errors = true; |
| 794 | break; |
| 795 | } |
| 796 | cmdp++; |
| 797 | } |
| 798 | |
| 799 | //Validations |
| 800 | if (errors ) return usage_sm_info(); |
| 801 | |
| 802 | smart_card_atr_t card; |
| 803 | if (!smart_getATR(&card)) { |
| 804 | if (!silent) PrintAndLogEx(WARNING, "smart card select failed"); |
| 805 | return 1; |
| 806 | } |
| 807 | |
| 808 | // print header |
| 809 | PrintAndLogEx(INFO, "--- Smartcard Information ---------"); |
| 810 | PrintAndLogEx(INFO, "-------------------------------------------------------------"); |
| 811 | PrintAndLogEx(INFO, "ISO7618-3 ATR : %s", sprint_hex(card.atr, card.atr_len)); |
| 812 | PrintAndLogEx(INFO, "\nhttp://smartcard-atr.appspot.com/parse?ATR=%s", sprint_hex_inrow(card.atr, card.atr_len) ); |
| 813 | |
| 814 | // print ATR |
| 815 | PrintAndLogEx(NORMAL, ""); |
| 816 | PrintAndLogEx(INFO, "ATR"); |
| 817 | PrintATR(card.atr, card.atr_len); |
| 818 | |
| 819 | // print D/F (brom byte TA1 or defaults) |
| 820 | PrintAndLogEx(NORMAL, ""); |
| 821 | PrintAndLogEx(INFO, "D/F (TA1)"); |
| 822 | int Di = GetATRDi(card.atr, card.atr_len); |
| 823 | int Fi = GetATRFi(card.atr, card.atr_len); |
| 824 | float F = GetATRF(card.atr, card.atr_len); |
| 825 | if (GetATRTA1(card.atr, card.atr_len) == 0x11) |
| 826 | PrintAndLogEx(INFO, "Using default values..."); |
| 827 | |
| 828 | PrintAndLogEx(NORMAL, "\t- Di=%d", Di); |
| 829 | PrintAndLogEx(NORMAL, "\t- Fi=%d", Fi); |
| 830 | PrintAndLogEx(NORMAL, "\t- F=%.1f MHz", F); |
| 831 | |
| 832 | if (Di && Fi) { |
| 833 | PrintAndLogEx(NORMAL, "\t- Cycles/ETU=%d", Fi/Di); |
| 834 | PrintAndLogEx(NORMAL, "\t- %.1f bits/sec at 4MHz", (float)4000000 / (Fi/Di)); |
| 835 | PrintAndLogEx(NORMAL, "\t- %.1f bits/sec at Fmax=%.1fMHz", (F * 1000000) / (Fi/Di), F); |
| 836 | } else { |
| 837 | PrintAndLogEx(WARNING, "\t- Di or Fi is RFU."); |
| 838 | }; |
| 839 | |
| 840 | return 0; |
| 841 | } |
| 842 | |
| 843 | int CmdSmartReader(const char *Cmd){ |
| 844 | uint8_t cmdp = 0; |
| 845 | bool errors = false, silent = false; |
| 846 | |
| 847 | while (param_getchar(Cmd, cmdp) != 0x00 && !errors) { |
| 848 | switch (tolower(param_getchar(Cmd, cmdp))) { |
| 849 | case 'h': return usage_sm_reader(); |
| 850 | case 's': |
| 851 | silent = true; |
| 852 | break; |
| 853 | default: |
| 854 | PrintAndLogEx(WARNING, "Unknown parameter '%c'", param_getchar(Cmd, cmdp)); |
| 855 | errors = true; |
| 856 | break; |
| 857 | } |
| 858 | cmdp++; |
| 859 | } |
| 860 | |
| 861 | //Validations |
| 862 | if (errors ) return usage_sm_reader(); |
| 863 | |
| 864 | smart_card_atr_t card; |
| 865 | if (!smart_getATR(&card)) { |
| 866 | if (!silent) PrintAndLogEx(WARNING, "smart card select failed"); |
| 867 | return 1; |
| 868 | } |
| 869 | |
| 870 | PrintAndLogEx(INFO, "ISO7816-3 ATR : %s", sprint_hex(card.atr, card.atr_len)); |
| 871 | return 0; |
| 872 | } |
| 873 | |
| 874 | |
| 875 | static int CmdSmartSetClock(const char *Cmd){ |
| 876 | uint8_t cmdp = 0; |
| 877 | bool errors = false; |
| 878 | uint8_t clock = 0; |
| 879 | while (param_getchar(Cmd, cmdp) != 0x00 && !errors) { |
| 880 | switch (tolower(param_getchar(Cmd, cmdp))) { |
| 881 | case 'h': return usage_sm_setclock(); |
| 882 | case 'c': |
| 883 | clock = param_get8ex(Cmd, cmdp+1, 2, 10); |
| 884 | if ( clock > 2) |
| 885 | errors = true; |
| 886 | |
| 887 | cmdp += 2; |
| 888 | break; |
| 889 | default: |
| 890 | PrintAndLogEx(WARNING, "Unknown parameter '%c'", param_getchar(Cmd, cmdp)); |
| 891 | errors = true; |
| 892 | break; |
| 893 | } |
| 894 | } |
| 895 | |
| 896 | //Validations |
| 897 | if (errors || cmdp == 0) return usage_sm_setclock(); |
| 898 | |
| 899 | UsbCommand c = {CMD_SMART_SETCLOCK, {clock, 0, 0}}; |
| 900 | clearCommandBuffer(); |
| 901 | SendCommand(&c); |
| 902 | UsbCommand resp; |
| 903 | if ( !WaitForResponseTimeout(CMD_ACK, &resp, 2500) ) { |
| 904 | PrintAndLogEx(WARNING, "smart card select failed"); |
| 905 | return 1; |
| 906 | } |
| 907 | |
| 908 | uint8_t isok = resp.arg[0] & 0xFF; |
| 909 | if (!isok) { |
| 910 | PrintAndLogEx(WARNING, "smart card set clock failed"); |
| 911 | return 1; |
| 912 | } |
| 913 | |
| 914 | switch (clock) { |
| 915 | case 0: |
| 916 | PrintAndLogEx(SUCCESS, "Clock changed to 16mhz giving 10800 baudrate"); |
| 917 | break; |
| 918 | case 1: |
| 919 | PrintAndLogEx(SUCCESS, "Clock changed to 8mhz giving 21600 baudrate"); |
| 920 | break; |
| 921 | case 2: |
| 922 | PrintAndLogEx(SUCCESS, "Clock changed to 4mhz giving 86400 baudrate"); |
| 923 | break; |
| 924 | default: |
| 925 | break; |
| 926 | } |
| 927 | return 0; |
| 928 | } |
| 929 | |
| 930 | |
| 931 | static int CmdSmartList(const char *Cmd) { |
| 932 | CmdHFList("7816"); |
| 933 | return 0; |
| 934 | } |
| 935 | |
| 936 | |
| 937 | static int CmdSmartBruteforceSFI(const char *Cmd) { |
| 938 | |
| 939 | char ctmp = tolower(param_getchar(Cmd, 0)); |
| 940 | if (ctmp == 'h') return usage_sm_brute(); |
| 941 | |
| 942 | uint8_t data[5] = {0x00, 0xB2, 0x00, 0x00, 0x00}; |
| 943 | |
| 944 | PrintAndLogEx(INFO, "Selecting card"); |
| 945 | if ( !smart_select(false) ) { |
| 946 | return 1; |
| 947 | } |
| 948 | |
| 949 | PrintAndLogEx(INFO, "Selecting PSE aid"); |
| 950 | CmdSmartRaw("s 0 t d 00a404000e325041592e5359532e4444463031"); |
| 951 | CmdSmartRaw("0 t d 00a4040007a000000004101000"); // mastercard |
| 952 | // CmdSmartRaw("0 t d 00a4040007a0000000031010"); // visa |
| 953 | |
| 954 | PrintAndLogEx(INFO, "starting"); |
| 955 | |
| 956 | int response_len = 0; |
| 957 | uint8_t* response = malloc(ISO7816_MAX_FRAME_SIZE); |
| 958 | if (!response) |
| 959 | return 1; |
| 960 | |
| 961 | for (uint8_t i=1; i < 4; i++) { |
| 962 | for (int p1=1; p1 < 5; p1++) { |
| 963 | |
| 964 | data[2] = p1; |
| 965 | data[3] = (i << 3) + 4; |
| 966 | |
| 967 | smart_transmit(data, sizeof(data), SC_RAW_T0, response, &response_len, ISO7816_MAX_FRAME_SIZE); |
| 968 | |
| 969 | if ( response[0] == 0x6C ) { |
| 970 | data[4] = response[1]; |
| 971 | smart_transmit(data, sizeof(data), SC_RAW_T0, response, &response_len, ISO7816_MAX_FRAME_SIZE); |
| 972 | |
| 973 | // TLV decoder |
| 974 | if (response_len > 4) |
| 975 | TLVPrintFromBuffer(response+1, response_len-3); |
| 976 | |
| 977 | data[4] = 0; |
| 978 | } |
| 979 | memset(response, 0x00, ISO7816_MAX_FRAME_SIZE); |
| 980 | } |
| 981 | } |
| 982 | free(response); |
| 983 | return 0; |
| 984 | } |
| 985 | |
| 986 | static command_t CommandTable[] = { |
| 987 | {"help", CmdHelp, 1, "This help"}, |
| 988 | {"select", CmdSmartSelect, 1, "Select the Smartcard Reader to use"}, |
| 989 | {"list", CmdSmartList, 1, "List ISO 7816 history"}, |
| 990 | {"info", CmdSmartInfo, 1, "Tag information"}, |
| 991 | {"reader", CmdSmartReader, 1, "Act like an IS07816 reader"}, |
| 992 | {"raw", CmdSmartRaw, 1, "Send raw hex data to tag"}, |
| 993 | {"upgrade", CmdSmartUpgrade, 0, "Upgrade firmware"}, |
| 994 | {"setclock", CmdSmartSetClock, 1, "Set clock speed"}, |
| 995 | {"brute", CmdSmartBruteforceSFI, 1, "Bruteforce SFI"}, |
| 996 | {NULL, NULL, 0, NULL} |
| 997 | }; |
| 998 | |
| 999 | |
| 1000 | int CmdSmartcard(const char *Cmd) { |
| 1001 | clearCommandBuffer(); |
| 1002 | CmdsParse(CommandTable, Cmd); |
| 1003 | return 0; |
| 1004 | } |
| 1005 | |
| 1006 | |
| 1007 | static int CmdHelp(const char *Cmd) { |
| 1008 | CmdsHelp(CommandTable); |
| 1009 | return 0; |
| 1010 | } |