text
[RRG-proxmark3.git] / client / luascripts / hf_mfp_raw.lua
blob28fec1474591b918e25925f73a913263092a957a
1 local cmds = require('commands')
2 local lib14a = require('read14a')
3 local getopt = require('getopt')
4 local ansicolors = require('ansicolors')
6 copyright = ''
7 author = 'Dominic Celiano'
8 version = 'v1.0.2'
9 desc = [[
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.
17 example = [[
18 1. script run hf_mfp_raw
20 usage = [[
21 script run hf_mfp_raw [-h]
23 arguments = [[
24 -h : this help
28 -- Default
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
34 POWEROFF = 'OFF'
35 WRITEPERSO = '03A8'
36 COMMITPERSO = '03AA'
37 AUTH_FIRST = '0370'
38 AUTH_CONT = '0372'
39 AUTH_NONFIRST = '0376'
40 PREPAREPC = '03F0'
41 PROXIMITYCHECK = '03F2'
42 VERIFYPC = '03FD'
43 READPLAINNOMACUNMACED = '0336'
45 ---
46 -- This is only meant to be used when errors occur
47 local function oops(err)
48 print('ERROR:', err)
49 core.clearCommandBuffer()
50 return nil, err
51 end
52 ---
53 -- Usage help
54 local function help()
55 print(copyright)
56 print(author)
57 print(version)
58 print(desc)
59 print(ansicolors.cyan..'Usage'..ansicolors.reset)
60 print(usage)
61 print(ansicolors.cyan..'Arguments'..ansicolors.reset)
62 print(arguments)
63 print(ansicolors.cyan..'Example usage'..ansicolors.reset)
64 print(example)
65 end
66 ---
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
72 if crc then
73 flags = flags + lib14a.ISO14A_COMMAND.ISO14A_APPEND_CRC
74 end
75 if power then
76 flags = flags + lib14a.ISO14A_COMMAND.ISO14A_NO_DISCONNECT
77 end
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
82 data = rawdata
84 local ignore_response = false
85 local result, err = command:sendMIX(ignore_response)
86 if result then
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
93 return returned_bytes
94 else
95 return nil
96 end
97 else
98 oops("Error sending the card raw data.")
99 return nil
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:
111 -- 0x90 - OK
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
120 numsectors = 39
121 elseif(cardsize == 2) then
122 numsectors = 31
123 else
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.")
141 --CardMasterKey
142 blocknum = "9000"
143 writeBlock(blocknum, SIXTEEN_BYTES_ZEROS)
144 --CardConfigurationKey
145 blocknum = "9001"
146 writeBlock(blocknum, SIXTEEN_BYTES_ZEROS)
147 --L3SwitchKey
148 blocknum = "9003"
149 writeBlock(blocknum, SIXTEEN_BYTES_ZEROS)
150 --SL1CardAuthKey
151 blocknum = "9004"
152 writeBlock(blocknum, SIXTEEN_BYTES_ZEROS)
153 --L3SectorSwitchKey
154 blocknum = "9006"
155 writeBlock(blocknum, SIXTEEN_BYTES_ZEROS)
156 --L1L3MixSectorSwitchKey
157 blocknum = "9007"
158 writeBlock(blocknum, SIXTEEN_BYTES_ZEROS)
159 --VC Keys
160 --VCProximityKey
161 blocknum = "A001"
162 writeBlock(blocknum, SIXTEEN_BYTES_ZEROS)
163 --VCSelectENCKey
164 blocknum = "A080"
165 writeBlock(blocknum, SIXTEEN_BYTES_ZEROS)
166 --VCSelectMACKey
167 blocknum = "A081"
168 writeBlock(blocknum, SIXTEEN_BYTES_ZEROS)
169 --TransactionMACKey1
170 blocknum = "C000"
171 writeBlock(blocknum, SIXTEEN_BYTES_ZEROS)
172 --TransactionMACConfKey1
173 blocknum = "C001"
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.
226 final_output = ""
227 j = 3
228 for i = 1,8 do
229 final_output = final_output .. string.sub(RndR, j, j + 1) .. string.sub(RndC, j, j + 1)
230 j = j + 4
232 return final_output
235 local function proximityCheck()
236 --PreparePC--
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
243 pps_present = true
244 else
245 pps_present = false
247 pubRespTime = string.sub(response, 7, 10)
248 if(pps_present == true) then
249 pps = string.sub(response, 11, 12)
250 else
251 pps = ''
253 print("OPT = " .. OPT .. " pubRespTime = " .. pubRespTime .. " pps = " .. pps)
255 --PC--
256 RndC = "0001020304050607" --Random Challenge
257 num_rounds = 8 --Needs to be 1, 2, 4, or 8
258 part_len = 8 / num_rounds
259 j = 1
260 RndR = ""
261 for i = 1,num_rounds do
262 pRndC = ""
263 for q = 1,(part_len*2) do
264 pRndC = pRndC .. string.sub(RndC,j,j)
265 j = j + 1
267 commandString = PROXIMITYCHECK .. "0" .. tostring(part_len) .. pRndC
268 pRndR = string.sub(sendRaw(commandString, true, true), 3, 3+part_len)
269 RndR = RndR .. pRndR
271 print("RndC = " .. RndC .. " RndR = " .. RndR)
273 --VerifyPC--
274 MAC_input = "FD" .. OPT .. pubRespTime
275 if pps_present then
276 MAC_input = MAC_input .. pps
278 rnum_concat = ""
279 rnum_concat = RndR .. RndC --temporary (only works for when a single random challenge (8 bytes) is sent)
280 -- j = 1
281 -- for i = 1,8 do
282 -- rnum_concat = rnum_concat .. string.sub(RndR, j, j + 1) .. string.sub(RndC, j, j + 1)
283 -- j = j + 2
284 -- end
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
306 function main(args)
308 local o, a
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)
316 if not info then
317 lib14a.disconnect()
318 return oops(err)
322 response = sendRaw("D01100", true, true)
323 if not response then
324 lib14a.disconnect()
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.
334 --writePerso()
335 --commitPerso("03") --move to SL3
336 --getVersion()
337 proximityCheck()
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 Proxmark3
352 sendRaw(POWEROFF, false, false)
354 lib14a.disconnect()
357 main(args)