From bd174abd6b0b795e1493415a48087c3b4e8cc4c7 Mon Sep 17 00:00:00 2001 From: Joris Date: Wed, 14 Oct 2020 11:05:43 +0000 Subject: [PATCH] Upload New File --- src/Ultralight_C.ino | 739 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 739 insertions(+) create mode 100644 src/Ultralight_C.ino diff --git a/src/Ultralight_C.ino b/src/Ultralight_C.ino new file mode 100644 index 0000000..c158f11 --- /dev/null +++ b/src/Ultralight_C.ino @@ -0,0 +1,739 @@ +/* + * -------------------------------------------------------------------------------------------------------------------- + * Example sketch/program showing how to read and modify data from a Mifare Ultralight C PICC + * -------------------------------------------------------------------------------------------------------------------- + * + * This code enables basic communication with a Mifare Ultralight C PICC. + * It allows reading and writing of data including basic operations for password protection. + * + * This sketch imlements NO measures for page blocking and using OTP and the upwarts counter + * + * This sketch should run stable on a Arduinno Uno with RC522 module. + * But the code itself is very sensitive for modifications due to my bad programming + * or stability issues with the used libraries. If you change / optimize the code, be + * prepared for some weird complications + * + * have fun! + * + * @license Released into the public domain. + * + * use + * - auth 49454D4B41455242214E4143554F5946 for authentication (with standard key) + * - newKey 49454D4B41455242214E4143554F5946 for writing a new key (in this case the standard key) to the PICC. + * - dunp to list the content of the PICC (data might not visible due to read-protection) + * - wchar 10 hello world to write "hello world" starting at page 10 + * - whex 10 0123456789ABC to write HEX values starting at page 10 + * - protect 25 to protect the page from 25 upward + * - setpbit 1 to set the protection to write-protected + * + * + * Typical pin layout used: + * ----------------------------------------------------------------------------------------- + * MFRC522 Arduino Arduino Arduino Arduino Arduino + * Reader/PCD Uno/101 Mega Nano v3 Leonardo/Micro Pro Micro + * Signal Pin Pin Pin Pin Pin Pin + * ----------------------------------------------------------------------------------------- + * RST/Reset RST 9 5 D9 RESET/ICSP-5 RST + * SPI SS SDA(SS) 10 53 D10 10 10 + * SPI MOSI MOSI 11 / ICSP-4 51 D11 ICSP-4 16 + * SPI MISO MISO 12 / ICSP-1 50 D12 ICSP-1 14 + * SPI SCK SCK 13 / ICSP-3 52 D13 ICSP-3 15 + */ + +/* + * IMPORTANT!!! + * in order to be able to read Mifare Ultralight C PICC the typical RC522-module needs to be modified. + * L1 and L2 must be exchanged for better inductors (e.g. FERROCORE CW1008-2200). Otherwise this code will not work! + */ + +/* + * The Mifare Ultralight C encrypted authentification process can be found here: https://www.nxp.com/docs/en/data-sheet/MF0ICU2.pdf + */ + +#include +#include +#include + +#define RST_PIN 9 // Configurable, see typical pin layout above +#define SS_PIN 10 // Configurable, see typical pin layout above + +DES des; + +MFRC522 mfrc522(SS_PIN, RST_PIN); // Create MFRC522 instance +MFRC522::StatusCode status; + + +//3DES-Key (This is the standard key according to the NPX specification) +byte C_key[] = { 0x49, 0x45, 0x4D, 0x4B, 0x41, 0x45, 0x52, 0x42, 0x21, 0x4E, 0x41, 0x43, 0x55, 0x4F, 0x59, 0x46, 0x49, 0x45, 0x4D, 0x4B, 0x41, 0x45, 0x52, 0x42}; + +uint64_t IV=0; +boolean isPresent=0; +boolean isEncrypted=0; +boolean isUlC=0; +char Scmd[28]; +char Sdata[64]; + +// byte page=0; + +byte buffer[24]; +boolean isLocked[255]; // which Pages are locked to read only +byte startProtect=255; // starting from which page, the data is protectet by the key +boolean access; // how is the data protected: 0=write protection (data visible) 1=read/write protection (data = 0x00) + +void setup() { + Serial.begin(115200); // Initialize serial communications with the PC + while (!Serial); // Do nothing if no serial port is opened (added for Arduinos based on ATMEGA32U4) + des.init(C_key,IV); + SPI.begin(); // Init SPI bus + mfrc522.PCD_Init(); // Init MFRC522 + mfrc522.PCD_DumpVersionToSerial(); // Show details of PCD - MFRC522 Card Reader details + Serial.println(F("Read/Write Mifare Ultralight C")); +} + + +void loop() { +int i=0; +boolean IsCommand=1; +char x; + + if(Serial.available() > 0){ + // Serial.println(freeRam()); + while(i<40){ + while(!Serial.available()); + x=Serial.read(); + if(IsCommand) + Scmd[i]=x; + else + Sdata[i]=x; + if(x == '\n'){ // newline + if(IsCommand){ + Scmd[i]=0x00; // proper Ending + i=41; // exit + } + else{ + Sdata[i]=0x00; + i=41; + } + } + else if(x == ' '){ + if(IsCommand==1){ + IsCommand=0; + Scmd[i]=0x00; + i=0; + } + else + i++; + } + else + i++; + } + if(!isPresent) + Serial.println(F("No PICC available!")); + else{ + if(!strcmp(Scmd,"dump")){ // dump data + DumpDataUltralight(); + } + else if(!strcmp(Scmd,"auth")){ + RequestAuthUltralightC(); + } + else if(!strcmp(Scmd,"newKey")){ + newKey(); + } + else if (!strcmp(Scmd,"wchar")){ + writeData(0); + } + else if (!strcmp(Scmd,"whex")){ + writeData(1); + } + else if (!strcmp(Scmd,"protect")){ + protect(1); + } + else if (!strcmp(Scmd,"setpbit")){ + protect(0); + } + } + Serial.flush(); + Scmd[0]=0x00; + Sdata[0]=0x00; + } + // Look for new cards + if(!isPresent){ + if (!mfrc522.PICC_IsNewCardPresent()) { + return; + } + + // Select one of the cards + if (!mfrc522.PICC_ReadCardSerial()) { + return; + } + else{ + isPresent=1; + if(mfrc522.uid.sak == 0x00){ + if(checkIfUltralight()){ + Serial.println (F("Mifare Ultralight C PICC found!")); + getPageInfo(); + isUlC=1; + } + else{ + Serial.println (F("Other Mifare Ultralight compatible PICC found")); + isUlC=0; + } + Serial.print(F("UID: ")); + dumpInfo(mfrc522.uid.uidByte, mfrc522.uid.size); + } + else{ + Serial.println (F("Other Card found, not compatible!")); + mfrc522.PICC_HaltA(); + isPresent=0; + return; + } + } + } + if(isPresent){ // test read - it it fails, the PICC is most likely gone + byte byteCount = sizeof(buffer); + status = mfrc522.MIFARE_Read(0, buffer, &byteCount); + if(status!=mfrc522.STATUS_OK){ + isPresent=0; + isEncrypted=0; + mfrc522.PCD_StopCrypto1(); + Serial.println("Card gone..."); + } + } +} + + + +/* + * This small routine starts a request for encryption. + * for details see https://www.nxp.com/docs/en/data-sheet/MF0ICU2.pdf + */ +void RequestAuthUltralightC(void){ +int i; + byte AuthBuffer[24] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; // + byte AuthLength=24; + byte RndA[8]={0,0,0,0,0,0,0,0}; + byte RndB[8]={0,0,0,0,0,0,0,0}; // decrypted RndB + byte rRndB[8]={0,0,0,0,0,0,0,0}; // rotated RndB + byte encRndA[8]={0,0,0,0,0,0,0,0}; // encrypted RndA' from the PICC + byte dRndA[8]={0,0,0,0,0,0,0,0}; // decrypted RndA + byte message[24]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; // Message to transfer + byte iv_ar[8]; // this is the starting IV for decryption + byte byteCount=sizeof(buffer); + + if(isEncrypted){ + Serial.println(F("PICC already authenticated")); + return; + } + // sparse and set the key + if(!setAuthKey()){ + Serial.println(F("key could not be set")); + return; + } + // set IV to 0x00 + for(i=0 ; i<8 ; i++){ + iv_ar[i] = 0x00; + } + memcpy(&IV,iv_ar,8); // set IV + des.set_IV(IV); + + // Build command buffer + AuthBuffer[0] = 0x1A; // CMD_3DES_AUTH -> Ultralight C 3DES Authentication. + AuthBuffer[1] = 0x00; // + + // Calculate CRC_A + status = mfrc522.PCD_CalculateCRC(AuthBuffer, 2, &AuthBuffer[2]); + if (status != mfrc522.STATUS_OK) { + return; + } + + AuthLength=sizeof(AuthBuffer); + + // Transmit the buffer and receive the response, validate CRC_A. + status = mfrc522.PCD_TransceiveData(AuthBuffer, 4, AuthBuffer, &AuthLength, NULL, 0, true); + if (status != mfrc522.STATUS_OK) { + Serial.println("Ultralight C Auth failed"); + Serial.println(mfrc522.GetStatusCodeName(status)); + Serial.print(F("Reply: ")); + dumpInfo(AuthBuffer,AuthLength); + return; + } + memcpy(message,AuthBuffer+1,8); // copy the enc(RndB) from the message + memcpy(iv_ar,message,8); // use enc(RndB) as new IV for the next encryption. + + des.set_size(8); + des.tdesCbcDecipher(message,RndB); // decrypt enc(RndB) -> now we have RndB + + randomSeed(AuthBuffer[1]); // create RndA + int number; + for(int i=0; i<8; i++){ + number=random(255); + RndA[i]=number&0xFF; // + } + + memcpy(rRndB, RndB ,8); // copy RndB + rol(rRndB,8); // create RndB' + memcpy(message,RndA,8); // copy RndA in the first part of message + memcpy(message+8, rRndB ,8); // adding RndB' + + AuthBuffer[0] = 0xAF; // set the PCD-command + + memcpy(&IV,iv_ar,8); // set IV = to enk(RndB) + des.set_IV(IV); + des.set_size(16); + des.tdesCbcEncipher(message, &AuthBuffer[1]); + + status = mfrc522.PCD_CalculateCRC(AuthBuffer, 17, &AuthBuffer[17]); + if (status != mfrc522.STATUS_OK) { + return; + } + + memcpy(&IV,&AuthBuffer[9],8); // set IV to decrypt reply from PICC enc(RndA') -> RndA + des.set_IV(IV); + + status = mfrc522.PCD_TransceiveData(AuthBuffer, 19, AuthBuffer, &AuthLength, NULL, 0, true); + if (status != mfrc522.STATUS_OK) { + Serial.print(F("Auth failed failed: ")); + Serial.println(mfrc522.GetStatusCodeName(status)); + Serial.println(F("Reply: ")); + dumpInfo(AuthBuffer,AuthLength); + return; + } + else{ + if(AuthBuffer[0]==0x00){ // reply from PICC should start with 0x00 + memcpy(encRndA, &AuthBuffer[1],8); // copy enc(RndA') + des.set_size(8); + des.tdesCbcDecipher(encRndA,dRndA); // decrypt now we have decrypted RndA' + rol(RndA,8); // rotate orgiginal RndA to RndA' + for(i=0; i<8; i++){ // compare it + if(RndA[i] != dRndA[i]){ + i=9; + } + } + if(i==8){ + Serial.println(F("Keys are correct :-)")); + isEncrypted=1; + } + else + Serial.println(F("Keys do not match")); + } + else{ + Serial.println(F("Wrong answer!!!")); + } + } + return; +} + + +byte DumpDataUltralight(void){ +byte page=0; + + for(byte i=0; i<255; i++) // initialize lock bit table + isLocked[i]=0; + byte Count = sizeof(buffer); + + Serial.println(F("Page lock auth 0 1 2 3")); + getPageInfo(); // get information about acess + status = mfrc522.MIFARE_Read(page, buffer, &Count); + while((status==mfrc522.STATUS_OK)){ // This loop stops at an NAK-answer + Serial.print(page); + if(page<10) + Serial.print(F(" ")); + Serial.print(F(" ")); + if(isLocked[page]) + Serial.print(F(" x ")); + else + Serial.print(F(" ")); + if(page>=startProtect){ + if(access) + Serial.print(F(" w ")); + else + Serial.print(F("r/w ")); + } + else + Serial.print(F(" ")); + dumpInfo(buffer,4); + page++; + status = mfrc522.MIFARE_Read(page, buffer, &Count); + } + Count = sizeof(buffer); + status=mfrc522.PICC_WakeupA(buffer, &Count); // needed to wake up the card after receiving a NAK-answer + return page; +} + + +boolean checkIfUltralight(void){ +byte Count=sizeof(buffer); + + if(mfrc522.MIFARE_Read(43, buffer, &Count)==mfrc522.STATUS_OK){ + if(mfrc522.MIFARE_Read(44, buffer, &Count)==mfrc522.STATUS_OK){ + return 0; + } + else{ + Count = sizeof(buffer); + status=mfrc522.PICC_WakeupA(buffer, &Count); // needed to wake up the card after receiving a NAK-answer + return 1; + } + } + Count = sizeof(buffer); + status=mfrc522.PICC_WakeupA(buffer, &Count); // needed to wake up the card after receiving a NAK-answer + Serial.println(mfrc522.GetStatusCodeName(status)); + return 0; +} + + +boolean getPageInfo(){ + int i; + uint32_t mask=0; + byte bsize = sizeof(buffer); + status = mfrc522.MIFARE_Read(2, buffer, &bsize); // read the first lock bits + if(status==mfrc522.STATUS_OK){ + mask=buffer[3]; + mask=mask<<8; + mask=mask+buffer[2]; + isLocked[0]=1; + isLocked[1]=1; + isLocked[2]=1; + + if(mask&0x0001){ + isLocked[3]=1; + } + mask=mask>>1; + + if(mask&0x0001){ + for(i=0;i<6;i++) + isLocked[4+i]=1; + } + mask=mask>>1; + + if(mask&0x0001){ + for(i=0;i<6;i++) + isLocked[10+i]=1; + } + mask=mask>>1; + + for(i=0;i<13;i++){ + if(mask&0x0001) + isLocked[3+i]=1; + mask=mask>>1; + } + } + else{ + bsize = sizeof(buffer); + status=mfrc522.PICC_WakeupA(buffer, &bsize); // needed to wake up the card after receiving a NAK-answer + return 0; + } + status = mfrc522.MIFARE_Read(40, buffer, &bsize); + if(status==mfrc522.STATUS_OK){ + mask=buffer[1]; + mask=mask<<8; + mask=mask+buffer[0]; + int j=16; + for(i=0; i<8; i++){ + if((i%4)==0){ + if(mask&0x0001) + setBooleanBits(isLocked+j, 12); + } + else{ + if(mask&0x0001) + setBooleanBits(isLocked+j, 4); + j=j+4; + } + mask=mask>>1; + } + + + for(i=0;i<3;i++){ + if(mask&0x0001) + isLocked[41+i]=1; + mask=mask>>1; + } + + if(mask&0x0001) + setBooleanBits(isLocked+44, 4); + mask=mask>>1; + + for(i=0;i<3;i++){ + if(mask&0x0001) + isLocked[41+i]=1; + mask=mask>>1; + } + + if(mask&0x0001) + setBooleanBits(isLocked+44, 4); + mask=mask>>1; + + } + else{ + bsize = sizeof(buffer); + status=mfrc522.PICC_WakeupA(buffer, &bsize); // needed to wake up the card after receiving a NAK-answer + return 0; + } + status = mfrc522.MIFARE_Read(42, buffer, &bsize); + if(status==mfrc522.STATUS_OK){ + startProtect=buffer[0]; + } + else{ + bsize = sizeof(buffer); + status=mfrc522.PICC_WakeupA(buffer, &bsize); // needed to wake up the card after receiving a NAK-answer + return 0; + } + status = mfrc522.MIFARE_Read(43, buffer, &bsize); + if(status==mfrc522.STATUS_OK){ + access=buffer[0]&0x01; + } + else{ + bsize = sizeof(buffer); + status=mfrc522.PICC_WakeupA(buffer, &bsize); // needed to wake up the card after receiving a NAK-answer + return 0; + } + return 1; +} + +void setBooleanBits(boolean *ar, int len){ + for(int i=0; i48) + value=48; + } + else{ + if(value>1) + value=1; + } + + for(byte i=0;i<4;i++) + buffer[i]=0; + + buffer[0]=value; + + status = mfrc522.MIFARE_Ultralight_Write(page, buffer, 4); + if(status==mfrc522.STATUS_OK){ + if(mode){ + Serial.print(F("password protection starts now at page: ")); + Serial.println(value); + } + else{ + Serial.print(F("protection bit is set to: ")); + if(value) + Serial.println(F("write protected")); + else + Serial.println(F("read/write protected")); + } + } + else{ + if(mode) + Serial.print(F("ERROR: password protection not set: ")); + else + Serial.print(F("ERROR: protection bit not set: ")); + Serial.println(mfrc522.GetStatusCodeName(status)); + } +} + + +boolean setAuthKey(void){ +int i; + for(i=0;i<32;i++){ + if(Sdata[i]==0x00){ + Serial.println(F("Key too short! (needs to be 32 digits)")); + return 0; + } + else{ + if((i%2==0)){ + C_key[i/2]=char2byte(&Sdata[i]); + } + } + } + for(i=0;i<8;i++){ + C_key[i+16]=C_key[i]; + } + des.change_key(C_key); + return 1; +} + + +boolean newKey(void){ +byte i,j,pos; +byte pos_ar[4]={7,3,15,11}; +byte Buffer[4]; +byte page=0; + + if(!isEncrypted){ + Serial.println(F("please authenticate fist")); + return 0; + } + if(setAuthKey()){ + for(j=0;j<4;j++){ + page=0x2C+j; + pos=pos_ar[j]; + for(i=0;i<4;i++){ + Buffer[i]=C_key[pos-i]; + } + status = mfrc522.MIFARE_Ultralight_Write(page, Buffer, 4); + } + if(status==mfrc522.STATUS_OK){ + Serial.println(F("new key is written to the card!")); + } + else{ + Serial.print(F("new key could not be written: ")); + Serial.println(mfrc522.GetStatusCodeName(status)); + // restart the PICC + mfrc522.PICC_IsNewCardPresent(); // is required, as the PICC remains silent after a NAK + mfrc522.PICC_ReadCardSerial(); + } + } + else + return 0; + + return 1; +} + + + +void writeData(boolean mode){ +byte page=0; +byte i=0; +byte Buffer[4]={0,0,0,0}; + + if(isUlC) + Serial.println(F("Ultralight C")); + else + Serial.println(F("not a Ultralight C PICC!")); + + while((Sdata[i]!=' ')&&(i<3)){ + page = page * 10; + page = page + Sdata[i]-48; + i++; + } + i++; // to overcome the ' ' + + if(page<4){ + Serial.println(F("no user memory here")); + return; + } + if(isUlC&&(page>39)){ + Serial.println(F("no user memory here")); + return; + } + byte j=0; + boolean done=0; + for(; page<40; page++){ + for(j=0;j<4;j++){ + if(Sdata[i]==0x00){ + while(j<4){ + Buffer[j]=0; + j++; + } + done=1; + } + else{ + if(mode){ + Buffer[j]=char2byte(Sdata+i); + i=i+2; + } + else{ + Buffer[j]=Sdata[i]; + i++; + } + } + } + + status = mfrc522.MIFARE_Ultralight_Write(page, Buffer, 4); + Serial.print(F("writing page ")); + Serial.print(page); + Serial.println(mfrc522.GetStatusCodeName(status)); + if(status != mfrc522.STATUS_OK){ + return; + } + if(done) + return; + } +} + + + +// Needed to create RndB' out of RndB +void rol(byte *data, int len){ + byte first = data[0]; + for (int i = 0; i < len-1; i++) { + data[i] = data[i+1]; + } + data[len-1] = first; +} + + + +void dumpInfo(byte *ar, int len){ + for(int i=0 ; i= '0' && c <= '9') { + x *= 16; + x += c - '0'; + } + else if (c >= 'A' && c <= 'F') { + x *= 16; + x += (c - 'A') + 10; + } + else if (c >= 'a' && c <= 'f') { + x *= 16; + x += (c - 'a') + 10; + } + s++; + } + return x; +} + + +/* + * If the hex key is: "00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F" + * then you have to write the sequence "07 06 05 04 03 02 01 00 0F 0E 0D 0C 0B 0A 09 08" in 4 pages, + * from page 0x2C (44) to page 0x2F (47). + * + * if you want to change where (which pages) the authentication is required, here is how to do that: + * 0x2A defines the page address from which the authentication is required. E.g. if 0x2A = 0x30 no authentication is needed all as memory goes up to 0x2F. + * 0x2B defines if authentication is required for read/write (0x2B=0) or only for write access (0x2B=1) + +On example of Key1 = 0001020304050607h and Key2 = 08090A0B0C0D0E0Fh, +the command sequence needed for key programming with WRITE command is: +•A2 2C 07 06 05 04 CRC +•A2 2D 03 02 01 00 CRC +•A2 2E 0F 0E 0D 0C CRC +•A2 2F 0B 0A 09 08 CRC + +*/