local cmds = require('commands') local lib14a = require('read14a') SIXTEEN_BYTES_ZEROS = "00000000000000000000000000000000" GETVERS_INIT = "0360" -- Begins the GetVersion command GETVERS_CONT = "03AF" -- Continues the GetVersion command POWEROFF = "OFF" WRITEPERSO = "03A8" COMMITPERSO = "03AA" AUTH_FIRST = "0370" AUTH_CONT = "0372" AUTH_NONFIRST = "0376" PREPAREPC = "03F0" PROXIMITYCHECK = "03F2" VERIFYPC = "03FD" READPLAINNOMACUNMACED = "0336" --- -- This is only meant to be used when errors occur local function oops(err) print("ERROR: ",err) end --- -- Used to send raw data to the firmware to subsequently forward the data to the card. local function sendRaw(rawdata, crc, power) print((": %s"):format(rawdata)) local flags = lib14a.ISO14A_COMMAND.ISO14A_RAW if crc then flags = flags + lib14a.ISO14A_COMMAND.ISO14A_APPEND_CRC end if power then flags = flags + lib14a.ISO14A_COMMAND.ISO14A_NO_DISCONNECT end local command = Command:new{cmd = cmds.CMD_READER_ISO_14443a, arg1 = flags, -- Send raw arg2 = string.len(rawdata) / 2, -- arg2 contains the length, which is half the length of the ASCII-string rawdata data = rawdata} local ignore_response = false local result, err = lib14a.sendToDevice(command, ignore_response) if result then --unpack the first 4 parts of the result as longs, and the last as an extremely long string to later be cut down based on arg1, the number of bytes returned local count,cmd,arg1,arg2,arg3,data = bin.unpack('LLLLH512',result) returned_bytes = string.sub(data, 1, arg1 * 2) if #returned_bytes > 0 then print((": %s"):format(returned_bytes)) -- need to multiply by 2 because the hex digits are actually two bytes when they are strings return returned_bytes else return nil end else oops("Error sending the card raw data.") return nil end end -- Sends an instruction to do nothing, only disconnect local function disconnect() local command = Command:new{cmd = cmds.CMD_READER_ISO_14443a, arg1 = 0, } -- We can ignore the response here, no ACK is returned for this command -- Check /armsrc/iso14443a.c, ReaderIso14443a() for details return lib14a.sendToDevice(command,true) end local function writePerso() -- Used to write any data, including the keys (Key A and Key B), for all the sectors. -- writePerso() command parameters: -- 1 byte - 0xA8 - Command Code -- 2 bytes - Address of the first block or key to be written to (40 blocks are numbered from 0x0000 to 0x00FF) -- X bytes - The data bytes to be written, starting from the first block. Amount of data sent can be from 16 to 240 bytes in 16 byte increments. This allows -- up to 15 blocks to be written at once. -- response from PICC: -- 0x90 - OK -- 0x09 - targeted block is invalid for writes, i.e. block 0, which contains manufacturer data -- 0x0B - command invalid -- 0x0C - unexpected command length cardsize = 4 --need to set to 4 for 4k or 2 for 2k if(cardsize == 4) then numsectors = 39 elseif(cardsize == 2) then numsectors = 31 else oops("Invalid card size") end -- Write to the AES sector keys print("Setting AES Sector keys") for i=0,numsectors do --for each sector number local keyA_block = "40" .. string.format("%02x", i * 2) local keyB_block = "40" .. string.format("%02x", (i * 2) + 1) --Can also calculate the keys fancily to make them unique, if desired keyA = SIXTEEN_BYTES_ZEROS keyB = SIXTEEN_BYTES_ZEROS writeBlock(keyA_block, keyA) writeBlock(keyB_block, keyB) end print("Finished setting AES Sector keys") print("Setting misc keys which haven't been set yet.") --CardMasterKey blocknum = "9000" writeBlock(blocknum, SIXTEEN_BYTES_ZEROS) --CardConfigurationKey blocknum = "9001" writeBlock(blocknum, SIXTEEN_BYTES_ZEROS) --L3SwitchKey blocknum = "9003" writeBlock(blocknum, SIXTEEN_BYTES_ZEROS) --SL1CardAuthKey blocknum = "9004" writeBlock(blocknum, SIXTEEN_BYTES_ZEROS) --L3SectorSwitchKey blocknum = "9006" writeBlock(blocknum, SIXTEEN_BYTES_ZEROS) --L1L3MixSectorSwitchKey blocknum = "9007" writeBlock(blocknum, SIXTEEN_BYTES_ZEROS) --VC Keys --VCProximityKey blocknum = "A001" writeBlock(blocknum, SIXTEEN_BYTES_ZEROS) --VCSelectENCKey blocknum = "A080" writeBlock(blocknum, SIXTEEN_BYTES_ZEROS) --VCSelectMACKey blocknum = "A081" writeBlock(blocknum, SIXTEEN_BYTES_ZEROS) --TransactionMACKey1 blocknum = "C000" writeBlock(blocknum, SIXTEEN_BYTES_ZEROS) --TransactionMACConfKey1 blocknum = "C001" writeBlock(blocknum, SIXTEEN_BYTES_ZEROS) print("Finished setting misc keys.") print("WritePerso finished! Card is ready to move into new security level.") end local function writeBlock(blocknum, data) -- Method writes 16 bytes of the string sent (data) to the specified block number -- The block numbers sent to the card need to be in little endian format (i.e. block 0x0001 is sent as 0x1000) blocknum_little_endian = string.sub(blocknum, 3, 4) .. string.sub(blocknum, 1, 2) commandString = WRITEPERSO .. blocknum_little_endian .. data --Write 16 bytes (32 hex chars). response = sendRaw(commandString, true, true) --0x90 is returned upon success if string.sub(response, 3, 4) ~= "90" then oops(("error occurred while trying to write to block %s"):format(blocknum)) end end local function authenticateAES() -- Used to try to authenticate with the AES keys we programmed into the card, to ensure the authentication works correctly. commandString = AUTH_FIRST commandString = commandString .. "" end local function getVersion() sendRaw(GETVERS_INIT, true, true) sendRaw(GETVERS_CONT, true, true) sendRaw(GETVERS_CONT, true, true) end local function commitPerso(SL) --pass SL as "01" to move to SL1 or "03" to move to SL3. commandString = COMMITPERSO .. SL response = sendRaw(commandString, true, true) --0x90 is returned upon success if string.sub(response, 3, 4) ~= "90" then oops("error occurred while trying to switch security level") end end local function calculateMAC(MAC_input) -- Pad the input if it is not a multiple of 16 bytes (32 nibbles). if(string.len(MAC_input) % 32 ~= 0) then MAC_input = MAC_input .. "80" end while(string.len(MAC_input) % 32 ~= 0) do MAC_input = MAC_input .. "0" end print("Padded MAC Input = " .. MAC_input .. ", length (bytes) = " .. string.len(MAC_input) / 2) --The MAC would actually be calculated here, and the output stored in raw_output raw_output = "00010203040506070001020304050607" -- Dummy filler for now of 16-byte output. To be filled with actual MAC for testing purposes. -- The final 8-byte MAC output is a concatenation of every 2nd byte starting from the second MSB. final_output = "" j = 3 for i = 1,8 do final_output = final_output .. string.sub(RndR, j, j + 1) .. string.sub(RndC, j, j + 1) j = j + 4 end return final_output end local function proximityCheck() --PreparePC-- commandString = PREPAREPC response = sendRaw(commandString, true, true) if not response then return oops("not a Mifare plus card") end OPT = string.sub(response, 5, 6) if tonumber(OPT) == 1 then pps_present = true else pps_present = false end pubRespTime = string.sub(response, 7, 10) if(pps_present == true) then pps = string.sub(response, 11, 12) else pps = '' end print("OPT = " .. OPT .. " pubRespTime = " .. pubRespTime .. " pps = " .. pps) --PC-- RndC = "0001020304050607" --Random Challenge num_rounds = 8 --Needs to be 1, 2, 4, or 8 part_len = 8 / num_rounds j = 1 RndR = "" for i = 1,num_rounds do pRndC = "" for q = 1,(part_len*2) do pRndC = pRndC .. string.sub(RndC,j,j) j = j + 1 end commandString = PROXIMITYCHECK .. "0" .. tostring(part_len) .. pRndC pRndR = string.sub(sendRaw(commandString, true, true), 3, 3+part_len) RndR = RndR .. pRndR end print("RndC = " .. RndC .. " RndR = " .. RndR) --VerifyPC-- MAC_input = "FD" .. OPT .. pubRespTime if pps_present then MAC_input = MAC_input .. pps end rnum_concat = "" rnum_concat = RndR .. RndC --temporary (only works for when a single random challenge (8 bytes) is sent) -- j = 1 -- for i = 1,8 do -- rnum_concat = rnum_concat .. string.sub(RndR, j, j + 1) .. string.sub(RndC, j, j + 1) -- j = j + 2 -- end MAC_input = MAC_input .. rnum_concat print("Concatenation of random numbers = " .. rnum_concat) print("Final PCD concatenation before input into MAC function = " .. MAC_input) MAC_tag = calculateMAC(MAC_input) print("8-byte PCD MAC_tag (placeholder - currently incorrect) = " .. MAC_tag) commandString = VERIFYPC .. MAC_tag response = sendRaw(commandString, true, true) print(#response, response) if #response < 20 then return oops("Wrong response length (expected 20, got "..#response..") exiting") end PICC_MAC = string.sub(response, 5, 20) print("8-byte MAC returned by PICC = " .. PICC_MAC) MAC_input = "90" .. string.sub(MAC_input, 3) print("Final PICC concatenation before input into MAC function = " .. MAC_input) MAC_tag = calculateMAC(MAC_input) print("8-byte PICC MAC_tag (placeholder - currently incorrect) = " .. MAC_tag) end --- -- The main entry point function main(args) -- Initialize the card using the already-present read14a library -- Perform PPS (Protocol and Parameter Selection) check to finish the ISO 14443-4 protocol. info,err = lib14a.read(true, false) if not info then oops(err); disconnect(); return; end -- response = sendRaw("D01100", true, true) if not response then oops("No response from PPS check"); disconnect(); return; end print("Connected to") print(" Type : "..info.name) print(" UID : "..info.uid) -- Now, the card is initialized and we can do more interesting things. --writePerso() --commitPerso("03") --move to SL3 --getVersion() proximityCheck() --commandString = VERIFYPC .. "186EFDE8DDC7D30B" -- MAC = f5180d6e 40fdeae8 e9dd6ac7 bcd3350b -- response = sendRaw(commandString, true, true) -- attempt to read VCProximityKey at block A001 -- commandString = READPLAINNOMACUNMACED .. "01A0" .. "01" -- response = sendRaw(commandString, true, true) -- authenticate with CardConfigurationKey -- commandString = AUTH_FIRST .. "0190" .. "00" -- response = sendRaw(commandString, true, true) -- Power off the Proxmark sendRaw(POWEROFF, false, false) disconnect() end main(args) -- Call the main function