-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Expand file tree
/
Copy pathhf_st25_tearoff.c
More file actions
1170 lines (1011 loc) · 41.8 KB
/
Copy pathhf_st25_tearoff.c
File metadata and controls
1170 lines (1011 loc) · 41.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//-----------------------------------------------------------------------------
// Copyright (C) SecLabz, 2025
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// See LICENSE.txt for the text of the license.
//-----------------------------------------------------------------------------
// Standalone mode for reading/storing and restoring ST25TB tags with tear-off for counters.
// Handles a collection of tags. Click swaps between Store and Restore modes.
// Requires WITH_FLASH enabled at compile time.
// Only tested on a Proxmark3 Easy with flash
//
// The initial mode is learning/storing with LED D.
// In this mode, the Proxmark3 is looking for an ST25TB tag, reads all its data,
// and stores the tag's contents to flash memory for later restoration.
//
// Clicking the button once will toggle to restore mode (LED C).
// In this mode, the Proxmark3 searches for an ST25TB tag and, if found, compares
// its UID with previously stored tags. If there's a match, it will restore the
// tag data from flash memory, including counter blocks using tear-off technique.
//
// The standalone supports a collection of up to 8 different ST25TB tags.
//
// Special handling is implemented for counter blocks 5 & 6. For these blocks,
// the tear-off technique is used to manipulate counters that normally can only
// be decremented, allowing restoration of previously stored counter values even
// if they're higher than the current value.
//
// Holding the button down for 1 second will exit the standalone mode.
//
// LEDs:
// LED D = Learn/Store mode (reading and storing tag data)
// LED C = Restore mode (writing stored data back to tags)
// LED A (blinking) = Operation successful
// LED B (blinking) = Operation failed
//
// Flash memory is required for this standalone mode to function properly.
//
//-----------------------------------------------------------------------------
//=============================================================================
// INCLUDES
//=============================================================================
// System includes
#include <string.h> // memcpy, memset
// Proxmark3 includes
#include "standalone.h"
#include "proxmark3_arm.h"
#include "appmain.h"
#include "fpgaloader.h"
#include "iso14443b.h" // ISO14443B operations
#include "util.h"
#include "spiffs.h" // Flash memory filesystem access
#include "dbprint.h"
#include "ticks.h"
#include "BigBuf.h"
#include "protocols.h"
#include "crc16.h" // compute_crc
//=============================================================================
// FLASH MEMORY REQUIREMENT CHECK
//=============================================================================
#ifndef WITH_FLASH
#error "This standalone mode requires WITH_FLASH to be defined. Please recompile with flash memory support."
#endif
//=============================================================================
// CONSTANTS & DEFINITIONS
//=============================================================================
// File and data structure constants
#define HF_ST25TB_MULTI_SR_FILE "hf_st25tb_tags.bin" // Store/Restore filename
#define ST25TB_BLOCK_COUNT 16 // ST25TB512 or similar with 16 blocks
#define ST25TB_BLOCK_SIZE 4 // 4 bytes per block
#define ST25TB_COUNTER_BLOCK_5 5 // Counter block indices
#define ST25TB_COUNTER_BLOCK_6 6
#define ST25TB_DATA_SIZE (ST25TB_BLOCK_COUNT * ST25TB_BLOCK_SIZE)
#define MAX_SAVED_TAGS 8 // Allow storing up to 8 tags
// Tear-off constants
#define TEAR_OFF_START_OFFSET_US 150
#define TEAR_OFF_ADJUSTMENT_US 25
#define PRE_READ_DELAY_US 0
#define TEAR_OFF_WRITE_RETRY_COUNT 30
#define TEAR_OFF_CONSOLIDATE_READ_COUNT 6
#define TEAR_OFF_CONSOLIDATE_WAIT_READ_COUNT 2
#define TEAR_OFF_CONSOLIDATE_WAIT_MS 2000
// Display/console colors
#define RESET "\033[0m"
#define BOLD "\033[01m"
#define RED "\033[31m"
#define BLUE "\033[34m"
#define GREEN "\033[32m"
// Bit manipulation macros
#define IS_ONE_BIT(value, index) ((value) & ((uint32_t)1 << (index)))
#define IS_ZERO_BIT(value, index) (!IS_ONE_BIT(value, index))
#define RF_SWTICH_OFF() FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF)
//=============================================================================
// TYPE DEFINITIONS
//=============================================================================
// Operation modes
typedef enum {
MODE_LEARN = 0, // Store/learn tag data
MODE_RESTORE = 1 // Restore tag data
} standalone_mode_t;
// Operation states
typedef enum {
STATE_BUSY = 0, // Actively processing
STATE_DONE = 1, // Operation completed successfully
STATE_ERROR = 2 // Operation failed
} standalone_state_t;
// Structure to hold tag data in RAM
typedef struct {
uint64_t uid;
uint32_t blocks[ST25TB_BLOCK_COUNT];
uint32_t otp;
bool data_valid; // Flag to indicate if this slot holds valid data
} st25tb_data_t;
//=============================================================================
// GLOBAL VARIABLES
//=============================================================================
// Tag collection and state tracking
static st25tb_data_t g_stored_tags[MAX_SAVED_TAGS];
static uint8_t g_valid_tag_count = 0; // Number of valid entries
static standalone_mode_t g_current_mode = MODE_LEARN; // Current operation mode
static standalone_state_t current_state = STATE_BUSY; // Current operation state
static unsigned long g_prng_seed = 1; // Used for PRNG
//=============================================================================
// FUNCTION DECLARATIONS
//=============================================================================
// Core utility functions
static int dummy_rand(void);
uint64_t bytes_to_num_le(const uint8_t *src, size_t len);
// UI/LED interaction functions
static void update_leds_mode(standalone_mode_t mode);
static void indicate_success(void);
static void indicate_failure(void);
// Flash storage operations
static bool load_tags_from_flash(st25tb_data_t collection[MAX_SAVED_TAGS]);
static bool save_tags_to_flash(const st25tb_data_t collection[MAX_SAVED_TAGS]);
static int find_tag_by_uid(const uint64_t uid);
static int find_free_tag_slot(void);
// ISO14443B communication functions
static void iso14443b_setup_light(void);
// Tag read/write operations
static bool st25tb_tag_get_basic_info(iso14b_card_select_t *card_info);
static bool st25tb_tag_read(st25tb_data_t *tag_data_slot);
static bool st25tb_tag_restore(const st25tb_data_t *stored_data_slot);
static void st25tb_tag_print(st25tb_data_t *tag);
// Tear-off operations
static int st25tb_cmd_write_block(uint8_t block_address, uint8_t *block);
static bool st25tb_write_block_with_retry(uint8_t block_address, uint32_t target_value);
static int st25tb_tear_off_read_block(uint8_t block_address, uint32_t *block_value);
static void st25tb_tear_off_write_block(uint8_t block_address, uint32_t data, uint16_t tearoff_delay_us);
static int8_t st25tb_tear_off_retry_write_verify(uint8_t block_address, uint32_t target_value, uint32_t max_try_count, int sleep_time_ms, uint32_t *read_back_value);
static int8_t st25tb_tear_off_is_consolidated(const uint8_t block_address, uint32_t value, int repeat_read, int sleep_time_ms, uint32_t *read_value);
static int8_t st25tb_tear_off_consolidate_block(const uint8_t block_address, uint32_t current_value, uint32_t target_value, uint32_t *read_back_value);
static uint32_t st25tb_tear_off_next_value(uint32_t current_value, bool randomness);
static void st25tb_tear_off_adjust_timing(int *tear_off_us, uint32_t tear_off_adjustment_us);
static void st25tb_tear_off_log(int tear_off_us, char *color, uint32_t value);
static int8_t st25tb_tear_off_write_counter(uint8_t block_address, uint32_t target_value, uint32_t tear_off_adjustment_us, uint32_t safety_value);
// Main application functions
static void run_learn_function(void);
static void run_restore_function(void);
void ModInfo(void);
void RunMod(void);
//=============================================================================
// CORE UTILITY FUNCTIONS
//=============================================================================
/**
* @brief Simple PRNG implementation
* @return Random integer
*/
static int dummy_rand(void) {
g_prng_seed = g_prng_seed * 1103515245 + 12345;
return (unsigned int)(g_prng_seed / 65536) % 32768;
}
/**
* @brief Convert bytes to number (little-endian)
* @param src Source byte array
* @param len Length of array
* @return Converted 64-bit value
*/
uint64_t bytes_to_num_le(const uint8_t *src, size_t len) {
uint64_t num = 0;
size_t i;
if (len > sizeof(uint64_t)) {
len = sizeof(uint64_t);
}
// Iterate from LSB to MSB
for (i = 0; i < len; ++i) {
num |= ((uint64_t)src[i] << (i * 8));
}
return num;
}
//=============================================================================
// UI/LED INTERACTION FUNCTIONS
//=============================================================================
/**
* @brief Update LEDs to indicate current mode and state
* @param mode Current operation mode
*/
static void update_leds_mode(standalone_mode_t mode) {
LEDsoff();
if (mode == MODE_LEARN) {
LED_D_ON();
} else { // MODE_RESTORE
LED_C_ON();
}
}
/**
* @brief Indicate successful operation with LED sequence
*/
static void indicate_success(void) {
// Blink Green LED (A) 3 times quickly for success
for (int i = 0; i < 3; ++i) {
LED_A_ON();
SpinDelay(150);
LED_A_OFF();
SpinDelay(150);
}
}
/**
* @brief Indicate failed operation with LED sequence
*/
static void indicate_failure(void) {
// Blink Red LED (B) 3 times quickly for failure
for (int i = 0; i < 3; ++i) {
LED_B_ON();
SpinDelay(150);
LED_B_OFF();
SpinDelay(150);
}
}
//=============================================================================
// FLASH STORAGE OPERATIONS
//=============================================================================
/**
* @brief Load tag collection from flash
* @param collection Array to store loaded data
* @return true if successful, false otherwise
*/
static bool load_tags_from_flash(st25tb_data_t collection[MAX_SAVED_TAGS]) {
// Check if file exists
if (!exists_in_spiffs(HF_ST25TB_MULTI_SR_FILE)) {
return false; // File doesn't exist, nothing to load
}
// Verify file size
uint32_t size = size_in_spiffs(HF_ST25TB_MULTI_SR_FILE);
if (size != sizeof(g_stored_tags)) {
Dbprintf(_RED_("Flash file size mismatch (expected %zu, got %u). Wiping old file."),
sizeof(g_stored_tags), size);
// Remove corrupted file
rdv40_spiffs_remove(HF_ST25TB_MULTI_SR_FILE, RDV40_SPIFFS_SAFETY_SAFE);
return false;
}
// Read file contents
int res = rdv40_spiffs_read(HF_ST25TB_MULTI_SR_FILE, (uint8_t *)collection,
size, RDV40_SPIFFS_SAFETY_SAFE);
if (res != SPIFFS_OK) {
Dbprintf(_RED_("Failed to read tag collection from flash (err %d)"), res);
// Mark all as invalid if read failed
for (int i = 0; i < MAX_SAVED_TAGS; i++)
collection[i].data_valid = false;
return false;
}
return true;
}
/**
* @brief Save tag collection to flash
* @param collection Array of tag data to save
* @return true if successful, false otherwise
*/
static bool save_tags_to_flash(const st25tb_data_t collection[MAX_SAVED_TAGS]) {
int res = rdv40_spiffs_write(HF_ST25TB_MULTI_SR_FILE, (uint8_t *)collection,
sizeof(g_stored_tags), RDV40_SPIFFS_SAFETY_SAFE);
return (res == SPIFFS_OK);
}
/**
* @brief Find a tag in the collection by UID
* @param uid UID to search for
* @return Index of tag in collection, or -1 if not found
*/
static int find_tag_by_uid(const uint64_t uid) {
for (int i = 0; i < MAX_SAVED_TAGS; i++) {
if (g_stored_tags[i].data_valid && g_stored_tags[i].uid == uid) {
return i;
}
}
return -1; // Not found
}
/**
* @brief Find next empty slot in the collection
* @return Index of empty slot, or -1 if collection is full
*/
static int find_free_tag_slot(void) {
for (int i = 0; i < MAX_SAVED_TAGS; i++) {
if (!g_stored_tags[i].data_valid) {
return i;
}
}
return -1; // Collection is full
}
//=============================================================================
// ISO14443B COMMUNICATION FUNCTIONS
//=============================================================================
/**
* @brief Stripped version of "iso14443b_setup" that avoids unnecessary LED
* operations and uses shorter delays
*/
static void iso14443b_setup_light(void) {
RF_SWTICH_OFF();
FpgaDownloadAndGo(FPGA_BITSTREAM_HF);
// Set up the synchronous serial port
FpgaSetupSsc(FPGA_MAJOR_MODE_HF_READER);
// Signal field is on with the appropriate LED
#ifdef RDV4
FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER | FPGA_HF_READER_MODE_SEND_SHALLOW_MOD_RDV4);
#else
FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER | FPGA_HF_READER_MODE_SEND_SHALLOW_MOD);
#endif
SpinDelayUs(250);
// Start the timer
StartCountSspClk();
}
//=============================================================================
// TAG READ/WRITE OPERATIONS
//=============================================================================
/**
* @brief Select a ST25TB tag and get basic info
* @param card_info Pointer to store card info
* @return true if successful, false otherwise
*/
static bool st25tb_tag_get_basic_info(iso14b_card_select_t *card_info) {
iso14443b_setup_light();
int res = iso14443b_select_srx_card(card_info);
RF_SWTICH_OFF();
return (res == PM3_SUCCESS);
}
/**
* @brief Read all data from a ST25TB tag
* @param tag_data_slot Pointer to store tag data
* @return true if successful, false otherwise
*/
static bool st25tb_tag_read(st25tb_data_t *tag_data_slot) {
iso14443b_setup_light();
iso14b_card_select_t card_info;
uint8_t block[ST25TB_BLOCK_SIZE];
int res;
bool success = true;
// Select card
res = iso14443b_select_srx_card(&card_info);
if (res != PM3_SUCCESS) {
RF_SWTICH_OFF();
return false;
}
Dbprintf("Found ST tag. Reading %d blocks...", ST25TB_BLOCK_COUNT);
tag_data_slot->uid = bytes_to_num_le(card_info.uid, sizeof(tag_data_slot->uid));
// Read all data blocks
for (uint8_t block_address = 0; block_address < ST25TB_BLOCK_COUNT; block_address++) {
WDT_HIT();
res = read_14b_srx_block(block_address, block);
if (res != PM3_SUCCESS) {
Dbprintf(_RED_("Failed to read block %d"), block_address);
success = false;
break;
}
// Store the read block data
tag_data_slot->blocks[block_address] = bytes_to_num_le(block, ST25TB_BLOCK_SIZE);
if (g_dbglevel >= DBG_DEBUG) {
Dbprintf("Read Block %02d: %08X", block_address, tag_data_slot->blocks[block_address]);
}
SpinDelay(5); // Small delay between block reads
}
// Read OTP block
res = read_14b_srx_block(255, block);
if (res != PM3_SUCCESS) {
Dbprintf(_RED_("Failed to read otp block"));
success = false;
} else {
tag_data_slot->otp = bytes_to_num_le(block, ST25TB_BLOCK_SIZE);
}
RF_SWTICH_OFF();
tag_data_slot->data_valid = success;
return success;
}
/**
* @brief Restore data to a ST25TB tag
* @param stored_data_slot Pointer to stored tag data
* @return true if successful, false otherwise
*/
static bool st25tb_tag_restore(const st25tb_data_t *stored_data_slot) {
if (!stored_data_slot->data_valid) {
DbpString(_RED_("Restore error: Slot data is invalid."));
return false;
}
iso14443b_setup_light();
iso14b_card_select_t card_info;
int res;
bool success = true;
res = iso14443b_select_srx_card(&card_info);
if (res != PM3_SUCCESS) {
DbpString("Restore failed: No tag found or selection failed.");
RF_SWTICH_OFF();
return false;
}
uint64_t tag_uid = bytes_to_num_le(card_info.uid, sizeof(uint64_t));
// Verify UID match before restoring
if (tag_uid != stored_data_slot->uid) {
Dbprintf("Restore failed: UID mismatch (Tag: %llX, Slot: %llX)", tag_uid, stored_data_slot->uid);
RF_SWTICH_OFF();
return false;
}
Dbprintf("Found ST tag, UID: %llX. Starting restore...", tag_uid);
// Process all blocks
for (uint8_t block_address = 0; block_address < ST25TB_BLOCK_COUNT; block_address++) {
WDT_HIT();
uint32_t stored_value = stored_data_slot->blocks[block_address];
if (g_dbglevel >= DBG_DEBUG) {
Dbprintf("Restoring Block %02d: %08X", block_address, stored_value);
}
// Special handling for counter blocks 5 and 6
if (block_address == ST25TB_COUNTER_BLOCK_5 || block_address == ST25TB_COUNTER_BLOCK_6) {
uint32_t current_value = 0;
res = st25tb_tear_off_read_block(block_address, ¤t_value);
if (res != PM3_SUCCESS) {
Dbprintf(_RED_("Failed to read current counter value for block %d"), block_address);
success = false;
break;
}
if (g_dbglevel >= DBG_DEBUG) {
Dbprintf("Counter Block %d: Stored=0x%08X, Current=0x%08X",
block_address, stored_value, current_value);
}
// Only use tear-off logic if stored value is greater
if (stored_value > current_value) {
// The st25tb_tear_off_write_counter function handles the tear-off logic
if (st25tb_tear_off_write_counter(block_address, stored_value, TEAR_OFF_ADJUSTMENT_US, 0x1000) != 0) {
Dbprintf(_RED_("Tear-off write failed for counter block %d"), block_address);
success = false;
break;
}
Dbprintf("Used tear-off write for counter block %d", block_address);
} else if (stored_value < current_value) {
// Standard write for when stored value is less than current
if (!st25tb_write_block_with_retry(block_address, stored_value)) {
Dbprintf(_RED_("Failed to write block %d"), block_address);
success = false;
break;
}
} else {
Dbprintf("Counter block %d already has the target value (0x%08X). Skipping write.",
block_address, stored_value);
}
} else {
// Standard write for non-counter blocks
if (!st25tb_write_block_with_retry(block_address, stored_value)) {
Dbprintf(_RED_("Failed to write block %d with value 0x%08X"), block_address, stored_value);
success = false;
break;
}
}
SpinDelay(10); // Delay between writes
}
RF_SWTICH_OFF();
return success;
}
/**
* @brief Print tag data in formatted table
* @param tag Pointer to tag data
*/
static void st25tb_tag_print(st25tb_data_t *tag) {
uint8_t i;
Dbprintf("UID: %016llX", tag->uid);
Dbprintf("+---------------+----------+--------------------+");
Dbprintf("| BLOCK ADDRESS | VALUE | DESCRIPTION |");
Dbprintf("+---------------+----------+--------------------+");
for (i = 0; i < 16; i++) {
if (i == 2) {
Dbprintf("| %03d | %08X | Lockable EEPROM |", i, tag->blocks[i]);
} else if (i == 5) {
Dbprintf("| %03d | %08X | Count down |", i, tag->blocks[i]);
} else if (i == 6) {
Dbprintf("| %03d | %08X | counter |", i, tag->blocks[i]);
} else if (i == 11) {
Dbprintf("| %03d | %08X | Lockable EEPROM |", i, tag->blocks[i]);
} else {
Dbprintf("| %03d | %08X | |", i, tag->blocks[i]);
}
if (i == 4 || i == 6 || i == 15) {
Dbprintf("+---------------+----------+--------------------+");
}
}
Dbprintf("| %03d | %08X | System OTP bits |", 255, tag->otp);
Dbprintf("+---------------+----------+--------------------+");
}
//=============================================================================
// TEAR-OFF OPERATIONS
//=============================================================================
/**
* @brief Read a block
* @param block_address Block address to read
* @param block_value Pointer to store read value
* @return Result code (0 for success)
*/
static int st25tb_tear_off_read_block(uint8_t block_address, uint32_t *block_value) {
int res;
iso14b_card_select_t card;
iso14443b_setup_light();
res = iso14443b_select_srx_card(&card);
if (res != PM3_SUCCESS) {
goto out;
}
uint8_t block[ST25TB_BLOCK_SIZE];
res = read_14b_srx_block(block_address, block);
if (res == PM3_SUCCESS) {
*block_value = bytes_to_num_le(block, ST25TB_BLOCK_SIZE);
}
out:
RF_SWTICH_OFF();
return res;
}
/**
* @brief Low-level block write function
* @param block_address Block number to write
* @param block Block data
* @return Result code (0 for success)
*/
static int st25tb_cmd_write_block(uint8_t block_address, uint8_t *block) {
uint8_t cmd[] = {ISO14443B_WRITE_BLK, block_address, block[0], block[1], block[2], block[3], 0x00, 0x00};
AddCrc14B(cmd, 6);
uint32_t start_time = 0;
uint32_t eof_time = 0;
CodeAndTransmit14443bAsReader(cmd, sizeof(cmd), &start_time, &eof_time, true);
return PM3_SUCCESS;
}
/**
* @brief Write a block with retry mechanism
* @param block_address Block number to write
* @param target_value Value to write
* @return true if successful, false otherwise
*/
static bool st25tb_write_block_with_retry(uint8_t block_address, uint32_t target_value) {
uint32_t read_back_value = 0;
int max_retries = 5;
if (st25tb_tear_off_retry_write_verify(block_address, target_value, max_retries, 0, &read_back_value) != 0) {
return false;
}
return (read_back_value == target_value);
}
/**
* @brief Write a block with tear-off capability
* @param block_address Block number to write
* @param data Data to write
* @param tearoff_delay_us Tear-off delay in microseconds
*/
static void st25tb_tear_off_write_block(uint8_t block_address, uint32_t data, uint16_t tearoff_delay_us) {
iso14443b_setup_light();
uint8_t block[ST25TB_BLOCK_SIZE];
block[0] = (data & 0xFF);
block[1] = (data >> 8) & 0xFF;
block[2] = (data >> 16) & 0xFF;
block[3] = (data >> 24) & 0xFF;
iso14b_card_select_t card;
int res = iso14443b_select_srx_card(&card);
if (res != PM3_SUCCESS) {
goto out;
}
res = st25tb_cmd_write_block(block_address, block);
// Tear off the communication at precise timing
SpinDelayUsPrecision(tearoff_delay_us);
FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF);
out:
RF_SWTICH_OFF();
}
/**
* @brief Write a block with retry and verification
* @param block_address Block address to write
* @param target_value Value to write
* @param max_try_count Maximum number of retries
* @param sleep_time_ms Sleep time between retries in milliseconds
* @param read_back_value Pointer to store read-back value
* @return 0 for success, -1 for failure
*/
static int8_t st25tb_tear_off_retry_write_verify(uint8_t block_address, uint32_t target_value,
uint32_t max_try_count, int sleep_time_ms,
uint32_t *read_back_value) {
int i = 0;
*read_back_value = ~target_value; // Initialize to ensure the loop runs at least once
while (*read_back_value != target_value && i < max_try_count) {
st25tb_tear_off_write_block(block_address, target_value, 6000); // Long delay for reliability
if (sleep_time_ms > 0) SpinDelayUsPrecision(sleep_time_ms * 1000);
st25tb_tear_off_read_block(block_address, read_back_value);
if (sleep_time_ms > 0) SpinDelayUsPrecision(sleep_time_ms * 1000);
i++;
}
return (*read_back_value == target_value) ? 0 : -1;
}
/**
* @brief Check if a block's value is consolidated (stable)
* @param block_address Block address to check
* @param value Expected value
* @param repeat_read Number of reads to perform
* @param sleep_time_ms Sleep time between reads in milliseconds
* @param read_value Pointer to store read value
* @return 0 if consolidated, -1 otherwise
*/
static int8_t st25tb_tear_off_is_consolidated(const uint8_t block_address, uint32_t value,
int repeat_read, int sleep_time_ms,
uint32_t *read_value) {
int result;
for (int i = 0; i < repeat_read; i++) {
if (sleep_time_ms > 0) SpinDelayUsPrecision(sleep_time_ms * 1000);
result = st25tb_tear_off_read_block(block_address, read_value);
if (result != 0 || value != *read_value) {
return -1; // Read error or value changed
}
}
return 0; // Value remained stable
}
/**
* @brief Consolidate a block to a stable state
* @param block_address Block address to consolidate
* @param current_value Current value
* @param target_value Target value
* @param read_back_value Pointer to store read-back value
* @return 0 for success, -1 for failure
*/
static int8_t st25tb_tear_off_consolidate_block(const uint8_t block_address, uint32_t current_value,
uint32_t target_value, uint32_t *read_back_value) {
int8_t result;
uint32_t consolidation_value;
// Determine the value to write for consolidation based on target and current state
if (target_value <= 0xFFFFFFFD && current_value >= (target_value + 2)) {
consolidation_value = target_value + 2;
} else {
consolidation_value = current_value;
}
// Try writing value - 1
result = st25tb_tear_off_retry_write_verify(block_address, consolidation_value - 1,
TEAR_OFF_WRITE_RETRY_COUNT, 0, read_back_value);
if (result != 0) {
Dbprintf("Consolidation failed at step 1 (write 0x%08X)", consolidation_value - 1);
return -1;
}
// If value is not FE or target is not FD, try writing value - 2
if (*read_back_value != 0xFFFFFFFE || target_value == 0xFFFFFFFD) {
result = st25tb_tear_off_retry_write_verify(block_address, consolidation_value - 2,
TEAR_OFF_WRITE_RETRY_COUNT, 0, read_back_value);
if (result != 0) {
Dbprintf("Consolidation failed at step 2 (write 0x%08X)", consolidation_value - 2);
return -1;
}
}
// Final checks for stability of unstable high values (due to internal dual counters)
if (result == 0 && target_value > 0xFFFFFFFD && *read_back_value > 0xFFFFFFFD) {
result = st25tb_tear_off_is_consolidated(block_address, *read_back_value,
TEAR_OFF_CONSOLIDATE_READ_COUNT, 0, read_back_value);
if (result == 0) {
result = st25tb_tear_off_is_consolidated(block_address, *read_back_value,
TEAR_OFF_CONSOLIDATE_WAIT_READ_COUNT,
TEAR_OFF_CONSOLIDATE_WAIT_MS, read_back_value);
if (result != 0) {
Dbprintf("Consolidation failed stability check (long wait)");
return -1;
}
} else {
Dbprintf("Consolidation failed stability check (short wait)");
return -1;
}
}
return 0;
}
/**
* @brief Calculate next value for counter decrement
* @param current_value Current counter value
* @param randomness Whether to use randomization
* @return Next value to attempt
*/
static uint32_t st25tb_tear_off_next_value(uint32_t current_value, bool randomness) {
uint32_t value = 0;
int8_t index = 31;
// Simple decrement for smaller values
if (current_value < 0x0000FFFF) {
return (current_value > 0) ? current_value - 1 : 0;
}
// Loop through each bit starting from the most significant bit (MSB)
while (index >= 0) {
// Find the most significant '1' bit
if (value == 0 && IS_ONE_BIT(current_value, index)) {
// Create a mask with '1's up to this position
value = 0xFFFFFFFF >> (31 - index);
index--; // Move to the next bit
}
// Once the first '1' is found, look for the first '0' after it
if (value != 0 && IS_ZERO_BIT(current_value, index)) {
index++; // Go back to the position of the '0'
// Clear the bit at this '0' position in our mask
value &= ~((uint32_t)1 << index);
// Optional randomization: flip a random bit below the found '0'
if (randomness && value < 0xF0000000 && index > 1) {
value ^= ((uint32_t)1 << (dummy_rand() % index));
}
return value;
}
index--;
}
return (current_value > 0) ? current_value - 1 : 0;
}
/**
* @brief Adjust timing for tear-off operations
* @param tear_off_us Pointer to current tear-off timing
* @param tear_off_adjustment_us Adjustment amount
*/
static void st25tb_tear_off_adjust_timing(int *tear_off_us, uint32_t tear_off_adjustment_us) {
if (*tear_off_us > TEAR_OFF_START_OFFSET_US) {
*tear_off_us -= tear_off_adjustment_us;
}
}
/**
* @brief Log tear-off operation details
* @param tear_off_us Current tear-off timing
* @param color Color code for output
* @param value Value being processed
*/
static void st25tb_tear_off_log(int tear_off_us, char *color, uint32_t value) {
char binaryRepresentation[33];
for (int i = 31; i >= 0; i--) {
binaryRepresentation[31 - i] = IS_ONE_BIT(value, i) ? '1' : '0';
}
binaryRepresentation[32] = '\0';
Dbprintf("%s%08X%s : %s%s%s : %d us", color, value, RESET, color, binaryRepresentation, RESET, tear_off_us);
}
/**
* @brief Main tear-off counter write function
* @param block_address Block address to write
* @param target_value Target value
* @param tear_off_adjustment_us Adjustment for tear-off timing
* @param safety_value Safety threshold to prevent going below
* @return 0 for success, non-zero for failure
*/
static int8_t st25tb_tear_off_write_counter(uint8_t block_address, uint32_t target_value,
uint32_t tear_off_adjustment_us, uint32_t safety_value) {
int result;
bool trigger = true;
uint32_t read_value = 0;
uint32_t current_value = 0;
uint32_t last_consolidated_value = 0;
uint32_t tear_off_value = 0;
int tear_off_us = TEAR_OFF_START_OFFSET_US;
if (tear_off_adjustment_us == 0) {
tear_off_adjustment_us = TEAR_OFF_ADJUSTMENT_US;
}
// Initial read to get the current counter value
result = st25tb_tear_off_read_block(block_address, ¤t_value);
if (result != PM3_SUCCESS) {
Dbprintf("Initial read failed for block %d", block_address);
return -1; // Indicate failure
}
// Calculate the first value to attempt writing via tear-off
tear_off_value = st25tb_tear_off_next_value(current_value, false);
Dbprintf(" Target block: %d", block_address);
Dbprintf("Current value: 0x%08X", current_value);
Dbprintf(" Target value: 0x%08X", target_value);
Dbprintf(" Safety value: 0x%08X", safety_value);
Dbprintf("Adjustment us: %u", tear_off_adjustment_us);
// Check if tear-off is even possible or needed
if (tear_off_value == 0 && current_value != 0) {
Dbprintf("Tear-off technique not possible from current value.");
return -1;
}
if (current_value == target_value) {
Dbprintf("Current value already matches target value.");
return 0;
}
// Main tear-off loop
for (;;) {
// Safety check: ensure we don't go below the safety threshold
if (tear_off_value < safety_value) {
Dbprintf("Stopped. Safety threshold reached (next value 0x%08X < safety 0x%08X)",
tear_off_value, safety_value);
return -1;
}
// Perform the tear-off write attempt
st25tb_tear_off_write_block(block_address, tear_off_value, tear_off_us);
// Read back the value after the attempt
result = st25tb_tear_off_read_block(block_address, &read_value);
if (result != 0) {
continue; // Retry the loop if read fails (ex: tag is removed from the read for a short period)
}
// Analyze the result and decide next action
if (read_value > current_value) {
// Partial write succeeded (successful tear-off)
if (read_value >= 0xFFFFFFFE ||
(read_value - 2) > target_value ||
read_value != last_consolidated_value ||
((read_value & 0xF0000000) > (current_value & 0xF0000000))) { // Major bit flip
result = st25tb_tear_off_consolidate_block(block_address, read_value,
target_value, ¤t_value);
if (result == 0 && current_value == target_value) {
st25tb_tear_off_log(tear_off_us, GREEN, read_value);
Dbprintf("Target value 0x%08X reached successfully!", target_value);
return 0;
}
if (read_value != last_consolidated_value) {
st25tb_tear_off_adjust_timing(&tear_off_us, tear_off_adjustment_us);
}
last_consolidated_value = read_value;
tear_off_value = st25tb_tear_off_next_value(current_value, false);
trigger = true;
st25tb_tear_off_log(tear_off_us, GREEN, read_value);
}
} else if (read_value == tear_off_value) {
// Write succeeded completely (no tear-off effect)
if (trigger) {
tear_off_value = st25tb_tear_off_next_value(tear_off_value, true);
trigger = false;
} else {
tear_off_value = st25tb_tear_off_next_value(read_value, false);
trigger = true;
}
current_value = read_value;
st25tb_tear_off_adjust_timing(&tear_off_us, tear_off_adjustment_us);
st25tb_tear_off_log(tear_off_us, BLUE, read_value);
} else if (read_value < tear_off_value) {
// Partial write succeeded (successful tear-off) but lower value
tear_off_value = st25tb_tear_off_next_value(read_value, false);
st25tb_tear_off_adjust_timing(&tear_off_us, tear_off_adjustment_us);
current_value = read_value;
trigger = true;
st25tb_tear_off_log(tear_off_us, RED, read_value);
}
// Increment tear-off timing for the next attempt
tear_off_us++;
// Check for user interruption
WDT_HIT();
if (BUTTON_PRESS()) {
DbpString("Tear-off stopped by user.");
return -1;
}
}
return -1;
}
//=============================================================================
// MAIN APPLICATION FUNCTIONS
//=============================================================================
/**
* @brief Learn/store function implementation
*/
static void run_learn_function(void) {
st25tb_data_t temp_tag_data; // Temporary buffer to read into
memset(&temp_tag_data, 0, sizeof(temp_tag_data));
if (st25tb_tag_read(&temp_tag_data)) {
st25tb_tag_print(&temp_tag_data);
int slot_index = find_tag_by_uid(temp_tag_data.uid);
if (slot_index != -1) {
Dbprintf("Tag with UID %llX already in Slot %d. Overwriting...",
temp_tag_data.uid, slot_index);
} else {
slot_index = find_free_tag_slot();