1 //-----------------------------------------------------------------------------
2 // Ultralight Code (c) 2021 Iceman
3 // This code is licensed to you under the terms of the GNU GPL, version 2 or,
4 // at your option, any later version. See the LICENSE.txt file for the text of
6 //-----------------------------------------------------------------------------
7 // High frequency MIFARE ULTRALIGHT / Jooki commands
8 //-----------------------------------------------------------------------------
9 #include "cmdhfjooki.h"
11 #include <string.h> // memset
12 #include "commonutil.h" // ARRAYLEN
13 #include "ui.h" // PrintAndLog
14 #include "cmdparser.h"
15 #include "generator.h"
17 #include "nfc/ndef.h" // print decode ndef
18 #include "mifare/mifarehost.h" // mfemlsetmem_xt
19 #include "cliparser.h"
22 #include "fileutils.h" // convert_mfu..
24 static int CmdHelp(const char *Cmd
);
29 const char figdesc
[40];
30 const char typedesc
[12];
31 } PACKED jooki_figure_t
;
38 } PACKED jooki_test_t
;
40 // sample set for selftest.
41 jooki_test_t jooks
[] = {
42 { {0x04, 0xDA, 0xB7, 0x6A, 0xE7, 0x4C, 0x80}, "ruxow8lnn88uyeX+", 0x01, 0x00},
43 { {0x04, 0xf0, 0x22, 0xc2, 0x33, 0x5e, 0x80}, "\0", 0x01, 0x00},
44 { {0x04, 0x8C, 0xEC, 0xDA, 0xF0, 0x4A, 0x80}, "ONrsVf7jX6IaSNV6", 0x01, 0x01},
45 { {0x04, 0x92, 0xA7, 0x6A, 0xE7, 0x4C, 0x81}, "Hjjpcx/mZwuveTF+", 0x01, 0x02},
46 { {0x04, 0xD0, 0xB0, 0x3A, 0xD3, 0x63, 0x80}, "\0", 0x01, 0x02},
47 { {0x04, 0x96, 0x42, 0xDA, 0xF0, 0x4A, 0x80}, "vEWy0WO9wZNEzEok", 0x01, 0x03},
48 { {0x04, 0x33, 0xb5, 0x62, 0x39, 0x4d, 0x80}, "\0", 0x01, 0x03},
49 { {0x04, 0x17, 0xB7, 0x3A, 0xD3, 0x63, 0x81}, "f0axEma+g2WnLGAm", 0x01, 0x05},
50 { {0x04, 0x84, 0x27, 0x6A, 0xE7, 0x4C, 0x80}, "VZB/OLBwOiM5Mpnp", 0x01, 0x05},
51 { {0x04, 0x28, 0xF4, 0xDA, 0xF0, 0x4A, 0x81}, "7WzlgEzqLgwTnWNy", 0x01, 0x05},
54 jooki_figure_t jooks_figures
[] = {
55 {0x01, 0x00, "Dragon", "Figurine"},
56 {0x01, 0x01, "Fox", "Figurine"},
57 {0x01, 0x02, "Ghost", "Figurine"},
58 {0x01, 0x03, "Knight", "Figurine"},
59 {0x01, 0x04, "ThankYou", "Figurine"},
60 {0x01, 0x05, "Whale", "Figurine"},
61 {0x01, 0x06, "Black Dragon", "Figurine"},
62 {0x01, 0x07, "Black Fox", "Figurine"},
63 {0x01, 0x08, "Black Knight", "Figurine"},
64 {0x01, 0x09, "Black Whale", "Figurine"},
65 {0x01, 0x0A, "White Dragon", "Figurine"},
66 {0x01, 0x0B, "White Fox", "Figurine"},
67 {0x01, 0x0C, "White Knight", "Figurine"},
68 {0x01, 0x0D, "White Whale", "Figurine"},
70 {0x02, 0x00, "Generic Flat", "Stone"},
72 {0x03, 0x00, "record", "Sys"},
73 {0x03, 0x01, "factory_mode_on", "Sys"},
74 {0x03, 0x02, "factory_mode_off", "Sys"},
75 {0x03, 0x03, "airplane_mode_on", "Sys"},
76 {0x03, 0x04, "airplane_mode_off", "Sys"},
77 {0x03, 0x05, "toy_safe_on", "Sys"},
78 {0x03, 0x06, "toy_safe_off", "Sys"},
79 {0x03, 0x07, "wifi_on", "Sys"},
80 {0x03, 0x08, "wifi_off", "Sys"},
81 {0x03, 0x09, "bt_on", "Sys"},
82 {0x03, 0x0A, "bt_off", "Sys"},
83 {0x03, 0x0B, "production_finished", "Sys"},
85 {0x04, 0x00, "test.0", "Test"},
86 {0x04, 0x01, "test.1", "Test"},
87 {0x04, 0x02, "test.2", "Test"},
88 {0x04, 0x03, "test.3", "Test"},
89 {0x04, 0x04, "test.4", "Test"},
90 {0x04, 0x05, "test.5", "Test"},
91 {0x04, 0x06, "test.6", "Test"},
92 {0x04, 0x07, "test.7", "Test"},
93 {0x04, 0x08, "test.8", "Test"},
94 {0x04, 0x09, "test.9", "Test"},
95 {0x04, 0x10, "test.10", "Test"},
96 {0x04, 0x11, "test.11", "Test"},
97 {0x04, 0x12, "test.12", "Test"},
98 {0x04, 0x13, "test.13", "Test"},
99 {0x04, 0x14, "test.14", "Test"},
100 {0x04, 0x15, "test.15", "Test"},
101 {0x04, 0x16, "test.16", "Test"},
102 {0x04, 0x17, "test.17", "Test"},
103 {0x04, 0x18, "test.18", "Test"},
104 {0x04, 0x19, "test.19", "Test"},
105 {0x04, 0x20, "test.20", "Test"},
108 static int jooki_lookup(uint8_t tid
, uint8_t fid
) {
109 for (int i
= 0; i
< ARRAYLEN(jooks_figures
); i
++) {
110 jooki_figure_t tmp
= jooks_figures
[i
];
111 if (tmp
.typeid == tid
&& tmp
.figureid
== fid
) {
118 const uint8_t jooki_secret
[] = {0x20, 0x20, 0x20, 0x6D, 0x24, 0x0B, 0xEB, 0x94, 0x2C, 0x80, 0x45, 0x16};
119 const uint8_t NFC_SECRET
[] = { 0x03, 0x9c, 0x25, 0x6f, 0xb9, 0x2e, 0xe8, 0x08, 0x09, 0x83, 0xd9, 0x33, 0x56};
121 #define JOOKI_UID_LEN 7
122 #define JOOKI_IV_LEN 3
123 #define JOOKI_B64_LEN (16 + 1)
124 #define JOOKI_PLAIN_LEN 12
126 static int jooki_encode(uint8_t *iv
, uint8_t tid
, uint8_t fid
, uint8_t *uid
, uint8_t *out
) {
128 PrintAndLogEx(ERR
, "(encode jooki) base64ndef param is NULL");
133 if (iv
== NULL
|| uid
== NULL
) {
134 PrintAndLogEx(ERR
, "(encode jooki) iv or uid param is NULL");
138 uint8_t d
[JOOKI_PLAIN_LEN
] = {iv
[0], iv
[1], iv
[2], tid
, fid
, uid
[0], uid
[1], uid
[2], uid
[3], uid
[4], uid
[5], uid
[6]};
139 uint8_t enc
[JOOKI_PLAIN_LEN
] = {0};
140 for (uint8_t i
= 0; i
< JOOKI_PLAIN_LEN
; i
++) {
143 enc
[i
] = d
[i
] ^ NFC_SECRET
[i
];
145 enc
[i
] = d
[i
] ^ NFC_SECRET
[i
] ^ d
[i
% 3];
148 PrintAndLogEx(DEBUG
, "encoded result.... %s", sprint_hex(enc
, sizeof(enc
)));
153 mbedtls_base64_encode(b64
, sizeof(b64
), &b64len
, (const unsigned char *)enc
, sizeof(enc
));
154 memcpy(out
, b64
, b64len
);
158 static int jooki_decode(uint8_t *b64
, uint8_t *result
) {
159 uint8_t ndef
[JOOKI_PLAIN_LEN
] = {0};
160 size_t outputlen
= 0;
161 mbedtls_base64_decode(ndef
, sizeof(ndef
), &outputlen
, (const unsigned char *)b64
, 16);
163 PrintAndLogEx(DEBUG
, "(decode_jooki) raw encoded... " _GREEN_("%s"), sprint_hex(ndef
, sizeof(ndef
)));
165 for (uint8_t i
= 0; i
< JOOKI_PLAIN_LEN
; i
++) {
167 result
[i
] = ndef
[i
] ^ NFC_SECRET
[i
];
169 result
[i
] = ndef
[i
] ^ NFC_SECRET
[i
] ^ ndef
[i
% 3] ^ NFC_SECRET
[i
% 3];
171 PrintAndLogEx(DEBUG
, "(decode_jooki) plain......... %s", sprint_hex(result
, sizeof(ndef
)));
175 static int jooki_create_ndef(uint8_t *b64ndef
, uint8_t *ndefrecord
) {
176 // sample of url: https://s.jooki.rocks/s/?s=ONrsVf7jX6IaSNV6
177 if (ndefrecord
== NULL
) {
178 PrintAndLogEx(ERR
, "(jooki_create_ndef) ndefrecord param is NULL");
182 "\x01\x03\xa0\x0c\x34\x03\x29\xd1"
183 "\x01\x25\x55\x04\x73\x2e\x6a\x6f"
184 "\x6f\x6b\x69\x2e\x72\x6f\x63\x6b"
185 "\x73\x2f\x73\x2f\x3f\x73\x3d", 31);
186 memcpy(ndefrecord
+ 31, b64ndef
, 16);
187 memcpy(ndefrecord
+ 47, "\x0a\xFE\x00\x00\x00", 5);
191 static void jooki_printEx(uint8_t *b64
, uint8_t *iv
, uint8_t tid
, uint8_t fid
, uint8_t *uid
, bool verbose
) {
192 int idx
= jooki_lookup(tid
, fid
);
194 PrintAndLogEx(INFO
, "Encoded URL.. %s ( %s )", sprint_hex(b64
, 12), b64
);
195 PrintAndLogEx(INFO
, "Figurine..... %02x %02x - " _GREEN_("%s, %s")
198 , (idx
!= -1) ? jooks_figures
[idx
].typedesc
: "n/a"
199 , (idx
!= -1) ? jooks_figures
[idx
].figdesc
: "n/a"
201 PrintAndLogEx(INFO
, "iv........... %s", sprint_hex(iv
, JOOKI_IV_LEN
));
202 PrintAndLogEx(INFO
, "uid.......... %s", sprint_hex(uid
, JOOKI_UID_LEN
));
204 uint8_t ndefmsg
[52] = {0};
205 jooki_create_ndef(b64
, ndefmsg
);
206 PrintAndLogEx(INFO
, "NDEF raw..... %s", sprint_hex_inrow(ndefmsg
, sizeof(ndefmsg
)));
209 int res
= NDEFRecordsDecodeAndPrint(ndefmsg
, sizeof(ndefmsg
));
210 if (res
!= PM3_SUCCESS
) {
211 NDEFDecodeAndPrint(ndefmsg
, sizeof(ndefmsg
), verbose
);
216 static void jooki_print(uint8_t *b64
, uint8_t *result
, bool verbose
) {
217 if (b64
== NULL
|| result
== NULL
)
220 uint8_t iv
[JOOKI_IV_LEN
] = {0};
221 uint8_t uid
[JOOKI_UID_LEN
] = {0};
222 memcpy(iv
, result
, JOOKI_IV_LEN
);
223 uint8_t tid
= result
[3];
224 uint8_t fid
= result
[4];
225 memcpy(uid
, result
+ 5, JOOKI_UID_LEN
);
227 jooki_printEx(b64
, iv
, tid
, fid
, uid
, verbose
);
230 static int jooki_selftest(void) {
232 PrintAndLogEx(INFO
, "======== " _CYAN_("selftest") " ===========================================");
233 for (int i
= 0; i
< ARRAYLEN(jooks
); i
++) {
234 if (strlen(jooks
[i
].b64
) == 0)
237 uint8_t iv
[JOOKI_IV_LEN
] = {0};
238 uint8_t uid
[JOOKI_UID_LEN
] = {0};
239 uint8_t result
[JOOKI_PLAIN_LEN
] = {0};
240 jooki_decode((uint8_t *)jooks
[i
].b64
, result
);
242 memcpy(iv
, result
, JOOKI_IV_LEN
);
243 uint8_t tid
= result
[3];
244 uint8_t fid
= result
[4];
245 memcpy(uid
, result
+ 5, sizeof(uid
));
247 bool tid_ok
= (tid
== jooks
[i
].typeid);
248 bool fid_ok
= (fid
== jooks
[i
].figureid
);
249 bool uid_ok
= (memcmp(uid
, jooks
[i
].uid
, sizeof(uid
)) == 0);
251 int idx
= jooki_lookup(tid
, fid
);
253 PrintAndLogEx(INFO
, "Encoded URL.. %s ( %s )", sprint_hex((const uint8_t *)jooks
[i
].b64
, 12), jooks
[i
].b64
);
254 PrintAndLogEx(INFO
, "Type......... %02x - " _GREEN_("%s") " ( %s )", tid
, (idx
!= -1) ? jooks_figures
[idx
].typedesc
: "n/a", tid_ok
? _GREEN_("ok") : _RED_("fail"));
255 PrintAndLogEx(INFO
, "Figurine..... %02x - " _GREEN_("%s") " ( %s )", fid
, (idx
!= -1) ? jooks_figures
[idx
].figdesc
: "n/a", fid_ok
? _GREEN_("ok") : _RED_("fail"));
256 PrintAndLogEx(INFO
, "iv........... %s", sprint_hex(iv
, sizeof(iv
)));
257 PrintAndLogEx(INFO
, "uid.......... %s ( %s )", sprint_hex(uid
, sizeof(uid
)), uid_ok
? _GREEN_("ok") : _RED_("fail"));
259 uint8_t b64
[JOOKI_B64_LEN
] = {0};
260 memset(b64
, 0, sizeof(b64
));
261 jooki_encode(iv
, tid
, fid
, uid
, b64
);
263 uint8_t ndefmsg
[52] = {0};
264 jooki_create_ndef(b64
, ndefmsg
);
265 PrintAndLogEx(INFO
, "NDEF raw .... %s", sprint_hex(ndefmsg
, sizeof(ndefmsg
)));
267 int status
= NDEFRecordsDecodeAndPrint(ndefmsg
, sizeof(ndefmsg
));
268 if (status
!= PM3_SUCCESS
) {
269 status
= NDEFDecodeAndPrint(ndefmsg
, sizeof(ndefmsg
), true);
271 PrintAndLogEx(INFO
, "==================================================================");
276 static int CmdHF14AJookiEncode(const char *Cmd
) {
277 CLIParserContext
*ctx
;
278 CLIParserInit(&ctx
, "hf jooki encode",
279 "Encode a Jooki token to base64 NDEF URI format",
280 "hf jooki encode -t --> selftest\n"
281 "hf jooki encode -r --dragon --> read uid from tag and use for encoding\n"
282 "hf jooki encode --uid 04010203040506 --dragon\n"
283 "hf jooki encode --uid 04010203040506 --tid 1 --fid 1"
288 arg_str0("u", "uid", "<hex>", "uid bytes"),
289 arg_lit0("r", NULL
, "read uid from tag instead"),
290 arg_lit0("t", NULL
, "selftest"),
291 arg_lit0("v", "verbose", "verbose output"),
292 arg_lit0(NULL
, "dragon", "figurine type"),
293 arg_lit0(NULL
, "fox", "figurine type"),
294 arg_lit0(NULL
, "ghost", "figurine type"),
295 arg_lit0(NULL
, "knight", "figurine type"),
296 arg_lit0(NULL
, "whale", "figurine type"),
297 arg_lit0(NULL
, "blackdragon", "figurine type"),
298 arg_lit0(NULL
, "blackfox", "figurine type"),
299 arg_lit0(NULL
, "blackknight", "figurine type"),
300 arg_lit0(NULL
, "blackwhale", "figurine type"),
301 arg_lit0(NULL
, "whitedragon", "figurine type"),
302 arg_lit0(NULL
, "whitefox", "figurine type"),
303 arg_lit0(NULL
, "whiteknight", "figurine type"),
304 arg_lit0(NULL
, "whitewhale", "figurine type"),
305 arg_u64_0(NULL
, "tid", "<dec>", "figurine type id"),
306 arg_u64_0(NULL
, "fid", "<dec>", "figurine id"),
309 CLIExecWithReturn(ctx
, Cmd
, argtable
, false);
311 uint8_t uid
[JOOKI_UID_LEN
] = {0x00};
312 memset(uid
, 0x0, sizeof(uid
));
313 int res
= CLIParamHexToBuf(arg_get_str(ctx
, 1), uid
, sizeof(uid
), &ulen
);
319 bool use_tag
= arg_get_lit(ctx
, 2);
320 bool selftest
= arg_get_lit(ctx
, 3);
321 bool verbose
= arg_get_lit(ctx
, 4);
322 bool t0
= arg_get_lit(ctx
, 5);
323 bool t1
= arg_get_lit(ctx
, 6);
324 bool t2
= arg_get_lit(ctx
, 7);
325 bool t3
= arg_get_lit(ctx
, 8);
326 bool t5
= arg_get_lit(ctx
, 9);
327 bool t6
= arg_get_lit(ctx
, 10);
328 bool t7
= arg_get_lit(ctx
, 11);
329 bool t8
= arg_get_lit(ctx
, 12);
330 bool t9
= arg_get_lit(ctx
, 13);
331 bool ta
= arg_get_lit(ctx
, 14);
332 bool tb
= arg_get_lit(ctx
, 15);
333 bool tc
= arg_get_lit(ctx
, 16);
334 bool td
= arg_get_lit(ctx
, 17);
336 uint8_t ftid
= arg_get_u32_def(ctx
, 18, 0);
337 uint8_t ffid
= arg_get_u32_def(ctx
, 19, 0);
339 bool figure_abbr
= true;
344 return jooki_selftest();
353 if (ftid
> 0x04 || ffid
> 0x20) {
354 PrintAndLogEx(ERR
, "Use a valid Figure Type ID and Figure ID");
358 uint8_t figure_abbr_val
= t0
+ t1
+ t2
+ t3
+ t5
+ t6
+ t7
+ t8
+ t9
+ ta
+ tb
+ tc
+ td
;
360 if (figure_abbr_val
> 1) {
361 PrintAndLogEx(ERR
, "Select one tag type or use figurine type id and figurine id");
365 if (figure_abbr_val
== 1 && !figure_abbr
) {
366 PrintAndLogEx(ERR
, "Use either --tid and --fid or one of the figurine types");
402 uint8_t iv
[JOOKI_IV_LEN
] = {0x80, 0x77, 0x51};
404 res
= ul_read_uid(uid
);
405 if (res
!= PM3_SUCCESS
) {
409 if (ulen
!= JOOKI_UID_LEN
) {
410 PrintAndLogEx(ERR
, "Wrong length of UID, expect %u, got %d", JOOKI_UID_LEN
, ulen
);
415 uint8_t b64
[JOOKI_B64_LEN
] = {0};
416 memset(b64
, 0, sizeof(b64
));
417 jooki_encode(iv
, tid
, fid
, uid
, b64
);
418 jooki_printEx(b64
, iv
, tid
, fid
, uid
, verbose
);
422 static int CmdHF14AJookiDecode(const char *Cmd
) {
423 CLIParserContext
*ctx
;
424 CLIParserInit(&ctx
, "hf jooki decode",
425 "Decode a base64-encode Jooki token in NDEF URI format",
426 "hf jooki decode -d 7WzlgEzqLgwTnWNy"
431 arg_str1("d", "data", "<base64>", "base64 url parameter"),
432 arg_lit0("v", "verbose", "verbose output"),
435 CLIExecWithReturn(ctx
, Cmd
, argtable
, false);
437 uint8_t b64
[JOOKI_B64_LEN
] = {0x00};
438 memset(b64
, 0x0, sizeof(b64
));
439 CLIGetStrWithReturn(ctx
, 1, b64
, &dlen
);
440 bool verbose
= arg_get_lit(ctx
, 2);
443 uint8_t result
[JOOKI_PLAIN_LEN
] = {0};
444 int res
= jooki_decode(b64
, result
);
445 if (res
== PM3_SUCCESS
) {
446 jooki_print(b64
, result
, verbose
);
451 static int CmdHF14AJookiSim(const char *Cmd
) {
452 CLIParserContext
*ctx
;
453 CLIParserInit(&ctx
, "hf jooki sim",
454 "Simulate a Jooki token. Either `hf mfu eload` before or use `-d` param",
455 "hf jooki sim --> use token in emulator memory\n"
456 "hf jooki sim -b 7WzlgEzqLgwTnWNy --> using base64 url parameter"
461 arg_str0("b", "b64", "<base64>", "base64 url parameter"),
464 CLIExecWithReturn(ctx
, Cmd
, argtable
, true);
466 uint8_t b64
[JOOKI_B64_LEN
] = {0x00};
467 memset(b64
, 0x0, sizeof(b64
));
468 CLIGetStrWithReturn(ctx
, 1, b64
, &dlen
);
471 uint8_t result
[JOOKI_PLAIN_LEN
] = {0};
472 int res
= jooki_decode(b64
, result
);
473 if (res
!= PM3_SUCCESS
) {
477 jooki_print(b64
, result
, false);
479 // copy UID from base64 url parameter
480 uint8_t uid
[7] = {0};
481 memcpy(uid
, result
+ 5, 7);
484 uint8_t *data
= calloc(144, sizeof(uint8_t));
486 memcpy(data
, uid
, 3);
487 memcpy(data
+ (1 * 4), uid
+ 3, 4);
490 data
[3] = 0x88 ^ data
[0] ^ data
[1] ^ data
[2];
493 data
[8] = data
[4] ^ data
[5] ^ data
[6] ^ data
[7];
495 // copy NDEF magic firs, skip BBC1
496 memcpy(data
+ (2 * 4) + 1, "\x48\x00\x00\xE1\x10\x12\x00", 7);
499 jooki_create_ndef(b64
, data
+ (4 * 4));
501 // convert plain or old mfu format to new format
502 size_t datalen
= 144;
503 res
= convert_mfu_dump_format(&data
, &datalen
, true);
504 if (res
!= PM3_SUCCESS
) {
505 PrintAndLogEx(FAILED
, "Failed convert on load to new Ultralight/NTAG format");
510 mfu_dump_t
*mfu_dump
= (mfu_dump_t
*)data
;
511 memcpy(mfu_dump
->version
, "\x00\x04\x04\x02\x01\x00\x0F\x03", 8);
512 mfu_dump
->counter_tearing
[2][3] = 0xBD;
513 mfu_dump
->pages
= 0x2c;
515 printMFUdumpEx(mfu_dump
, mfu_dump
->pages
+ 1, 0);
517 // upload to emulator memory
518 PrintAndLogEx(INFO
, "Uploading to emulator memory");
520 PrintAndLogEx(INFO
, "." NOLF
);
522 conn
.block_after_ACK
= true;
523 uint8_t blockwidth
= 4, counter
= 0, blockno
= 0;
525 if (datalen
== blockwidth
) {
526 // Disable fast mode on last packet
527 conn
.block_after_ACK
= false;
530 if (mfEmlSetMem_xt(data
+ counter
, blockno
, 1, blockwidth
) != PM3_SUCCESS
) {
531 PrintAndLogEx(FAILED
, "Cant set emul block: %3d", blockno
);
535 PrintAndLogEx(NORMAL
, "." NOLF
);
538 counter
+= blockwidth
;
539 datalen
-= blockwidth
;
541 PrintAndLogEx(NORMAL
, "\n");
550 // NTAG, 7 byte UID in eloaded data.
552 payload
.flags
= FLAG_UID_IN_EMUL
;
553 payload
.exitAfter
= 0;
554 memcpy(payload
.uid
, uid
, sizeof(uid
));
556 clearCommandBuffer();
557 SendCommandNG(CMD_HF_ISO14443A_SIMULATE
, (uint8_t *)&payload
, sizeof(payload
));
558 PacketResponseNG resp
;
560 PrintAndLogEx(INFO
, "Press " _GREEN_("<Enter>") " or pm3-button to abort simulation");
562 if (kbd_enter_pressed()) {
563 SendCommandNG(CMD_BREAK_LOOP
, NULL
, 0);
564 PrintAndLogEx(DEBUG
, "User aborted");
568 if (WaitForResponseTimeout(CMD_HF_MIFARE_SIMULATE
, &resp
, 1500) == 0)
571 if (resp
.status
!= PM3_SUCCESS
)
575 PrintAndLogEx(INFO
, "Done");
576 PrintAndLogEx(HINT
, "Try `" _YELLOW_("hf 14a list") "` to view trace log");
580 static int CmdHF14AJookiClone(const char *Cmd
) {
581 CLIParserContext
*ctx
;
582 CLIParserInit(&ctx
, "hf jooki clone",
583 "Write a Jooki token to a Ultralight or NTAG tag",
584 "hf jooki clone -d <hex bytes> --> where hex is raw NDEF\n"
585 "hf jooki clone --b64 7WzlgEzqLgwTnWNy --> using base64 url parameter"
590 arg_str0("b", "b64", "<base64>", "base64 url parameter"),
591 arg_str0("d", "data", "<hex>", "raw NDEF bytes"),
592 arg_str0("p", "pwd", "<hex>", "password for authentication (EV1/NTAG 4 bytes)"),
595 CLIExecWithReturn(ctx
, Cmd
, argtable
, false);
597 uint8_t b64
[JOOKI_B64_LEN
] = {0x00};
598 memset(b64
, 0x0, sizeof(b64
));
599 CLIGetStrWithReturn(ctx
, 1, b64
, &blen
);
602 uint8_t data
[52] = {0x00};
603 memset(data
, 0x0, sizeof(data
));
604 int res
= CLIParamHexToBuf(arg_get_str(ctx
, 2), data
, sizeof(data
), &dlen
);
607 PrintAndLogEx(FAILED
, "Error parsing bytes");
612 uint8_t pwd
[4] = {0x00};
613 CLIGetHexWithReturn(ctx
, 3, pwd
, &plen
);
617 PrintAndLogEx(ERR
, "Wrong data length. Expected 52 got %d", dlen
);
621 bool has_pwd
= false;
626 // 0 - no authentication
628 uint8_t keytype
= 0, blockno
= 4, i
= 0;
630 while ((i
* 4) < dlen
) {
632 uint8_t cmddata
[8] = {0};
633 memcpy(cmddata
, data
+ (i
* 4), 4);
635 memcpy(cmddata
+ 4, pwd
, 4);
638 clearCommandBuffer();
639 SendCommandMIX(CMD_HF_MIFAREU_WRITEBL
, blockno
, keytype
, 0, cmddata
, sizeof(cmddata
));
641 PacketResponseNG resp
;
642 if (WaitForResponseTimeout(CMD_ACK
, &resp
, 1500)) {
643 uint8_t isOK
= resp
.oldarg
[0] & 0xff;
644 PrintAndLogEx(SUCCESS
, "Write block %d ( %s )", blockno
, isOK
? _GREEN_("ok") : _RED_("fail"));
646 PrintAndLogEx(WARNING
, "Command execute timeout");
653 PrintAndLogEx(INFO
, "Done");
654 PrintAndLogEx(HINT
, "Try `" _YELLOW_("hf mfu ndefread") "` to view");
658 static command_t CommandTable
[] = {
659 {"help", CmdHelp
, AlwaysAvailable
, "This help"},
660 {"clone", CmdHF14AJookiClone
, IfPm3Iso14443a
, "Write a Jooki token"},
661 {"decode", CmdHF14AJookiDecode
, AlwaysAvailable
, "Decode Jooki token"},
662 {"encode", CmdHF14AJookiEncode
, AlwaysAvailable
, "Encode Jooki token"},
663 {"sim", CmdHF14AJookiSim
, IfPm3Iso14443a
, "Simulate Jooki token"},
664 {NULL
, NULL
, NULL
, NULL
}
667 static int CmdHelp(const char *Cmd
) {
668 (void)Cmd
; // Cmd is not used so far
669 CmdsHelp(CommandTable
);
673 int CmdHF_Jooki(const char *Cmd
) {
674 clearCommandBuffer();
675 return CmdsParse(CommandTable
, Cmd
);