diff --git a/client/cmdhffelica.c b/client/cmdhffelica.c index 19bc807df..398b771d3 100644 --- a/client/cmdhffelica.c +++ b/client/cmdhffelica.c @@ -292,7 +292,7 @@ static int usage_hf_felica_authentication1() { PrintAndLogEx(NORMAL, " - Number of Services m: 1-byte (1 <= n <= 8)"); PrintAndLogEx(NORMAL, " - Service Code List: 2n byte"); PrintAndLogEx(NORMAL, " - 3DES-Key: 128-bit master secret used for the encryption"); - PrintAndLogEx(NORMAL, " - M1c: Encrypted random number (challenge for tag authentication) 8-byte"); + PrintAndLogEx(NORMAL, " - M1c: Encrypted random number - challenge for tag authentication (8-byte)"); PrintAndLogEx(NORMAL, " - Response:"); PrintAndLogEx(NORMAL, " - Response Code: 11h 1-byte"); PrintAndLogEx(NORMAL, " - Manufacture ID(IDm): 8-byte"); @@ -301,7 +301,7 @@ static int usage_hf_felica_authentication1() { PrintAndLogEx(NORMAL, " - Success: Card Mode switches to Mode1. You can check this with the request response command."); PrintAndLogEx(NORMAL, " - Unsuccessful: Card should not respond at all."); - PrintAndLogEx(NORMAL, "\nUsage: hf felica auth1 [-h][-i] <01 Number of Areas hex> <0A0B... Area Code List hex> <01 Number of Services hex> <0A0B... Service Code List hex> <0x0102030405060809 3DES-key hex (128bit)>"); + PrintAndLogEx(NORMAL, "\nUsage: hf felica auth1 [-h][-i] <01 Number of Areas hex> <0A0B... Area Code List hex> <01 Number of Services hex> <0A0B... Service Code List hex> <0x0102030405060809... 3DES-key hex (16-byte)>"); PrintAndLogEx(NORMAL, " -h this help"); PrintAndLogEx(NORMAL, " -i <0A0B0C ... hex> set custom IDm to use"); PrintAndLogEx(NORMAL, "\nExamples: "); @@ -311,6 +311,30 @@ static int usage_hf_felica_authentication1() { return PM3_SUCCESS; } +static int usage_hf_felica_authentication2() { + PrintAndLogEx(NORMAL, "\nInfo: Complete mutual authentication. This command can only be executed subsquent to Authentication1" + " command."); + PrintAndLogEx(NORMAL, " - Auth2 Parameters:"); + PrintAndLogEx(NORMAL, " - Manufacturer IDm: (8-byte)"); + PrintAndLogEx(NORMAL, " - M3c: card challenge (8-byte)"); + PrintAndLogEx(NORMAL, " - 3DES Key: key used for decryption of M3c (16-byte)"); + PrintAndLogEx(NORMAL, " - Response (encrypted):"); + PrintAndLogEx(NORMAL, " - Response Code: 13h (1-byte)"); + PrintAndLogEx(NORMAL, " - IDtc: (8-byte)"); + PrintAndLogEx(NORMAL, " - IDi (encrypted): (8-byte)"); + PrintAndLogEx(NORMAL, " - PMi (encrypted): (8-byte)"); + PrintAndLogEx(NORMAL, " - Success: Card switches to mode2 and sends response frame."); + PrintAndLogEx(NORMAL, " - Unsuccessful: Card should not respond at all."); + PrintAndLogEx(NORMAL, "\nUsage: hf felica auth2 [-h][-i] <0102030405060708 M3c hex> <0x0102030405060809... 3DES-key hex (16-byte)>"); + PrintAndLogEx(NORMAL, " -h this help"); + PrintAndLogEx(NORMAL, " -i <0A0B0C ... hex> set custom IDm to use"); + PrintAndLogEx(NORMAL, "\nExamples: "); + PrintAndLogEx(NORMAL, " hf felica auth2 0102030405060708 AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBB"); + PrintAndLogEx(NORMAL, " hf felica auth2 -i 11100910C11BC407 0102030405060708 AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBB\n\n"); + + return PM3_SUCCESS; +} + /** * Wait for response from pm3 or timeout. * Checks if receveid bytes have a valid CRC. @@ -530,6 +554,18 @@ int send_wr_unencrypted(uint8_t flags, uint16_t datalen, uint8_t *data, bool ver } } +/** + * Reverses the master secret. Example: AA AA AA AA AA AA AA BB to BB AA AA AA AA AA AA AA + * @param master_key the secret which order will be reversed. + * @param length in bytes of the master secret. + * @param reverse_master_key output in which the reversed secret is stored. + */ +static void reverse_3des_key(uint8_t *master_key, int length, uint8_t *reverse_master_key) { + for (int i = 0; i < length; i++) { + reverse_master_key[i] = master_key[(length - 1) - i]; + } +}; + /** * Command parser for auth1 * @param Cmd input data of the user. @@ -539,6 +575,7 @@ static int CmdHFFelicaAuthentication1(const char *Cmd) { if (strlen(Cmd) < 4) { return usage_hf_felica_authentication1(); } + PrintAndLogEx(INFO, "EXPERIMENTAL COMMAND"); uint8_t data[PM3_CMD_DATA_SIZE]; bool custom_IDm = false; strip_cmds(Cmd); @@ -582,7 +619,7 @@ static int CmdHFFelicaAuthentication1(const char *Cmd) { } } - // READER CHALLENGE - (RANDOM To Encrypt) + // READER CHALLENGE - (RANDOM To Encrypt = Rac) unsigned char input[8]; input[0] = 0x1; input[1] = 0x2; @@ -592,6 +629,7 @@ static int CmdHFFelicaAuthentication1(const char *Cmd) { input[5] = 0x6; input[6] = 0x7; input[7] = 0x8; + PrintAndLogEx(INFO, "Reader challenge (unencrypted): %s", sprint_hex(input, 8)); unsigned char output[8]; // Create M1c Challenge with 3DES (3 Keys = 24, 2 Keys = 16) uint8_t master_key[PM3_CMD_DATA_SIZE]; @@ -603,6 +641,7 @@ static int CmdHFFelicaAuthentication1(const char *Cmd) { PrintAndLogEx(INFO, "3DES Master Secret: %s", sprint_hex(master_key, 24)); } else if (param_getlength(Cmd, paramCount) == 32) { param_gethex(Cmd, paramCount, master_key, 32); + // Assumption: Master secret split in half for Kac, Kbc mbedtls_des3_set2key_enc(&des3_ctx, master_key); PrintAndLogEx(INFO, "3DES Master Secret: %s", sprint_hex(master_key, 16)); } else { @@ -631,12 +670,149 @@ static int CmdHFFelicaAuthentication1(const char *Cmd) { PrintAndLogEx(ERR, "\nGot no Response from card"); return PM3_ERFTRANS; } else { - PrintAndLogEx(NORMAL, "AUTH1 SUCCESS!"); - PrintAndLogEx(NORMAL, "%s", sprint_hex(resp.data.asBytes, 256)); + felica_auth1_response_t auth1_response; + memcpy(&auth1_response, (felica_auth1_response_t *)resp.data.asBytes, sizeof(felica_auth1_response_t)); + if (auth1_response.frame_response.IDm[0] != 0) { + PrintAndLogEx(SUCCESS, "\nGot auth1 response:"); + PrintAndLogEx(SUCCESS, "IDm: %s", sprint_hex(auth1_response.frame_response.IDm, sizeof(auth1_response.frame_response.IDm))); + PrintAndLogEx(SUCCESS, "M2C: %s", sprint_hex(auth1_response.m2c, sizeof(auth1_response.m2c))); + PrintAndLogEx(SUCCESS, "M3C: %s", sprint_hex(auth1_response.m3c, sizeof(auth1_response.m3c))); + // Assumption: Key swap method used + uint8_t reverse_master_key[PM3_CMD_DATA_SIZE]; + reverse_3des_key(master_key, 16, reverse_master_key); + mbedtls_des3_set2key_dec(&des3_ctx, reverse_master_key); + bool isKeyCorrect = false; + unsigned char p2c[8]; + mbedtls_des3_crypt_ecb(&des3_ctx, auth1_response.m2c, p2c); + for (int i = 0; i < 8; i++) { + if (p2c[i] != input[i]) { + isKeyCorrect = false; + break; + } else { + isKeyCorrect = true; + } + } + if (isKeyCorrect) { + PrintAndLogEx(SUCCESS, "\nAuth1 done with correct key material! Use Auth2 now with M3C and same key"); + } else { + PrintAndLogEx(INFO, "3DES secret (swapped decryption): %s", sprint_hex(reverse_master_key, 16)); + PrintAndLogEx(INFO, "P2c: %s", sprint_hex(p2c, 8)); + PrintAndLogEx(ERR, "Can't decrypt M2C with master secret (P1c != P2c)! Probably wrong keys or wrong decryption method"); + } + } } return PM3_SUCCESS; } +/** + * Command parser for auth2 + * @param Cmd input data of the user. + * @return client result code. + */ +static int CmdHFFelicaAuthentication2(const char *Cmd) { + if (strlen(Cmd) < 2) { + return usage_hf_felica_authentication2(); + } + PrintAndLogEx(INFO, "EXPERIMENTAL COMMAND - M2c/P2c will be not checked"); + uint8_t data[PM3_CMD_DATA_SIZE]; + bool custom_IDm = false; + strip_cmds(Cmd); + uint16_t datalen = 18; // Length (1), Command ID (1), IDm (8), M4c (8) + uint8_t paramCount = 0; + uint8_t flags = 0; + int i = 0; + while (Cmd[i] != '\0') { + if (Cmd[i] == '-') { + switch (tolower(Cmd[i + 1])) { + case 'h': + return usage_hf_felica_authentication2(); + case 'i': + paramCount++; + custom_IDm = true; + if (!add_param(Cmd, paramCount, data, 2, 16)) { + return PM3_EINVARG; + } + paramCount++; + i += 16; + break; + default: + return usage_hf_felica_authentication1(); + } + } + i++; + } + data[0] = int_to_hex(&datalen); + data[1] = 0x12; // Command ID + if (!custom_IDm && !check_last_idm(data, datalen)) { + return PM3_EINVARG; + } + // M3c (8) + unsigned char m3c[8]; + if (add_param(Cmd, paramCount, m3c, 0, 16)) { + paramCount++; + } else { + return PM3_EINVARG; + } + // Create M4c challenge response with 3DES + uint8_t master_key[PM3_CMD_DATA_SIZE]; + uint8_t reverse_master_key[PM3_CMD_DATA_SIZE]; + mbedtls_des3_context des3_ctx; + mbedtls_des3_init(&des3_ctx); + unsigned char p3c[8]; + if (param_getlength(Cmd, paramCount) == 32) { + param_gethex(Cmd, paramCount, master_key, 32); + reverse_3des_key(master_key, 16, reverse_master_key); + mbedtls_des3_set2key_dec(&des3_ctx, reverse_master_key); + mbedtls_des3_set2key_enc(&des3_ctx, master_key); + // Assumption: Key swap method used for E2 + PrintAndLogEx(INFO, "3DES Master Secret (encryption): %s", sprint_hex(master_key, 16)); + PrintAndLogEx(INFO, "3DES Master Secret (decryption): %s", sprint_hex(reverse_master_key, 16)); + } else { + PrintAndLogEx(ERR, "Invalid key length"); + return PM3_EINVARG; + } + // Decrypt m3c with reverse_master_key + mbedtls_des3_crypt_ecb(&des3_ctx, m3c, p3c); + PrintAndLogEx(INFO, "3DES decrypted M3c = P3c: %s", sprint_hex(p3c, 8)); + // Encrypt p3c with master_key + unsigned char m4c[8]; + mbedtls_des3_crypt_ecb(&des3_ctx, p3c, m4c); + PrintAndLogEx(INFO, "3DES encrypted M4c: %s", sprint_hex(m4c, 8)); + + // Add M4c Challenge to frame + int frame_position = 10; + for (int i = 0; i < 8; i++) { + data[frame_position++] = m4c[i]; + } + + AddCrc(data, datalen); + datalen += 2; + flags |= FELICA_APPEND_CRC; + flags |= FELICA_RAW; + + PrintAndLogEx(INFO, "Client Send AUTH2 Frame: %s", sprint_hex(data, datalen)); + clear_and_send_command(flags, datalen, data, 0); + + PacketResponseNG resp; + if (!waitCmdFelica(0, &resp, 1)) { + PrintAndLogEx(ERR, "\nGot no Response from card"); + return PM3_ERFTRANS; + } else { + felica_auth2_response_t auth2_response; + memcpy(&auth2_response, (felica_auth2_response_t *)resp.data.asBytes, sizeof(felica_auth2_response_t)); + if (auth2_response.code[0] != 0x12) { + PrintAndLogEx(SUCCESS, "\nGot auth2 response:"); + PrintAndLogEx(SUCCESS, "IDtc: %s", sprint_hex(auth2_response.IDtc, sizeof(auth2_response.IDtc))); + PrintAndLogEx(SUCCESS, "IDi (encrypted): %s", sprint_hex(auth2_response.IDi, sizeof(auth2_response.IDi))); + PrintAndLogEx(SUCCESS, "PMi (encrypted): %s", sprint_hex(auth2_response.PMi, sizeof(auth2_response.PMi))); + } else { + PrintAndLogEx(ERR, "\nGot wrong frame format."); + } + } + return PM3_SUCCESS; +} + + /** * Command parser for wrunencrypted. * @param Cmd input data of the user. @@ -1644,8 +1820,8 @@ static command_t CommandTable[] = { {"wrunencrypted", CmdHFFelicaWriteWithoutEncryption, IfPm3Felica, "write Block Data to an authentication-not-required Service."}, {"scsvcode", CmdHFFelicaNotImplementedYet, IfPm3Felica, "acquire Area Code and Service Code."}, {"rqsyscode", CmdHFFelicaRequestSystemCode, IfPm3Felica, "acquire System Code registered to the card."}, - {"auth1", CmdHFFelicaAuthentication1, IfPm3Felica, "authenticate a card. Start mutual authentication with Auth1 (v1)"}, - {"auth2", CmdHFFelicaNotImplementedYet, IfPm3Felica, "allow a card to authenticate a Reader/Writer. Auth2 (v1)"}, + {"auth1", CmdHFFelicaAuthentication1, IfPm3Felica, "authenticate a card. Start mutual authentication with Auth1"}, + {"auth2", CmdHFFelicaAuthentication2, IfPm3Felica, "allow a card to authenticate a Reader/Writer. Complete mutual authentication"}, {"read", CmdHFFelicaNotImplementedYet, IfPm3Felica, "read Block Data from authentication-required Service."}, //{"write", CmdHFFelicaNotImplementedYet, IfPm3Felica, "write Block Data to an authentication-required Service."}, //{"scsvcodev2", CmdHFFelicaNotImplementedYet, IfPm3Felica, "verify the existence of Area or Service, and to acquire Key Version."}, diff --git a/include/mifare.h b/include/mifare.h index e278b3794..63b14c117 100644 --- a/include/mifare.h +++ b/include/mifare.h @@ -220,6 +220,20 @@ typedef struct { uint8_t option_version_list[4]; } PACKED felica_request_spec_response_t; +typedef struct { + felica_frame_response_t frame_response; + uint8_t m2c[8]; + uint8_t m3c[8]; +} PACKED felica_auth1_response_t; + +typedef struct { + uint8_t code[1]; + uint8_t IDtc[8]; + uint8_t IDi[8]; + uint8_t PMi[8]; +} PACKED felica_auth2_response_t; + + typedef enum FELICA_COMMAND { FELICA_CONNECT = (1 << 0), FELICA_NO_DISCONNECT = (1 << 1),