1 local cmds
= require('commands')
2 local lib14a
= require('read14a')
3 local getopt
= require('getopt')
4 local ansicolors
= require('ansicolors')
7 author
= 'Dominic Celiano'
10 Purpose: Lua script to communicate with the Mifare Plus EV1, including personalization (setting the keys) and proximity check. Manually edit the file to add to the commands you can send the card.
11 Please read the NXP manual before running this script to prevent making irreversible changes. Also note:
12 - The Mifare Plus must start in SL0 for personalization. Card can then be moved to SL1 or SL3.
13 - The keys are hardcoded in the script to "00...". Unless you change this, only use this script for testing purposes.
14 - Make sure you choose your card size correctly (2kB or 4kB).
15 Small changes can be to made this script to communicate with the Mifare Plus S, X, or SE.
18 1. script run hf_mfp_raw
21 script run hf_mfp_raw [-h]
29 SIXTEEN_BYTES_ZEROS
= '00000000000000000000000000000000'
31 -- ISO7816 commands used
32 GETVERS_INIT
= '0360' -- Begins the GetVersion command
33 GETVERS_CONT
= '03AF' -- Continues the GetVersion command
39 AUTH_NONFIRST
= '0376'
41 PROXIMITYCHECK
= '03F2'
43 READPLAINNOMACUNMACED
= '0336'
46 -- This is only meant to be used when errors occur
47 local function oops(err
)
49 core
.clearCommandBuffer()
59 print(ansicolors
.cyan
..'Usage'..ansicolors
.reset
)
61 print(ansicolors
.cyan
..'Arguments'..ansicolors
.reset
)
63 print(ansicolors
.cyan
..'Example usage'..ansicolors
.reset
)
67 -- Used to send raw data to the firmware to subsequently forward the data to the card.
68 local function sendRaw(rawdata
, crc
, power
)
69 print(("<sent>: %s"):format(rawdata
))
71 local flags
= lib14a
.ISO14A_COMMAND
.ISO14A_RAW
73 flags
= flags
+ lib14a
.ISO14A_COMMAND
.ISO14A_APPEND_CRC
76 flags
= flags
+ lib14a
.ISO14A_COMMAND
.ISO14A_NO_DISCONNECT
79 local command
= Command
:newMIX
{cmd
= cmds
.CMD_HF_ISO14443A_READER
,
80 arg1
= flags
, -- Send raw
81 arg2
= string.len(rawdata
) / 2, -- arg2 contains the length, which is half the length of the ASCII-string rawdata
84 local ignore_response
= false
85 local result
, err
= command
:sendMIX(ignore_response
)
87 --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
88 local count
,cmd
,arg1
,arg2
,arg3
,data
= bin
.unpack('LLLLH512',result
)
90 returned_bytes
= string.sub(data
, 1, arg1
* 2)
91 if #returned_bytes
> 0 then
92 print(("<recvd>: %s"):format(returned_bytes
)) -- need to multiply by 2 because the hex digits are actually two bytes when they are strings
98 oops("Error sending the card raw data.")
103 local function writePerso()
104 -- Used to write any data, including the keys (Key A and Key B), for all the sectors.
105 -- writePerso() command parameters:
106 -- 1 byte - 0xA8 - Command Code
107 -- 2 bytes - Address of the first block or key to be written to (40 blocks are numbered from 0x0000 to 0x00FF)
108 -- 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
109 -- up to 15 blocks to be written at once.
110 -- response from PICC:
112 -- 0x09 - targeted block is invalid for writes, i.e. block 0, which contains manufacturer data
113 -- 0x0B - command invalid
114 -- 0x0C - unexpected command length
118 cardsize
= 4 --need to set to 4 for 4k or 2 for 2k
119 if(cardsize
== 4) then
121 elseif(cardsize
== 2) then
124 oops("Invalid card size")
127 -- Write to the AES sector keys
128 print("Setting AES Sector keys")
129 for i
=0,numsectors
do --for each sector number
130 local keyA_block
= "40" .. string.format("%02x", i
* 2)
131 local keyB_block
= "40" .. string.format("%02x", (i
* 2) + 1)
132 --Can also calculate the keys fancily to make them unique, if desired
133 keyA
= SIXTEEN_BYTES_ZEROS
134 keyB
= SIXTEEN_BYTES_ZEROS
135 writeBlock(keyA_block
, keyA
)
136 writeBlock(keyB_block
, keyB
)
138 print("Finished setting AES Sector keys")
140 print("Setting misc keys which haven't been set yet.")
143 writeBlock(blocknum
, SIXTEEN_BYTES_ZEROS
)
144 --CardConfigurationKey
146 writeBlock(blocknum
, SIXTEEN_BYTES_ZEROS
)
149 writeBlock(blocknum
, SIXTEEN_BYTES_ZEROS
)
152 writeBlock(blocknum
, SIXTEEN_BYTES_ZEROS
)
155 writeBlock(blocknum
, SIXTEEN_BYTES_ZEROS
)
156 --L1L3MixSectorSwitchKey
158 writeBlock(blocknum
, SIXTEEN_BYTES_ZEROS
)
162 writeBlock(blocknum
, SIXTEEN_BYTES_ZEROS
)
165 writeBlock(blocknum
, SIXTEEN_BYTES_ZEROS
)
168 writeBlock(blocknum
, SIXTEEN_BYTES_ZEROS
)
171 writeBlock(blocknum
, SIXTEEN_BYTES_ZEROS
)
172 --TransactionMACConfKey1
174 writeBlock(blocknum
, SIXTEEN_BYTES_ZEROS
)
175 print("Finished setting misc keys.")
177 print("WritePerso finished! Card is ready to move into new security level.")
180 local function writeBlock(blocknum
, data
)
181 -- Method writes 16 bytes of the string sent (data) to the specified block number
182 -- The block numbers sent to the card need to be in little endian format (i.e. block 0x0001 is sent as 0x1000)
183 blocknum_little_endian
= string.sub(blocknum
, 3, 4) .. string.sub(blocknum
, 1, 2)
184 commandString
= WRITEPERSO
.. blocknum_little_endian
.. data
--Write 16 bytes (32 hex chars).
185 response
= sendRaw(commandString
, true, true) --0x90 is returned upon success
186 if string.sub(response
, 3, 4) ~= "90" then
187 oops(("error occurred while trying to write to block %s"):format(blocknum
))
191 local function authenticateAES()
192 -- Used to try to authenticate with the AES keys we programmed into the card, to ensure the authentication works correctly.
193 commandString
= AUTH_FIRST
194 commandString
= commandString
.. ''
197 local function getVersion()
198 sendRaw(GETVERS_INIT
, true, true)
199 sendRaw(GETVERS_CONT
, true, true)
200 sendRaw(GETVERS_CONT
, true, true)
203 local function commitPerso(SL
)
204 --pass SL as "01" to move to SL1 or "03" to move to SL3.
205 commandString
= COMMITPERSO
.. SL
206 response
= sendRaw(commandString
, true, true) --0x90 is returned upon success
207 if string.sub(response
, 3, 4) ~= "90" then
208 oops("error occurred while trying to switch security level")
212 local function calculateMAC(MAC_input
)
213 -- Pad the input if it is not a multiple of 16 bytes (32 nibbles).
214 if(string.len(MAC_input
) % 32 ~= 0) then
215 MAC_input
= MAC_input
.. "80"
217 while(string.len(MAC_input
) % 32 ~= 0) do
218 MAC_input
= MAC_input
.. "0"
220 print("Padded MAC Input = " .. MAC_input
.. ", length (bytes) = " .. string.len(MAC_input
) / 2)
222 --The MAC would actually be calculated here, and the output stored in raw_output
223 raw_output
= "00010203040506070001020304050607" -- Dummy filler for now of 16-byte output. To be filled with actual MAC for testing purposes.
225 -- The final 8-byte MAC output is a concatenation of every 2nd byte starting from the second MSB.
229 final_output
= final_output
.. string.sub(RndR
, j
, j
+ 1) .. string.sub(RndC
, j
, j
+ 1)
235 local function proximityCheck()
237 commandString
= PREPAREPC
238 response
= sendRaw(commandString
, true, true)
239 if not response
then return oops("This card is not support the proximity check command.") end
241 OPT
= string.sub(response
, 5, 6)
242 if tonumber(OPT
) == 1 then
247 pubRespTime
= string.sub(response
, 7, 10)
248 if(pps_present
== true) then
249 pps
= string.sub(response
, 11, 12)
253 print("OPT = " .. OPT
.. " pubRespTime = " .. pubRespTime
.. " pps = " .. pps
)
256 RndC
= "0001020304050607" --Random Challenge
257 num_rounds
= 8 --Needs to be 1, 2, 4, or 8
258 part_len
= 8 / num_rounds
261 for i
= 1,num_rounds
do
263 for q
= 1,(part_len
*2) do
264 pRndC
= pRndC
.. string.sub(RndC
,j
,j
)
267 commandString
= PROXIMITYCHECK
.. "0" .. tostring(part_len
) .. pRndC
268 pRndR
= string.sub(sendRaw(commandString
, true, true), 3, 3+part_len
)
271 print("RndC = " .. RndC
.. " RndR = " .. RndR
)
274 MAC_input
= "FD" .. OPT
.. pubRespTime
276 MAC_input
= MAC_input
.. pps
279 rnum_concat
= RndR
.. RndC
--temporary (only works for when a single random challenge (8 bytes) is sent)
282 -- rnum_concat = rnum_concat .. string.sub(RndR, j, j + 1) .. string.sub(RndC, j, j + 1)
285 MAC_input
= MAC_input
.. rnum_concat
286 print("Concatenation of random numbers = " .. rnum_concat
)
287 print("Final PCD concatenation before input into MAC function = " .. MAC_input
)
288 MAC_tag
= calculateMAC(MAC_input
)
289 print("8-byte PCD MAC_tag (placeholder - currently incorrect) = " .. MAC_tag
)
290 commandString
= VERIFYPC
.. MAC_tag
291 response
= sendRaw(commandString
, true, true)
292 print(#response
, response
)
293 if #response
< 20 then return oops("Wrong response length (expected 20, got "..#response
..") exiting") end
295 PICC_MAC
= string.sub(response
, 5, 20)
296 print("8-byte MAC returned by PICC = " .. PICC_MAC
)
297 MAC_input
= "90" .. string.sub(MAC_input
, 3)
298 print("Final PICC concatenation before input into MAC function = " .. MAC_input
)
299 MAC_tag
= calculateMAC(MAC_input
)
300 print("8-byte PICC MAC_tag (placeholder - currently incorrect) = " .. MAC_tag
)
305 -- The main entry point
309 for o
, a
in getopt
.getopt(args
, 'h') do -- Populate command line arguments
310 if o
== "h" then return help() end
313 -- Initialize the card using the already-present read14a library
314 -- Perform RATS and PPS (Protocol and Parameter Selection) check to finish the ISO 14443-4 protocol.
315 info
,err
= lib14a
.read(true, false)
322 response
= sendRaw("D01100", true, true)
325 return oops("No response from PPS check")
328 print("Connected to")
329 print(" Type : "..info
.name
)
330 print(" UID : "..info
.uid
)
332 -- Now, the card is initialized and we can do more interesting things.
335 --commitPerso("03") --move to SL3
339 --commandString = VERIFYPC .. "186EFDE8DDC7D30B"
340 -- MAC = f5180d6e 40fdeae8 e9dd6ac7 bcd3350b
341 -- response = sendRaw(commandString, true, true)
343 -- attempt to read VCProximityKey at block A001
344 -- commandString = READPLAINNOMACUNMACED .. "01A0" .. "01"
345 -- response = sendRaw(commandString, true, true)
347 -- authenticate with CardConfigurationKey
348 -- commandString = AUTH_FIRST .. "0190" .. "00"
349 -- response = sendRaw(commandString, true, true)
351 -- Power off the Proxmark
352 sendRaw(POWEROFF
, false, false)