//----------------------------------------------------------------------------- // Copyright (C) 2020 tharexde // // modified iceman, 2020 // // This code is licensed to you under the terms of the GNU GPL, version 2 or, // at your option, any later version. See the LICENSE.txt file for the text of // the license. //----------------------------------------------------------------------------- // Low frequency EM4x50 commands //----------------------------------------------------------------------------- #include "cliparser.h" #include "cmdlfem4x50.h" #include #include "cmdparser.h" // command_t #include "fileutils.h" #include "commonutil.h" #include "pmflash.h" #include "cmdflashmemspiffs.h" #define BYTES2UINT32(x) ((x[0] << 24) | (x[1] << 16) | (x[2] << 8) | (x[3])) static int CmdHelp(const char *Cmd); static void prepare_result(const uint8_t *data, int fwr, int lwr, em4x50_word_t *words) { // restructure received result in "em4x50_word_t" structure for (int i = fwr; i <= lwr; i++) { for (int j = 0; j < 4; j++) { words[i].byte[j] = data[i * 4 + (3 - j)]; } } } static void print_result(const em4x50_word_t *words, int fwr, int lwr) { // print available information for given word from fwr to lwr, i.e. // bit table + summary lines with hex notation of word (msb + lsb) PrintAndLogEx(NORMAL, ""); PrintAndLogEx(INFO, " # | word (msb) | word (lsb) | desc"); PrintAndLogEx(INFO, "----+-------------+-------------+--------------------"); for (int i = fwr; i <= lwr; i++) { char s[50] = {0}; switch (i) { case EM4X50_DEVICE_PASSWORD: sprintf(s, _YELLOW_("password, write only")); break; case EM4X50_PROTECTION: sprintf(s, _YELLOW_("protection cfg (locked)")); break; case EM4X50_CONTROL: sprintf(s, _YELLOW_("control cfg (locked)")); break; case EM4X50_DEVICE_SERIAL: sprintf(s, _YELLOW_("device serial number (read only)")); break; case EM4X50_DEVICE_ID: sprintf(s, _YELLOW_("device identification (read only)")); break; default: sprintf(s, "user data"); break; } char r[30] = {0}; for (int j = 3; j >= 0; j--) { sprintf(r + strlen(r), "%02x ", reflect8(words[i].byte[j])); } PrintAndLogEx(INFO, " %2i | " _GREEN_("%s") "| %s| %s", i, sprint_hex(words[i].byte, 4), r, s ); } PrintAndLogEx(INFO, "----+-------------+-------------+--------------------"); } static void print_info_result(uint8_t *data, bool verbose) { // display all information of info result in structured format em4x50_word_t words[EM4X50_NO_WORDS]; prepare_result(data, 0, EM4X50_NO_WORDS - 1, words); bool bpwc = words[EM4X50_CONTROL].byte[CONFIG_BLOCK] & PASSWORD_CHECK; bool braw = words[EM4X50_CONTROL].byte[CONFIG_BLOCK] & READ_AFTER_WRITE; int fwr = reflect8(words[EM4X50_CONTROL].byte[FIRST_WORD_READ]); int lwr = reflect8(words[EM4X50_CONTROL].byte[LAST_WORD_READ]); int fwrp = reflect8(words[EM4X50_PROTECTION].byte[FIRST_WORD_READ_PROTECTED]); int lwrp = reflect8(words[EM4X50_PROTECTION].byte[LAST_WORD_READ_PROTECTED]); int fwwi = reflect8(words[EM4X50_PROTECTION].byte[FIRST_WORD_WRITE_INHIBITED]); int lwwi = reflect8(words[EM4X50_PROTECTION].byte[LAST_WORD_WRITE_INHIBITED]); PrintAndLogEx(NORMAL, ""); PrintAndLogEx(INFO, "--- " _CYAN_("Tag Information") " ---------------------------"); // data section PrintAndLogEx(NORMAL, ""); PrintAndLogEx(INFO, _YELLOW_("EM4x50 data:")); if (verbose) { print_result(words, 0, EM4X50_NO_WORDS - 1); } else { print_result(words, EM4X50_DEVICE_SERIAL, EM4X50_DEVICE_ID); } // configuration section PrintAndLogEx(NORMAL, ""); PrintAndLogEx(INFO, "---- " _CYAN_("Configuration") " ----"); PrintAndLogEx(INFO, "first word read %3i", fwr); PrintAndLogEx(INFO, "last word read %3i", lwr); PrintAndLogEx(INFO, "password check %3s", (bpwc) ? _RED_("on") : _GREEN_("off")); PrintAndLogEx(INFO, "read after write %3s", (braw) ? "on" : "off"); PrintAndLogEx(NORMAL, ""); PrintAndLogEx(INFO, "--------- " _CYAN_("Protection") " ---------"); PrintAndLogEx(INFO, "first word read protected %3i", fwrp); PrintAndLogEx(INFO, "last word read protected %3i", lwrp); PrintAndLogEx(INFO, "first word write inhibited %3i", fwwi); PrintAndLogEx(INFO, "last word write inhibited %3i", lwwi); PrintAndLogEx(NORMAL, ""); PrintAndLogEx(INFO, "zero values may indicate read protection"); PrintAndLogEx(NORMAL, ""); } static int em4x50_load_file(const char *filename, uint8_t *data, size_t data_len, size_t *bytes_read) { // read data from dump file; file type is derived from file name extension int res = 0; uint32_t serial = 0x0, device_id = 0x0; if (str_endswith(filename, ".eml")) res = loadFileEML(filename, data, bytes_read) != PM3_SUCCESS; else if (str_endswith(filename, ".json")) res = loadFileJSON(filename, data, data_len, bytes_read, NULL); else res = loadFile(filename, ".bin", data, data_len, bytes_read); if ((res != PM3_SUCCESS) && (*bytes_read != DUMP_FILESIZE)) return PM3_EFILE; // valid em4x50 data? serial = bytes_to_num(data + 4 * EM4X50_DEVICE_SERIAL, 4); device_id = bytes_to_num(data + 4 * EM4X50_DEVICE_ID, 4); if (serial == device_id) { PrintAndLogEx(WARNING, "No valid em4x50 data in file %s.", filename); return PM3_ENODATA; } return PM3_SUCCESS; } static void em4x50_seteml(uint8_t *src, uint32_t offset, uint32_t numofbytes) { // fast push mode conn.block_after_ACK = true; for (size_t i = offset; i < numofbytes; i += PM3_CMD_DATA_SIZE) { size_t len = MIN((numofbytes - i), PM3_CMD_DATA_SIZE); if (len == numofbytes - i) { // Disable fast mode on last packet conn.block_after_ACK = false; } clearCommandBuffer(); SendCommandOLD(CMD_LF_EM4X50_ESET, i, len, 0, src + i, len); } } int CmdEM4x50ELoad(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "lf em 4x50 eload", "Loads EM4x50 tag dump into emulator memory on device.", "lf em 4x50 eload -f mydump.bin\n" "lf em 4x50 eload -f mydump.eml\n" "lf em 4x50 eload -f mydump.json" ); void *argtable[] = { arg_param_begin, arg_str1("f", "filename", "", "dump filename"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); int fnlen = 0; char filename[FILE_PATH_SIZE] = {0}; CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen); CLIParserFree(ctx); // read data from dump file; file type has to be "bin", "eml" or "json" size_t bytes_read = 0; uint8_t data[DUMP_FILESIZE] = {0x0}; if (em4x50_load_file(filename, data, DUMP_FILESIZE, &bytes_read) != PM3_SUCCESS) { PrintAndLogEx(FAILED, "Read error"); return PM3_EFILE; } // upload to emulator memory PrintAndLogEx(INFO, "Uploading dump " _YELLOW_("%s") " to emulator memory", filename); em4x50_seteml(data, 0, DUMP_FILESIZE); PrintAndLogEx(INFO, "Done"); return PM3_SUCCESS; } int CmdEM4x50ESave(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "lf em 4x50 esave", "Saves bin/eml/json dump file of emulator memory.", "lf em 4x50 esave -> use UID as filename\n" "lf em 4x50 esave -f mydump.bin\n" "lf em 4x50 esave -f mydump.eml\n" "lf em 4x50 esave -f mydump.json\n" ); void *argtable[] = { arg_param_begin, arg_str0("f", "filename", "", "data filename"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); int fnlen = 0; char filename[FILE_PATH_SIZE] = {0}; CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen); CLIParserFree(ctx); // download emulator memory PrintAndLogEx(SUCCESS, "Reading emulator memory..."); uint8_t data[DUMP_FILESIZE] = {0x0}; if (GetFromDevice(BIG_BUF_EML, data, DUMP_FILESIZE, 0, NULL, 0, NULL, 2500, false) == false) { PrintAndLogEx(WARNING, "Fail, transfer from device time-out"); return PM3_ETIMEOUT; } // valid em4x50 data? uint32_t serial = bytes_to_num(data + 4 * EM4X50_DEVICE_SERIAL, 4); uint32_t device_id = bytes_to_num(data + 4 * EM4X50_DEVICE_ID, 4); if (serial == device_id) { PrintAndLogEx(WARNING, "No valid em4x50 data in flash memory."); return PM3_ENODATA; } // user supplied filename? if (fnlen == 0) { PrintAndLogEx(INFO, "Using UID as filename"); char *fptr = filename; fptr += snprintf(fptr, sizeof(filename), "lf-4x50-"); FillFileNameByUID(fptr, (uint8_t *)&data[4 * EM4X50_DEVICE_ID], "-dump", 4); } saveFile(filename, ".bin", data, DUMP_FILESIZE); saveFileEML(filename, data, DUMP_FILESIZE, 4); saveFileJSON(filename, jsfEM4x50, data, DUMP_FILESIZE, NULL); return PM3_SUCCESS; } int CmdEM4x50EView(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "lf em 4x50 eview", "Displays em4x50 content of emulator memory.", "lf em 4x50 eview\n" ); void *argtable[] = { arg_param_begin, arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); CLIParserFree(ctx); // download emulator memory PrintAndLogEx(SUCCESS, "Reading emulator memory..."); uint8_t data[DUMP_FILESIZE] = {0x0}; if (GetFromDevice(BIG_BUF_EML, data, DUMP_FILESIZE, 0, NULL, 0, NULL, 2500, false) == false) { PrintAndLogEx(WARNING, "Fail, transfer from device time-out"); return PM3_ETIMEOUT; } // valid em4x50 data? uint32_t serial = bytes_to_num(data + 4 * EM4X50_DEVICE_SERIAL, 4); uint32_t device_id = bytes_to_num(data + 4 * EM4X50_DEVICE_ID, 4); if (serial == device_id) { PrintAndLogEx(WARNING, "No valid em4x50 data in emulator memory."); return PM3_ENODATA; } em4x50_word_t words[EM4X50_NO_WORDS]; for (int i = 0; i < EM4X50_NO_WORDS; i++) { memcpy(words[i].byte, data + i * 4, 4); } print_result(words, 0, EM4X50_NO_WORDS - 1); PrintAndLogEx(NORMAL, ""); return PM3_SUCCESS; } int CmdEM4x50Login(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "lf em 4x50 login", "Login into EM4x50 tag.", "lf em 4x50 login -p 12345678 -> login with password 12345678\n" ); void *argtable[] = { arg_param_begin, arg_str1("p", "passsword", "", "password, 4 bytes, lsb"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); int pwd_len = 0; uint8_t pwd[4] = {0x0}; CLIGetHexWithReturn(ctx, 1, pwd, &pwd_len); CLIParserFree(ctx); if (pwd_len != 4) { PrintAndLogEx(FAILED, "password length must be 4 bytes"); return PM3_EINVARG; } uint32_t password = BYTES2UINT32(pwd); // start clearCommandBuffer(); PacketResponseNG resp; SendCommandNG(CMD_LF_EM4X50_LOGIN, (uint8_t *)&password, sizeof(password)); WaitForResponse(CMD_LF_EM4X50_LOGIN, &resp); // print response if (resp.status == PM3_SUCCESS) PrintAndLogEx(SUCCESS, "Login " _GREEN_("ok")); else PrintAndLogEx(FAILED, "Login " _RED_("failed")); return resp.status; } int CmdEM4x50Brute(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "lf em 4x50 brute", "Tries to bruteforce the password of a EM4x50.\n" "Function can be stopped by pressing pm3 button.", "lf em 4x50 brute --first 12330000 --last 12340000 -> tries pwds from 0x12330000 to 0x1234000000\n" ); void *argtable[] = { arg_param_begin, arg_str1(NULL, "first", "", "first password (start), 4 bytes, lsb"), arg_str1(NULL, "last", "", "last password (stop), 4 bytes, lsb"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); int first_len = 0; uint8_t first[4] = {0, 0, 0, 0}; CLIGetHexWithReturn(ctx, 1, first, &first_len); int last_len = 0; uint8_t last[4] = {0, 0, 0, 0}; CLIGetHexWithReturn(ctx, 2, last, &last_len); CLIParserFree(ctx); if (first_len != 4) { PrintAndLogEx(FAILED, "password length must be 4 bytes"); return PM3_EINVARG; } if (last_len != 4) { PrintAndLogEx(FAILED, "password length must be 4 bytes"); return PM3_EINVARG; } em4x50_data_t etd; etd.password1 = BYTES2UINT32(first); etd.password2 = BYTES2UINT32(last); // 27 passwords/second (empirical value) const int speed = 27; // print some information int no_iter = etd.password2 - etd.password1 + 1; int dur_s = no_iter / speed; int dur_h = dur_s / 3600; int dur_m = (dur_s - dur_h * 3600) / 60; dur_s -= dur_h * 3600 + dur_m * 60; PrintAndLogEx(INFO, "Trying %i passwords in range [0x%08x, 0x%08x]" , no_iter , etd.password1 , etd.password2 ); PrintAndLogEx(INFO, "Estimated duration: %ih%im%is", dur_h, dur_m, dur_s); // start clearCommandBuffer(); PacketResponseNG resp; SendCommandNG(CMD_LF_EM4X50_BRUTE, (uint8_t *)&etd, sizeof(etd)); WaitForResponse(CMD_LF_EM4X50_BRUTE, &resp); // print response if (resp.status == PM3_SUCCESS) PrintAndLogEx(SUCCESS, "Password " _GREEN_("found") ": 0x%08x", resp.data.asDwords[0]); else PrintAndLogEx(FAILED, "Password: " _RED_("not found")); return PM3_SUCCESS; } // upload passwords from given dictionary to device and start check; // if no filename is given dictionary "t55xx_default_pwds.dic" is used int CmdEM4x50Chk(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "lf em 4x50 chk", "Dictionary attack against EM4x50.", "lf em 4x50 chk -> uses T55xx default dictionary\n" "lf em 4x50 chk -f my.dic" ); void *argtable[] = { arg_param_begin, arg_str0("f", "filename", "", "dictionary filename"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); int fnlen = 0; char filename[FILE_PATH_SIZE] = {0}; CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen); CLIParserFree(ctx); if (IfPm3Flash() == false) { PrintAndLogEx(WARNING, "no flash memory available"); return PM3_EFLASH; } // no filename -> default = t55xx_default_pwds if (strlen(filename) == 0) { snprintf(filename, sizeof(filename), "t55xx_default_pwds"); PrintAndLogEx(INFO, "treating file as T55xx keys"); } size_t datalen = 0; uint8_t data[FLASH_MEM_MAX_SIZE] = {0x0}; uint8_t *keys = data; uint32_t key_count = 0; int res = loadFileDICTIONARY(filename, data, &datalen, 4, &key_count); if (res || !key_count) return PM3_EFILE; PrintAndLogEx(INFO, "You can cancel this operation by pressing the pm3 button"); int status = PM3_EFAILED; int keyblock = 2000; // block with 2000 bytes -> 500 keys uint8_t destfn[32] = "em4x50_chk.bin"; PacketResponseNG resp; int bytes_remaining = datalen; while (bytes_remaining > 0) { PrintAndLogEx(INPLACE, "Remaining keys: %i ", bytes_remaining / 4); // upload to flash. datalen = MIN(bytes_remaining, keyblock); res = flashmem_spiffs_load((char*)destfn, keys, datalen); if (res != PM3_SUCCESS) { PrintAndLogEx(WARNING, "SPIFFS upload failed"); return res; } clearCommandBuffer(); SendCommandNG(CMD_LF_EM4X50_CHK, destfn, sizeof(destfn)); WaitForResponseTimeoutW(CMD_LF_EM4X50_CHK, &resp, -1, false); status = resp.status; if ((status == PM3_SUCCESS) || (status == PM3_EOPABORTED)) break; bytes_remaining -= keyblock; keys += keyblock; } PrintAndLogEx(NORMAL, ""); // print response if (status == PM3_SUCCESS) { PrintAndLogEx(SUCCESS, "Key " _GREEN_("found: %02x %02x %02x %02x"), resp.data.asBytes[3], resp.data.asBytes[2], resp.data.asBytes[1], resp.data.asBytes[0] ); } else { PrintAndLogEx(FAILED, "No key found"); } PrintAndLogEx(INFO, "Done"); return PM3_SUCCESS; } //quick test for EM4x50 tag bool detect_4x50_block(void) { em4x50_data_t etd = { .pwd_given = false, .addr_given = true, .addresses = (EM4X50_DEVICE_SERIAL << 8) | EM4X50_DEVICE_SERIAL, }; em4x50_word_t words[EM4X50_NO_WORDS]; return (em4x50_read(&etd, words) == PM3_SUCCESS); } int read_em4x50_uid(void) { em4x50_data_t etd = { .pwd_given = false, .addr_given = true, .addresses = (EM4X50_DEVICE_SERIAL << 8) | EM4X50_DEVICE_SERIAL, }; em4x50_word_t words[EM4X50_NO_WORDS]; int res = em4x50_read(&etd, words); if (res == PM3_SUCCESS) PrintAndLogEx(INFO, " Serial: " _GREEN_("%s"), sprint_hex(words[EM4X50_DEVICE_SERIAL].byte, 4)); return res; } // envoke reading // - with given address (option b) (and optional password if address is // read protected) -> selective read mode int em4x50_read(em4x50_data_t *etd, em4x50_word_t *out) { em4x50_data_t edata = { .pwd_given = false, .addr_given = false }; if (etd != NULL) { edata = *etd; } clearCommandBuffer(); SendCommandNG(CMD_LF_EM4X50_READ, (uint8_t *)&edata, sizeof(edata)); PacketResponseNG resp; if (!WaitForResponseTimeout(CMD_LF_EM4X50_READ, &resp, TIMEOUT_CMD)) { PrintAndLogEx(WARNING, "(em4x50) timeout while waiting for reply."); return PM3_ETIMEOUT; } if (resp.status != PM3_SUCCESS) return PM3_ESOFT; uint8_t *data = resp.data.asBytes; em4x50_word_t words[EM4X50_NO_WORDS]; prepare_result(data, etd->addresses & 0xFF, (etd->addresses >> 8) & 0xFF, words); if (out != NULL) memcpy(out, &words, sizeof(em4x50_word_t) * EM4X50_NO_WORDS); print_result(words, etd->addresses & 0xFF, (etd->addresses >> 8) & 0xFF); return PM3_SUCCESS; } int CmdEM4x50Read(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "lf em 4x50 rdbl", "Reads single EM4x50 block/word.", "lf em 4x50 rdbl -b 3\n" "lf em 4x50 rdbl -b 32 -p 12345678 -> reads block 32 with pwd 0x12345678\n" ); void *argtable[] = { arg_param_begin, arg_int1("b", "block", "", "block/word address"), arg_str0("p", "pwd", "", "password, 4 hex bytes, lsb"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); int addr = arg_get_int_def(ctx, 1, 0); int pwd_len = 0; uint8_t pwd[4] = {0x0}; CLIGetHexWithReturn(ctx, 2, pwd, &pwd_len); CLIParserFree(ctx); if (addr <= 0 || addr >= EM4X50_NO_WORDS) { return PM3_EINVARG; } em4x50_data_t etd; // init memset(&etd, 0x00, sizeof(em4x50_data_t)); etd.addr_given = false; etd.pwd_given = false; etd.addresses = (addr << 8) | addr; etd.addr_given = true; if (pwd_len) { if (pwd_len != 4) { PrintAndLogEx(FAILED, "password length must be 4 bytes instead of %d", pwd_len); return PM3_EINVARG; } else { etd.password1 = BYTES2UINT32(pwd); etd.pwd_given = true; } } return em4x50_read(&etd, NULL); } // envoke reading of a EM4x50 tag which has to be on the antenna because // decoding is done by the device (not on client side) int CmdEM4x50Info(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "lf em 4x50 info", "Tag information EM4x50.", "lf em 4x50 info\n" "lf em 4x50 info -v -> show data section\n" "lf em 4x50 info -p 12345678 -> uses pwd 0x12345678\n" ); void *argtable[] = { arg_param_begin, arg_str0("p", "pwd", "", "password, 4 hex bytes, lsb"), arg_lit0("v", "verbose", "additional output of data section"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); int pwd_len = 0; uint8_t pwd[4] = {0x0}; CLIGetHexWithReturn(ctx, 1, pwd, &pwd_len); bool verb = arg_get_lit(ctx, 2); CLIParserFree(ctx); em4x50_data_t etd = {.pwd_given = false}; if (pwd_len) { if (pwd_len != 4) { PrintAndLogEx(FAILED, "password length must be 4 bytes instead of %d", pwd_len); return PM3_EINVARG; } else { etd.password1 = BYTES2UINT32(pwd); etd.pwd_given = true; } } clearCommandBuffer(); SendCommandNG(CMD_LF_EM4X50_INFO, (uint8_t *)&etd, sizeof(etd)); PacketResponseNG resp; if (!WaitForResponseTimeout(CMD_LF_EM4X50_INFO, &resp, TIMEOUT_CMD)) { PrintAndLogEx(WARNING, "Timeout while waiting for reply."); return PM3_ETIMEOUT; } if (resp.status == PM3_SUCCESS) print_info_result(resp.data.asBytes, verb); else PrintAndLogEx(FAILED, "Reading tag " _RED_("failed")); return resp.status; } int CmdEM4x50Reader(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "lf em 4x50 reader", "Shows standard read data of EM4x50 tag.", "lf em 4x50 reader\n" "lf em 4x50 reader -@ -> continuous reader mode" ); void *argtable[] = { arg_param_begin, arg_lit0("@", NULL, "optional - continuous reader mode"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); bool cm = arg_get_lit(ctx, 1); CLIParserFree(ctx); // start do { PacketResponseNG resp; clearCommandBuffer(); SendCommandNG(CMD_LF_EM4X50_READER, 0, 0); WaitForResponseTimeoutW(CMD_LF_EM4X50_READER, &resp, -1, false); // iceman, misuse of return status code. int now = resp.status; if (now > 0) { em4x50_word_t words[EM4X50_NO_WORDS]; prepare_result(resp.data.asBytes, 0, now - 1, words); PrintAndLogEx(NORMAL, ""); PrintAndLogEx(INFO, " word (msb) | word (lsb) "); PrintAndLogEx(INFO, "-------------+-------------"); for (int i = 0; i < now; i++) { char r[30]; memset(r, 0, sizeof(r)); for (int j = 3; j >= 0; j--) { sprintf(r + strlen(r), "%02x ", reflect8(words[i].byte[j])); } PrintAndLogEx(INFO, _GREEN_(" %s") "| %s", sprint_hex(words[i].byte, 4), r); } PrintAndLogEx(INFO, "-------------+-------------"); } } while (cm && !kbd_enter_pressed()); return PM3_SUCCESS; } int CmdEM4x50Dump(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "lf em 4x50 dump", "Reads all blocks/words from EM4x50 tag and saves dump in bin/eml/json format.", "lf em 4x50 dump\n" "lf em 4x50 dump -f mydump.eml\n" "lf em 4x50 dump -p 12345678\n" "lf em 4x50 dump -f mydump.eml -p 12345678" ); void *argtable[] = { arg_param_begin, arg_str0("f", "filename", "", "dump filename (bin/eml/json)"), arg_str0("p", "pwd", "", "password, 4 hex bytes, lsb"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); int fnLen = 0; char filename[FILE_PATH_SIZE] = {0}; CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)filename, FILE_PATH_SIZE, &fnLen); int pwd_len = 0; uint8_t pwd[4] = {0x0}; CLIGetHexWithReturn(ctx, 2, pwd, &pwd_len); CLIParserFree(ctx); em4x50_data_t etd = {.pwd_given = false}; if (pwd_len) { if (pwd_len != 4) { PrintAndLogEx(FAILED, "password length must be 4 bytes"); CLIParserFree(ctx); return PM3_EINVARG; } else { etd.password1 = BYTES2UINT32(pwd); etd.pwd_given = true; } } PrintAndLogEx(INFO, "Reading EM4x50 tag"); clearCommandBuffer(); SendCommandNG(CMD_LF_EM4X50_INFO, (uint8_t *)&etd, sizeof(etd)); PacketResponseNG resp; if (!WaitForResponseTimeout(CMD_LF_EM4X50_INFO, &resp, TIMEOUT_CMD)) { PrintAndLogEx(WARNING, "Timeout while waiting for reply."); return PM3_ETIMEOUT; } if (resp.status != PM3_SUCCESS) { PrintAndLogEx(FAILED, "Reading tag " _RED_("failed")); return PM3_ESOFT; } // structured format em4x50_word_t words[EM4X50_NO_WORDS]; prepare_result(resp.data.asBytes, 0, EM4X50_NO_WORDS - 1, words); // result output PrintAndLogEx(INFO, _YELLOW_("EM4x50 data:")); print_result(words, 0, EM4X50_NO_WORDS - 1); // user supplied filename? if (fnLen == 0) { PrintAndLogEx(INFO, "Using UID as filename"); char *fptr = filename; fptr += sprintf(fptr, "lf-4x50-"); FillFileNameByUID(fptr, words[EM4X50_DEVICE_ID].byte, "-dump", 4); } uint8_t data[DUMP_FILESIZE] = {0}; for (int i = 0; i < EM4X50_NO_WORDS; i++) { memcpy(data + (i * 4), words[i].byte, 4); } saveFile(filename, ".bin", data, sizeof(data)); saveFileEML(filename, data, sizeof(data), 4); saveFileJSON(filename, jsfEM4x50, data, sizeof(data), NULL); return PM3_SUCCESS; } // envoke writing a single word (32 bit) to a EM4x50 tag int CmdEM4x50Write(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "lf em 4x50 wrbl", "Writes single block/word to EM4x50 tag.", "lf em 4x50 wrbl -b 3 -d 4f22e7ff \n" "lf em 4x50 wrbl -b 3 -d 4f22e7ff -p 12345678\n" ); void *argtable[] = { arg_param_begin, arg_int1("b", "block", "", "block/word address, dec"), arg_str1("d", "data", "", "data, 4 bytes, lsb"), arg_str0("p", "pwd", "", "password, 4 bytes, lsb"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); int addr = arg_get_int_def(ctx, 1, 0); int word_len = 0; uint8_t word[4] = {0x0}; CLIGetHexWithReturn(ctx, 2, word, &word_len); int pwd_len = 0; uint8_t pwd[4] = {0x0}; CLIGetHexWithReturn(ctx, 3, pwd, &pwd_len); CLIParserFree(ctx); if (addr <= 0 || addr >= EM4X50_NO_WORDS) { PrintAndLogEx(FAILED, "address has to be within range [0, 31]"); return PM3_EINVARG; } if (word_len != 4) { PrintAndLogEx(FAILED, "word/data length must be 4 bytes instead of %d", word_len); return PM3_EINVARG; } em4x50_data_t etd = {.pwd_given = false}; if (pwd_len) { if (pwd_len != 4) { PrintAndLogEx(FAILED, "password length must be 4 bytes instead of %d", pwd_len); return PM3_EINVARG; } else { etd.password1 = BYTES2UINT32(pwd); etd.pwd_given = true; } } etd.addresses = (addr << 8) | addr; etd.addr_given = true; etd.word = BYTES2UINT32(word); clearCommandBuffer(); SendCommandNG(CMD_LF_EM4X50_WRITE, (uint8_t *)&etd, sizeof(etd)); PacketResponseNG resp; if (!WaitForResponseTimeout(CMD_LF_EM4X50_WRITE, &resp, TIMEOUT_CMD)) { PrintAndLogEx(WARNING, "Timeout while waiting for reply."); return PM3_ETIMEOUT; } int status = resp.status; if (status == PM3_ETEAROFF) return status; if (status != PM3_SUCCESS) { PrintAndLogEx(FAILED, "Writing " _RED_("failed")); return PM3_ESOFT; } // display result of writing operation in structured format uint8_t *data = resp.data.asBytes; em4x50_word_t words[EM4X50_NO_WORDS]; prepare_result(data, addr, addr, words); print_result(words, addr, addr); PrintAndLogEx(SUCCESS, "Successfully wrote to tag"); PrintAndLogEx(HINT, "Try `" _YELLOW_("lf em 4x50 rdbl -a %u") "` - to read your data", addr); return PM3_SUCCESS; } // envokes changing the password of EM4x50 tag int CmdEM4x50WritePwd(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "lf em 4x50 writepwd", "Writes EM4x50 password.", "lf em 4x50 writepwd -p 4f22e7ff -n 12345678" ); void *argtable[] = { arg_param_begin, arg_str1("p", "pwd", "", "password, 4 hex bytes, lsb"), arg_str1("n", "new", "", "new password, 4 hex bytes, lsb"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); int pwd_len = 0; uint8_t pwd[4] = {0x0}; CLIGetHexWithReturn(ctx, 1, pwd, &pwd_len); int npwd_len = 0; uint8_t npwd[4] = {0x0}; CLIGetHexWithReturn(ctx, 2, npwd, &npwd_len); CLIParserFree(ctx); em4x50_data_t etd; if (pwd_len != 4) { PrintAndLogEx(FAILED, "password length must be 4 bytes instead of %d", pwd_len); return PM3_EINVARG; } else { etd.password1 = BYTES2UINT32(pwd); } if (npwd_len != 4) { PrintAndLogEx(FAILED, "password length must be 4 bytes instead of %d", npwd_len); return PM3_EINVARG; } else { etd.password2 = BYTES2UINT32(npwd); } PacketResponseNG resp; clearCommandBuffer(); SendCommandNG(CMD_LF_EM4X50_WRITEPWD, (uint8_t *)&etd, sizeof(etd)); if (!WaitForResponseTimeout(CMD_LF_EM4X50_WRITEPWD, &resp, TIMEOUT_CMD)) { PrintAndLogEx(WARNING, "Timeout while waiting for reply."); return PM3_ETIMEOUT; } if (resp.status == PM3_ETEAROFF) return PM3_SUCCESS; if (resp.status != PM3_SUCCESS) { PrintAndLogEx(FAILED, "Writing password (" _RED_("failed") ")"); return PM3_EFAILED; } PrintAndLogEx(SUCCESS, "Writing new password %s (%s)" , sprint_hex_inrow(npwd, sizeof(npwd)) , _GREEN_("ok") ); return PM3_SUCCESS; } // fills EM4x50 tag with zeros including password int CmdEM4x50Wipe(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "lf em 4x50 wipe", "Wipes EM4x50 tag by filling it with zeros, including the new password\n" "Must give a password.", "lf em 4x50 wipe -p 12345678" ); void *argtable[] = { arg_param_begin, arg_str1("p", "passsword", "", "password, 4 bytes, lsb"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); int pwd_len = 0; uint8_t pwd[4] = {0x0}; CLIGetHexWithReturn(ctx, 1, pwd, &pwd_len); CLIParserFree(ctx); if (pwd_len != 4) { PrintAndLogEx(FAILED, "password length must be 4 bytes instead of %d", pwd_len); CLIParserFree(ctx); return PM3_EINVARG; } em4x50_data_t etd = {.pwd_given = false, .word = 0x0, .password2 = 0x0}; etd.password1 = BYTES2UINT32(pwd); etd.pwd_given = true; // clear password PacketResponseNG resp; clearCommandBuffer(); SendCommandNG(CMD_LF_EM4X50_WRITEPWD, (uint8_t *)&etd, sizeof(etd)); if (!WaitForResponseTimeout(CMD_LF_EM4X50_WRITEPWD, &resp, TIMEOUT_CMD)) { PrintAndLogEx(WARNING, "Timeout while waiting for reply."); return PM3_ETIMEOUT; } if (resp.status == PM3_SUCCESS) { PrintAndLogEx(SUCCESS, "Resetting password to 00000000 (" _GREEN_("ok") ")"); } else { PrintAndLogEx(FAILED, "Resetting password " _RED_("failed")); return PM3_ESOFT; } // from now on new password 0x0 etd.password1 = 0x0; // clear data (words 1 to 31) for (int i = 1; i < EM4X50_DEVICE_SERIAL; i++) { // no login necessary for blocks 3 to 31 etd.pwd_given = (i <= EM4X50_CONTROL); PrintAndLogEx(INPLACE, "Wiping block %i", i); etd.addresses = i << 8 | i; clearCommandBuffer(); SendCommandNG(CMD_LF_EM4X50_WRITE, (uint8_t *)&etd, sizeof(etd)); if (!WaitForResponseTimeout(CMD_LF_EM4X50_WRITE, &resp, TIMEOUT_CMD)) { PrintAndLogEx(WARNING, "Timeout while waiting for reply."); return PM3_ETIMEOUT; } if (resp.status != PM3_SUCCESS) { PrintAndLogEx(NORMAL, ""); PrintAndLogEx(FAILED, "Wiping data " _RED_("failed")); return PM3_ESOFT; } } PrintAndLogEx(NORMAL, ""); PrintAndLogEx(INFO, "Done"); return PM3_SUCCESS; } int CmdEM4x50Restore(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "lf em 4x50 restore", "Restores data from dumpfile onto a Em4x50 tag.\n" "if used with -u, the filetemplate `lf-4x50-UID-dump.bin` is used as filename", "lf em 4x50 restore -u 1b5aff5c -> uses lf-4x50-1B5AFF5C-dump.bin\n" "lf em 4x50 restore -f mydump.eml\n" "lf em 4x50 restore -u 1b5aff5c -p 12345678\n" "lf em 4x50 restore -f mydump.eml -p 12345678\n" ); void *argtable[] = { arg_param_begin, arg_str0("u", "uid", "", "uid, 4 hex bytes, msb"), arg_str0("f", "filename", "", "dump filename (bin/eml/json)"), arg_str0("p", "pwd", "", "password, 4 hex bytes, lsb"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); int uidLen = 0; uint8_t uid[4] = {0x0}; CLIGetHexWithReturn(ctx, 1, uid, &uidLen); int fnlen = 0; char filename[FILE_PATH_SIZE] = {0}; CLIParamStrToBuf(arg_get_str(ctx, 2), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen); int pwd_len = 0; uint8_t pwd[4] = {0x0}; CLIGetHexWithReturn(ctx, 3, pwd, &pwd_len); CLIParserFree(ctx); if ((uidLen && fnlen) || (!uidLen && !fnlen)) { PrintAndLogEx(FAILED, "either use option 'u' or option 'f'"); return PM3_EINVARG; } int startblock = EM4X50_CONTROL + 1; em4x50_data_t etd = {.pwd_given = false}; if (pwd_len) { if (pwd_len != 4) { PrintAndLogEx(FAILED, "password length must be 4 bytes instead of %d", pwd_len); return PM3_EINVARG; } else { etd.password1 = BYTES2UINT32(pwd); etd.pwd_given = true; // if password is available protection and control word can be restored startblock = EM4X50_PROTECTION; } } if (uidLen) { PrintAndLogEx(INFO, "Using UID as filename"); char *fptr = filename; fptr += sprintf(fptr, "lf-4x50-"); FillFileNameByUID(fptr, uid, "-dump", 4); } PrintAndLogEx(INFO, "Restoring " _YELLOW_("%s")" to card", filename); // read data from dump file; file type has to be "bin", "eml" or "json" uint8_t data[DUMP_FILESIZE] = {0x0}; size_t bytes_read = 0; if (em4x50_load_file(filename, data, DUMP_FILESIZE, &bytes_read) != PM3_SUCCESS) return PM3_EFILE; for (int i = startblock; i < EM4X50_DEVICE_SERIAL; i++) { PrintAndLogEx(INPLACE, "Restoring block %i", i); etd.addresses = i << 8 | i; etd.word = reflect32(BYTES2UINT32((data + 4 * i))); PacketResponseNG resp; clearCommandBuffer(); SendCommandNG(CMD_LF_EM4X50_WRITE, (uint8_t *)&etd, sizeof(etd)); if (!WaitForResponseTimeout(CMD_LF_EM4X50_WRITE, &resp, TIMEOUT_CMD)) { PrintAndLogEx(NORMAL, ""); PrintAndLogEx(WARNING, "Timeout while waiting for reply."); return PM3_ETIMEOUT; } if (resp.status != PM3_SUCCESS) { PrintAndLogEx(NORMAL, ""); PrintAndLogEx(FAILED, "Restoring data " _RED_("failed")); return PM3_ESOFT; } } PrintAndLogEx(NORMAL, ""); PrintAndLogEx(INFO, "Done"); return PM3_SUCCESS; } int CmdEM4x50Sim(const char *Cmd) { int status = PM3_EFAILED; uint32_t password = 0; CLIParserContext *ctx; CLIParserInit(&ctx, "lf em 4x50 sim", "Simulates a EM4x50 tag.\n" "Upload using `lf em 4x50 eload`", "lf em 4x50 sim" "lf em 4x50 sim -p 27182818 -> uses password for eload data" ); void *argtable[] = { arg_param_begin, arg_str0("p", "passsword", "", "password, 4 bytes, lsb"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); int pwd_len = 0; uint8_t pwd[4] = {0}; CLIGetHexWithReturn(ctx, 1, pwd, &pwd_len); CLIParserFree(ctx); if (pwd_len) { if (pwd_len != 4) { PrintAndLogEx(FAILED, "password length must be 4 bytes instead of %d", pwd_len); return PM3_EINVARG; } else { password = BYTES2UINT32(pwd); } } CLIParserFree(ctx); PrintAndLogEx(INFO, "Simulating data from emulator memory"); clearCommandBuffer(); SendCommandNG(CMD_LF_EM4X50_SIM, (uint8_t *)&password, sizeof(password)); PacketResponseNG resp; PrintAndLogEx(INFO, "Press pm3-button to abort simulation"); bool keypress = kbd_enter_pressed(); while (keypress == false) { keypress = kbd_enter_pressed(); if (WaitForResponseTimeout(CMD_LF_EM4X50_SIM, &resp, 1500)) { status = resp.status; break; } } if (keypress) { SendCommandNG(CMD_BREAK_LOOP, NULL, 0); status = PM3_EOPABORTED; } if ((status == PM3_SUCCESS) || (status == PM3_EOPABORTED)) PrintAndLogEx(INFO, "Done"); else PrintAndLogEx(FAILED, "No valid em4x50 data in memory"); return resp.status; } static command_t CommandTable[] = { {"help", CmdHelp, AlwaysAvailable, "This help"}, {"brute", CmdEM4x50Brute, IfPm3EM4x50, "guess password of EM4x50"}, {"chk", CmdEM4x50Chk, IfPm3EM4x50, "check passwords from dictionary"}, {"dump", CmdEM4x50Dump, IfPm3EM4x50, "dump EM4x50 tag"}, {"info", CmdEM4x50Info, IfPm3EM4x50, "tag information EM4x50"}, {"login", CmdEM4x50Login, IfPm3EM4x50, "login into EM4x50"}, {"rdbl", CmdEM4x50Read, IfPm3EM4x50, "read word data from EM4x50"}, {"wrbl", CmdEM4x50Write, IfPm3EM4x50, "write word data to EM4x50"}, {"writepwd", CmdEM4x50WritePwd, IfPm3EM4x50, "change password of EM4x50"}, {"wipe", CmdEM4x50Wipe, IfPm3EM4x50, "wipe EM4x50 tag"}, {"reader", CmdEM4x50Reader, IfPm3EM4x50, "show standard read mode data of EM4x50"}, {"restore", CmdEM4x50Restore, IfPm3EM4x50, "restore EM4x50 dump to tag"}, {"sim", CmdEM4x50Sim, IfPm3EM4x50, "simulate EM4x50 tag"}, {"eload", CmdEM4x50ELoad, IfPm3EM4x50, "upload dump of EM4x50 to emulator memory"}, {"esave", CmdEM4x50ESave, IfPm3EM4x50, "save emulator memory to file"}, {"eview", CmdEM4x50EView, IfPm3EM4x50, "view EM4x50 content in emulator memory"}, {NULL, NULL, NULL, NULL} }; static int CmdHelp(const char *Cmd) { (void)Cmd; // Cmd is not used so far CmdsHelp(CommandTable); return PM3_SUCCESS; } int CmdLFEM4X50(const char *Cmd) { clearCommandBuffer(); return CmdsParse(CommandTable, Cmd); }