1 //-----------------------------------------------------------------------------
2 // Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // See LICENSE.txt for the text of the license.
15 //-----------------------------------------------------------------------------
16 // iso14443-4 mifare commands
17 //-----------------------------------------------------------------------------
21 #include "commonutil.h" // ARRAYLEN
22 #include "comms.h" // DropField
25 #include "crypto/libpcrypto.h"
27 static bool g_verbose_mode
= false;
28 void mfpSetVerboseMode(bool verbose
) {
29 g_verbose_mode
= verbose
;
32 static const PlusErrorsElm_t PlusErrors
[] = {
34 {0x00, "Transfer cannot be granted within the current authentication."},
35 {0x06, "Access Conditions not fulfilled. Block does not exist, block is not a value block."},
36 {0x07, "Too many read or write commands in the session or in the transaction."},
37 {0x08, "Invalid MAC in command or response"},
38 {0x09, "Block Number is not valid"},
39 {0x0a, "Invalid block number, not existing block number"},
40 {0x0b, "The current command code not available at the current card state."},
41 {0x0c, "Length error"},
42 {0x0f, "General Manipulation Error. Failure in the operation of the PICC (cannot write to the data block), etc."},
46 const char *mfpGetErrorDescription(uint8_t errorCode
) {
47 for (int i
= 0; i
< ARRAYLEN(PlusErrors
); i
++)
48 if (errorCode
== PlusErrors
[i
].Code
)
49 return PlusErrors
[i
].Description
;
51 return PlusErrors
[0].Description
;
54 AccessConditions_t MFAccessConditions
[] = {
55 {0x00, "read AB; write AB; increment AB; decrement transfer restore AB", "transport config"},
56 {0x01, "read AB; decrement transfer restore AB", "value block"},
57 {0x02, "read AB", "read/write block"},
58 {0x03, "read B; write B", "read/write block"},
59 {0x04, "read AB; write B", "read/write block"},
60 {0x05, "read B", "read/write block"},
61 {0x06, "read AB; write B; increment B; decrement transfer restore AB", "value block"},
62 {0x07, "none", "read/write block"}
65 AccessConditions_t MFAccessConditionsTrailer
[] = {
66 {0x00, "read A by A; read ACCESS by A; read/write B by A", ""},
67 {0x01, "write A by A; read/write ACCESS by A; read/write B by A", ""},
68 {0x02, "read ACCESS by A; read B by A", ""},
69 {0x03, "write A by B; read ACCESS by AB; write ACCESS by B; write B by B", ""},
70 {0x04, "write A by B; read ACCESS by AB; write B by B", ""},
71 {0x05, "read ACCESS by AB; write ACCESS by B", ""},
72 {0x06, "read ACCESS by AB", ""},
73 {0x07, "read ACCESS by AB", ""}
76 bool mfValidateAccessConditions(const uint8_t *data
) {
77 uint8_t nd1
= NIBBLE_LOW(data
[0]);
78 uint8_t nd2
= NIBBLE_HIGH(data
[0]);
79 uint8_t nd3
= NIBBLE_LOW(data
[1]);
80 uint8_t d1
= NIBBLE_HIGH(data
[1]);
81 uint8_t d2
= NIBBLE_LOW(data
[2]);
82 uint8_t d3
= NIBBLE_HIGH(data
[2]);
84 return ((nd1
== (d1
^ 0xF)) && (nd2
== (d2
^ 0xF)) && (nd3
== (d3
^ 0xF)));
87 bool mfReadOnlyAccessConditions(uint8_t blockn
, const uint8_t *data
) {
89 uint8_t d1
= NIBBLE_HIGH(data
[1]) >> blockn
;
90 uint8_t d2
= NIBBLE_LOW(data
[2]) >> blockn
;
91 uint8_t d3
= NIBBLE_HIGH(data
[2]) >> blockn
;
92 uint8_t cond
= (d1
& 0x01) << 2 | (d2
& 0x01) << 1 | (d3
& 0x01);
95 if ((cond
== 0x02) || (cond
== 0x06) || (cond
== 0x07)) return true;
97 if ((cond
== 0x02) || (cond
== 0x05)) return true;
102 const char *mfGetAccessConditionsDesc(uint8_t blockn
, const uint8_t *data
) {
103 uint8_t d1
= NIBBLE_HIGH(data
[1]) >> blockn
;
104 uint8_t d2
= NIBBLE_LOW(data
[2]) >> blockn
;
105 uint8_t d3
= NIBBLE_HIGH(data
[2]) >> blockn
;
107 uint8_t cond
= (d1
& 0x01) << 2 | (d2
& 0x01) << 1 | (d3
& 0x01);
110 for (int i
= 0; i
< ARRAYLEN(MFAccessConditionsTrailer
); i
++)
111 if (MFAccessConditionsTrailer
[i
].cond
== cond
) {
112 return MFAccessConditionsTrailer
[i
].description
;
115 for (int i
= 0; i
< ARRAYLEN(MFAccessConditions
); i
++)
116 if (MFAccessConditions
[i
].cond
== cond
) {
117 return MFAccessConditions
[i
].description
;
121 static char none
[] = "none";
125 uint8_t mf_get_accesscondition(uint8_t blockn
, const uint8_t *data
) {
126 uint8_t d1
= NIBBLE_HIGH(data
[1]) >> blockn
;
127 uint8_t d2
= NIBBLE_LOW(data
[2]) >> blockn
;
128 uint8_t d3
= NIBBLE_HIGH(data
[2]) >> blockn
;
129 return (d1
& 0x01) << 2 | (d2
& 0x01) << 1 | (d3
& 0x01);
133 static int CalculateEncIVCommand(mf4Session_t *mf4session, uint8_t *iv, bool verbose) {
134 memcpy(&iv[0], &mf4session->TI, 4);
135 memcpy(&iv[4], &mf4session->R_Ctr, 2);
136 memcpy(&iv[6], &mf4session->W_Ctr, 2);
137 memcpy(&iv[8], &mf4session->R_Ctr, 2);
138 memcpy(&iv[10], &mf4session->W_Ctr, 2);
139 memcpy(&iv[12], &mf4session->R_Ctr, 2);
140 memcpy(&iv[14], &mf4session->W_Ctr, 2);
145 static int CalculateEncIVResponse(mf4Session *mf4session, uint8_t *iv, bool verbose) {
146 memcpy(&iv[0], &mf4session->R_Ctr, 2);
147 memcpy(&iv[2], &mf4session->W_Ctr, 2);
148 memcpy(&iv[4], &mf4session->R_Ctr, 2);
149 memcpy(&iv[6], &mf4session->W_Ctr, 2);
150 memcpy(&iv[8], &mf4session->R_Ctr, 2);
151 memcpy(&iv[10], &mf4session->W_Ctr, 2);
152 memcpy(&iv[12], &mf4session->TI, 4);
158 int CalculateMAC(mf4Session_t
*mf4session
, MACType_t mtype
, uint8_t blockNum
, uint8_t blockCount
, uint8_t *data
, int datalen
, uint8_t *mac
, bool verbose
) {
159 if (!mf4session
|| !mf4session
->Authenticated
|| !mac
|| !data
|| !datalen
)
162 memset(mac
, 0x00, 8);
164 uint16_t ctr
= mf4session
->R_Ctr
;
168 ctr
= mf4session
->W_Ctr
;
175 uint8_t macdata
[2049] = {data
[0], (ctr
& 0xFF), (ctr
>> 8), 0};
176 int macdatalen
= datalen
;
177 memcpy(&macdata
[3], mf4session
->TI
, 4);
181 memcpy(&macdata
[7], &data
[1], datalen
- 1);
182 macdatalen
= datalen
+ 6;
185 macdata
[7] = blockNum
;
187 macdata
[9] = blockCount
;
188 memcpy(&macdata
[10], &data
[1], datalen
- 1);
189 macdatalen
= datalen
+ 9;
192 memcpy(&macdata
[7], &data
[1], datalen
- 1);
193 macdatalen
= datalen
+ 6;
201 PrintAndLogEx(INFO
, "MAC data[%d]: %s", macdatalen
, sprint_hex(macdata
, macdatalen
));
203 return aes_cmac8(NULL
, mf4session
->Kmac
, macdata
, mac
, macdatalen
);
206 int MifareAuth4(mf4Session_t
*mf4session
, const uint8_t *keyn
, uint8_t *key
, bool activateField
, bool leaveSignalON
, bool dropFieldIfError
, bool verbose
, bool silentMode
) {
207 uint8_t data
[257] = {0};
210 uint8_t RndA
[17] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00};
211 uint8_t RndB
[17] = {0};
217 mf4session
->Authenticated
= false;
219 uint8_t cmd1
[] = {0x70, keyn
[1], keyn
[0], 0x00};
220 int res
= ExchangeRAW14a(cmd1
, sizeof(cmd1
), activateField
, true, data
, sizeof(data
), &datalen
, silentMode
);
221 if (res
!= PM3_SUCCESS
) {
222 if (silentMode
== false) {
223 PrintAndLogEx(ERR
, "Exchange raw error: %d", res
);
226 if (dropFieldIfError
) {
233 PrintAndLogEx(INFO
, "< phase1: %s", sprint_hex(data
, datalen
));
237 if (!silentMode
) PrintAndLogEx(ERR
, "Card response wrong length: %d", datalen
);
238 if (dropFieldIfError
) DropField();
239 return PM3_EWRONGANSWER
;
242 if (data
[0] != 0x90) {
243 if (!silentMode
) PrintAndLogEx(ERR
, "Card response error: %02x %s", data
[0], mfpGetErrorDescription(data
[0]));
244 if (dropFieldIfError
) DropField();
245 return PM3_EWRONGANSWER
;
248 if (datalen
!= 19) { // code 1b + 16b + crc 2b
249 if (!silentMode
) PrintAndLogEx(ERR
, "Card response must be 19 bytes long instead of: %d", datalen
);
250 if (dropFieldIfError
) DropField();
251 return PM3_EWRONGANSWER
;
254 aes_decode(NULL
, key
, &data
[1], RndB
, 16);
257 PrintAndLogEx(INFO
, "RndB: %s", sprint_hex(RndB
, 16));
260 uint8_t cmd2
[33] = {0};
263 uint8_t raw
[32] = {0};
264 memmove(raw
, RndA
, 16);
265 memmove(&raw
[16], &RndB
[1], 16);
267 aes_encode(NULL
, key
, raw
, &cmd2
[1], 32);
269 PrintAndLogEx(INFO
, ">phase2: %s", sprint_hex(cmd2
, 33));
271 res
= ExchangeRAW14a(cmd2
, sizeof(cmd2
), false, true, data
, sizeof(data
), &datalen
, silentMode
);
272 if (res
!= PM3_SUCCESS
) {
273 if (silentMode
== false) {
274 PrintAndLogEx(ERR
, "Exchange raw error: %d", res
);
276 if (dropFieldIfError
) {
283 PrintAndLogEx(INFO
, "< phase2: %s", sprint_hex(data
, datalen
));
286 aes_decode(NULL
, key
, &data
[1], raw
, 32);
289 PrintAndLogEx(INFO
, "res: %s", sprint_hex(raw
, 32));
290 PrintAndLogEx(INFO
, "RndA`: %s", sprint_hex(&raw
[4], 16));
293 if (memcmp(&raw
[4], &RndA
[1], 16)) {
294 if (!silentMode
) PrintAndLogEx(ERR
, "\nAuthentication FAILED. rnd is not equal");
296 PrintAndLogEx(ERR
, "RndA reader: %s", sprint_hex(&RndA
[1], 16));
297 PrintAndLogEx(ERR
, "RndA card: %s", sprint_hex(&raw
[4], 16));
299 if (dropFieldIfError
) DropField();
300 return PM3_EWRONGANSWER
;
304 PrintAndLogEx(INFO
, " TI: %s", sprint_hex(raw
, 4));
305 PrintAndLogEx(INFO
, "pic: %s", sprint_hex(&raw
[20], 6));
306 PrintAndLogEx(INFO
, "pcd: %s", sprint_hex(&raw
[26], 6));
309 uint8_t kenc
[16] = {0};
310 memcpy(&kenc
[0], &RndA
[11], 5);
311 memcpy(&kenc
[5], &RndB
[11], 5);
312 for (int i
= 0; i
< 5; i
++) {
313 kenc
[10 + i
] = RndA
[4 + i
] ^ RndB
[4 + i
];
317 aes_encode(NULL
, key
, kenc
, kenc
, 16);
319 PrintAndLogEx(INFO
, "kenc: %s", sprint_hex(kenc
, 16));
322 uint8_t kmac
[16] = {0};
323 memcpy(&kmac
[0], &RndA
[7], 5);
324 memcpy(&kmac
[5], &RndB
[7], 5);
325 for (int i
= 0; i
< 5; i
++) {
326 kmac
[10 + i
] = RndA
[0 + i
] ^ RndB
[0 + i
];
330 aes_encode(NULL
, key
, kmac
, kmac
, 16);
332 PrintAndLogEx(INFO
, "kmac: %s", sprint_hex(kmac
, 16));
335 if (leaveSignalON
== false) {
340 PrintAndLogEx(NORMAL
, "");
344 mf4session
->Authenticated
= true;
345 mf4session
->R_Ctr
= 0;
346 mf4session
->W_Ctr
= 0;
347 mf4session
->KeyNum
= keyn
[1] + (keyn
[0] << 8);
348 memmove(mf4session
->RndA
, RndA
, 16);
349 memmove(mf4session
->RndB
, RndB
, 16);
350 memmove(mf4session
->Key
, key
, 16);
351 memmove(mf4session
->TI
, raw
, 4);
352 memmove(mf4session
->PICCap2
, &raw
[20], 6);
353 memmove(mf4session
->PCDCap2
, &raw
[26], 6);
354 memmove(mf4session
->Kenc
, kenc
, 16);
355 memmove(mf4session
->Kmac
, kmac
, 16);
359 PrintAndLogEx(INFO
, "Authentication OK");
365 static int intExchangeRAW14aPlus(uint8_t *datain
, int datainlen
, bool activateField
, bool leaveSignalON
, uint8_t *dataout
, int maxdataoutlen
, int *dataoutlen
) {
366 if (g_verbose_mode
) {
367 PrintAndLogEx(INFO
, ">>> %s", sprint_hex(datain
, datainlen
));
370 int res
= ExchangeRAW14a(datain
, datainlen
, activateField
, leaveSignalON
, dataout
, maxdataoutlen
, dataoutlen
, false);
372 if (g_verbose_mode
) {
373 PrintAndLogEx(INFO
, "<<< %s", sprint_hex(dataout
, *dataoutlen
));
378 int MFPWritePerso(const uint8_t *keyNum
, const uint8_t *key
, bool activateField
, bool leaveSignalON
, uint8_t *dataout
, int maxdataoutlen
, int *dataoutlen
) {
379 uint8_t rcmd
[3 + 16] = {0xa8, keyNum
[1], keyNum
[0], 0x00};
380 memmove(&rcmd
[3], key
, 16);
382 return intExchangeRAW14aPlus(rcmd
, sizeof(rcmd
), activateField
, leaveSignalON
, dataout
, maxdataoutlen
, dataoutlen
);
385 int MFPCommitPerso(bool activateField
, bool leaveSignalON
, uint8_t *dataout
, int maxdataoutlen
, int *dataoutlen
) {
386 uint8_t rcmd
[1] = {0xaa};
388 return intExchangeRAW14aPlus(rcmd
, sizeof(rcmd
), activateField
, leaveSignalON
, dataout
, maxdataoutlen
, dataoutlen
);
391 int MFPReadBlock(mf4Session_t
*mf4session
, bool plain
, bool nomaccmd
, bool nomacres
, uint8_t blockNum
, uint8_t blockCount
, bool activateField
, bool leaveSignalON
, uint8_t *dataout
, int maxdataoutlen
, int *dataoutlen
, uint8_t *mac
) {
395 cmdb
= cmdb
^ 0x01; // If we do not want MAC in reply, remove 0x01
399 cmdb
= cmdb
^ 0x02; // If we do not need an encrypted transmission, add 0x02
403 cmdb
= cmdb
^ 0x04; // If we do not want to send a MAC, remove 0x04
406 uint8_t rcmd1
[4] = {cmdb
, blockNum
, 0x00, blockCount
};
407 uint8_t maccmddat
[8] = {0};
408 uint8_t rcmd
[nomaccmd
? 4 : 12];
410 if (nomaccmd
== false && mf4session
) {
411 CalculateMAC(mf4session
, mtypReadCmd
, blockNum
, blockCount
, rcmd1
, 4, &maccmddat
[0], g_verbose_mode
);
414 memmove(rcmd
, rcmd1
, 4);
415 if (nomaccmd
== false) {
416 memmove(&rcmd
[4], maccmddat
, 8);
419 int res
= intExchangeRAW14aPlus(rcmd
, sizeof(rcmd
), activateField
, leaveSignalON
, dataout
, maxdataoutlen
, dataoutlen
);
420 if (res
!= PM3_SUCCESS
) {
428 if (mf4session
&& !nomacres
&& *dataoutlen
> 11) {
429 CalculateMAC(mf4session
, mtypReadResp
, blockNum
, blockCount
, dataout
, *dataoutlen
- 8 - 2, mac
, g_verbose_mode
);
435 int MFPWriteBlock(mf4Session_t
*mf4session
, bool plain
, bool nomacres
, uint8_t blockNum
, uint8_t blockHdr
, const uint8_t *data
, bool activateField
, bool leaveSignalON
, uint8_t *dataout
, int maxdataoutlen
, int *dataoutlen
, uint8_t *mac
) {
438 cmdb
= cmdb
^ 0x01; // If we do not want MAC in reply, remove 0x01
442 cmdb
= cmdb
^ 0x02; // If we do not need an encrypted transmission, add 0x02
445 uint8_t rcmd
[1 + 2 + 16 + 8] = {cmdb
, blockNum
, blockHdr
};
446 memmove(&rcmd
[3], data
, 16);
448 CalculateMAC(mf4session
, mtypWriteCmd
, blockNum
, 1, rcmd
, 19, &rcmd
[19], g_verbose_mode
);
451 int res
= intExchangeRAW14aPlus(rcmd
, sizeof(rcmd
), activateField
, leaveSignalON
, dataout
, maxdataoutlen
, dataoutlen
);
452 if (res
!= PM3_SUCCESS
) {
460 if (mf4session
&& mac
&& *dataoutlen
> 3 && !nomacres
) {
461 CalculateMAC(mf4session
, mtypWriteResp
, blockNum
, 1, dataout
, *dataoutlen
, mac
, g_verbose_mode
);
467 int mfpReadSector(uint8_t sectorNo
, uint8_t keyType
, uint8_t *key
, uint8_t *dataout
, bool verbose
) {
468 uint8_t keyn
[2] = {0};
471 uint16_t uKeyNum
= 0x4000 + sectorNo
* 2 + (keyType
? 1 : 0);
472 keyn
[0] = uKeyNum
>> 8;
473 keyn
[1] = uKeyNum
& 0xff;
475 PrintAndLogEx(INFO
, "--sector[%u]:%02x key:%04x", mfNumBlocksPerSector(sectorNo
), sectorNo
, uKeyNum
);
478 mf4Session_t _session
;
479 int res
= MifareAuth4(&_session
, keyn
, key
, true, true, true, verbose
, false);
481 PrintAndLogEx(ERR
, "Sector %u authentication error: %d", sectorNo
, res
);
485 uint8_t data
[250] = {0};
487 uint8_t mac
[8] = {0};
488 uint8_t firstBlockNo
= mfFirstBlockOfSector(sectorNo
);
489 for (int n
= firstBlockNo
; n
< firstBlockNo
+ mfNumBlocksPerSector(sectorNo
); n
++) {
490 res
= MFPReadBlock(&_session
, plain
, false, false, n
& 0xff, 1, false, true, data
, sizeof(data
), &datalen
, mac
);
492 PrintAndLogEx(ERR
, "Sector %u read error: %d", sectorNo
, res
);
497 if (datalen
&& data
[0] != 0x90) {
498 PrintAndLogEx(ERR
, "Sector %u card read error: %02x %s", sectorNo
, data
[0], mfpGetErrorDescription(data
[0]));
502 if (datalen
!= 1 + 16 + 8 + 2) {
503 PrintAndLogEx(ERR
, "Sector %u error returned data length:%d", sectorNo
, datalen
);
508 memcpy(&dataout
[(n
- firstBlockNo
) * 16], &data
[1], 16);
511 PrintAndLogEx(INFO
, "data[%03d]: %s", n
, sprint_hex(&data
[1], 16));
513 if (memcmp(&data
[1 + 16], mac
, 8)) {
514 PrintAndLogEx(WARNING
, "WARNING: mac on block %d not equal...", n
);
515 PrintAndLogEx(WARNING
, "MAC card: %s", sprint_hex(&data
[1 + 16], 8));
516 PrintAndLogEx(WARNING
, "MAC reader: %s", sprint_hex(mac
, 8));
518 if (verbose
== false) {
523 PrintAndLogEx(INFO
, "MAC: %s", sprint_hex(&data
[1 + 16], 8));
532 int MFPGetSignature(bool activateField
, bool leaveSignalON
, uint8_t *dataout
, int maxdataoutlen
, int *dataoutlen
) {
533 uint8_t c
[] = {0x3c, 0x00};
534 return intExchangeRAW14aPlus(c
, sizeof(c
), activateField
, leaveSignalON
, dataout
, maxdataoutlen
, dataoutlen
);
537 int MFPGetVersion(bool activateField
, bool leaveSignalON
, uint8_t *dataout
, int maxdataoutlen
, int *dataoutlen
) {
538 uint8_t tmp
[20] = {0};
539 uint8_t c
[] = {0x60};
540 int res
= intExchangeRAW14aPlus(c
, sizeof(c
), activateField
, true, tmp
, maxdataoutlen
, dataoutlen
);
547 memcpy(dataout
, tmp
+ 1, (*dataoutlen
- 3));
550 // MFDES_ADDITIONAL_FRAME
551 if (tmp
[0] == 0xAF) {
553 res
= intExchangeRAW14aPlus(c
, sizeof(c
), false, true, tmp
, maxdataoutlen
, dataoutlen
);
554 if (res
== PM3_SUCCESS
) {
556 memcpy(dataout
+ 7, tmp
+ 1, (*dataoutlen
- 3));
558 // MFDES_ADDITIONAL_FRAME
559 res
= intExchangeRAW14aPlus(c
, sizeof(c
), false, false, tmp
, maxdataoutlen
, dataoutlen
);
560 if (res
== PM3_SUCCESS
) {
561 if (tmp
[0] == 0x90) {
562 memcpy(dataout
+ 7 + 7, tmp
+ 1, (*dataoutlen
- 3));
572 // Mifare Memory Structure: up to 32 Sectors with 4 blocks each (1k and 2k cards),
573 // plus evtl. 8 sectors with 16 blocks each (4k cards)
574 uint8_t mfNumBlocksPerSector(uint8_t sectorNo
) {
582 uint8_t mfFirstBlockOfSector(uint8_t sectorNo
) {
586 return 32 * 4 + (sectorNo
- 32) * 16;
590 uint8_t mfSectorTrailerOfSector(uint8_t sectorNo
) {
592 return (sectorNo
* 4) | 0x03;
594 return (32 * 4 + (sectorNo
- 32) * 16) | 0x0f;
598 // assumes blockno is 0-255..
599 uint8_t mfSectorTrailer(uint16_t blockNo
) {
600 if (blockNo
< 32 * 4) {
601 return (blockNo
| 0x03);
603 return (blockNo
| 0x0F);
607 // assumes blockno is 0-255..
608 bool mfIsSectorTrailer(uint16_t blockNo
) {
609 return (blockNo
== mfSectorTrailer(blockNo
));
612 // assumes blockno is 0-255..
613 uint8_t mfSectorNum(uint16_t blockNo
) {
614 if (blockNo
< 32 * 4)
615 return (blockNo
/ 4);
617 return (32 + (blockNo
- 32 * 4) / 16);
621 bool mfIsSectorTrailerBasedOnBlocks(uint8_t sectorno
, uint16_t blockno
) {
623 return ((blockno
| 0x03) == blockno
);
625 return ((blockno
| 0x0F) == blockno
);