textual
[RRG-proxmark3.git] / client / src / cmdhfjooki.c
blob2758a7cb231776a476a3312ee97e5b2bdcb5dba0
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
5 // the license.
6 //-----------------------------------------------------------------------------
7 // High frequency MIFARE ULTRALIGHT / Jooki commands
8 //-----------------------------------------------------------------------------
9 #include "cmdhfjooki.h"
10 #include <ctype.h>
11 #include <string.h> // memset
12 #include "commonutil.h" // ARRAYLEN
13 #include "ui.h" // PrintAndLog
14 #include "cmdparser.h"
15 #include "generator.h"
16 #include "base64.h"
17 #include "nfc/ndef.h" // print decode ndef
18 #include "mifare/mifarehost.h" // mfemlsetmem_xt
19 #include "cliparser.h"
20 #include "cmdhfmfu.h"
21 #include "cmdmain.h"
22 #include "fileutils.h" // convert_mfu..
24 static int CmdHelp(const char *Cmd);
26 typedef struct {
27 uint8_t typeid;
28 uint8_t figureid;
29 const char figdesc[40];
30 const char typedesc[12];
31 } PACKED jooki_figure_t;
33 typedef struct {
34 uint8_t uid[7];
35 const char b64[17];
36 uint8_t typeid;
37 uint8_t figureid;
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) {
112 return i;
115 return -1;
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) {
127 if (out == NULL) {
128 PrintAndLogEx(ERR, "(encode jooki) base64ndef param is NULL");
129 return PM3_EINVARG;
132 out[0] = 0x00;
133 if (iv == NULL || uid == NULL) {
134 PrintAndLogEx(ERR, "(encode jooki) iv or uid param is NULL");
135 return PM3_EINVARG;
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++) {
142 if (i < 3)
143 enc[i] = d[i] ^ NFC_SECRET[i];
144 else
145 enc[i] = d[i] ^ NFC_SECRET[i] ^ d[i % 3];
148 PrintAndLogEx(DEBUG, "encoded result.... %s", sprint_hex(enc, sizeof(enc)));
150 size_t b64len = 0;
151 uint8_t b64[20];
152 memset(b64, 0, 20);
153 mbedtls_base64_encode(b64, sizeof(b64), &b64len, (const unsigned char *)enc, sizeof(enc));
154 memcpy(out, b64, b64len);
155 return PM3_SUCCESS;
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++) {
166 if (i < 3)
167 result[i] = ndef[i] ^ NFC_SECRET[i];
168 else
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)));
172 return PM3_SUCCESS;
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");
179 return PM3_EINVARG;
181 memcpy(ndefrecord,
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);
188 return PM3_SUCCESS;
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")
196 , tid
197 , fid
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)));
208 if (verbose) {
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)
218 return;
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)
235 continue;
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, "==================================================================");
273 return PM3_SUCCESS;
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"
286 void *argtable[] = {
287 arg_param_begin,
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"),
307 arg_param_end
309 CLIExecWithReturn(ctx, Cmd, argtable, false);
310 int ulen = 0;
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);
314 if (res) {
315 CLIParserFree(ctx);
316 return PM3_EINVARG;
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;
341 CLIParserFree(ctx);
343 if (selftest) {
344 return jooki_selftest();
347 uint8_t tid, fid;
349 if (ftid || ffid) {
350 figure_abbr = false;
353 if (ftid > 0x04 || ffid > 0x20) {
354 PrintAndLogEx(ERR, "Use a valid Figure Type ID and Figure ID");
355 return PM3_EINVARG;
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");
362 return PM3_EINVARG;
365 if (figure_abbr_val == 1 && !figure_abbr) {
366 PrintAndLogEx(ERR, "Use either --tid and --fid or one of the figurine types");
367 return PM3_EINVARG;
370 if (figure_abbr) {
371 tid = 0x01;
372 } else {
373 tid = ftid;
375 fid = ffid;
377 if (t1)
378 fid = 0x01;
379 if (t2)
380 fid = 0x02;
381 if (t3)
382 fid = 0x03;
383 if (t5)
384 fid = 0x05;
385 if (t6)
386 fid = 0x06;
387 if (t7)
388 fid = 0x07;
389 if (t8)
390 fid = 0x08;
391 if (t9)
392 fid = 0x09;
393 if (ta)
394 fid = 0x0a;
395 if (tb)
396 fid = 0x0b;
397 if (tc)
398 fid = 0x0c;
399 if (td)
400 fid = 0x0d;
402 uint8_t iv[JOOKI_IV_LEN] = {0x80, 0x77, 0x51};
403 if (use_tag) {
404 res = ul_read_uid(uid);
405 if (res != PM3_SUCCESS) {
406 return res;
408 } else {
409 if (ulen != JOOKI_UID_LEN) {
410 PrintAndLogEx(ERR, "Wrong length of UID, expect %u, got %d", JOOKI_UID_LEN, ulen);
411 return PM3_EINVARG;
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);
419 return PM3_SUCCESS;
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"
429 void *argtable[] = {
430 arg_param_begin,
431 arg_str1("d", "data", "<base64>", "base64 url parameter"),
432 arg_lit0("v", "verbose", "verbose output"),
433 arg_param_end
435 CLIExecWithReturn(ctx, Cmd, argtable, false);
436 int dlen = 16;
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);
441 CLIParserFree(ctx);
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);
448 return PM3_SUCCESS;
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"
459 void *argtable[] = {
460 arg_param_begin,
461 arg_str0("b", "b64", "<base64>", "base64 url parameter"),
462 arg_param_end
464 CLIExecWithReturn(ctx, Cmd, argtable, true);
465 int dlen = 16;
466 uint8_t b64[JOOKI_B64_LEN] = {0x00};
467 memset(b64, 0x0, sizeof(b64));
468 CLIGetStrWithReturn(ctx, 1, b64, &dlen);
469 CLIParserFree(ctx);
471 uint8_t result[JOOKI_PLAIN_LEN] = {0};
472 int res = jooki_decode(b64, result);
473 if (res != PM3_SUCCESS) {
474 return res;
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);
483 // hf mfu sim...
484 uint8_t *data = calloc(144, sizeof(uint8_t));
486 memcpy(data, uid, 3);
487 memcpy(data + (1 * 4), uid + 3, 4);
489 // bbc0
490 data[3] = 0x88 ^ data[0] ^ data[1] ^ data[2];
492 // bbc1
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);
498 // copy raw NDEF
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");
506 free(data);
507 return res;
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);
521 // fast push mode
522 conn.block_after_ACK = true;
523 uint8_t blockwidth = 4, counter = 0, blockno = 0;
524 while (datalen) {
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);
532 free(data);
533 return PM3_ESOFT;
535 PrintAndLogEx(NORMAL, "." NOLF);
536 fflush(stdout);
537 blockno++;
538 counter += blockwidth;
539 datalen -= blockwidth;
541 PrintAndLogEx(NORMAL, "\n");
543 struct {
544 uint8_t tagtype;
545 uint8_t flags;
546 uint8_t uid[10];
547 uint8_t exitAfter;
548 } PACKED payload;
550 // NTAG, 7 byte UID in eloaded data.
551 payload.tagtype = 7;
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");
561 for (;;) {
562 if (kbd_enter_pressed()) {
563 SendCommandNG(CMD_BREAK_LOOP, NULL, 0);
564 PrintAndLogEx(DEBUG, "User aborted");
565 break;
568 if (WaitForResponseTimeout(CMD_HF_MIFARE_SIMULATE, &resp, 1500) == 0)
569 continue;
571 if (resp.status != PM3_SUCCESS)
572 break;
574 free(data);
575 PrintAndLogEx(INFO, "Done");
576 PrintAndLogEx(HINT, "Try `" _YELLOW_("hf 14a list") "` to view trace log");
577 return PM3_SUCCESS;
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"
588 void *argtable[] = {
589 arg_param_begin,
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)"),
593 arg_param_end
595 CLIExecWithReturn(ctx, Cmd, argtable, false);
596 int blen = 16;
597 uint8_t b64[JOOKI_B64_LEN] = {0x00};
598 memset(b64, 0x0, sizeof(b64));
599 CLIGetStrWithReturn(ctx, 1, b64, &blen);
601 int dlen = 0;
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);
605 if (res) {
606 CLIParserFree(ctx);
607 PrintAndLogEx(FAILED, "Error parsing bytes");
608 return PM3_EINVARG;
611 int plen = 0;
612 uint8_t pwd[4] = {0x00};
613 CLIGetHexWithReturn(ctx, 3, pwd, &plen);
614 CLIParserFree(ctx);
616 if (dlen != 52) {
617 PrintAndLogEx(ERR, "Wrong data length. Expected 52 got %d", dlen);
618 return PM3_EINVARG;
621 bool has_pwd = false;
622 if (plen == 4) {
623 has_pwd = true;
626 // 0 - no authentication
627 // 2 - pwd (4 bytes)
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);
634 if (has_pwd) {
635 memcpy(cmddata + 4, pwd, 4);
636 keytype = 2;
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"));
645 } else {
646 PrintAndLogEx(WARNING, "Command execute timeout");
649 blockno++;
650 i++;
653 PrintAndLogEx(INFO, "Done");
654 PrintAndLogEx(HINT, "Try `" _YELLOW_("hf mfu ndefread") "` to view");
655 return PM3_SUCCESS;
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);
670 return PM3_SUCCESS;
673 int CmdHF_Jooki(const char *Cmd) {
674 clearCommandBuffer();
675 return CmdsParse(CommandTable, Cmd);