refactor: hitag S cleanup

This commit is contained in:
douniwan5788 2024-09-17 00:50:25 +08:00
commit 3fabc97fdb

View file

@ -39,15 +39,17 @@
#define CRC_POLYNOM 0x1D #define CRC_POLYNOM 0x1D
static struct hitagS_tag tag = { static struct hitagS_tag tag = {
.data.pages = .data.pages = {
{
// Plain mode: | Authentication mode: // Plain mode: | Authentication mode:
[0] = {0x88, 0xcd, 0x6d, 0xf3}, // UID | UID [0] = {0x5F, 0xC2, 0x11, 0x84}, // UID | UID
[1] = {0xca, 0x24, 0x00, 0x00}, // CON0 CON1 CON2 Reserved | CON0 CON1 CON2 PWDH0 // HITAG S 256
[2] = {0xaa, 0xaa, 0xaa, 0xaa}, // Data | PWDL0 PWDL1 KEYH0 KEYH1 [1] = {0xC9, 0x00, 0x00, 0xAA}, // CON0 CON1 CON2 Reserved | CON0 CON1 CON2 PWDH0
[3] = {0x55, 0x55, 0x55, 0x55}, // Data | KEYL0 KEYL1 KEYL2 KEYL3 [2] = {0x48, 0x54, 0x4F, 0x4E}, // Data | PWDL0 PWDL1 KEYH0 KEYH1
[4] = {0xff, 0x80, 0x00, 0x00}, // Data [3] = {0x4D, 0x49, 0x4B, 0x52}, // Data | KEYL0 KEYL1 KEYL2 KEYL3
[4] = {0xFF, 0x80, 0x00, 0x00}, // Data
[5] = {0x00, 0x00, 0x00, 0x00}, // Data [5] = {0x00, 0x00, 0x00, 0x00}, // Data
[6] = {0x00, 0x00, 0x00, 0x00}, // Data
[7] = {0x57, 0x5F, 0x4F, 0x48}, // Data
// up to index 63 for HITAG S2048 public data // up to index 63 for HITAG S2048 public data
}, },
}; };
@ -62,11 +64,11 @@ typedef enum modulation {
} MOD; } MOD;
static MOD m = AC2K; // used modulation static MOD m = AC2K; // used modulation
static uint32_t temp_uid; static uint32_t reader_selected_uid;
static int temp2 = 0; static int rotate_uid = 0;
static int sof_bits; // number of start-of-frame bits static int sof_bits; // number of start-of-frame bits
static uint8_t pwdh0, pwdl0, pwdl1; // password bytes static uint8_t pwdh0, pwdl0, pwdl1; // password bytes
static uint32_t rnd = 0x74124485; // random number static uint8_t rnd[] = {0x74, 0x12, 0x44, 0x85}; // random number
//#define SENDBIT_TEST //#define SENDBIT_TEST
/* array index 3 2 1 0 // bytes in sim.bin file are 0 1 2 3 /* array index 3 2 1 0 // bytes in sim.bin file are 0 1 2 3
@ -143,6 +145,19 @@ static void calc_crc(unsigned char *crc, unsigned char data, unsigned char Bitco
} while (--Bitcount); } while (--Bitcount);
} }
static void update_tag_max_page(void) {
//check which memorysize this tag has
if ((tag.data.s.CON0 & 0x3) == 0x00) {
tag.max_page = 32 / (HITAGS_PAGE_SIZE * 8) - 1;
} else if ((tag.data.s.CON0 & 0x3) == 0x1) {
tag.max_page = 256 / (HITAGS_PAGE_SIZE * 8) - 1;
} else if ((tag.data.s.CON0 & 0x3) == 0x2) {
tag.max_page = 2048 / (HITAGS_PAGE_SIZE * 8) - 1;
} else {
tag.max_page = HITAGS_MAX_PAGES - 1;
}
}
static void hitag_send_bit(int bit, bool ledcontrol) { static void hitag_send_bit(int bit, bool ledcontrol) {
if (ledcontrol) LED_A_ON(); if (ledcontrol) LED_A_ON();
@ -387,21 +402,11 @@ static void hts_stop_clock(void) {
*/ */
static int check_select(const uint8_t *rx, uint32_t uid) { static int check_select(const uint8_t *rx, uint32_t uid) {
unsigned char resp[48];
uint32_t ans = 0x0;
for (int i = 0; i < 48; i++) {
resp[i] = (rx[i / 8] >> (7 - (i % 8))) & 0x1;
}
for (int i = 0; i < 32; i++) {
ans += resp[5 + i] << (31 - i);
}
// global var? // global var?
temp_uid = ans; concatbits((uint8_t *)&reader_selected_uid, 0, rx, 5, 32);
reader_selected_uid = BSWAP_32(reader_selected_uid);
if (ans == BSWAP_32(tag.data.s.uid_le)) { if (reader_selected_uid == uid) {
return 1; return 1;
} }
@ -441,6 +446,8 @@ static void hts_handle_reader_command(uint8_t *rx, const size_t rxlen,
// Reset the transmission frame length // Reset the transmission frame length
*txlen = 0; *txlen = 0;
// Reset the frame modulation
hts_set_frame_modulation();
// Try to find out which command was send by selecting on length (in bits) // Try to find out which command was send by selecting on length (in bits)
switch (rxlen) { switch (rxlen) {
@ -472,11 +479,10 @@ static void hts_handle_reader_command(uint8_t *rx, const size_t rxlen,
} }
//send uid as a response //send uid as a response
*txlen = 32; *txlen = 32;
for (int i = 0; i < 4; i++) { memcpy(tx, tag.data.pages[HITAGS_UID_PADR], HITAGS_PAGE_SIZE);
tx[i] = (BSWAP_32(tag.data.s.uid_le) >> (24 - (i * 8))) & 0xFF;
}
break; break;
} }
// case 14 to 44 AC SEQUENCE
case 45: { case 45: {
//select command from reader received //select command from reader received
DBG DbpString("SELECT"); DBG DbpString("SELECT");
@ -487,18 +493,15 @@ static void hts_handle_reader_command(uint8_t *rx, const size_t rxlen,
//if the right tag was selected //if the right tag was selected
*txlen = 32; *txlen = 32;
hts_set_frame_modulation();
//send configuration //send configuration
for (int i = 0; i < 4; i++) { memcpy(tx, tag.data.pages[HITAGS_CONFIG_PADR], HITAGS_PAGE_SIZE - 1);
tx[i] = tag.data.pages[1][i];
}
tx[3] = 0xff; tx[3] = 0xff;
if (tag.mode != HT_STANDARD) { if (tag.mode != HT_STANDARD) {
//add crc8
*txlen = 40; *txlen += 8;
crc = CRC_PRESET; crc = CRC_PRESET;
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
@ -512,34 +515,29 @@ static void hts_handle_reader_command(uint8_t *rx, const size_t rxlen,
} }
case 64: { case 64: {
//challenge message received //challenge message received
DBG Dbprintf("Challenge for UID: %X", temp_uid); DBG Dbprintf("Challenge for UID: %X", reader_selected_uid);
temp2++; rotate_uid++;
*txlen = 32; *txlen = 32;
// init crypt engine // init crypt engine
state = ht2_hitag2_init(reflect64(tag.data.s.key) >> 16 & 0xFFFFFFFFFFFF, state = ht2_hitag2_init(reflect48(tag.data.s.key), reflect32(tag.data.s.uid_le), reflect32(*(uint32_t *)rx));
REV32((tag.data.pages[0][3] << 24) + (tag.data.pages[0][2] << 16) + (tag.data.pages[0][1] << 8) + tag.data.pages[0][0]), DBG Dbhexdump(8, tx, false);
REV32((rx[3] << 24) + (rx[2] << 16) + (rx[1] << 8) + rx[0])
);
DBG Dbprintf(", {0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X, 0x%02X}", rx[0], rx[1], rx[2], rx[3], rx[4], rx[5], rx[6], rx[7]);
hts_set_frame_modulation();
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
ht2_hitag2_byte(&state); ht2_hitag2_byte(&state);
} }
//send con2, pwdh0, pwdl0, pwdl1 encrypted as a response //send con2, pwdh0, pwdl0, pwdl1 encrypted as a response
tx[0] = ht2_hitag2_byte(&state) ^ tag.data.pages[1][2]; tx[0] = ht2_hitag2_byte(&state) ^ tag.data.pages[HITAGS_CONFIG_PADR][2];
tx[1] = ht2_hitag2_byte(&state) ^ tag.data.s.pwdh0; tx[1] = ht2_hitag2_byte(&state) ^ tag.data.s.pwdh0;
tx[2] = ht2_hitag2_byte(&state) ^ tag.data.s.pwdl0; tx[2] = ht2_hitag2_byte(&state) ^ tag.data.s.pwdl0;
tx[3] = ht2_hitag2_byte(&state) ^ tag.data.s.pwdl1; tx[3] = ht2_hitag2_byte(&state) ^ tag.data.s.pwdl1;
if (tag.mode != HT_STANDARD) { if (tag.mode != HT_STANDARD) {
//add crc8 //add crc8
*txlen = 40; *txlen += 8;
crc = CRC_PRESET; crc = CRC_PRESET;
calc_crc(&crc, tag.data.pages[1][2], 8); calc_crc(&crc, tag.data.pages[HITAGS_CONFIG_PADR][2], 8);
calc_crc(&crc, tag.data.s.pwdh0, 8); calc_crc(&crc, tag.data.s.pwdh0, 8);
calc_crc(&crc, tag.data.s.pwdl0, 8); calc_crc(&crc, tag.data.s.pwdl0, 8);
calc_crc(&crc, tag.data.s.pwdl1, 8); calc_crc(&crc, tag.data.s.pwdl1, 8);
@ -549,7 +547,7 @@ static void hts_handle_reader_command(uint8_t *rx, const size_t rxlen,
* some readers do not allow to authenticate multiple times in a row with the same tag. * some readers do not allow to authenticate multiple times in a row with the same tag.
* use this to change the uid between authentications. * use this to change the uid between authentications.
if (temp2 % 2 == 0) { if (rotate_uid % 2 == 0) {
tag.data.s.uid_le = 0x44332211; tag.data.s.uid_le = 0x44332211;
} else { } else {
tag.data.s.uid_le = 0x88776655; tag.data.s.uid_le = 0x88776655;
@ -563,25 +561,17 @@ static void hts_handle_reader_command(uint8_t *rx, const size_t rxlen,
//data received to be written //data received to be written
if (tag.tstate == HT_WRITING_PAGE_DATA) { if (tag.tstate == HT_WRITING_PAGE_DATA) {
tag.tstate = HT_NO_OP; tag.tstate = HT_NO_OP;
tag.data.pages[page_to_be_written][0] = rx[0]; memcpy(tag.data.pages[page_to_be_written], rx, HITAGS_PAGE_SIZE);
tag.data.pages[page_to_be_written][1] = rx[1];
tag.data.pages[page_to_be_written][2] = rx[2];
tag.data.pages[page_to_be_written][3] = rx[3];
//send ack //send ack
*txlen = 2; *txlen = 2;
tx[0] = 0x40; tx[0] = 0x40;
page_to_be_written = 0; page_to_be_written = 0;
hts_set_frame_modulation();
} else if (tag.tstate == HT_WRITING_BLOCK_DATA) { } else if (tag.tstate == HT_WRITING_BLOCK_DATA) {
tag.data.pages[page_to_be_written][0] = rx[0]; memcpy(tag.data.pages[page_to_be_written], rx, HITAGS_PAGE_SIZE);
tag.data.pages[page_to_be_written][1] = rx[1];
tag.data.pages[page_to_be_written][2] = rx[2];
tag.data.pages[page_to_be_written][3] = rx[3];
//send ack //send ack
*txlen = 2; *txlen = 2;
tx[0] = 0x40; tx[0] = 0x40;
hts_set_frame_modulation();
page_to_be_written++; page_to_be_written++;
block_data_left--; block_data_left--;
@ -594,24 +584,25 @@ static void hts_handle_reader_command(uint8_t *rx, const size_t rxlen,
} }
case 20: { case 20: {
//write page, write block, read page or read block command received //write page, write block, read page or read block command received
uint8_t page = ((rx[0] & 0x0f) << 4) + ((rx[1] & 0xf0) >> 4);
// TODO: handle over max_page readonly to 00000000. 82xx mode
if (page > tag.max_page) {
*txlen = 0;
break;
}
if ((rx[0] & 0xf0) == HITAGS_READ_PAGE) { //read page if ((rx[0] & 0xf0) == HITAGS_READ_PAGE) { //read page
//send page data //send page data
uint8_t page = ((rx[0] & 0x0f) << 4) + ((rx[1] & 0xf0) >> 4);
*txlen = 32; *txlen = 32;
tx[0] = tag.data.pages[page][0]; memcpy(tx, tag.data.pages[page], HITAGS_PAGE_SIZE);
tx[1] = tag.data.pages[page][1];
tx[2] = tag.data.pages[page][2];
tx[3] = tag.data.pages[page][3];
if (tag.data.s.LKP && page == 1) { if (tag.data.s.auth && page == HITAGS_CONFIG_PADR) {
tx[3] = 0xFF; tx[3] = 0xFF;
} }
hts_set_frame_modulation();
if (tag.mode != HT_STANDARD) { if (tag.mode != HT_STANDARD) {
//add crc8 //add crc8
*txlen = 40; *txlen += 8;
crc = CRC_PRESET; crc = CRC_PRESET;
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
calc_crc(&crc, tx[i], 8); calc_crc(&crc, tx[i], 8);
@ -619,46 +610,30 @@ static void hts_handle_reader_command(uint8_t *rx, const size_t rxlen,
tx[4] = crc; tx[4] = crc;
} }
if (tag.data.s.LKP && (page == 2 || page == 3)) { if (tag.data.s.auth && tag.data.s.LKP && (page == 2 || page == 3)) {
//if reader asks for key or password and the LKP-mark is set do not respond //if reader asks for key or password and the LKP-mark is set do not respond
sof_bits = 0;
*txlen = 0; *txlen = 0;
} }
} else if ((rx[0] & 0xf0) == HITAGS_READ_BLOCK) { //read block } else if ((rx[0] & 0xf0) == HITAGS_READ_BLOCK) { //read block
// TODO: handle auth LKP
uint8_t page = ((rx[0] & 0x0f) << 4) + ((rx[1] & 0xf0) >> 4); *txlen = (HITAGS_BLOCK_SIZE - (page % 4) * HITAGS_PAGE_SIZE) * 8;
*txlen = 32 * 4;
//send page,...,page+3 data //send page,...,page+3 data
for (int i = 0; i < 4; i++) { memcpy(tx, tag.data.pages[page], *txlen / 8);
tx[0 + i * 4] = tag.data.pages[page + 0 + i * 4][0];
tx[1 + i * 4] = tag.data.pages[page + 1 + i * 4][1];
tx[2 + i * 4] = tag.data.pages[page + 2 + i * 4][2];
tx[3 + i * 4] = tag.data.pages[page + 3 + i * 4][3];
}
hts_set_frame_modulation();
if (tag.mode != HT_STANDARD) { if (tag.mode != HT_STANDARD) {
//add crc8 //add crc8
*txlen = 32 * 4 + 8;
crc = CRC_PRESET; crc = CRC_PRESET;
for (int i = 0; i < 16; i++) { for (int i = 0; i < *txlen / 8; i++) {
calc_crc(&crc, tx[i], 8); calc_crc(&crc, tx[i], 8);
} }
*txlen += 8;
tx[16] = crc; tx[16] = crc;
} }
if ((page) % 4 != 0 || (tag.data.s.LKP && (page) == 0)) {
sof_bits = 0;
*txlen = 0;
}
} else if ((rx[0] & 0xf0) == HITAGS_WRITE_PAGE) { //write page } else if ((rx[0] & 0xf0) == HITAGS_WRITE_PAGE) { //write page
// TODO: handle con2 LCK*
uint8_t page = ((rx[0] & 0x0f) << 4) + ((rx[1] & 0xf0) >> 4);
if ((tag.data.s.LCON && page == 1) if ((tag.data.s.LCON && page == 1)
|| (tag.data.s.LKP && (page == 2 || page == 3))) { || (tag.data.s.LKP && (page == 2 || page == 3))) {
//deny //deny
@ -672,11 +647,9 @@ static void hts_handle_reader_command(uint8_t *rx, const size_t rxlen,
} }
} else if ((rx[0] & 0xf0) == HITAGS_WRITE_BLOCK) { //write block } else if ((rx[0] & 0xf0) == HITAGS_WRITE_BLOCK) { //write block
// TODO: handle LCON con2 LCK*
uint8_t page = ((rx[0] & 0x0f) << 4) + ((rx[1] & 0xf0) >> 4); if ((tag.data.s.LCON && page == 1)
hts_set_frame_modulation(); || (tag.data.s.LKP && (page == 2 || page == 3))) {
if (page % 4 != 0 || page == 0) {
//deny //deny
*txlen = 0; *txlen = 0;
} else { } else {
@ -684,7 +657,7 @@ static void hts_handle_reader_command(uint8_t *rx, const size_t rxlen,
*txlen = 2; *txlen = 2;
tx[0] = 0x40; tx[0] = 0x40;
page_to_be_written = page; page_to_be_written = page;
block_data_left = 4; block_data_left = 4 - (page % 4);
tag.tstate = HT_WRITING_BLOCK_DATA; tag.tstate = HT_WRITING_BLOCK_DATA;
} }
} }
@ -729,21 +702,13 @@ void hts_simulate(bool tag_mem_supplied, const uint8_t *data, bool ledcontrol) {
// read tag data into memory // read tag data into memory
if (tag_mem_supplied) { if (tag_mem_supplied) {
DbpString("Loading hitag S memory..."); DbpString("Loading hitag S memory...");
memcpy((uint8_t *)tag.data.pages, data, 4 * 64); memcpy(tag.data.pages, data, HITAGS_MAX_BYTE_SIZE);
} else { } else {
// use the last read tag // use the last read tag
} }
//con0 // max_page
tag.max_page = 64; update_tag_max_page();
if ((tag.data.pages[1][0] & 0x2) == 0 && (tag.data.pages[1][0] & 0x1) == 1) {
tag.max_page = 8;
}
if ((tag.data.pages[1][0] & 0x2) == 0 && (tag.data.pages[1][0] & 0x1) == 0) {
tag.max_page = 0;
}
for (int i = 0; i < tag.max_page; i++) { for (int i = 0; i < tag.max_page; i++) {
DBG Dbprintf("Page[%2d]: %02X %02X %02X %02X", DBG Dbprintf("Page[%2d]: %02X %02X %02X %02X",
@ -1123,7 +1088,7 @@ static int hts_select_tag(const lf_hitag_data_t *packet, uint8_t *tx, size_t siz
return -1; return -1;
} }
memcpy(&tag.data.pages[HITAGS_UID_PADR], rx, HITAGS_PAGE_SIZE); memcpy(tag.data.pages[HITAGS_UID_PADR], rx, HITAGS_PAGE_SIZE);
DBG Dbprintf("UID... %02X%02X%02X%02X", rx[0], rx[1], rx[2], rx[3]); DBG Dbprintf("UID... %02X%02X%02X%02X", rx[0], rx[1], rx[2], rx[3]);
@ -1142,37 +1107,24 @@ static int hts_select_tag(const lf_hitag_data_t *packet, uint8_t *tx, size_t siz
return -1; return -1;
} }
memcpy(&tag.data.pages[HITAGS_CONFIG_PADR], rx, HITAGS_PAGE_SIZE - 1); memcpy(tag.data.pages[HITAGS_CONFIG_PADR], rx, HITAGS_PAGE_SIZE - 1);
//check which memorysize this tag has update_tag_max_page();
if ((tag.data.s.CON0 & 0x3) == 0x00) {
tag.max_page = 32 / 32;
} else if ((tag.data.s.CON0 & 0x3) == 0x1) {
tag.max_page = 256 / 32;
} else if ((tag.data.s.CON0 & 0x3) == 0x2) {
tag.max_page = 2048 / 32;
}
DBG Dbprintf("conf 0: %02X conf 1: %02X conf 2: %02X", tag.data.pages[HITAGS_CONFIG_PADR][0], tag.data.pages[HITAGS_CONFIG_PADR][1], tag.data.pages[HITAGS_CONFIG_PADR][2]); DBG Dbprintf("conf 0: %02X conf 1: %02X conf 2: %02X", tag.data.pages[HITAGS_CONFIG_PADR][0], tag.data.pages[HITAGS_CONFIG_PADR][1], tag.data.pages[HITAGS_CONFIG_PADR][2]);
if (tag.data.s.auth == 1) { if (tag.data.s.auth == 1) {
uint64_t key = 0; uint64_t key_le = 0;
// if the tag is in authentication mode try the key or challenge // if the tag is in authentication mode try the key or challenge
if (packet->cmd == RHTSF_KEY || packet->cmd == WHTSF_KEY) { if (packet->cmd == RHTSF_KEY || packet->cmd == WHTSF_KEY) {
DBG DbpString("Authenticating using key:"); DBG DbpString("Authenticating using key:");
DBG Dbhexdump(6, packet->key, false); DBG Dbhexdump(6, packet->key, false);
key = ((uint64_t)packet->key[0]) << 0 | key_le = *(uint64_t *)packet->key;
((uint64_t)packet->key[1]) << 8 |
((uint64_t)packet->key[2]) << 16 |
((uint64_t)packet->key[3]) << 24 |
((uint64_t)packet->key[4]) << 32 |
((uint64_t)packet->key[5]) << 40
;
uint64_t state = ht2_hitag2_init(REV64(key), reflect32(tag.data.s.uid_le), REV32(rnd)); uint64_t state = ht2_hitag2_init(reflect48(key_le), reflect32(tag.data.s.uid_le), reflect32(*(uint32_t*)rnd));
uint8_t auth_ks[4]; uint8_t auth_ks[4];
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
@ -1180,8 +1132,7 @@ static int hts_select_tag(const lf_hitag_data_t *packet, uint8_t *tx, size_t siz
} }
txlen = 0; txlen = 0;
uint8_t revrnd[4] = {rnd, rnd >> 8, rnd >> 16, rnd >> 24}; txlen = concatbits(tx, txlen, rnd, 0, 32);
txlen = concatbits(tx, txlen, revrnd, 0, 32);
txlen = concatbits(tx, txlen, auth_ks, 0, 32); txlen = concatbits(tx, txlen, auth_ks, 0, 32);
DBG Dbprintf("%02X %02X %02X %02X %02X %02X %02X %02X", tx[0], tx[1], tx[2], tx[3], tx[4], tx[5], tx[6], tx[7]); DBG Dbprintf("%02X %02X %02X %02X %02X %02X %02X %02X", tx[0], tx[1], tx[2], tx[3], tx[4], tx[5], tx[6], tx[7]);
@ -1256,8 +1207,8 @@ static int hts_select_tag(const lf_hitag_data_t *packet, uint8_t *tx, size_t siz
} }
//encrypted con2,password received. //encrypted con2,password received.
DBG Dbprintf("UID... %X", BSWAP_32(tag.data.s.uid_le)); DBG Dbprintf("UID... %08X", BSWAP_32(tag.data.s.uid_le));
DBG Dbprintf("RND... %X", rnd); DBG Dbprintf("RND... %02X%02X%02X%02X", rnd[0], rnd[1], rnd[2], rnd[3]);
//decrypt password //decrypt password
pwdh0 = 0; pwdh0 = 0;
@ -1265,7 +1216,7 @@ static int hts_select_tag(const lf_hitag_data_t *packet, uint8_t *tx, size_t siz
pwdl1 = 0; pwdl1 = 0;
if (packet->cmd == RHTSF_KEY || packet->cmd == WHTSF_KEY) { if (packet->cmd == RHTSF_KEY || packet->cmd == WHTSF_KEY) {
uint64_t state = ht2_hitag2_init(REV64(key), reflect32(tag.data.s.uid_le), REV32(rnd)); uint64_t state = ht2_hitag2_init(reflect48(key_le), reflect32(tag.data.s.uid_le), reflect32(*(uint32_t *)rnd));
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
ht2_hitag2_byte(&state); ht2_hitag2_byte(&state);
} }
@ -1513,7 +1464,7 @@ int hts_read_uid(uint32_t *uid, bool ledcontrol, bool send_answer) {
int status = PM3_SUCCESS; int status = PM3_SUCCESS;
if (rxlen == 32) { if (rxlen == 32) {
memcpy(&tag.data.pages[0], rx, HITAGS_PAGE_SIZE); memcpy(tag.data.pages[0], rx, HITAGS_PAGE_SIZE);
if (uid) { if (uid) {
*uid = BSWAP_32(tag.data.s.uid_le); *uid = BSWAP_32(tag.data.s.uid_le);