Added mifarePlus.lua script for communication with Mifare Plus. (#593)
[proxmark3-svn] / client / scripts / mifarePlus.lua
1 local cmds = require('commands')
2 local lib14a = require('read14a')
3 getopt = require('getopt') -- Used to get command line arguments
4
5 example = "script run mifarePlus"
6 author = "Dominic Celiano"
7 desc =
8 [[
9 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.
10
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
16 Small changes can be to made this script to communicate with the Mifare Plus S, X, or SE.
17
18 Arguments:
19 -h : this help
20 ]]
21
22 SIXTEEN_BYTES_ZEROS = "00000000000000000000000000000000"
23
24 GETVERS_INIT = "0360" -- Begins the GetVersion command
25 GETVERS_CONT = "03AF" -- Continues the GetVersion command
26 POWEROFF = "OFF"
27 WRITEPERSO = "03A8"
28 COMMITPERSO = "03AA"
29 AUTH_FIRST = "0370"
30 AUTH_CONT = "0372"
31 AUTH_NONFIRST = "0376"
32 PREPAREPC = "03F0"
33 PROXIMITYCHECK = "03F2"
34 VERIFYPC = "03FD"
35 READPLAINNOMACUNMACED = "0336"
36
37 ---
38 -- This is only meant to be used when errors occur
39 function oops(err)
40 print("ERROR: ",err)
41 end
42
43 ---
44 -- Usage help
45 function help()
46 print(desc)
47 print("Example usage")
48 print(example)
49 end
50
51 ---
52 -- Used to send raw data to the firmware to subsequently forward the data to the card.
53 function sendRaw(rawdata, crc, power)
54 print(("<sent>: %s"):format(rawdata))
55
56 local flags = lib14a.ISO14A_COMMAND.ISO14A_RAW
57 if crc then
58 flags = flags + lib14a.ISO14A_COMMAND.ISO14A_APPEND_CRC
59 end
60 if power then
61 flags = flags + lib14a.ISO14A_COMMAND.ISO14A_NO_DISCONNECT
62 end
63
64 local command = Command:new{cmd = cmds.CMD_READER_ISO_14443a,
65 arg1 = flags, -- Send raw
66 arg2 = string.len(rawdata) / 2, -- arg2 contains the length, which is half the length of the ASCII-string rawdata
67 data = rawdata}
68 local ignore_response = false
69 local result, err = lib14a.sendToDevice(command, ignore_response)
70 if result then
71 --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
72 local count,cmd,arg1,arg2,arg3,data = bin.unpack('LLLLH512',result)
73 returned_bytes = string.sub(data, 1, arg1 * 2)
74 if returned_bytes ~= "" then
75 print(("<recvd>: %s"):format(returned_bytes)) -- need to multiply by 2 because the hex digits are actually two bytes when they are strings
76 end
77 return returned_bytes
78 else
79 err = "Error sending the card raw data."
80 oops(err)
81 end
82 end
83
84 function writePerso()
85 -- Used to write any data, including the keys (Key A and Key B), for all the sectors.
86 -- writePerso() command parameters:
87 -- 1 byte - 0xA8 - Command Code
88 -- 2 bytes - Address of the first block or key to be written to (40 blocks are numbered from 0x0000 to 0x00FF)
89 -- 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
90 -- up to 15 blocks to be written at once.
91 -- response from PICC:
92 -- 0x90 - OK
93 -- 0x09 - targeted block is invalid for writes, i.e. block 0, which contains manufacturer data
94 -- 0x0B - command invalid
95 -- 0x0C - unexpected command length
96
97
98
99 cardsize = 4 --need to set to 4 for 4k or 2 for 2k
100 if(cardsize == 4) then
101 numsectors = 39
102 elseif(cardsize == 2) then
103 numsectors = 31
104 else
105 oops("Invalid card size")
106 end
107
108 -- Write to the AES sector keys
109 print("Setting AES Sector keys")
110 for i=0,numsectors do --for each sector number
111 local keyA_block = "40" .. string.format("%02x", i * 2)
112 local keyB_block = "40" .. string.format("%02x", (i * 2) + 1)
113 --Can also calculate the keys fancily to make them unique, if desired
114 keyA = SIXTEEN_BYTES_ZEROS
115 keyB = SIXTEEN_BYTES_ZEROS
116 writeBlock(keyA_block, keyA)
117 writeBlock(keyB_block, keyB)
118 end
119 print("Finished setting AES Sector keys")
120
121 print("Setting misc keys which haven't been set yet.")
122 --CardMasterKey
123 blocknum = "9000"
124 writeBlock(blocknum, SIXTEEN_BYTES_ZEROS)
125 --CardConfigurationKey
126 blocknum = "9001"
127 writeBlock(blocknum, SIXTEEN_BYTES_ZEROS)
128 --L3SwitchKey
129 blocknum = "9003"
130 writeBlock(blocknum, SIXTEEN_BYTES_ZEROS)
131 --SL1CardAuthKey
132 blocknum = "9004"
133 writeBlock(blocknum, SIXTEEN_BYTES_ZEROS)
134 --L3SectorSwitchKey
135 blocknum = "9006"
136 writeBlock(blocknum, SIXTEEN_BYTES_ZEROS)
137 --L1L3MixSectorSwitchKey
138 blocknum = "9007"
139 writeBlock(blocknum, SIXTEEN_BYTES_ZEROS)
140 --VC Keys
141 --VCProximityKey
142 blocknum = "A001"
143 writeBlock(blocknum, SIXTEEN_BYTES_ZEROS)
144 --VCSelectENCKey
145 blocknum = "A080"
146 writeBlock(blocknum, SIXTEEN_BYTES_ZEROS)
147 --VCSelectMACKey
148 blocknum = "A081"
149 writeBlock(blocknum, SIXTEEN_BYTES_ZEROS)
150 --TransactionMACKey1
151 blocknum = "C000"
152 writeBlock(blocknum, SIXTEEN_BYTES_ZEROS)
153 --TransactionMACConfKey1
154 blocknum = "C001"
155 writeBlock(blocknum, SIXTEEN_BYTES_ZEROS)
156 print("Finished setting misc keys.")
157
158 print("WritePerso finished! Card is ready to move into new security level.")
159 end
160
161 function writeBlock(blocknum, data)
162 -- Method writes 16 bytes of the string sent (data) to the specified block number
163 -- The block numbers sent to the card need to be in little endian format (i.e. block 0x0001 is sent as 0x1000)
164 blocknum_little_endian = string.sub(blocknum, 3, 4) .. string.sub(blocknum, 1, 2)
165 commandString = WRITEPERSO .. blocknum_little_endian .. data --Write 16 bytes (32 hex chars).
166 response = sendRaw(commandString, true, true) --0x90 is returned upon success
167 if string.sub(response, 3, 4) ~= "90" then
168 oops(("error occurred while trying to write to block %s"):format(blocknum))
169 end
170 end
171
172 function authenticateAES()
173 -- Used to try to authenticate with the AES keys we programmed into the card, to ensure the authentication works correctly.
174 commandString = AUTH_FIRST
175 commandString = commandString .. ""
176 end
177
178 function getVersion()
179 sendRaw(GETVERS_INIT, true, true)
180 sendRaw(GETVERS_CONT, true, true)
181 sendRaw(GETVERS_CONT, true, true)
182 end
183
184 function commitPerso(SL)
185 --pass SL as "01" to move to SL1 or "03" to move to SL3.
186 commandString = COMMITPERSO .. SL
187 response = sendRaw(commandString, true, true) --0x90 is returned upon success
188 if string.sub(response, 3, 4) ~= "90" then
189 oops("error occurred while trying to switch security level")
190 end
191 end
192
193 function calculateMAC(MAC_input)
194 -- Pad the input if it is not a multiple of 16 bytes (32 nibbles).
195 if(string.len(MAC_input) % 32 ~= 0) then
196 MAC_input = MAC_input .. "80"
197 end
198 while(string.len(MAC_input) % 32 ~= 0) do
199 MAC_input = MAC_input .. "0"
200 end
201 print("Padded MAC Input = " .. MAC_input .. ", length (bytes) = " .. string.len(MAC_input) / 2)
202
203 --The MAC would actually be calculated here, and the output stored in raw_output
204 raw_output = "00010203040506070001020304050607" -- Dummy filler for now of 16-byte output. To be filled with actual MAC for testing purposes.
205
206 -- The final 8-byte MAC output is a concatenation of every 2nd byte starting from the second MSB.
207 final_output = ""
208 j = 3
209 for i = 1,8 do
210 final_output = final_output .. string.sub(RndR, j, j + 1) .. string.sub(RndC, j, j + 1)
211 j = j + 4
212 end
213 return final_output
214 end
215
216 function proximityCheck()
217 --PreparePC--
218 commandString = PREPAREPC
219 response = sendRaw(commandString, true, true)
220 if(response == "") then
221 print("ERROR: This card does not support the Proximity Check command.")
222 return
223 end
224 OPT = string.sub(response, 5, 6)
225 if(tonumber(OPT) == 1) then
226 pps_present = true
227 else
228 pps_present = false
229 end
230 pubRespTime = string.sub(response, 7, 10)
231 if(pps_present == true) then
232 pps = string.sub(response, 11, 12)
233 else
234 pps = nil
235 end
236 print("OPT = " .. OPT .. " pubRespTime = " .. pubRespTime .. " pps = " .. pps)
237
238 --PC--
239 RndC = "0001020304050607" --Random Challenge
240 num_rounds = 8 --Needs to be 1, 2, 4, or 8
241 part_len = 8 / num_rounds
242 j = 1
243 RndR = ""
244 for i = 1,num_rounds do
245 pRndC = ""
246 for q = 1,(part_len*2) do
247 pRndC = pRndC .. string.sub(RndC,j,j)
248 j = j + 1
249 end
250 commandString = PROXIMITYCHECK .. "0" .. tostring(part_len) .. pRndC
251 pRndR = string.sub(sendRaw(commandString, true, true), 3, 3+part_len)
252 RndR = RndR .. pRndR
253 end
254 print("RndC = " .. RndC .. " RndR = " .. RndR)
255
256 --VerifyPC--
257 MAC_input = "FD" .. OPT .. pubRespTime
258 if(pps_present == true) then
259 MAC_input = MAC_input .. pps
260 end
261 rnum_concat = ""
262 rnum_concat = RndR .. RndC --temporary (only works for when a single random challenge (8 bytes) is sent)
263 -- j = 1
264 -- for i = 1,8 do
265 -- rnum_concat = rnum_concat .. string.sub(RndR, j, j + 1) .. string.sub(RndC, j, j + 1)
266 -- j = j + 2
267 -- end
268 MAC_input = MAC_input .. rnum_concat
269 print("Concatenation of random numbers = " .. rnum_concat)
270 print("Final PCD concatenation before input into MAC function = " .. MAC_input)
271 MAC_tag = calculateMAC(MAC_input)
272 print("8-byte PCD MAC_tag (placeholder - currently incorrect) = " .. MAC_tag)
273 commandString = VERIFYPC .. MAC_tag
274 response = sendRaw(commandString, true, true)
275 print(response)
276 PICC_MAC = string.sub(response, 5, 20)
277 print("8-byte MAC returned by PICC = " .. PICC_MAC)
278 MAC_input = "90" .. string.sub(MAC_input, 3)
279 print("Final PICC concatenation before input into MAC function = " .. MAC_input)
280 MAC_tag = calculateMAC(MAC_input)
281 print("8-byte PICC MAC_tag (placeholder - currently incorrect) = " .. MAC_tag)
282
283 end
284
285 ---
286 -- The main entry point
287 function main(args)
288 print("") -- Print a blank line to make things look cleaner
289 for o, a in getopt.getopt(args, 'h') do -- Populate command line arguments
290 if o == "h" then help() return end
291 end
292
293 -- Initialize the card using the already-present read14a library
294 info,err = lib14a.read14443a(true, false)
295 --Perform RATS and PPS (Protocol and Parameter Selection) check to finish the ISO 14443-4 protocol.
296 response = sendRaw("e050", true, true)
297 if(response == "") then
298 print("No response from RATS.")
299 end
300 response = sendRaw("D01100", true, true)
301 if(response == "") then
302 print("No response from PPS check.")
303 end
304 if err then
305 oops(err)
306 sendRaw(POWEROFF, false, false)
307 return
308 else
309 print(("Connected to card with a UID of %s."):format(info.uid))
310 end
311
312
313 -- Now, the card is initialized and we can do more interesting things.
314
315 --writePerso()
316 --commitPerso("03") --move to SL3
317 --getVersion()
318 proximityCheck()
319
320 --commandString = VERIFYPC .. "186EFDE8DDC7D30B"
321 -- MAC = f5180d6e 40fdeae8 e9dd6ac7 bcd3350b
322 -- response = sendRaw(commandString, true, true)
323
324 -- attempt to read VCProximityKey at block A001
325 -- commandString = READPLAINNOMACUNMACED .. "01A0" .. "01"
326 -- response = sendRaw(commandString, true, true)
327
328 -- authenticate with CardConfigurationKey
329 -- commandString = AUTH_FIRST .. "0190" .. "00"
330 -- response = sendRaw(commandString, true, true)
331
332 -- Power off the Proxmark
333 sendRaw(POWEROFF, false, false)
334
335
336
337 end
338
339
340 main(args) -- Call the main function
Impressum, Datenschutz