diff --git a/application.fam b/application.fam index 769226c..7329886 100644 --- a/application.fam +++ b/application.fam @@ -5,7 +5,7 @@ App( name="Quac!", # Displayed in menus apptype=FlipperAppType.EXTERNAL, entry_point="quac_app", - stack_size=2 * 1024, + stack_size=4 * 1024, # Increased for picopass emulation fap_category="Tools", # Optional values fap_version="0.9.1", @@ -14,4 +14,10 @@ App( fap_author="Roberto De Feo", fap_weburl="https://github.com/rdefeo/quac", fap_icon_assets="images", # Image assets to compile for this application + fap_private_libs=[ + Lib( + name="loclass", + cflags=["-O3"], + ), + ], ) diff --git a/item.c b/item.c index e34e729..1699079 100644 --- a/item.c +++ b/item.c @@ -164,6 +164,8 @@ ItemType item_get_item_type_from_extension(const char* ext) { type = Item_NFC; } else if(!strcmp(ext, ".ibtn")) { type = Item_iButton; + } else if(!strcmp(ext, ".picopass")) { + type = Item_Picopass; } else if(!strcmp(ext, ".qpl")) { type = Item_Playlist; } diff --git a/item.h b/item.h index 890d621..257e0f1 100644 --- a/item.h +++ b/item.h @@ -4,7 +4,7 @@ // Max length of a filename, final path element only #define MAX_NAME_LEN (size_t)64 -#define MAX_EXT_LEN (size_t)6 +#define MAX_EXT_LEN (size_t)12 // Increased for .picopass extension /** Defines an individual item action or item group. Each object contains * the relevant file and type information needed to both render correctly @@ -17,6 +17,7 @@ typedef enum { Item_IR, Item_NFC, Item_iButton, + Item_Picopass, Item_Playlist, Item_Group, Item_Settings, diff --git a/lib/loclass/optimized_cipher.c b/lib/loclass/optimized_cipher.c new file mode 100644 index 0000000..79d86b1 --- /dev/null +++ b/lib/loclass/optimized_cipher.c @@ -0,0 +1,319 @@ +//----------------------------------------------------------------------------- +// Borrowed initially from https://github.com/holiman/loclass +// Copyright (C) 2014 Martin Holst Swende +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// WARNING +// +// THIS CODE IS CREATED FOR EXPERIMENTATION AND EDUCATIONAL USE ONLY. +// +// USAGE OF THIS CODE IN OTHER WAYS MAY INFRINGE UPON THE INTELLECTUAL +// PROPERTY OF OTHER PARTIES, SUCH AS INSIDE SECURE AND HID GLOBAL, +// AND MAY EXPOSE YOU TO AN INFRINGEMENT ACTION FROM THOSE PARTIES. +// +// THIS CODE SHOULD NEVER BE USED TO INFRINGE PATENTS OR INTELLECTUAL PROPERTY RIGHTS. +//----------------------------------------------------------------------------- +// It is a reconstruction of the cipher engine used in iClass, and RFID techology. +// +// The implementation is based on the work performed by +// Flavio D. Garcia, Gerhard de Koning Gans, Roel Verdult and +// Milosch Meriac in the paper "Dismantling IClass". +//----------------------------------------------------------------------------- +/* + This file contains an optimized version of the MAC-calculation algorithm. Some measurements on + a std laptop showed it runs in about 1/3 of the time: + + Std: 0.428962 + Opt: 0.151609 + + Additionally, it is self-reliant, not requiring e.g. bitstreams from the cipherutils, thus can + be easily dropped into a code base. + + The optimizations have been performed in the following steps: + * Parameters passed by reference instead of by value. + * Iteration instead of recursion, un-nesting recursive loops into for-loops. + * Handling of bytes instead of individual bits, for less shuffling and masking + * Less creation of "objects", structs, and instead reuse of alloc:ed memory + * Inlining some functions via #define:s + + As a consequence, this implementation is less generic. Also, I haven't bothered documenting this. + For a thorough documentation, check out the MAC-calculation within cipher.c instead. + + -- MHS 2015 +**/ + +/** + + The runtime of opt_doTagMAC_2() with the MHS optimized version was 403 microseconds on Proxmark3. + This was still to slow for some newer readers which didn't want to wait that long. + + Further optimizations to speedup the MAC calculations: + * Optimized opt_Tt logic + * Look up table for opt_select + * Removing many unnecessary bit maskings (& 0x1) + * updating state in place instead of alternating use of a second state structure + * remove the necessity to reverse bits of input and output bytes + + opt_doTagMAC_2() now completes in 270 microseconds. + + -- piwi 2019 +**/ + +/** + add the possibility to do iCLASS on device only + -- iceman 2020 +**/ + +#include "optimized_cipher.h" +#include "optimized_elite.h" +#include "optimized_ikeys.h" +#include "optimized_cipherutils.h" + +static const uint8_t loclass_opt_select_LUT[256] = { + 00, 03, 02, 01, 02, 03, 00, 01, 04, 07, 07, 04, 06, 07, 05, 04, 01, 02, 03, 00, 02, 03, 00, 01, + 05, 06, 06, 05, 06, 07, 05, 04, 06, 05, 04, 07, 04, 05, 06, 07, 06, 05, 05, 06, 04, 05, 07, 06, + 07, 04, 05, 06, 04, 05, 06, 07, 07, 04, 04, 07, 04, 05, 07, 06, 06, 05, 04, 07, 04, 05, 06, 07, + 02, 01, 01, 02, 00, 01, 03, 02, 03, 00, 01, 02, 00, 01, 02, 03, 07, 04, 04, 07, 04, 05, 07, 06, + 00, 03, 02, 01, 02, 03, 00, 01, 00, 03, 03, 00, 02, 03, 01, 00, 05, 06, 07, 04, 06, 07, 04, 05, + 05, 06, 06, 05, 06, 07, 05, 04, 02, 01, 00, 03, 00, 01, 02, 03, 06, 05, 05, 06, 04, 05, 07, 06, + 03, 00, 01, 02, 00, 01, 02, 03, 07, 04, 04, 07, 04, 05, 07, 06, 02, 01, 00, 03, 00, 01, 02, 03, + 02, 01, 01, 02, 00, 01, 03, 02, 03, 00, 01, 02, 00, 01, 02, 03, 03, 00, 00, 03, 00, 01, 03, 02, + 04, 07, 06, 05, 06, 07, 04, 05, 00, 03, 03, 00, 02, 03, 01, 00, 01, 02, 03, 00, 02, 03, 00, 01, + 05, 06, 06, 05, 06, 07, 05, 04, 04, 07, 06, 05, 06, 07, 04, 05, 04, 07, 07, 04, 06, 07, 05, 04, + 01, 02, 03, 00, 02, 03, 00, 01, 01, 02, 02, 01, 02, 03, 01, 00}; + +/********************** the table above has been generated with this code: ******** +#include "util.h" +static void init_opt_select_LUT(void) { + for (int r = 0; r < 256; r++) { + uint8_t r_ls2 = r << 2; + uint8_t r_and_ls2 = r & r_ls2; + uint8_t r_or_ls2 = r | r_ls2; + uint8_t z0 = (r_and_ls2 >> 5) ^ ((r & ~r_ls2) >> 4) ^ ( r_or_ls2 >> 3); + uint8_t z1 = (r_or_ls2 >> 6) ^ ( r_or_ls2 >> 1) ^ (r >> 5) ^ r; + uint8_t z2 = ((r & ~r_ls2) >> 4) ^ (r_and_ls2 >> 3) ^ r; + loclass_opt_select_LUT[r] = (z0 & 4) | (z1 & 2) | (z2 & 1); + } + print_result("", loclass_opt_select_LUT, 256); +} +***********************************************************************************/ + +static inline void loclass_opt_successor(const uint8_t* k, LoclassState_t* s, uint8_t y) { + uint16_t Tt = s->t & 0xc533; + Tt = Tt ^ (Tt >> 1); + Tt = Tt ^ (Tt >> 4); + Tt = Tt ^ (Tt >> 10); + Tt = Tt ^ (Tt >> 8); + + s->t = (s->t >> 1); + s->t |= (Tt ^ (s->r >> 7) ^ (s->r >> 3)) << 15; + + uint8_t opt_B = s->b; + opt_B ^= s->b >> 6; + opt_B ^= s->b >> 5; + opt_B ^= s->b >> 4; + + s->b = s->b >> 1; + s->b |= (opt_B ^ s->r) << 7; + + uint8_t Tt1 = Tt & 0x01; + uint8_t opt_select = loclass_opt_select_LUT[s->r] ^ Tt1 ^ ((Tt1 ^ (y & 0x01)) << 1); + + uint8_t r = s->r; + s->r = (k[opt_select] ^ s->b) + s->l; + s->l = s->r + r; +} + +static inline void loclass_opt_suc( + const uint8_t* k, + LoclassState_t* s, + const uint8_t* in, + uint8_t length, + bool add32Zeroes) { + for(int i = 0; i < length; i++) { + uint8_t head = in[i]; +#pragma GCC unroll 8 + for(int j = 0; j < 8; j++) { + loclass_opt_successor(k, s, head); + head >>= 1; + } + } + // For tag MAC, an additional 32 zeroes + if(add32Zeroes) { + for(int i = 0; i < 32; i++) { + loclass_opt_successor(k, s, 0); + } + } +} + +static inline void loclass_opt_output(const uint8_t* k, LoclassState_t* s, uint8_t* buffer) { +#pragma GCC unroll 4 + for(uint8_t times = 0; times < 4; times++) { + uint8_t bout = 0; + bout |= (s->r & 0x4) >> 2; + loclass_opt_successor(k, s, 0); + bout |= (s->r & 0x4) >> 1; + loclass_opt_successor(k, s, 0); + bout |= (s->r & 0x4); + loclass_opt_successor(k, s, 0); + bout |= (s->r & 0x4) << 1; + loclass_opt_successor(k, s, 0); + bout |= (s->r & 0x4) << 2; + loclass_opt_successor(k, s, 0); + bout |= (s->r & 0x4) << 3; + loclass_opt_successor(k, s, 0); + bout |= (s->r & 0x4) << 4; + loclass_opt_successor(k, s, 0); + bout |= (s->r & 0x4) << 5; + loclass_opt_successor(k, s, 0); + buffer[times] = bout; + } +} + +static void loclass_opt_MAC(uint8_t* k, uint8_t* input, uint8_t* out) { + LoclassState_t _init = { + ((k[0] ^ 0x4c) + 0xEC) & 0xFF, // l + ((k[0] ^ 0x4c) + 0x21) & 0xFF, // r + 0x4c, // b + 0xE012 // t + }; + + loclass_opt_suc(k, &_init, input, 12, false); + loclass_opt_output(k, &_init, out); +} + +static void loclass_opt_MAC_N(uint8_t* k, uint8_t* input, uint8_t in_size, uint8_t* out) { + LoclassState_t _init = { + ((k[0] ^ 0x4c) + 0xEC) & 0xFF, // l + ((k[0] ^ 0x4c) + 0x21) & 0xFF, // r + 0x4c, // b + 0xE012 // t + }; + + loclass_opt_suc(k, &_init, input, in_size, false); + loclass_opt_output(k, &_init, out); +} + +void loclass_opt_doReaderMAC(uint8_t* cc_nr_p, uint8_t* div_key_p, uint8_t mac[4]) { + uint8_t dest[] = {0, 0, 0, 0, 0, 0, 0, 0}; + loclass_opt_MAC(div_key_p, cc_nr_p, dest); + memcpy(mac, dest, 4); +} + +void loclass_opt_doReaderMAC_2( + LoclassState_t _init, + const uint8_t* nr, + uint8_t mac[4], + const uint8_t* div_key_p) { + loclass_opt_suc(div_key_p, &_init, nr, 4, false); + loclass_opt_output(div_key_p, &_init, mac); +} + +void loclass_doMAC_N(uint8_t* in_p, uint8_t in_size, uint8_t* div_key_p, uint8_t mac[4]) { + uint8_t dest[] = {0, 0, 0, 0, 0, 0, 0, 0}; + loclass_opt_MAC_N(div_key_p, in_p, in_size, dest); + memcpy(mac, dest, 4); +} + +void loclass_opt_doTagMAC(uint8_t* cc_p, const uint8_t* div_key_p, uint8_t mac[4]) { + LoclassState_t _init = { + ((div_key_p[0] ^ 0x4c) + 0xEC) & 0xFF, // l + ((div_key_p[0] ^ 0x4c) + 0x21) & 0xFF, // r + 0x4c, // b + 0xE012 // t + }; + loclass_opt_suc(div_key_p, &_init, cc_p, 12, true); + loclass_opt_output(div_key_p, &_init, mac); +} + +/** + * The tag MAC can be divided (both can, but no point in dividing the reader mac) into + * two functions, since the first 8 bytes are known, we can pre-calculate the state + * reached after feeding CC to the cipher. + * @param cc_p + * @param div_key_p + * @return the cipher state + */ +LoclassState_t loclass_opt_doTagMAC_1(uint8_t* cc_p, const uint8_t* div_key_p) { + LoclassState_t _init = { + ((div_key_p[0] ^ 0x4c) + 0xEC) & 0xFF, // l + ((div_key_p[0] ^ 0x4c) + 0x21) & 0xFF, // r + 0x4c, // b + 0xE012 // t + }; + loclass_opt_suc(div_key_p, &_init, cc_p, 8, false); + return _init; +} + +/** + * The second part of the tag MAC calculation, since the CC is already calculated into the state, + * this function is fed only the NR, and internally feeds the remaining 32 0-bits to generate the tag + * MAC response. + * @param _init - precalculated cipher state + * @param nr - the reader challenge + * @param mac - where to store the MAC + * @param div_key_p - the key to use + */ +void loclass_opt_doTagMAC_2( + LoclassState_t _init, + const uint8_t* nr, + uint8_t mac[4], + const uint8_t* div_key_p) { + loclass_opt_suc(div_key_p, &_init, nr, 4, true); + loclass_opt_output(div_key_p, &_init, mac); +} + +/** + * The second part of the tag MAC calculation, since the CC is already calculated into the state, + * this function is fed only the NR, and generates both the reader and tag MACs. + * @param _init - precalculated cipher state + * @param nr - the reader challenge + * @param rmac - where to store the reader MAC + * @param tmac - where to store the tag MAC + * @param div_key_p - the key to use + */ +void loclass_opt_doBothMAC_2( + LoclassState_t _init, + const uint8_t* nr, + uint8_t rmac[4], + uint8_t tmac[4], + const uint8_t* div_key_p) { + LoclassState_t* s = &_init; + loclass_opt_suc(div_key_p, s, nr, 4, false); + loclass_opt_output(div_key_p, s, rmac); + loclass_opt_output(div_key_p, s, tmac); +} + +void loclass_iclass_calc_div_key( + const uint8_t* csn, + const uint8_t* key, + uint8_t* div_key, + bool elite) { + if(elite) { + uint8_t keytable[128] = {0}; + uint8_t key_index[8] = {0}; + uint8_t key_sel[8] = {0}; + uint8_t key_sel_p[8] = {0}; + loclass_hash2(key, keytable); + loclass_hash1(csn, key_index); + for(uint8_t i = 0; i < 8; i++) key_sel[i] = keytable[key_index[i]]; + + //Permute from iclass format to standard format + loclass_permutekey_rev(key_sel, key_sel_p); + loclass_diversifyKey(csn, key_sel_p, div_key); + } else { + loclass_diversifyKey(csn, key, div_key); + } +} diff --git a/lib/loclass/optimized_cipher.h b/lib/loclass/optimized_cipher.h new file mode 100644 index 0000000..d8c6451 --- /dev/null +++ b/lib/loclass/optimized_cipher.h @@ -0,0 +1,117 @@ +//----------------------------------------------------------------------------- +// Borrowed initially from https://github.com/holiman/loclass +// More recently from https://github.com/RfidResearchGroup/proxmark3 +// Copyright (C) 2014 Martin Holst Swende +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// WARNING +// +// THIS CODE IS CREATED FOR EXPERIMENTATION AND EDUCATIONAL USE ONLY. +// +// USAGE OF THIS CODE IN OTHER WAYS MAY INFRINGE UPON THE INTELLECTUAL +// PROPERTY OF OTHER PARTIES, SUCH AS INSIDE SECURE AND HID GLOBAL, +// AND MAY EXPOSE YOU TO AN INFRINGEMENT ACTION FROM THOSE PARTIES. +// +// THIS CODE SHOULD NEVER BE USED TO INFRINGE PATENTS OR INTELLECTUAL PROPERTY RIGHTS. +//----------------------------------------------------------------------------- +// It is a reconstruction of the cipher engine used in iClass, and RFID techology. +// +// The implementation is based on the work performed by +// Flavio D. Garcia, Gerhard de Koning Gans, Roel Verdult and +// Milosch Meriac in the paper "Dismantling IClass". +//----------------------------------------------------------------------------- +#ifndef OPTIMIZED_CIPHER_H +#define OPTIMIZED_CIPHER_H +#include +#include +#include +#include + +/** +* Definition 1 (Cipher state). A cipher state of iClass s is an element of F 40/2 +* consisting of the following four components: +* 1. the left register l = (l 0 . . . l 7 ) ∈ F 8/2 ; +* 2. the right register r = (r 0 . . . r 7 ) ∈ F 8/2 ; +* 3. the top register t = (t 0 . . . t 15 ) ∈ F 16/2 . +* 4. the bottom register b = (b 0 . . . b 7 ) ∈ F 8/2 . +**/ +typedef struct { + uint8_t l; + uint8_t r; + uint8_t b; + uint16_t t; +} LoclassState_t; + +/** The reader MAC is MAC(key, CC * NR ) + **/ +void loclass_opt_doReaderMAC(uint8_t* cc_nr_p, uint8_t* div_key_p, uint8_t mac[4]); + +void loclass_opt_doReaderMAC_2( + LoclassState_t _init, + const uint8_t* nr, + uint8_t mac[4], + const uint8_t* div_key_p); + +/** + * The tag MAC is MAC(key, CC * NR * 32x0)) + */ +void loclass_opt_doTagMAC(uint8_t* cc_p, const uint8_t* div_key_p, uint8_t mac[4]); + +/** + * The tag MAC can be divided (both can, but no point in dividing the reader mac) into + * two functions, since the first 8 bytes are known, we can pre-calculate the state + * reached after feeding CC to the cipher. + * @param cc_p + * @param div_key_p + * @return the cipher state + */ +LoclassState_t loclass_opt_doTagMAC_1(uint8_t* cc_p, const uint8_t* div_key_p); +/** + * The second part of the tag MAC calculation, since the CC is already calculated into the state, + * this function is fed only the NR, and internally feeds the remaining 32 0-bits to generate the tag + * MAC response. + * @param _init - precalculated cipher state + * @param nr - the reader challenge + * @param mac - where to store the MAC + * @param div_key_p - the key to use + */ +void loclass_opt_doTagMAC_2( + LoclassState_t _init, + const uint8_t* nr, + uint8_t mac[4], + const uint8_t* div_key_p); + +/** + * The same as loclass_opt_doTagMAC_2, but calculates both the reader and tag MACs at the same time + * @param _init - precalculated cipher state + * @param nr - the reader challenge + * @param rmac - where to store the reader MAC + * @param tmac - where to store the tag MAC + * @param div_key_p - the key to use + */ +void loclass_opt_doBothMAC_2( + LoclassState_t _init, + const uint8_t* nr, + uint8_t rmac[4], + uint8_t tmac[4], + const uint8_t* div_key_p); + +void loclass_doMAC_N(uint8_t* in_p, uint8_t in_size, uint8_t* div_key_p, uint8_t mac[4]); +void loclass_iclass_calc_div_key( + const uint8_t* csn, + const uint8_t* key, + uint8_t* div_key, + bool elite); +#endif // OPTIMIZED_CIPHER_H diff --git a/lib/loclass/optimized_cipherutils.c b/lib/loclass/optimized_cipherutils.c new file mode 100644 index 0000000..e6a87c4 --- /dev/null +++ b/lib/loclass/optimized_cipherutils.c @@ -0,0 +1,136 @@ +//----------------------------------------------------------------------------- +// Borrowed initially from https://github.com/holiman/loclass +// Copyright (C) 2014 Martin Holst Swende +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// WARNING +// +// THIS CODE IS CREATED FOR EXPERIMENTATION AND EDUCATIONAL USE ONLY. +// +// USAGE OF THIS CODE IN OTHER WAYS MAY INFRINGE UPON THE INTELLECTUAL +// PROPERTY OF OTHER PARTIES, SUCH AS INSIDE SECURE AND HID GLOBAL, +// AND MAY EXPOSE YOU TO AN INFRINGEMENT ACTION FROM THOSE PARTIES. +// +// THIS CODE SHOULD NEVER BE USED TO INFRINGE PATENTS OR INTELLECTUAL PROPERTY RIGHTS. +//----------------------------------------------------------------------------- +// It is a reconstruction of the cipher engine used in iClass, and RFID techology. +// +// The implementation is based on the work performed by +// Flavio D. Garcia, Gerhard de Koning Gans, Roel Verdult and +// Milosch Meriac in the paper "Dismantling IClass". +//----------------------------------------------------------------------------- +#include "optimized_cipherutils.h" +#include + +/** + * + * @brief Return and remove the first bit (x0) in the stream : + * @param stream + * @return + */ +bool loclass_headBit(LoclassBitstreamIn_t* stream) { + int bytepos = stream->position >> 3; // divide by 8 + int bitpos = (stream->position++) & 7; // mask out 00000111 + return (*(stream->buffer + bytepos) >> (7 - bitpos)) & 1; +} +/** + * @brief Return and remove the last bit (xn) in the stream: + * @param stream + * @return + */ +bool loclass_tailBit(LoclassBitstreamIn_t* stream) { + int bitpos = stream->numbits - 1 - (stream->position++); + + int bytepos = bitpos >> 3; + bitpos &= 7; + return (*(stream->buffer + bytepos) >> (7 - bitpos)) & 1; +} +/** + * @brief Pushes bit onto the stream + * @param stream + * @param bit + */ +void loclass_pushBit(LoclassBitstreamOut_t* stream, bool bit) { + int bytepos = stream->position >> 3; // divide by 8 + int bitpos = stream->position & 7; + *(stream->buffer + bytepos) |= (bit) << (7 - bitpos); + stream->position++; + stream->numbits++; +} + +/** + * @brief Pushes the lower six bits onto the stream + * as b0 b1 b2 b3 b4 b5 b6 + * @param stream + * @param bits + */ +void loclass_push6bits(LoclassBitstreamOut_t* stream, uint8_t bits) { + loclass_pushBit(stream, bits & 0x20); + loclass_pushBit(stream, bits & 0x10); + loclass_pushBit(stream, bits & 0x08); + loclass_pushBit(stream, bits & 0x04); + loclass_pushBit(stream, bits & 0x02); + loclass_pushBit(stream, bits & 0x01); +} + +/** + * @brief loclass_bitsLeft + * @param stream + * @return number of bits left in stream + */ +int loclass_bitsLeft(LoclassBitstreamIn_t* stream) { + return stream->numbits - stream->position; +} +/** + * @brief numBits + * @param stream + * @return Number of bits stored in stream + */ +void loclass_x_num_to_bytes(uint64_t n, size_t len, uint8_t* dest) { + while(len--) { + dest[len] = (uint8_t)n; + n >>= 8; + } +} + +uint64_t loclass_x_bytes_to_num(uint8_t* src, size_t len) { + uint64_t num = 0; + while(len--) { + num = (num << 8) | (*src); + src++; + } + return num; +} + +uint8_t loclass_reversebytes(uint8_t b) { + b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; + b = (b & 0xCC) >> 2 | (b & 0x33) << 2; + b = (b & 0xAA) >> 1 | (b & 0x55) << 1; + return b; +} + +void loclass_reverse_arraybytes(uint8_t* arr, size_t len) { + uint8_t i; + for(i = 0; i < len; i++) { + arr[i] = loclass_reversebytes(arr[i]); + } +} + +void loclass_reverse_arraycopy(uint8_t* arr, uint8_t* dest, size_t len) { + uint8_t i; + for(i = 0; i < len; i++) { + dest[i] = loclass_reversebytes(arr[i]); + } +} diff --git a/lib/loclass/optimized_cipherutils.h b/lib/loclass/optimized_cipherutils.h new file mode 100644 index 0000000..05b6820 --- /dev/null +++ b/lib/loclass/optimized_cipherutils.h @@ -0,0 +1,64 @@ +//----------------------------------------------------------------------------- +// Borrowed initially from https://github.com/holiman/loclass +// More recently from https://github.com/RfidResearchGroup/proxmark3 +// Copyright (C) 2014 Martin Holst Swende +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// WARNING +// +// THIS CODE IS CREATED FOR EXPERIMENTATION AND EDUCATIONAL USE ONLY. +// +// USAGE OF THIS CODE IN OTHER WAYS MAY INFRINGE UPON THE INTELLECTUAL +// PROPERTY OF OTHER PARTIES, SUCH AS INSIDE SECURE AND HID GLOBAL, +// AND MAY EXPOSE YOU TO AN INFRINGEMENT ACTION FROM THOSE PARTIES. +// +// THIS CODE SHOULD NEVER BE USED TO INFRINGE PATENTS OR INTELLECTUAL PROPERTY RIGHTS. +//----------------------------------------------------------------------------- +// It is a reconstruction of the cipher engine used in iClass, and RFID techology. +// +// The implementation is based on the work performed by +// Flavio D. Garcia, Gerhard de Koning Gans, Roel Verdult and +// Milosch Meriac in the paper "Dismantling IClass". +//----------------------------------------------------------------------------- +#ifndef CIPHERUTILS_H +#define CIPHERUTILS_H +#include +#include +#include + +typedef struct { + uint8_t* buffer; + uint8_t numbits; + uint8_t position; +} LoclassBitstreamIn_t; + +typedef struct { + uint8_t* buffer; + uint8_t numbits; + uint8_t position; +} LoclassBitstreamOut_t; + +bool loclass_headBit(LoclassBitstreamIn_t* stream); +bool loclass_tailBit(LoclassBitstreamIn_t* stream); +void loclass_pushBit(LoclassBitstreamOut_t* stream, bool bit); +int loclass_bitsLeft(LoclassBitstreamIn_t* stream); + +void loclass_push6bits(LoclassBitstreamOut_t* stream, uint8_t bits); +void loclass_x_num_to_bytes(uint64_t n, size_t len, uint8_t* dest); +uint64_t loclass_x_bytes_to_num(uint8_t* src, size_t len); +uint8_t loclass_reversebytes(uint8_t b); +void loclass_reverse_arraybytes(uint8_t* arr, size_t len); +void loclass_reverse_arraycopy(uint8_t* arr, uint8_t* dest, size_t len); +#endif // CIPHERUTILS_H diff --git a/lib/loclass/optimized_elite.c b/lib/loclass/optimized_elite.c new file mode 100644 index 0000000..e198a41 --- /dev/null +++ b/lib/loclass/optimized_elite.c @@ -0,0 +1,232 @@ +//----------------------------------------------------------------------------- +// Borrowed initially from https://github.com/holiman/loclass +// Copyright (C) 2014 Martin Holst Swende +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// WARNING +// +// THIS CODE IS CREATED FOR EXPERIMENTATION AND EDUCATIONAL USE ONLY. +// +// USAGE OF THIS CODE IN OTHER WAYS MAY INFRINGE UPON THE INTELLECTUAL +// PROPERTY OF OTHER PARTIES, SUCH AS INSIDE SECURE AND HID GLOBAL, +// AND MAY EXPOSE YOU TO AN INFRINGEMENT ACTION FROM THOSE PARTIES. +// +// THIS CODE SHOULD NEVER BE USED TO INFRINGE PATENTS OR INTELLECTUAL PROPERTY RIGHTS. +//----------------------------------------------------------------------------- +// It is a reconstruction of the cipher engine used in iClass, and RFID techology. +// +// The implementation is based on the work performed by +// Flavio D. Garcia, Gerhard de Koning Gans, Roel Verdult and +// Milosch Meriac in the paper "Dismantling IClass". +//----------------------------------------------------------------------------- +#include "optimized_elite.h" + +#include +#include +#include +#include +#include "optimized_ikeys.h" + +/** + * @brief Permutes a key from standard NIST format to Iclass specific format + * from http://www.proxmark.org/forum/viewtopic.php?pid=11220#p11220 + * + * If you loclass_permute [6c 8d 44 f9 2a 2d 01 bf] you get [8a 0d b9 88 bb a7 90 ea] as shown below. + * + * 1 0 1 1 1 1 1 1 bf + * 0 0 0 0 0 0 0 1 01 + * 0 0 1 0 1 1 0 1 2d + * 0 0 1 0 1 0 1 0 2a + * 1 1 1 1 1 0 0 1 f9 + * 0 1 0 0 0 1 0 0 44 + * 1 0 0 0 1 1 0 1 8d + * 0 1 1 0 1 1 0 0 6c + * + * 8 0 b 8 b a 9 e + * a d 9 8 b 7 0 a + * + * @param key + * @param dest + */ +void loclass_permutekey(const uint8_t key[8], uint8_t dest[8]) { + int i; + for(i = 0; i < 8; i++) { + dest[i] = (((key[7] & (0x80 >> i)) >> (7 - i)) << 7) | + (((key[6] & (0x80 >> i)) >> (7 - i)) << 6) | + (((key[5] & (0x80 >> i)) >> (7 - i)) << 5) | + (((key[4] & (0x80 >> i)) >> (7 - i)) << 4) | + (((key[3] & (0x80 >> i)) >> (7 - i)) << 3) | + (((key[2] & (0x80 >> i)) >> (7 - i)) << 2) | + (((key[1] & (0x80 >> i)) >> (7 - i)) << 1) | + (((key[0] & (0x80 >> i)) >> (7 - i)) << 0); + } +} +/** + * Permutes a key from iclass specific format to NIST format + * @brief loclass_permutekey_rev + * @param key + * @param dest + */ +void loclass_permutekey_rev(const uint8_t key[8], uint8_t dest[8]) { + int i; + for(i = 0; i < 8; i++) { + dest[7 - i] = (((key[0] & (0x80 >> i)) >> (7 - i)) << 7) | + (((key[1] & (0x80 >> i)) >> (7 - i)) << 6) | + (((key[2] & (0x80 >> i)) >> (7 - i)) << 5) | + (((key[3] & (0x80 >> i)) >> (7 - i)) << 4) | + (((key[4] & (0x80 >> i)) >> (7 - i)) << 3) | + (((key[5] & (0x80 >> i)) >> (7 - i)) << 2) | + (((key[6] & (0x80 >> i)) >> (7 - i)) << 1) | + (((key[7] & (0x80 >> i)) >> (7 - i)) << 0); + } +} + +/** + * Helper function for loclass_hash1 + * @brief loclass_rr + * @param val + * @return + */ +static uint8_t loclass_rr(uint8_t val) { + return val >> 1 | ((val & 1) << 7); +} + +/** + * Helper function for loclass_hash1 + * @brief rl + * @param val + * @return + */ +static uint8_t loclass_rl(uint8_t val) { + return val << 1 | ((val & 0x80) >> 7); +} + +/** + * Helper function for loclass_hash1 + * @brief loclass_swap + * @param val + * @return + */ +static uint8_t loclass_swap(uint8_t val) { + return ((val >> 4) & 0xFF) | ((val & 0xFF) << 4); +} + +/** + * Hash1 takes CSN as input, and determines what bytes in the keytable will be used + * when constructing the K_sel. + * @param csn the CSN used + * @param k output + */ +void loclass_hash1(const uint8_t csn[], uint8_t k[]) { + k[0] = csn[0] ^ csn[1] ^ csn[2] ^ csn[3] ^ csn[4] ^ csn[5] ^ csn[6] ^ csn[7]; + k[1] = csn[0] + csn[1] + csn[2] + csn[3] + csn[4] + csn[5] + csn[6] + csn[7]; + k[2] = loclass_rr(loclass_swap(csn[2] + k[1])); + k[3] = loclass_rl(loclass_swap(csn[3] + k[0])); + k[4] = ~loclass_rr(csn[4] + k[2]) + 1; + k[5] = ~loclass_rl(csn[5] + k[3]) + 1; + k[6] = loclass_rr(csn[6] + (k[4] ^ 0x3c)); + k[7] = loclass_rl(csn[7] + (k[5] ^ 0xc3)); + + k[7] &= 0x7F; + k[6] &= 0x7F; + k[5] &= 0x7F; + k[4] &= 0x7F; + k[3] &= 0x7F; + k[2] &= 0x7F; + k[1] &= 0x7F; + k[0] &= 0x7F; +} +/** +Definition 14. Define the rotate key function loclass_rk : (F 82 ) 8 × N → (F 82 ) 8 as +loclass_rk(x [0] . . . x [7] , 0) = x [0] . . . x [7] +loclass_rk(x [0] . . . x [7] , n + 1) = loclass_rk(loclass_rl(x [0] ) . . . loclass_rl(x [7] ), n) +**/ +static void loclass_rk(const uint8_t* key, uint8_t n, uint8_t* outp_key) { + memcpy(outp_key, key, 8); + uint8_t j; + while(n-- > 0) { + for(j = 0; j < 8; j++) outp_key[j] = loclass_rl(outp_key[j]); + } + return; +} + +static mbedtls_des_context loclass_ctx_enc; +static mbedtls_des_context loclass_ctx_dec; + +static void loclass_desdecrypt_iclass(uint8_t* iclass_key, uint8_t* input, uint8_t* output) { + uint8_t key_std_format[8] = {0}; + loclass_permutekey_rev(iclass_key, key_std_format); + mbedtls_des_setkey_dec(&loclass_ctx_dec, key_std_format); + mbedtls_des_crypt_ecb(&loclass_ctx_dec, input, output); +} + +static void loclass_desencrypt_iclass(const uint8_t* iclass_key, uint8_t* input, uint8_t* output) { + uint8_t key_std_format[8] = {0}; + loclass_permutekey_rev(iclass_key, key_std_format); + mbedtls_des_setkey_enc(&loclass_ctx_enc, key_std_format); + mbedtls_des_crypt_ecb(&loclass_ctx_enc, input, output); +} + +/** + * @brief Insert uint8_t[8] custom master key to calculate hash2 and return key_select. + * @param key unpermuted custom key + * @param loclass_hash1 loclass_hash1 + * @param key_sel output key_sel=h[loclass_hash1[i]] + */ +void loclass_hash2(const uint8_t* key64, uint8_t* outp_keytable) { + /** + *Expected: + * High Security Key Table + + 00 F1 35 59 A1 0D 5A 26 7F 18 60 0B 96 8A C0 25 C1 + 10 BF A1 3B B0 FF 85 28 75 F2 1F C6 8F 0E 74 8F 21 + 20 14 7A 55 16 C8 A9 7D B3 13 0C 5D C9 31 8D A9 B2 + 30 A3 56 83 0F 55 7E DE 45 71 21 D2 6D C1 57 1C 9C + 40 78 2F 64 51 42 7B 64 30 FA 26 51 76 D3 E0 FB B6 + 50 31 9F BF 2F 7E 4F 94 B4 BD 4F 75 91 E3 1B EB 42 + 60 3F 88 6F B8 6C 2C 93 0D 69 2C D5 20 3C C1 61 95 + 70 43 08 A0 2F FE B3 26 D7 98 0B 34 7B 47 70 A0 AB + + **** The 64-bit HS Custom Key Value = 5B7C62C491C11B39 ******/ + uint8_t key64_negated[8] = {0}; + uint8_t z[8][8] = {{0}, {0}}; + uint8_t temp_output[8] = {0}; + + //calculate complement of key + int i; + for(i = 0; i < 8; i++) key64_negated[i] = ~key64[i]; + + // Once again, key is on iclass-format + loclass_desencrypt_iclass(key64, key64_negated, z[0]); + + uint8_t y[8][8] = {{0}, {0}}; + + // y[0]=DES_dec(z[0],~key) + // Once again, key is on iclass-format + loclass_desdecrypt_iclass(z[0], key64_negated, y[0]); + + for(i = 1; i < 8; i++) { + loclass_rk(key64, i, temp_output); + loclass_desdecrypt_iclass(temp_output, z[i - 1], z[i]); + loclass_desencrypt_iclass(temp_output, y[i - 1], y[i]); + } + + if(outp_keytable != NULL) { + for(i = 0; i < 8; i++) { + memcpy(outp_keytable + i * 16, y[i], 8); + memcpy(outp_keytable + 8 + i * 16, z[i], 8); + } + } +} diff --git a/lib/loclass/optimized_elite.h b/lib/loclass/optimized_elite.h new file mode 100644 index 0000000..fba512a --- /dev/null +++ b/lib/loclass/optimized_elite.h @@ -0,0 +1,58 @@ +//----------------------------------------------------------------------------- +// Borrowed initially from https://github.com/holiman/loclass +// More recently from https://github.com/RfidResearchGroup/proxmark3 +// Copyright (C) 2014 Martin Holst Swende +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// WARNING +// +// THIS CODE IS CREATED FOR EXPERIMENTATION AND EDUCATIONAL USE ONLY. +// +// USAGE OF THIS CODE IN OTHER WAYS MAY INFRINGE UPON THE INTELLECTUAL +// PROPERTY OF OTHER PARTIES, SUCH AS INSIDE SECURE AND HID GLOBAL, +// AND MAY EXPOSE YOU TO AN INFRINGEMENT ACTION FROM THOSE PARTIES. +// +// THIS CODE SHOULD NEVER BE USED TO INFRINGE PATENTS OR INTELLECTUAL PROPERTY RIGHTS. +//----------------------------------------------------------------------------- +// It is a reconstruction of the cipher engine used in iClass, and RFID techology. +// +// The implementation is based on the work performed by +// Flavio D. Garcia, Gerhard de Koning Gans, Roel Verdult and +// Milosch Meriac in the paper "Dismantling IClass". +//----------------------------------------------------------------------------- +#ifndef ELITE_CRACK_H +#define ELITE_CRACK_H + +#include +#include + +void loclass_permutekey(const uint8_t key[8], uint8_t dest[8]); +/** + * Permutes a key from iclass specific format to NIST format + * @brief loclass_permutekey_rev + * @param key + * @param dest + */ +void loclass_permutekey_rev(const uint8_t key[8], uint8_t dest[8]); +/** + * Hash1 takes CSN as input, and determines what bytes in the keytable will be used + * when constructing the K_sel. + * @param csn the CSN used + * @param k output + */ +void loclass_hash1(const uint8_t* csn, uint8_t* k); +void loclass_hash2(const uint8_t* key64, uint8_t* outp_keytable); + +#endif diff --git a/lib/loclass/optimized_ikeys.c b/lib/loclass/optimized_ikeys.c new file mode 100644 index 0000000..d19f560 --- /dev/null +++ b/lib/loclass/optimized_ikeys.c @@ -0,0 +1,320 @@ +//----------------------------------------------------------------------------- +// Borrowed initially from https://github.com/holiman/loclass +// Copyright (C) 2014 Martin Holst Swende +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// WARNING +// +// THIS CODE IS CREATED FOR EXPERIMENTATION AND EDUCATIONAL USE ONLY. +// +// USAGE OF THIS CODE IN OTHER WAYS MAY INFRINGE UPON THE INTELLECTUAL +// PROPERTY OF OTHER PARTIES, SUCH AS INSIDE SECURE AND HID GLOBAL, +// AND MAY EXPOSE YOU TO AN INFRINGEMENT ACTION FROM THOSE PARTIES. +// +// THIS CODE SHOULD NEVER BE USED TO INFRINGE PATENTS OR INTELLECTUAL PROPERTY RIGHTS. +//----------------------------------------------------------------------------- +// It is a reconstruction of the cipher engine used in iClass, and RFID techology. +// +// The implementation is based on the work performed by +// Flavio D. Garcia, Gerhard de Koning Gans, Roel Verdult and +// Milosch Meriac in the paper "Dismantling IClass". +//----------------------------------------------------------------------------- + +/** +From "Dismantling iclass": + This section describes in detail the built-in key diversification algorithm of iClass. + Besides the obvious purpose of deriving a card key from a master key, this + algorithm intends to circumvent weaknesses in the cipher by preventing the + usage of certain ‘weak’ keys. In order to compute a diversified key, the iClass + reader first encrypts the card identity id with the master key K, using single + DES. The resulting ciphertext is then input to a function called loclass_hash0 which + outputs the diversified key k. + + k = loclass_hash0(DES enc (id, K)) + + Here the DES encryption of id with master key K outputs a cryptogram c + of 64 bits. These 64 bits are divided as c = x, y, z [0] , . . . , z [7] ∈ F 82 × F 82 × (F 62 ) 8 + which is used as input to the loclass_hash0 function. This function introduces some + obfuscation by performing a number of permutations, complement and modulo + operations, see Figure 2.5. Besides that, it checks for and removes patterns like + similar key bytes, which could produce a strong bias in the cipher. Finally, the + output of loclass_hash0 is the diversified card key k = k [0] , . . . , k [7] ∈ (F 82 ) 8 . + +**/ +#include "optimized_ikeys.h" + +#include +#include +#include +#include +#include "optimized_cipherutils.h" + +static const uint8_t loclass_pi[35] = {0x0F, 0x17, 0x1B, 0x1D, 0x1E, 0x27, 0x2B, 0x2D, 0x2E, + 0x33, 0x35, 0x39, 0x36, 0x3A, 0x3C, 0x47, 0x4B, 0x4D, + 0x4E, 0x53, 0x55, 0x56, 0x59, 0x5A, 0x5C, 0x63, 0x65, + 0x66, 0x69, 0x6A, 0x6C, 0x71, 0x72, 0x74, 0x78}; + +/** + * @brief The key diversification algorithm uses 6-bit bytes. + * This implementation uses 64 bit uint to pack seven of them into one + * variable. When they are there, they are placed as follows: + * XXXX XXXX N0 .... N7, occupying the last 48 bits. + * + * This function picks out one from such a collection + * @param all + * @param n bitnumber + * @return + */ +static uint8_t loclass_getSixBitByte(uint64_t c, int n) { + return (c >> (42 - 6 * n)) & 0x3F; +} + +/** + * @brief Puts back a six-bit 'byte' into a uint64_t. + * @param c buffer + * @param z the value to place there + * @param n bitnumber. + */ +static void loclass_pushbackSixBitByte(uint64_t* c, uint8_t z, int n) { + //0x XXXX YYYY ZZZZ ZZZZ ZZZZ + // ^z0 ^z7 + //z0: 1111 1100 0000 0000 + + uint64_t masked = z & 0x3F; + uint64_t eraser = 0x3F; + masked <<= 42 - 6 * n; + eraser <<= 42 - 6 * n; + + //masked <<= 6*n; + //eraser <<= 6*n; + + eraser = ~eraser; + (*c) &= eraser; + (*c) |= masked; +} +/** + * @brief Swaps the z-values. + * If the input value has format XYZ0Z1...Z7, the output will have the format + * XYZ7Z6...Z0 instead + * @param c + * @return + */ +static uint64_t loclass_swapZvalues(uint64_t c) { + uint64_t newz = 0; + loclass_pushbackSixBitByte(&newz, loclass_getSixBitByte(c, 0), 7); + loclass_pushbackSixBitByte(&newz, loclass_getSixBitByte(c, 1), 6); + loclass_pushbackSixBitByte(&newz, loclass_getSixBitByte(c, 2), 5); + loclass_pushbackSixBitByte(&newz, loclass_getSixBitByte(c, 3), 4); + loclass_pushbackSixBitByte(&newz, loclass_getSixBitByte(c, 4), 3); + loclass_pushbackSixBitByte(&newz, loclass_getSixBitByte(c, 5), 2); + loclass_pushbackSixBitByte(&newz, loclass_getSixBitByte(c, 6), 1); + loclass_pushbackSixBitByte(&newz, loclass_getSixBitByte(c, 7), 0); + newz |= (c & 0xFFFF000000000000); + return newz; +} + +/** +* @return 4 six-bit bytes chunked into a uint64_t,as 00..00a0a1a2a3 +*/ +static uint64_t loclass_ck(int i, int j, uint64_t z) { + if(i == 1 && j == -1) { + // loclass_ck(1, −1, z [0] . . . z [3] ) = z [0] . . . z [3] + return z; + } else if(j == -1) { + // loclass_ck(i, −1, z [0] . . . z [3] ) = loclass_ck(i − 1, i − 2, z [0] . . . z [3] ) + return loclass_ck(i - 1, i - 2, z); + } + + if(loclass_getSixBitByte(z, i) == loclass_getSixBitByte(z, j)) { + //loclass_ck(i, j − 1, z [0] . . . z [i] ← j . . . z [3] ) + uint64_t newz = 0; + int c; + for(c = 0; c < 4; c++) { + uint8_t val = loclass_getSixBitByte(z, c); + if(c == i) + loclass_pushbackSixBitByte(&newz, j, c); + else + loclass_pushbackSixBitByte(&newz, val, c); + } + return loclass_ck(i, j - 1, newz); + } else { + return loclass_ck(i, j - 1, z); + } +} +/** + + Definition 8. + Let the function check : (F 62 ) 8 → (F 62 ) 8 be defined as + check(z [0] . . . z [7] ) = loclass_ck(3, 2, z [0] . . . z [3] ) · loclass_ck(3, 2, z [4] . . . z [7] ) + + where loclass_ck : N × N × (F 62 ) 4 → (F 62 ) 4 is defined as + + loclass_ck(1, −1, z [0] . . . z [3] ) = z [0] . . . z [3] + loclass_ck(i, −1, z [0] . . . z [3] ) = loclass_ck(i − 1, i − 2, z [0] . . . z [3] ) + loclass_ck(i, j, z [0] . . . z [3] ) = + loclass_ck(i, j − 1, z [0] . . . z [i] ← j . . . z [3] ), if z [i] = z [j] ; + loclass_ck(i, j − 1, z [0] . . . z [3] ), otherwise + + otherwise. +**/ + +static uint64_t loclass_check(uint64_t z) { + //These 64 bits are divided as c = x, y, z [0] , . . . , z [7] + + // loclass_ck(3, 2, z [0] . . . z [3] ) + uint64_t ck1 = loclass_ck(3, 2, z); + + // loclass_ck(3, 2, z [4] . . . z [7] ) + uint64_t ck2 = loclass_ck(3, 2, z << 24); + + //The loclass_ck function will place the values + // in the middle of z. + ck1 &= 0x00000000FFFFFF000000; + ck2 &= 0x00000000FFFFFF000000; + + return ck1 | ck2 >> 24; +} + +static void loclass_permute( + LoclassBitstreamIn_t* p_in, + uint64_t z, + int l, + int r, + LoclassBitstreamOut_t* out) { + if(loclass_bitsLeft(p_in) == 0) return; + + bool pn = loclass_tailBit(p_in); + if(pn) { // pn = 1 + uint8_t zl = loclass_getSixBitByte(z, l); + + loclass_push6bits(out, zl + 1); + loclass_permute(p_in, z, l + 1, r, out); + } else { // otherwise + uint8_t zr = loclass_getSixBitByte(z, r); + + loclass_push6bits(out, zr); + loclass_permute(p_in, z, l, r + 1, out); + } +} + +/** + * @brief + *Definition 11. Let the function loclass_hash0 : F 82 × F 82 × (F 62 ) 8 → (F 82 ) 8 be defined as + * loclass_hash0(x, y, z [0] . . . z [7] ) = k [0] . . . k [7] where + * z'[i] = (z[i] mod (63-i)) + i i = 0...3 + * z'[i+4] = (z[i+4] mod (64-i)) + i i = 0...3 + * ẑ = check(z'); + * @param c + * @param k this is where the diversified key is put (should be 8 bytes) + * @return + */ +void loclass_hash0(uint64_t c, uint8_t k[8]) { + c = loclass_swapZvalues(c); + + //These 64 bits are divided as c = x, y, z [0] , . . . , z [7] + // x = 8 bits + // y = 8 bits + // z0-z7 6 bits each : 48 bits + uint8_t x = (c & 0xFF00000000000000) >> 56; + uint8_t y = (c & 0x00FF000000000000) >> 48; + uint64_t zP = 0; + + for(int n = 0; n < 4; n++) { + uint8_t zn = loclass_getSixBitByte(c, n); + uint8_t zn4 = loclass_getSixBitByte(c, n + 4); + uint8_t _zn = (zn % (63 - n)) + n; + uint8_t _zn4 = (zn4 % (64 - n)) + n; + loclass_pushbackSixBitByte(&zP, _zn, n); + loclass_pushbackSixBitByte(&zP, _zn4, n + 4); + } + + uint64_t zCaret = loclass_check(zP); + uint8_t p = loclass_pi[x % 35]; + + if(x & 1) //Check if x7 is 1 + p = ~p; + + LoclassBitstreamIn_t p_in = {&p, 8, 0}; + uint8_t outbuffer[] = {0, 0, 0, 0, 0, 0, 0, 0}; + LoclassBitstreamOut_t out = {outbuffer, 0, 0}; + loclass_permute(&p_in, zCaret, 0, 4, &out); //returns 48 bits? or 6 8-bytes + + //Out is now a buffer containing six-bit bytes, should be 48 bits + // if all went well + //Shift z-values down onto the lower segment + + uint64_t zTilde = loclass_x_bytes_to_num(outbuffer, sizeof(outbuffer)); + + zTilde >>= 16; + + for(int i = 0; i < 8; i++) { + // the key on index i is first a bit from y + // then six bits from z, + // then a bit from p + + // Init with zeroes + k[i] = 0; + // First, place yi leftmost in k + //k[i] |= (y << i) & 0x80 ; + + // First, place y(7-i) leftmost in k + k[i] |= (y << (7 - i)) & 0x80; + + uint8_t zTilde_i = loclass_getSixBitByte(zTilde, i); + // zTildeI is now on the form 00XXXXXX + // with one leftshift, it'll be + // 0XXXXXX0 + // So after leftshift, we can OR it into k + // However, when doing complement, we need to + // again MASK 0XXXXXX0 (0x7E) + zTilde_i <<= 1; + + //Finally, add bit from p or p-mod + //Shift bit i into rightmost location (mask only after complement) + uint8_t p_i = p >> i & 0x1; + + if(k[i]) { // yi = 1 + k[i] |= ~zTilde_i & 0x7E; + k[i] |= p_i & 1; + k[i] += 1; + + } else { // otherwise + k[i] |= zTilde_i & 0x7E; + k[i] |= (~p_i) & 1; + } + } +} +/** + * @brief Performs Elite-class key diversification + * @param csn + * @param key + * @param div_key + */ +void loclass_diversifyKey(const uint8_t* csn, const uint8_t* key, uint8_t* div_key) { + mbedtls_des_context loclass_ctx_enc; + + // Prepare the DES key + mbedtls_des_setkey_enc(&loclass_ctx_enc, key); + + uint8_t crypted_csn[8] = {0}; + + // Calculate DES(CSN, KEY) + mbedtls_des_crypt_ecb(&loclass_ctx_enc, csn, crypted_csn); + + //Calculate HASH0(DES)) + uint64_t c_csn = loclass_x_bytes_to_num(crypted_csn, sizeof(crypted_csn)); + + loclass_hash0(c_csn, div_key); +} diff --git a/lib/loclass/optimized_ikeys.h b/lib/loclass/optimized_ikeys.h new file mode 100644 index 0000000..f40e87b --- /dev/null +++ b/lib/loclass/optimized_ikeys.h @@ -0,0 +1,66 @@ +//----------------------------------------------------------------------------- +// Borrowed initially from https://github.com/holiman/loclass +// More recently from https://github.com/RfidResearchGroup/proxmark3 +// Copyright (C) 2014 Martin Holst Swende +// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// See LICENSE.txt for the text of the license. +//----------------------------------------------------------------------------- +// WARNING +// +// THIS CODE IS CREATED FOR EXPERIMENTATION AND EDUCATIONAL USE ONLY. +// +// USAGE OF THIS CODE IN OTHER WAYS MAY INFRINGE UPON THE INTELLECTUAL +// PROPERTY OF OTHER PARTIES, SUCH AS INSIDE SECURE AND HID GLOBAL, +// AND MAY EXPOSE YOU TO AN INFRINGEMENT ACTION FROM THOSE PARTIES. +// +// THIS CODE SHOULD NEVER BE USED TO INFRINGE PATENTS OR INTELLECTUAL PROPERTY RIGHTS. +//----------------------------------------------------------------------------- +// It is a reconstruction of the cipher engine used in iClass, and RFID techology. +// +// The implementation is based on the work performed by +// Flavio D. Garcia, Gerhard de Koning Gans, Roel Verdult and +// Milosch Meriac in the paper "Dismantling IClass". +//----------------------------------------------------------------------------- +#ifndef IKEYS_H +#define IKEYS_H + +#include + +/** + * @brief + *Definition 11. Let the function loclass_hash0 : F 82 × F 82 × (F 62 ) 8 → (F 82 ) 8 be defined as + * loclass_hash0(x, y, z [0] . . . z [7] ) = k [0] . . . k [7] where + * z'[i] = (z[i] mod (63-i)) + i i = 0...3 + * z'[i+4] = (z[i+4] mod (64-i)) + i i = 0...3 + * ẑ = check(z'); + * @param c + * @param k this is where the diversified key is put (should be 8 bytes) + * @return + */ +void loclass_hash0(uint64_t c, uint8_t k[8]); +/** + * @brief Performs Elite-class key diversification + * @param csn + * @param key + * @param div_key + */ + +void loclass_diversifyKey(const uint8_t* csn, const uint8_t* key, uint8_t* div_key); +/** + * @brief Permutes a key from standard NIST format to Iclass specific format + * @param key + * @param dest + */ + +#endif // IKEYS_H diff --git a/scenes/scene_items.c b/scenes/scene_items.c index ed47eb6..00074d8 100644 --- a/scenes/scene_items.c +++ b/scenes/scene_items.c @@ -20,6 +20,7 @@ static const ActionMenuItemType ItemToMenuItem[] = { [Item_IR] = ActionMenuItemTypeIR, [Item_NFC] = ActionMenuItemTypeNFC, [Item_iButton] = ActionMenuItemTypeiButton, + [Item_Picopass] = ActionMenuItemTypePicopass, [Item_Playlist] = ActionMenuItemTypePlaylist, [Item_Group] = ActionMenuItemTypeGroup, [Item_Settings] = ActionMenuItemTypeSettings, @@ -116,6 +117,10 @@ bool scene_items_on_event(void* context, SceneManagerEvent event) { item_items_view_free(app->items_view); app->items_view = new_items; scene_manager_next_scene(app->scene_manager, QScene_Items); + } else if(item->type == Item_Picopass) { + // Picopass uses a dedicated scene for continuous emulation + FURI_LOG_I(TAG, "Starting picopass emulation: %s", furi_string_get_cstr(item->name)); + scene_manager_next_scene(app->scene_manager, QScene_PicopassEmulate); } else { FURI_LOG_I( TAG, "Initiating item action: %s", furi_string_get_cstr(item->name)); diff --git a/scenes/scene_picopass_emulate.c b/scenes/scene_picopass_emulate.c new file mode 100644 index 0000000..d83d3e6 --- /dev/null +++ b/scenes/scene_picopass_emulate.c @@ -0,0 +1,591 @@ +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "quac.h" +#include "scenes.h" +#include "scene_items.h" + +#include +#include + +// Use quac's TAG defined in quac.h + +// Picopass constants +#define PICOPASS_BLOCK_LEN 8 +#define PICOPASS_MAX_APP_LIMIT 32 +#define PICOPASS_CSN_BLOCK_INDEX 0 +#define PICOPASS_CONFIG_BLOCK_INDEX 1 +#define PICOPASS_SECURE_AIA_BLOCK_INDEX 2 +#define PICOPASS_SECURE_KD_BLOCK_INDEX 3 +#define PICOPASS_SECURE_KC_BLOCK_INDEX 4 +#define PICOPASS_SECURE_EPURSE_BLOCK_INDEX 2 +#define PICOPASS_MAC_LEN 4 +#define PICOPASS_FUSE_CRYPT10 0x10 +#define PICOPASS_FUSE_CRYPT0 0x00 +#define PICOPASS_FDT_LISTEN_FC 1000 +#define PICOPASS_LISTENER_BUFFER_SIZE_MAX 255 + +// Picopass commands +#define PICOPASS_CMD_ACTALL 0x0A +#define PICOPASS_CMD_ACT 0x8E +#define PICOPASS_CMD_HALT 0x00 +#define PICOPASS_CMD_READ_OR_IDENTIFY 0x0C +#define PICOPASS_CMD_SELECT 0x81 +#define PICOPASS_CMD_READCHECK_KD 0x88 +#define PICOPASS_CMD_READCHECK_KC 0x18 +#define PICOPASS_CMD_CHECK 0x05 +#define PICOPASS_CMD_READ4 0x06 + +typedef struct { + uint8_t data[PICOPASS_BLOCK_LEN]; + bool valid; +} PicopassBlock; + +typedef struct { + PicopassBlock card_data[PICOPASS_MAX_APP_LIMIT]; +} PicopassData; + +typedef enum { + PicopassListenerStateIdle, + PicopassListenerStateHalt, + PicopassListenerStateActive, + PicopassListenerStateSelected, +} PicopassListenerState; + +typedef struct { + Nfc* nfc; + PicopassData* data; + PicopassListenerState state; + BitBuffer* tx_buffer; + BitBuffer* tmp_buffer; + uint8_t key_block_num; + LoclassState_t cipher_state; +} PicopassEmulator; + +// Emulator instance (stored in app context) +static PicopassEmulator* g_emu = NULL; +static bool g_emulation_running = false; + +// Helper to convert hex char to nibble +static bool hex_char_to_uint8(char hi, char lo, uint8_t* out) { + uint8_t result = 0; + + if(hi >= '0' && hi <= '9') result = (hi - '0') << 4; + else if(hi >= 'A' && hi <= 'F') result = (hi - 'A' + 10) << 4; + else if(hi >= 'a' && hi <= 'f') result = (hi - 'a' + 10) << 4; + else return false; + + if(lo >= '0' && lo <= '9') result |= (lo - '0'); + else if(lo >= 'A' && lo <= 'F') result |= (lo - 'A' + 10); + else if(lo >= 'a' && lo <= 'f') result |= (lo - 'a' + 10); + else return false; + + *out = result; + return true; +} + +// Helper to parse hex string to bytes (matches picopass implementation) +static bool picopass_hex_str_to_uint8(const char* value_str, size_t max, uint8_t* value) { + bool parse_success = false; + size_t count = 0; + + while(value_str[0] && value_str[1]) { + if(count++ >= max) { + parse_success = true; + break; + } + parse_success = hex_char_to_uint8(value_str[0], value_str[1], value++); + if(!parse_success) break; + value_str += 3; // Skip 2 hex chars + 1 space + } + return parse_success; +} + +// Global error message for debugging +static char g_parse_error[64] = {0}; + +// Load picopass file +static bool picopass_load_file(const char* path, PicopassData* data) { + bool parsed = false; + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* file = flipper_format_file_alloc(storage); + FuriString* temp_str = furi_string_alloc(); + FuriString* block_str = furi_string_alloc(); + + memset(data, 0, sizeof(PicopassData)); + snprintf(g_parse_error, sizeof(g_parse_error), "Unknown error"); + + do { + if(!flipper_format_file_open_existing(file, path)) { + snprintf(g_parse_error, sizeof(g_parse_error), "Can't open file"); + FURI_LOG_E(TAG, "Failed to open file: %s", path); + break; + } + + uint32_t version = 0; + if(!flipper_format_read_header(file, temp_str, &version)) { + snprintf(g_parse_error, sizeof(g_parse_error), "Can't read header"); + break; + } + + if(furi_string_cmp_str(temp_str, "Flipper Picopass device") != 0) { + snprintf(g_parse_error, sizeof(g_parse_error), "Bad header: %.20s", furi_string_get_cstr(temp_str)); + break; + } + + // Read blocks 0-5 (required blocks) + bool block_read = true; + const char* unknown_block = "?? ?? ?? ?? ?? ?? ?? ??"; + + for(size_t i = 0; i < 6; i++) { + furi_string_printf(temp_str, "Block %d", i); + if(!flipper_format_read_string(file, furi_string_get_cstr(temp_str), block_str)) { + snprintf(g_parse_error, sizeof(g_parse_error), "Can't read Block %d", (int)i); + block_read = false; + break; + } + if(furi_string_equal_str(block_str, unknown_block)) { + data->card_data[i].valid = false; + } else { + if(!picopass_hex_str_to_uint8( + furi_string_get_cstr(block_str), PICOPASS_BLOCK_LEN, data->card_data[i].data)) { + snprintf(g_parse_error, sizeof(g_parse_error), "Bad hex Block %d", (int)i); + block_read = false; + break; + } + data->card_data[i].valid = true; + } + } + if(!block_read) break; + + // Parse app blocks based on app_limit + size_t app_limit = data->card_data[PICOPASS_CONFIG_BLOCK_INDEX].data[0]; + if(app_limit > PICOPASS_MAX_APP_LIMIT) app_limit = PICOPASS_MAX_APP_LIMIT; + + for(size_t i = 6; i < app_limit; i++) { + furi_string_printf(temp_str, "Block %d", i); + if(!flipper_format_read_string(file, furi_string_get_cstr(temp_str), block_str)) { + break; // Optional blocks, don't fail if missing + } + if(furi_string_equal_str(block_str, unknown_block)) { + data->card_data[i].valid = false; + } else { + if(picopass_hex_str_to_uint8( + furi_string_get_cstr(block_str), PICOPASS_BLOCK_LEN, data->card_data[i].data)) { + data->card_data[i].valid = true; + } + } + } + + snprintf(g_parse_error, sizeof(g_parse_error), "OK"); + parsed = true; + } while(false); + + furi_string_free(temp_str); + furi_string_free(block_str); + flipper_format_free(file); + furi_record_close(RECORD_STORAGE); + + return parsed; +} + +// Initialize cipher state with the key +static void picopass_init_cipher_state(PicopassEmulator* emu) { + uint8_t key[PICOPASS_BLOCK_LEN] = {}; + memcpy(key, emu->data->card_data[emu->key_block_num].data, PICOPASS_BLOCK_LEN); + + uint8_t cc[PICOPASS_BLOCK_LEN] = {}; + memcpy(cc, emu->data->card_data[PICOPASS_SECURE_EPURSE_BLOCK_INDEX].data, PICOPASS_BLOCK_LEN); + + emu->cipher_state = loclass_opt_doTagMAC_1(cc, key); +} + +// Generate anti-collision CSN +static void picopass_write_anticoll_csn(const uint8_t* uid, BitBuffer* buffer) { + bit_buffer_reset(buffer); + for(size_t i = 0; i < PICOPASS_BLOCK_LEN; i++) { + bit_buffer_append_byte(buffer, (uid[i] >> 3) | (uid[(i + 1) % 8] << 5)); + } +} + +// Send frame with CRC +static void picopass_send_frame(PicopassEmulator* emu, BitBuffer* buffer) { + iso13239_crc_append(Iso13239CrcTypePicopass, buffer); + nfc_listener_tx(emu->nfc, buffer); +} + +// NFC listener callback +static NfcCommand picopass_listener_callback(NfcEvent event, void* context) { + PicopassEmulator* emu = context; + + if(event.type != NfcEventTypeRxEnd) { + return NfcCommandContinue; + } + + BitBuffer* rx_buf = event.data.buffer; + size_t rx_bits = bit_buffer_get_size(rx_buf); + + if(rx_bits < 8) return NfcCommandContinue; + + uint8_t cmd = bit_buffer_get_byte(rx_buf, 0); + + bool secured = (emu->data->card_data[PICOPASS_CONFIG_BLOCK_INDEX].data[7] & + PICOPASS_FUSE_CRYPT10) != PICOPASS_FUSE_CRYPT0; + + switch(cmd) { + case PICOPASS_CMD_ACTALL: + if(rx_bits == 8 && emu->state != PicopassListenerStateHalt) { + emu->state = PicopassListenerStateActive; + nfc_iso15693_listener_tx_sof(emu->nfc); + } + break; + + case PICOPASS_CMD_ACT: + if(rx_bits == 8 && emu->state == PicopassListenerStateActive) { + nfc_iso15693_listener_tx_sof(emu->nfc); + } + break; + + case PICOPASS_CMD_HALT: + if(rx_bits == 8) { + emu->state = PicopassListenerStateIdle; + nfc_iso15693_listener_tx_sof(emu->nfc); + } + break; + + case PICOPASS_CMD_READ_OR_IDENTIFY: + if(rx_bits == 8 && emu->state == PicopassListenerStateActive) { + // IDENTIFY - return anti-collision CSN + picopass_write_anticoll_csn(emu->data->card_data[PICOPASS_CSN_BLOCK_INDEX].data, emu->tx_buffer); + picopass_send_frame(emu, emu->tx_buffer); + } else if(rx_bits == 8 * 4 && emu->state == PicopassListenerStateSelected) { + // READ command + uint8_t block_num = bit_buffer_get_byte(rx_buf, 1); + if(block_num >= PICOPASS_MAX_APP_LIMIT) break; + + if(secured && (block_num == PICOPASS_SECURE_KD_BLOCK_INDEX || + block_num == PICOPASS_SECURE_KC_BLOCK_INDEX)) { + // Return 0xFF for key blocks + bit_buffer_reset(emu->tx_buffer); + for(size_t i = 0; i < PICOPASS_BLOCK_LEN; i++) { + bit_buffer_append_byte(emu->tx_buffer, 0xFF); + } + } else { + bit_buffer_copy_bytes(emu->tx_buffer, emu->data->card_data[block_num].data, PICOPASS_BLOCK_LEN); + } + picopass_send_frame(emu, emu->tx_buffer); + } + break; + + case PICOPASS_CMD_SELECT: + if(rx_bits == 8 * 9) { + const uint8_t* received_data = bit_buffer_get_data(rx_buf); + const uint8_t* csn = emu->data->card_data[PICOPASS_CSN_BLOCK_INDEX].data; + + // Check CSN based on state + if(emu->state == PicopassListenerStateHalt || emu->state == PicopassListenerStateIdle) { + if(memcmp(&received_data[1], csn, PICOPASS_BLOCK_LEN) != 0) { + if(emu->state == PicopassListenerStateActive) { + emu->state = PicopassListenerStateIdle; + } + break; + } + } else { + // Compare with anti-collision CSN + picopass_write_anticoll_csn(csn, emu->tmp_buffer); + if(memcmp(&received_data[1], bit_buffer_get_data(emu->tmp_buffer), PICOPASS_BLOCK_LEN) != 0) { + emu->state = PicopassListenerStateIdle; + break; + } + } + + emu->state = PicopassListenerStateSelected; + bit_buffer_copy_bytes(emu->tx_buffer, csn, PICOPASS_BLOCK_LEN); + picopass_send_frame(emu, emu->tx_buffer); + } + break; + + case PICOPASS_CMD_READCHECK_KD: + case PICOPASS_CMD_READCHECK_KC: + if(emu->state == PicopassListenerStateSelected && rx_bits == 8 * 2) { + uint8_t block_num = bit_buffer_get_byte(rx_buf, 1); + if(block_num != PICOPASS_SECURE_EPURSE_BLOCK_INDEX) break; + + uint8_t new_key_block = (cmd == PICOPASS_CMD_READCHECK_KD) ? + PICOPASS_SECURE_KD_BLOCK_INDEX : PICOPASS_SECURE_KC_BLOCK_INDEX; + + if(emu->key_block_num != new_key_block) { + emu->key_block_num = new_key_block; + picopass_init_cipher_state(emu); + } + + // Return e-purse block (no CRC for READCHECK) + bit_buffer_copy_bytes(emu->tx_buffer, emu->data->card_data[block_num].data, PICOPASS_BLOCK_LEN); + nfc_listener_tx(emu->nfc, emu->tx_buffer); + } + break; + + case PICOPASS_CMD_CHECK: + if(emu->state == PicopassListenerStateSelected && rx_bits == 8 * 9) { + if(!secured) break; + + const uint8_t* key = emu->data->card_data[emu->key_block_num].data; + bool have_key = emu->data->card_data[emu->key_block_num].valid; + const uint8_t* rx_data = bit_buffer_get_data(rx_buf); + + if(have_key) { + uint8_t rmac[4] = {}; + uint8_t tmac[4] = {}; + + loclass_opt_doBothMAC_2(emu->cipher_state, &rx_data[1], rmac, tmac, key); + + if(memcmp(&rx_data[5], rmac, PICOPASS_MAC_LEN) != 0) { + // Bad MAC - reset cipher and don't respond + picopass_init_cipher_state(emu); + break; + } + + // Send tag MAC response + bit_buffer_copy_bytes(emu->tx_buffer, tmac, sizeof(tmac)); + nfc_listener_tx(emu->nfc, emu->tx_buffer); + } else { + // CVE-2024-41566 exploit - dummy response + uint8_t dummy[4] = {0xFF, 0xFF, 0xFF, 0xFF}; + bit_buffer_copy_bytes(emu->tx_buffer, dummy, 4); + nfc_listener_tx(emu->nfc, emu->tx_buffer); + } + } + break; + + case PICOPASS_CMD_READ4: + if(emu->state == PicopassListenerStateSelected && rx_bits == 8 * 4) { + uint8_t block_num = bit_buffer_get_byte(rx_buf, 1); + if(block_num + 4 > PICOPASS_MAX_APP_LIMIT) break; + + bit_buffer_reset(emu->tx_buffer); + for(size_t i = 0; i < 4; i++) { + uint8_t bn = block_num + i; + if(secured && (bn == PICOPASS_SECURE_KD_BLOCK_INDEX || bn == PICOPASS_SECURE_KC_BLOCK_INDEX)) { + for(size_t j = 0; j < PICOPASS_BLOCK_LEN; j++) { + bit_buffer_append_byte(emu->tx_buffer, 0xFF); + } + } else { + for(size_t j = 0; j < PICOPASS_BLOCK_LEN; j++) { + bit_buffer_append_byte(emu->tx_buffer, emu->data->card_data[bn].data[j]); + } + } + } + picopass_send_frame(emu, emu->tx_buffer); + } + break; + } + + return NfcCommandContinue; +} + +static void scene_picopass_popup_callback(void* context) { + UNUSED(context); +} + +// Resolve a quac link file to get the actual path +static bool resolve_link_path(App* app, const FuriString* link_path, FuriString* resolved_path) { + bool success = false; + File* file_link = storage_file_alloc(app->storage); + + do { + if(!storage_file_open(file_link, furi_string_get_cstr(link_path), FSAM_READ, FSOM_OPEN_EXISTING)) { + FURI_LOG_E(TAG, "Failed to open link file: %s", furi_string_get_cstr(link_path)); + break; + } + + furi_string_reset(resolved_path); + const size_t file_size = storage_file_size(file_link); + size_t bytes_read = 0; + while(bytes_read < file_size) { + char buffer[65] = {0}; + const size_t chunk_size = MIN(file_size - bytes_read, sizeof(buffer) - 1); + size_t chunk_read = storage_file_read(file_link, buffer, chunk_size); + if(chunk_read != chunk_size) break; + bytes_read += chunk_read; + furi_string_cat_str(resolved_path, buffer); + } + furi_string_trim(resolved_path); + + if(bytes_read != file_size) { + FURI_LOG_E(TAG, "Error reading link file"); + break; + } + + if(!storage_file_exists(app->storage, furi_string_get_cstr(resolved_path))) { + FURI_LOG_E(TAG, "Linked file does not exist: %s", furi_string_get_cstr(resolved_path)); + break; + } + + success = true; + } while(false); + + storage_file_close(file_link); + storage_file_free(file_link); + return success; +} + +void scene_picopass_emulate_on_enter(void* context) { + App* app = context; + + // Get the selected item's path + if(app->selected_item < 0 || app->selected_item >= (int)ItemArray_size(app->items_view->items)) { + FURI_LOG_E(TAG, "Invalid selected item index: %d", app->selected_item); + // Yellow = bad index + notification_message(app->notifications, &sequence_set_only_red_255); + notification_message(app->notifications, &sequence_set_only_green_255); + furi_delay_ms(500); + notification_message(app->notifications, &sequence_reset_rgb); + scene_manager_previous_scene(app->scene_manager); + return; + } + + Item* item = ItemArray_get(app->items_view->items, app->selected_item); + + // Handle link resolution (like action_tx does) + FuriString* actual_path = furi_string_alloc_set(item->path); + if(item->is_link) { + FURI_LOG_I(TAG, "Resolving quac link: %s", furi_string_get_cstr(item->path)); + if(!resolve_link_path(app, item->path, actual_path)) { + FURI_LOG_E(TAG, "Failed to resolve link"); + furi_string_free(actual_path); + // Cyan = link resolution failed + notification_message(app->notifications, &sequence_set_only_blue_255); + notification_message(app->notifications, &sequence_set_only_green_255); + furi_delay_ms(500); + notification_message(app->notifications, &sequence_reset_rgb); + scene_manager_previous_scene(app->scene_manager); + return; + } + FURI_LOG_I(TAG, "Resolved to: %s", furi_string_get_cstr(actual_path)); + } + + const char* path = furi_string_get_cstr(actual_path); + FURI_LOG_I(TAG, "Starting picopass emulation: %s", path); + + // Check if file exists first + if(!storage_file_exists(app->storage, path)) { + FURI_LOG_E(TAG, "File does not exist: %s", path); + furi_string_free(actual_path); + // Blue = file doesn't exist + notification_message(app->notifications, &sequence_set_only_blue_255); + furi_delay_ms(500); + notification_message(app->notifications, &sequence_reset_rgb); + scene_manager_previous_scene(app->scene_manager); + return; + } + + // Allocate data structures + PicopassData* data = malloc(sizeof(PicopassData)); + if(!picopass_load_file(path, data)) { + FURI_LOG_E(TAG, "Failed to load picopass file: %s - %s", path, g_parse_error); + free(data); + furi_string_free(actual_path); + + // Show error on popup + Popup* popup = app->popup; + popup_reset(popup); + popup_set_header(popup, "Parse Error", 64, 10, AlignCenter, AlignTop); + popup_set_text(popup, g_parse_error, 64, 32, AlignCenter, AlignTop); + popup_set_timeout(popup, 3000); + popup_enable_timeout(popup); + popup_set_context(popup, app); + popup_set_callback(popup, scene_picopass_popup_callback); + + view_dispatcher_switch_to_view(app->view_dispatcher, QView_Popup); + + // Wait then go back + furi_delay_ms(3000); + scene_manager_previous_scene(app->scene_manager); + return; + } + + // Free the path string now that we've loaded the data + furi_string_free(actual_path); + + // Start NFC + Nfc* nfc = nfc_alloc(); + + // Allocate emulator + g_emu = malloc(sizeof(PicopassEmulator)); + g_emu->nfc = nfc; + g_emu->data = data; + g_emu->state = PicopassListenerStateIdle; + g_emu->tx_buffer = bit_buffer_alloc(PICOPASS_LISTENER_BUFFER_SIZE_MAX); + g_emu->tmp_buffer = bit_buffer_alloc(PICOPASS_LISTENER_BUFFER_SIZE_MAX); + g_emu->key_block_num = 0; + + // Configure NFC - order matters! + nfc_set_fdt_listen_fc(nfc, PICOPASS_FDT_LISTEN_FC); + nfc_config(nfc, NfcModeListener, NfcTechIso15693); + + // Start emulation + nfc_start(nfc, picopass_listener_callback, g_emu); + g_emulation_running = true; + + // Set up popup view + Popup* popup = app->popup; + popup_reset(popup); + popup_set_header(popup, "Emulating", 64, 20, AlignCenter, AlignTop); + popup_set_text(popup, "Picopass\n\nPress BACK to stop", 64, 35, AlignCenter, AlignTop); + popup_set_timeout(popup, 0); // No timeout + popup_set_context(popup, app); + popup_set_callback(popup, scene_picopass_popup_callback); + popup_disable_timeout(popup); + + // Start blinking LED + notification_message(app->notifications, &sequence_blink_start_magenta); + + view_dispatcher_switch_to_view(app->view_dispatcher, QView_Popup); +} + +bool scene_picopass_emulate_on_event(void* context, SceneManagerEvent event) { + App* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeBack) { + // Stop emulation and go back + consumed = true; + scene_manager_previous_scene(app->scene_manager); + } + + return consumed; +} + +void scene_picopass_emulate_on_exit(void* context) { + App* app = context; + + FURI_LOG_I(TAG, "Stopping picopass emulation"); + + // Stop LED + notification_message(app->notifications, &sequence_blink_stop); + + // Stop emulation + if(g_emu && g_emulation_running) { + nfc_stop(g_emu->nfc); + g_emulation_running = false; + + bit_buffer_free(g_emu->tx_buffer); + bit_buffer_free(g_emu->tmp_buffer); + nfc_free(g_emu->nfc); + free(g_emu->data); + free(g_emu); + g_emu = NULL; + } + + // Reset popup + popup_reset(app->popup); +} diff --git a/scenes/scene_picopass_emulate.h b/scenes/scene_picopass_emulate.h new file mode 100644 index 0000000..247f091 --- /dev/null +++ b/scenes/scene_picopass_emulate.h @@ -0,0 +1,5 @@ +#pragma once + +void scene_picopass_emulate_on_enter(void* context); +bool scene_picopass_emulate_on_event(void* context, SceneManagerEvent event); +void scene_picopass_emulate_on_exit(void* context); diff --git a/scenes/scenes.c b/scenes/scenes.c index ef3b2cf..220f79e 100644 --- a/scenes/scenes.c +++ b/scenes/scenes.c @@ -9,6 +9,7 @@ #include "scene_action_create_group.h" #include "scene_action_ir_list.h" #include "scene_about.h" +#include "scene_picopass_emulate.h" // define handler callbacks - order must match appScenes enum! void (*const app_on_enter_handlers[])(void* context) = { @@ -19,6 +20,7 @@ void (*const app_on_enter_handlers[])(void* context) = { scene_action_create_group_on_enter, scene_action_ir_list_on_enter, scene_about_on_enter, + scene_picopass_emulate_on_enter, }; bool (*const app_on_event_handlers[])(void* context, SceneManagerEvent event) = { scene_items_on_event, @@ -28,6 +30,7 @@ bool (*const app_on_event_handlers[])(void* context, SceneManagerEvent event) = scene_action_create_group_on_event, scene_action_ir_list_on_event, scene_about_on_event, + scene_picopass_emulate_on_event, }; void (*const app_on_exit_handlers[])(void* context) = { scene_items_on_exit, @@ -37,6 +40,7 @@ void (*const app_on_exit_handlers[])(void* context) = { scene_action_create_group_on_exit, scene_action_ir_list_on_exit, scene_about_on_exit, + scene_picopass_emulate_on_exit, }; const SceneManagerHandlers app_scene_handlers = { diff --git a/scenes/scenes.h b/scenes/scenes.h index 69021ad..931472d 100644 --- a/scenes/scenes.h +++ b/scenes/scenes.h @@ -9,6 +9,7 @@ typedef enum { QScene_ActionCreateGroup, QScene_ActionIRList, QScene_About, + QScene_PicopassEmulate, QScene_count } appScenes; diff --git a/views/action_menu.c b/views/action_menu.c index e3090f7..00ac6ff 100644 --- a/views/action_menu.c +++ b/views/action_menu.c @@ -29,6 +29,7 @@ static const Icon* ActionMenuIcons[] = { [ActionMenuItemTypeIR] = &I_IR_10px, [ActionMenuItemTypeNFC] = &I_NFC_10px, [ActionMenuItemTypeiButton] = &I_iButton_10px, + [ActionMenuItemTypePicopass] = &I_NFC_10px, // Uses NFC icon; add Picopass_10px.png for custom icon [ActionMenuItemTypePlaylist] = &I_Playlist_10px, [ActionMenuItemTypeGroup] = &I_Directory_10px, [ActionMenuItemTypeSettings] = &I_Settings_10px, diff --git a/views/action_menu.h b/views/action_menu.h index 70d59f7..7934d45 100644 --- a/views/action_menu.h +++ b/views/action_menu.h @@ -23,6 +23,7 @@ typedef enum { ActionMenuItemTypeIR, ActionMenuItemTypeNFC, ActionMenuItemTypeiButton, + ActionMenuItemTypePicopass, ActionMenuItemTypePlaylist, ActionMenuItemTypeGroup, ActionMenuItemTypeSettings,