feat: hf mfu ndefwrite command

This commit is contained in:
歐歪 committed 2026-08-22 02:11:29 +08:00
1 parent d3326dde78
commit cdca934f7e
6 files changed
+908 -3

No files matched your search

+1
View File
@@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file.
This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log...
## [unreleased][unreleased]
- Added `nfc encode` and `hf mfu ndefwrite` - build NDEF URI/Text/AAR records and write them to MIFARE Ultralight / NTAG tags, plus a reusable NDEF encoder in `nfc/ndef.c` (@0x6r1an0y)
- Fixed `hf iclass sam` - removed the old implementation (@iceman1001)
- Fixed Proxmark5 (PM5/AT32) failing to re-enter the bootloader (@nemanjan00)
- Fixed `hw fpga config` on Proxmark5 (PM5/AT32) now powers on the FPGA 24MHz clock (@nemanjan00)
+426
View File
@@ -7402,6 +7402,431 @@ int CmdHF14MfuNDEFRead(const char *Cmd) {
return status;
}
// Build a NDEF message from the CLI options and write it into the data area of a
// MIFARE Ultralight / NTAG tag. The tag has to be NDEF formatted already, this
// command never touches the Capability Container in block 3.
int CmdHF14MfuNDEFWrite(const char *Cmd) {
int ak_len = 0;
bool has_auth_key = false;
bool has_pwd = false;
bool swap_endian = false;
iso14a_card_select_t card;
uint8_t data[16] = {0x00};
uint8_t authenticationkey[16] = {0x00};
uint8_t *auth_key_ptr = authenticationkey;
uint8_t pack[4] = {0, 0, 0, 0};
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf mfu ndefwrite",
"Write NFC Data Exchange Format (NDEF) records to a MIFARE Ultralight / NTAG tag.\n"
"The tag must already be NDEF formatted, ie carry a Capability Container in block 3.\n"
"\n"
"Combine several of --uri, --text and --aar to build a multi record message,\n"
"records are added in that order. Alternatively supply raw NDEF bytes with -d\n"
"or -f, those get wrapped in a TLV container automatically when they are not\n"
"already. Use `nfc encode` to build such raw bytes offline.\n"
"\n"
"Note: the tag is re-selected and re-authenticated for every block written,\n"
"so a large message takes a while.",
"hf mfu ndefwrite --uri https://proxmark.com\n"
"hf mfu ndefwrite --uri tel:+123456789\n"
"hf mfu ndefwrite --text \"hello world\"\n"
"hf mfu ndefwrite --aar com.example.app\n"
"hf mfu ndefwrite --uri https://proxmark.com --aar com.example.app\n"
"hf mfu ndefwrite --uri https://proxmark.com -k ffffffff\n"
"hf mfu ndefwrite -d 0311D1010D550270726F786D61726B2E636F6DFE\n"
"hf mfu ndefwrite -f myfilename"
);
void *argtable[] = {
arg_param_begin,
arg_str0("k", "key", "<hex>", "Authentication key (UL-C/UL-AES 16 bytes, EV1/NTAG 4 bytes)"),
arg_lit0("l", NULL, "Swap entered key endianness"),
arg_str0(NULL, "uri", "<str>", "URI record. URL, tel:, mailto:, ..."),
arg_str0(NULL, "text", "<str>", "Text record"),
arg_str0(NULL, "lang", "<str>", "language code for the text record (default: en)"),
arg_str0(NULL, "aar", "<str>", "Android Application Record, ie an app package name"),
arg_str0("d", "data", "<hex>", "Raw NDEF bytes to write"),
arg_str0("f", "file", "<fn>", "Raw NDEF file to write"),
arg_lit0("v", "verbose", "Verbose output"),
arg_lit0(NULL, "schann", "use secure channel. Must have key"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
CLIGetHexWithReturn(ctx, 1, authenticationkey, &ak_len);
swap_endian = arg_get_lit(ctx, 2);
// CLIParamStrToBuf copies the trailing NUL as well but only rejects lengths
// strictly above maxdatalen, so leave room for that byte
int urilen = 0;
char uri[1024] = {0};
CLIParamStrToBuf(arg_get_str(ctx, 3), (uint8_t *)uri, sizeof(uri) - 1, &urilen);
int textlen = 0;
char text[1024] = {0};
CLIParamStrToBuf(arg_get_str(ctx, 4), (uint8_t *)text, sizeof(text) - 1, &textlen);
int langlen = 0;
char lang[32] = {0};
CLIParamStrToBuf(arg_get_str(ctx, 5), (uint8_t *)lang, sizeof(lang) - 1, &langlen);
int aarlen = 0;
char aar[256] = {0};
CLIParamStrToBuf(arg_get_str(ctx, 6), (uint8_t *)aar, sizeof(aar) - 1, &aarlen);
int rawlen = 0;
uint8_t raw[MFU_NDEF_MAX_BYTES] = {0};
CLIGetHexWithReturn(ctx, 7, raw, &rawlen);
int fnlen = 0;
char filename[FILE_PATH_SIZE] = {0};
CLIParamStrToBuf(arg_get_str(ctx, 8), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
bool verbose = arg_get_lit(ctx, 9);
bool use_schann = arg_get_lit(ctx, 10);
CLIParserFree(ctx);
bool has_records = (urilen || textlen || aarlen);
int sources = (has_records ? 1 : 0) + (rawlen ? 1 : 0) + (fnlen ? 1 : 0);
if (sources == 0) {
PrintAndLogEx(ERR, "Nothing to write. See `" _YELLOW_("hf mfu ndefwrite -h") "`");
return PM3_EINVARG;
}
if (sources > 1) {
PrintAndLogEx(ERR, "Use either the record options, -d or -f, not a mix of them");
return PM3_EINVARG;
}
if ((langlen != 0) && (textlen == 0)) {
PrintAndLogEx(WARNING, "--lang only applies to a text record, ignoring");
}
switch (ak_len) {
case 0:
break;
case 4:
has_pwd = true;
break;
case 16:
has_auth_key = true;
break;
default:
PrintAndLogEx(WARNING, "ERROR: Key is incorrect length\n");
return PM3_EINVARG;
}
if (use_schann && (has_auth_key == false)) {
PrintAndLogEx(WARNING, "Secure channel must be called with key");
return PM3_EINVARG;
}
// ------------------------------------------------------------------
// build the TLV block that goes onto the tag
// ------------------------------------------------------------------
uint8_t tlv[MFU_NDEF_MAX_BYTES] = {0};
size_t tlv_len = 0;
int res = PM3_SUCCESS;
if (has_records) {
NDEFRecordDesc_t recs[3] = {{0}};
size_t count = 0;
uint8_t p_uri[sizeof(uri) + 1] = {0};
size_t p_uri_len = 0;
if (urilen) {
res = NDEFEncodePayloadURI(uri, p_uri, sizeof(p_uri), &p_uri_len);
if (res != PM3_SUCCESS) {
PrintAndLogEx(ERR, "Failed to encode URI record");
return res;
}
recs[count].tnf = tnfWellKnownRecord;
recs[count].type = (const uint8_t *)NDEF_TYPE_URI;
recs[count].typeLen = strlen(NDEF_TYPE_URI);
recs[count].payload = p_uri;
recs[count].payloadLen = p_uri_len;
count++;
}
uint8_t p_text[sizeof(text) + sizeof(lang) + 1] = {0};
size_t p_text_len = 0;
if (textlen) {
res = NDEFEncodePayloadText(text, lang, p_text, sizeof(p_text), &p_text_len);
if (res != PM3_SUCCESS) {
PrintAndLogEx(ERR, "Failed to encode Text record");
return res;
}
recs[count].tnf = tnfWellKnownRecord;
recs[count].type = (const uint8_t *)NDEF_TYPE_TEXT;
recs[count].typeLen = strlen(NDEF_TYPE_TEXT);
recs[count].payload = p_text;
recs[count].payloadLen = p_text_len;
count++;
}
uint8_t p_aar[sizeof(aar)] = {0};
size_t p_aar_len = 0;
if (aarlen) {
res = NDEFEncodePayloadAAR(aar, p_aar, sizeof(p_aar), &p_aar_len);
if (res != PM3_SUCCESS) {
PrintAndLogEx(ERR, "Failed to encode Android Application Record");
return res;
}
recs[count].tnf = tnfExternalRecord;
recs[count].type = (const uint8_t *)NDEF_ANDROID_AAR;
recs[count].typeLen = strlen(NDEF_ANDROID_AAR);
recs[count].payload = p_aar;
recs[count].payloadLen = p_aar_len;
count++;
}
uint8_t msg[MFU_NDEF_MAX_BYTES] = {0};
size_t msg_len = 0;
res = NDEFEncodeMessage(recs, count, msg, sizeof(msg), &msg_len);
if (res != PM3_SUCCESS) {
PrintAndLogEx(ERR, "Failed to encode NDEF message");
return res;
}
res = NDEFEncodeTLV(msg, msg_len, tlv, sizeof(tlv), &tlv_len);
if (res != PM3_SUCCESS) {
PrintAndLogEx(ERR, "Failed to wrap NDEF message in a TLV container");
return res;
}
} else {
uint8_t *src = raw;
size_t src_len = (size_t)rawlen;
uint8_t *dump = NULL;
if (fnlen) {
size_t bytes_read = 0;
res = pm3_load_dump(filename, (void **)&dump, &bytes_read, sizeof(raw));
if (res != PM3_SUCCESS) {
return res;
}
src = dump;
src_len = bytes_read;
}
if (src_len == 0) {
PrintAndLogEx(ERR, "No NDEF data supplied");
free(dump);
return PM3_EINVARG;
}
// is it a TLV block already?
bool is_tlv = false;
switch (src[0]) {
case 0x00:
case 0x01:
case 0x02:
case 0x03:
case 0xFD:
case 0xFE:
is_tlv = true;
break;
default:
break;
}
if (is_tlv) {
if (src_len > sizeof(tlv)) {
PrintAndLogEx(ERR, "NDEF data too large, %zu bytes", src_len);
free(dump);
return PM3_EINVARG;
}
memcpy(tlv, src, src_len);
tlv_len = src_len;
} else {
if (verbose) {
PrintAndLogEx(INFO, "Raw data is not TLV wrapped, adding container");
}
res = NDEFEncodeTLV(src, src_len, tlv, sizeof(tlv), &tlv_len);
if (res != PM3_SUCCESS) {
PrintAndLogEx(ERR, "Failed to wrap NDEF message in a TLV container");
free(dump);
return res;
}
}
free(dump);
}
if (verbose) {
PrintAndLogEx(INFO, "TLV block [%zu]...", tlv_len);
print_hex_noascii_break(tlv, tlv_len, 32);
}
// ------------------------------------------------------------------
// find the tag and check the message fits
// ------------------------------------------------------------------
uint64_t tagtype = GetHF14AMfU_Type();
if (tagtype == MFU_TT_UL_ERROR) {
PrintAndLogEx(WARNING, "No Ultralight / NTAG based tag found");
return PM3_ESOFT;
}
ul_print_type(tagtype, 0);
if (swap_endian) {
if (ak_len == 16) {
if (((tagtype & MFU_TT_UL_C) == MFU_TT_UL_C)) {
auth_key_ptr = SwapEndian64(authenticationkey, ak_len, 8);
} else if (((tagtype & MFU_TT_UL_AES) == MFU_TT_UL_AES)) {
auth_key_ptr = SwapEndian64(authenticationkey, ak_len, 16);
}
} else if (ak_len == 4) {
auth_key_ptr = SwapEndian64(authenticationkey, ak_len, 4);
}
}
if (ul_auth_select(&card, tagtype, (has_auth_key || has_pwd), auth_key_ptr, pack, sizeof(pack), use_schann) == PM3_ESOFT) {
return PM3_ESOFT;
}
// read blocks 0..3 to get at the Capability Container
int status = ul_read(0, data, sizeof(data), use_schann);
if (status <= 0) {
DropField();
PrintAndLogEx(ERR, "Error: tag didnt answer to READ");
return PM3_ESOFT;
}
if (status != 16) {
DropField();
PrintAndLogEx(ERR, "Error: tag returned %d bytes, need 16 to read the NDEF Container", status);
return PM3_ESOFT;
}
if (ndef_print_CC(data + 12) == PM3_ESOFT) {
DropField();
PrintAndLogEx(ERR, "Error: tag didnt contain a NDEF Container");
PrintAndLogEx(HINT, "Hint: the tag needs to be NDEF formatted first");
return PM3_ESOFT;
}
// size of the NDEF data area
uint16_t ndef_size = ndef_get_maxsize(data + 12);
if (ndef_size == 0) {
DropField();
PrintAndLogEx(ERR, "Error: tag announces an empty NDEF data area");
return PM3_ESOFT;
}
// The data area starts at block 4, so cap the announced size to what the
// identified tag can physically hold. UL_MEMORY_ARRAY holds the last valid
// block number, hence the +1.
for (uint8_t idx = 1; idx < ARRAYLEN(UL_TYPES_ARRAY); idx++) {
if ((tagtype & UL_TYPES_ARRAY[idx]) == UL_TYPES_ARRAY[idx]) {
int avail = (UL_MEMORY_ARRAY[idx] + 1 - MFU_NDEF_FIRST_BLOCK) * MFU_BLOCK_SIZE;
if (avail > 0 && ndef_size > avail) {
PrintAndLogEx(INFO, "NDEF data area (%u bytes) is larger than the tag (%d bytes), using tag size"
, ndef_size
, avail
);
ndef_size = avail;
}
break;
}
}
// MLEN can announce up to 2040 bytes but WRITE addresses blocks with a single
// byte, so block 255 is the last one we can reach.
if (ndef_size > MFU_NDEF_MAX_BYTES) {
ndef_size = MFU_NDEF_MAX_BYTES;
}
if (tlv_len > ndef_size) {
DropField();
PrintAndLogEx(ERR, "NDEF message is too large for this tag");
PrintAndLogEx(ERR, " message..... " _RED_("%zu") " bytes", tlv_len);
PrintAndLogEx(ERR, " tag holds... " _GREEN_("%u") " bytes", ndef_size);
return PM3_EINVARG;
}
DropField();
// ------------------------------------------------------------------
// write it out, one block at a time
// ------------------------------------------------------------------
uint8_t keytype = 0;
if (has_auth_key || has_pwd) {
if ((tagtype & MFU_TT_UL_C) == MFU_TT_UL_C) {
keytype = 1; // UL_C auth
} else if ((tagtype & MFU_TT_UL_AES) == MFU_TT_UL_AES) {
keytype = 3; // UL_AES auth
} else {
keytype = 2; // UL_EV1/NTAG auth
}
}
// pad the tail so the last block is complete
size_t padded_len = (tlv_len + (MFU_BLOCK_SIZE - 1)) & ~((size_t)MFU_BLOCK_SIZE - 1);
uint16_t blocks = (uint16_t)(padded_len / MFU_BLOCK_SIZE);
PrintAndLogEx(INFO, "Writing " _YELLOW_("%u") " blocks ( %zu bytes ) from block " _YELLOW_("%d")
, blocks
, padded_len
, MFU_NDEF_FIRST_BLOCK
);
PrintAndLogEx(INFO, "Press " _GREEN_("<Enter>") " to abort");
for (uint16_t i = 0; i < blocks; i++) {
if (kbd_enter_pressed()) {
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(WARNING, "aborted via keyboard!");
if (i > 0) {
PrintAndLogEx(WARNING, "tag holds a partially written NDEF message, blocks %d..%u"
, MFU_NDEF_FIRST_BLOCK
, MFU_NDEF_FIRST_BLOCK + i - 1
);
}
return PM3_EOPABORTED;
}
uint8_t blockno = (uint8_t)(MFU_NDEF_FIRST_BLOCK + i);
res = mfu_write_block(tlv + (i * MFU_BLOCK_SIZE), MFU_BLOCK_SIZE, keytype, auth_key_ptr, blockno, use_schann);
if (res != PM3_SUCCESS) {
PrintAndLogEx(NORMAL, "");
// a protected tag simply stops answering, so the write times out
// rather than coming back with an explicit error
if (res == PM3_ETIMEOUT) {
PrintAndLogEx(FAILED, "Write block %u ( " _RED_("timeout") " )", blockno);
} else {
PrintAndLogEx(FAILED, "Write block %u ( " _RED_("fail") " )", blockno);
}
PrintAndLogEx(HINT, "Hint: Check password / key!");
if (i > 0) {
PrintAndLogEx(WARNING, "tag holds a partially written NDEF message, blocks %d..%u"
, MFU_NDEF_FIRST_BLOCK
, blockno - 1
);
}
return PM3_ESOFT;
}
PrintAndLogEx(INPLACE, "Block %u / %u", i + 1, blocks);
}
DropField();
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(SUCCESS, "Write ( " _GREEN_("ok") " )");
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf mfu ndefread") "` to verify");
PrintAndLogEx(NORMAL, "");
return PM3_SUCCESS;
}
// utility function. Retrieves emulator memory
static int GetMfuDumpFromEMul(mfu_dump_t **buf) {
@@ -8274,6 +8699,7 @@ static command_t CommandTable[] = {
{"incr", CmdHF14AMfUIncr, IfPm3Iso14443a, "Increments Ev1/NTAG counter"},
{"info", CmdHF14AMfUInfo, IfPm3Iso14443a, "Tag information"},
{"ndefread", CmdHF14MfuNDEFRead, IfPm3Iso14443a, "Prints NDEF records from card"},
{"ndefwrite", CmdHF14MfuNDEFWrite, IfPm3Iso14443a, "Write NDEF records to card"},
{"rdbl", CmdHF14AMfURdBl, IfPm3Iso14443a, "Read block"},
{"restore", CmdHF14AMfURestore, IfPm3Iso14443a, "Restore a dump file onto a tag"},
{"tamper", CmdHF14MfUTamper, IfPm3Iso14443a, "NTAG 213TT - Configure the tamper feature"},
+2 -1
View File
@@ -57,7 +57,8 @@ int mfu_get_version_uid(uint8_t *version, uint8_t *uid);
int mfuc_test_authentication_support(void);
int CmdHFMFUltra(const char *Cmd);
int CmdHF14MfuNDEFRead(const char *Cmd);
int CmdHF14MfuNDEFRead(const char *Cmd); // used by cmdnfc.c
int CmdHF14MfuNDEFWrite(const char *Cmd); // used by cmdnfc.c
int CmdHF14MfUTamper(const char *Cmd);
#define MFU_TT_UNKNOWN 0x0ULL
+183 -2
View File
@@ -178,6 +178,183 @@ static int CmdNfcDecode(const char *Cmd) {
return res;
}
static int CmdNfcEncode(const char *Cmd) {
#ifndef MAX_NDEF_LEN
#define MAX_NDEF_LEN 2048
#endif
CLIParserContext *ctx;
CLIParserInit(&ctx, "nfc encode",
"Encode NFC Data Exchange Format (NDEF) records.\n"
"Combine several options to build a multi record message,\n"
"records are added in the order listed below.\n"
"\n"
"By default a bare NDEF message is emitted. Use `--tlv` to wrap it in a\n"
"NFC Forum Type 2 tag container ( 03 <len> ... FE ), which is what the\n"
"`-d` parameter of `hf mf ndefwrite` and `hf 14a ndefwrite` expects.\n"
"`hf mfu ndefwrite` adds the container itself, so it does not need `--tlv`",
"nfc encode --uri https://proxmark.com\n"
"nfc encode --uri tel:+123456789\n"
"nfc encode --text \"hello world\"\n"
"nfc encode --aar com.example.app\n"
"nfc encode --uri https://proxmark.com --aar com.example.app\n"
"nfc encode --uri https://proxmark.com --tlv -f myfilename"
);
void *argtable[] = {
arg_param_begin,
arg_str0(NULL, "uri", "<str>", "URI record. URL, tel:, mailto:, ..."),
arg_str0(NULL, "text", "<str>", "Text record"),
arg_str0(NULL, "lang", "<str>", "language code for the text record (default: en)"),
arg_str0(NULL, "aar", "<str>", "Android Application Record, ie an app package name"),
arg_lit0(NULL, "tlv", "wrap message in a NFC Forum Type 2 tag TLV container"),
arg_str0("f", "file", "<fn>", "save raw bytes to file"),
arg_lit0("v", "verbose", "verbose output"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
// CLIParamStrToBuf copies the trailing NUL as well but only rejects lengths
// strictly above maxdatalen, so leave room for that byte
int urilen = 0;
char uri[1024] = {0};
CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)uri, sizeof(uri) - 1, &urilen);
int textlen = 0;
char text[1024] = {0};
CLIParamStrToBuf(arg_get_str(ctx, 2), (uint8_t *)text, sizeof(text) - 1, &textlen);
int langlen = 0;
char lang[32] = {0};
CLIParamStrToBuf(arg_get_str(ctx, 3), (uint8_t *)lang, sizeof(lang) - 1, &langlen);
int aarlen = 0;
char aar[256] = {0};
CLIParamStrToBuf(arg_get_str(ctx, 4), (uint8_t *)aar, sizeof(aar) - 1, &aarlen);
bool use_tlv = arg_get_lit(ctx, 5);
int fnlen = 0;
char filename[FILE_PATH_SIZE] = {0};
CLIParamStrToBuf(arg_get_str(ctx, 6), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen);
bool verbose = arg_get_lit(ctx, 7);
CLIParserFree(ctx);
if ((urilen == 0) && (textlen == 0) && (aarlen == 0)) {
PrintAndLogEx(ERR, "Must specify at least one record. See `" _YELLOW_("nfc encode -h") "`");
return PM3_EINVARG;
}
if ((langlen != 0) && (textlen == 0)) {
PrintAndLogEx(WARNING, "`--lang` only applies to a text record, ignoring");
}
NDEFRecordDesc_t recs[3] = {{0}};
size_t count = 0;
int res = PM3_SUCCESS;
uint8_t p_uri[sizeof(uri) + 1] = {0};
size_t p_uri_len = 0;
if (urilen) {
res = NDEFEncodePayloadURI(uri, p_uri, sizeof(p_uri), &p_uri_len);
if (res != PM3_SUCCESS) {
PrintAndLogEx(ERR, "Failed to encode URI record");
return res;
}
recs[count].tnf = tnfWellKnownRecord;
recs[count].type = (const uint8_t *)NDEF_TYPE_URI;
recs[count].typeLen = strlen(NDEF_TYPE_URI);
recs[count].payload = p_uri;
recs[count].payloadLen = p_uri_len;
count++;
}
uint8_t p_text[sizeof(text) + sizeof(lang) + 1] = {0};
size_t p_text_len = 0;
if (textlen) {
res = NDEFEncodePayloadText(text, lang, p_text, sizeof(p_text), &p_text_len);
if (res != PM3_SUCCESS) {
PrintAndLogEx(ERR, "Failed to encode Text record");
return res;
}
recs[count].tnf = tnfWellKnownRecord;
recs[count].type = (const uint8_t *)NDEF_TYPE_TEXT;
recs[count].typeLen = strlen(NDEF_TYPE_TEXT);
recs[count].payload = p_text;
recs[count].payloadLen = p_text_len;
count++;
}
uint8_t p_aar[sizeof(aar)] = {0};
size_t p_aar_len = 0;
if (aarlen) {
res = NDEFEncodePayloadAAR(aar, p_aar, sizeof(p_aar), &p_aar_len);
if (res != PM3_SUCCESS) {
PrintAndLogEx(ERR, "Failed to encode Android Application Record");
return res;
}
recs[count].tnf = tnfExternalRecord;
recs[count].type = (const uint8_t *)NDEF_ANDROID_AAR;
recs[count].typeLen = strlen(NDEF_ANDROID_AAR);
recs[count].payload = p_aar;
recs[count].payloadLen = p_aar_len;
count++;
}
uint8_t msg[MAX_NDEF_LEN] = {0};
size_t msglen = 0;
res = NDEFEncodeMessage(recs, count, msg, sizeof(msg), &msglen);
if (res != PM3_SUCCESS) {
PrintAndLogEx(ERR, "Failed to encode NDEF message");
return res;
}
uint8_t tlv[MAX_NDEF_LEN] = {0};
uint8_t *out = msg;
size_t outlen = msglen;
if (use_tlv) {
size_t tlvlen = 0;
res = NDEFEncodeTLV(msg, msglen, tlv, sizeof(tlv), &tlvlen);
if (res != PM3_SUCCESS) {
PrintAndLogEx(ERR, "Failed to wrap NDEF message in a TLV container");
return res;
}
out = tlv;
outlen = tlvlen;
}
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(INFO, "--- " _CYAN_("NDEF encoding") " ----------------");
PrintAndLogEx(SUCCESS, "Records......... " _YELLOW_("%zu"), count);
PrintAndLogEx(SUCCESS, "Message size.... " _YELLOW_("%zu") " bytes", msglen);
if (use_tlv) {
PrintAndLogEx(SUCCESS, "TLV size........ " _YELLOW_("%zu") " bytes", outlen);
}
PrintAndLogEx(NORMAL, "");
if (outlen > 64) {
print_hex_noascii_break(out, outlen, 32);
} else {
PrintAndLogEx(SUCCESS, _GREEN_("%s"), sprint_hex_inrow(out, outlen));
}
if (fnlen) {
saveFile(filename, ".bin", out, outlen);
}
// round trip it back through the decoder, so the user can see what a reader will see
if (use_tlv) {
NDEFDecodeAndPrint(out, outlen, verbose);
} else {
NDEFRecordsDecodeAndPrint(out, outlen, verbose);
}
return PM3_SUCCESS;
}
static int CmdNFCType1Read(const char *Cmd) {
return CmdHFTopazInfo(Cmd);
}
@@ -210,6 +387,10 @@ static int CmdNFCType2Read(const char *Cmd) {
return CmdHF14MfuNDEFRead(Cmd);
}
static int CmdNFCType2Write(const char *Cmd) {
return CmdHF14MfuNDEFWrite(Cmd);
}
static int CmdNFCType2Help(const char *Cmd);
static command_t CommandNFCType2Table[] = {
@@ -217,7 +398,7 @@ static command_t CommandNFCType2Table[] = {
{"--------", CmdNFCType2Help, AlwaysAvailable, "-------------- " _CYAN_("NFC Forum Tag Type 2") " ---------------"},
// {"format", CmdNFCType2Format, IfPm3Iso14443a, "format ISO-14443-a tag as NFC Tag"},
{"read", CmdNFCType2Read, IfPm3Iso14443a, "read NFC Forum Tag Type 2"},
// {"write", CmdNFCType2Write, IfPm3Iso14443a, "write NFC Forum Tag Type 2"},
{"write", CmdNFCType2Write, IfPm3Iso14443a, "write NFC Forum Tag Type 2"},
{"--------", CmdNFCType2Help, AlwaysAvailable, "--------------------- " _CYAN_("General") " ---------------------"},
{"help", CmdNFCType2Help, AlwaysAvailable, "This help"},
{NULL, NULL, NULL, NULL}
@@ -459,7 +640,7 @@ static command_t CommandTable[] = {
{"--------", CmdHelp, AlwaysAvailable, "--------------------- " _CYAN_("General") " ---------------------"},
{"help", CmdHelp, AlwaysAvailable, "This help"},
{"decode", CmdNfcDecode, AlwaysAvailable, "Decode NDEF records"},
// {"encode", CmdNfcEncode, AlwaysAvailable, "Encode NDEF records"},
{"encode", CmdNfcEncode, AlwaysAvailable, "Encode NDEF records"},
{NULL, NULL, NULL, NULL}
};
+265
View File
@@ -998,6 +998,17 @@ static int ndefDecodeExternal_record(NDEFHeader_t *ndef) {
return PM3_SUCCESS;
}
// Android Application Record
if ((ndef->TypeLen == strlen(NDEF_ANDROID_AAR)) &&
(strncmp((char *)ndef->Type, NDEF_ANDROID_AAR, ndef->TypeLen) == 0)) {
PrintAndLogEx(INFO
, " Android app... " _GREEN_("%.*s")
, (int)ndef->PayloadLen
, ndef->Payload
);
return PM3_SUCCESS;
}
PrintAndLogEx(INFO
, " URN... " _GREEN_("urn:nfc:ext:%.*s")
, (int)ndef->TypeLen
@@ -1393,3 +1404,257 @@ int NDEFGetTotalLength(uint8_t *ndef, size_t ndeflen, size_t *outlen) {
*outlen = idx;
return PM3_SUCCESS;
}
//-----------------------------------------------------------------------------
// NDEF encoding
//-----------------------------------------------------------------------------
// Find the longest matching abbreviation from URI_s[].
// Returns the prefix code and sets prefixLen to how many bytes of uri it covers.
// Code 0x00 means "no abbreviation", the whole string gets stored verbatim.
static uint8_t ndefEncodeURIPrefix(const char *uri, size_t *prefixLen) {
uint8_t code = 0;
size_t best = 0;
for (uint8_t i = 1; i < ARRAYLEN(URI_s); i++) {
size_t n = strlen(URI_s[i]);
if ((n > best) && (strncmp(uri, URI_s[i], n) == 0)) {
best = n;
code = i;
}
}
*prefixLen = best;
return code;
}
int NDEFEncodePayloadURI(const char *uri, uint8_t *buf, size_t bufLen, size_t *outLen) {
if ((uri == NULL) || (buf == NULL) || (outLen == NULL)) {
return PM3_EINVARG;
}
size_t uri_len = strlen(uri);
if (uri_len == 0) {
return PM3_EINVARG;
}
size_t prefix_len = 0;
uint8_t code = ndefEncodeURIPrefix(uri, &prefix_len);
size_t n = 1 + (uri_len - prefix_len);
if (n > bufLen) {
return PM3_EOVFLOW;
}
buf[0] = code;
memcpy(buf + 1, uri + prefix_len, uri_len - prefix_len);
*outLen = n;
return PM3_SUCCESS;
}
int NDEFEncodePayloadText(const char *text, const char *lang, uint8_t *buf, size_t bufLen, size_t *outLen) {
if ((text == NULL) || (buf == NULL) || (outLen == NULL)) {
return PM3_EINVARG;
}
if ((lang == NULL) || (strlen(lang) == 0)) {
lang = "en";
}
size_t lang_len = strlen(lang);
size_t text_len = strlen(text);
// the status byte carries the language code length in its lower 6 bits
if (lang_len > 0x3F) {
return PM3_EINVARG;
}
size_t n = 1 + lang_len + text_len;
if (n > bufLen) {
return PM3_EOVFLOW;
}
// bit 7 clear == UTF-8
buf[0] = (uint8_t)(lang_len & 0x3F);
memcpy(buf + 1, lang, lang_len);
memcpy(buf + 1 + lang_len, text, text_len);
*outLen = n;
return PM3_SUCCESS;
}
int NDEFEncodePayloadAAR(const char *pkg, uint8_t *buf, size_t bufLen, size_t *outLen) {
if ((pkg == NULL) || (buf == NULL) || (outLen == NULL)) {
return PM3_EINVARG;
}
size_t n = strlen(pkg);
if (n == 0) {
return PM3_EINVARG;
}
if (n > bufLen) {
return PM3_EOVFLOW;
}
memcpy(buf, pkg, n);
*outLen = n;
return PM3_SUCCESS;
}
int NDEFEncodeRecord(const NDEFRecordDesc_t *rec, bool mb, bool me, uint8_t *buf, size_t bufLen, size_t *outLen) {
if ((rec == NULL) || (buf == NULL) || (outLen == NULL)) {
return PM3_EINVARG;
}
if ((rec->typeLen > 255) || (rec->idLen > 255)) {
return PM3_EINVARG;
}
#if SIZE_MAX > 0xFFFFFFFF
// the long form payload length field is only four bytes wide, a larger payload
// cannot be expressed. unreachable where size_t is no wider than the field.
if (rec->payloadLen > 0xFFFFFFFF) {
return PM3_EINVARG;
}
#endif
if ((rec->typeLen && (rec->type == NULL)) ||
(rec->idLen && (rec->id == NULL)) ||
(rec->payloadLen && (rec->payload == NULL))) {
return PM3_EINVARG;
}
bool sr = (rec->payloadLen < 256);
bool il = (rec->idLen > 0);
size_t head = 1 + 1 + (sr ? 1 : 4) + (il ? 1 : 0) + rec->typeLen + rec->idLen;
// keep the total from wrapping before it reaches the bufLen check below
if (rec->payloadLen > SIZE_MAX - head) {
return PM3_EINVARG;
}
size_t n = head + rec->payloadLen;
if (n > bufLen) {
return PM3_EOVFLOW;
}
size_t idx = 0;
// MB | ME | CF | SR | IL | TNF. chunked records are never emitted
buf[idx] = (uint8_t)(rec->tnf & 0x07);
if (mb) {
buf[idx] |= 0x80;
}
if (me) {
buf[idx] |= 0x40;
}
if (sr) {
buf[idx] |= 0x10;
}
if (il) {
buf[idx] |= 0x08;
}
idx++;
buf[idx++] = (uint8_t)rec->typeLen;
if (sr) {
buf[idx++] = (uint8_t)rec->payloadLen;
} else {
buf[idx++] = (uint8_t)(rec->payloadLen >> 24);
buf[idx++] = (uint8_t)(rec->payloadLen >> 16);
buf[idx++] = (uint8_t)(rec->payloadLen >> 8);
buf[idx++] = (uint8_t)(rec->payloadLen);
}
if (il) {
buf[idx++] = (uint8_t)rec->idLen;
}
if (rec->typeLen) {
memcpy(buf + idx, rec->type, rec->typeLen);
idx += rec->typeLen;
}
if (rec->idLen) {
memcpy(buf + idx, rec->id, rec->idLen);
idx += rec->idLen;
}
if (rec->payloadLen) {
memcpy(buf + idx, rec->payload, rec->payloadLen);
idx += rec->payloadLen;
}
*outLen = idx;
return PM3_SUCCESS;
}
int NDEFEncodeMessage(const NDEFRecordDesc_t *recs, size_t count, uint8_t *buf, size_t bufLen, size_t *outLen) {
if ((recs == NULL) || (buf == NULL) || (outLen == NULL) || (count == 0)) {
return PM3_EINVARG;
}
size_t idx = 0;
for (size_t i = 0; i < count; i++) {
size_t n = 0;
int res = NDEFEncodeRecord(&recs[i], (i == 0), (i == (count - 1)), buf + idx, bufLen - idx, &n);
if (res != PM3_SUCCESS) {
return res;
}
idx += n;
}
*outLen = idx;
return PM3_SUCCESS;
}
int NDEFEncodeTLV(const uint8_t *msg, size_t msgLen, uint8_t *buf, size_t bufLen, size_t *outLen) {
if ((msg == NULL) || (buf == NULL) || (outLen == NULL) || (msgLen == 0)) {
return PM3_EINVARG;
}
// 0xFFFF is reserved, and the terminator has to fit as well
if (msgLen > 0xFFFE) {
return PM3_EINVARG;
}
// one byte length form up to 254, three byte form above that
size_t hdr = (msgLen < 0xFF) ? 2 : 4;
size_t n = hdr + msgLen + 1;
if (n > bufLen) {
return PM3_EOVFLOW;
}
size_t idx = 0;
buf[idx++] = 0x03;
if (msgLen < 0xFF) {
buf[idx++] = (uint8_t)msgLen;
} else {
buf[idx++] = 0xFF;
buf[idx++] = (uint8_t)(msgLen >> 8);
buf[idx++] = (uint8_t)(msgLen);
}
memcpy(buf + idx, msg, msgLen);
idx += msgLen;
buf[idx++] = 0xFE;
*outLen = idx;
return PM3_SUCCESS;
}
+31
View File
@@ -77,4 +77,35 @@ typedef struct {
int NDEFDecodeAndPrint(uint8_t *ndef, size_t ndefLen, bool verbose);
int NDEFRecordsDecodeAndPrint(uint8_t *ndefRecord, size_t ndefRecordLen, bool verbose);
int NDEFGetTotalLength(uint8_t *ndef, size_t ndeflen, size_t *outlen);
// NFC Forum well known types
#define NDEF_TYPE_URI "U"
#define NDEF_TYPE_TEXT "T"
// Android Application Record, TNF external
#define NDEF_ANDROID_AAR "android.com:pkg"
// Describes one record to be encoded.
// Field order intentionally matches gst_ndef_record_t in cmdhfgst.c so the two
// can eventually be folded together.
typedef struct {
TypeNameFormat_t tnf;
const uint8_t *type;
size_t typeLen;
const uint8_t *id;
size_t idLen;
const uint8_t *payload;
size_t payloadLen;
} NDEFRecordDesc_t;
// payload builders
int NDEFEncodePayloadURI(const char *uri, uint8_t *buf, size_t bufLen, size_t *outLen);
int NDEFEncodePayloadText(const char *text, const char *lang, uint8_t *buf, size_t bufLen, size_t *outLen);
int NDEFEncodePayloadAAR(const char *pkg, uint8_t *buf, size_t bufLen, size_t *outLen);
// record / message / TLV assembly
int NDEFEncodeRecord(const NDEFRecordDesc_t *rec, bool mb, bool me, uint8_t *buf, size_t bufLen, size_t *outLen);
int NDEFEncodeMessage(const NDEFRecordDesc_t *recs, size_t count, uint8_t *buf, size_t bufLen, size_t *outLen);
int NDEFEncodeTLV(const uint8_t *msg, size_t msgLen, uint8_t *buf, size_t bufLen, size_t *outLen);
#endif // _NDEF_H_