2 This is an example of Lua-scripting within proxmark3. This is a lua-side
3 implementation of hf mf chk
5 This code is licensed to you under the terms of the GNU GPL, version 2 or,
6 at your option, any later version. See the LICENSE.txt file for the text of
9 Copyright (C) 2013 m h swende <martin at swende.se>
11 -- Loads the commands-library
12 local cmds = require('commands')
13 -- Load the default keys
14 local keys = require('mf_default_keys')
15 -- Ability to read what card is there
16 local reader = require('read14a')
17 local getopt = require('getopt')
20 local LSHIFT = bit32.lshift
26 usage = "script run mfkeys"
27 desc = ("This script implements Mifare check keys. It utilises a large list of default keys (currently %d keys).\
28 If you want to add more, just put them inside /lualibs/mf_default_keys.lua\n"):format(#keys) ..
35 local TIMEOUT = 10000 -- 10 seconds
40 print("Example usage")
44 --[[This may be moved to a separate library at some point]]
48 -- Asks the user for Yes or No
49 confirm = function(message, ...)
51 message = message .. " [y]/[n] ?"
56 if answer == 'Y' or answer == "y" then
58 elseif answer == 'N' or answer == 'n' then
64 -- Asks the user for input
65 input = function (message , default)
67 if default ~= nil then
68 message = message .. " (default: ".. default.. " )"
70 message = message .." \n > "
74 if answer == '' then answer = default end
81 local function checkCommand(command)
83 --print("Sending this command : " .. tostring(command))
84 local usbcommand = command:getBytes()
85 core.SendCommand(usbcommand)
86 local result = core.WaitForResponseTimeout(cmds.CMD_ACK,TIMEOUT)
88 local count,cmd,arg0 = bin.unpack('LL',result)
90 local count,arg1,arg2,data = bin.unpack('LLH511',result,count)
94 --print("Key not found...")
98 print("Timeout while waiting for response. Increase TIMEOUT in keycheck.lua to wait longer")
99 return nil, "Timeout while waiting for device to respond"
103 function checkBlock(blockNo, keys, keyType)
104 -- The command data is only 512 bytes, each key is 6 bytes, meaning that we can send max 85 keys in one go.
105 -- If there's more, we need to split it up
106 local start, remaining= 1, #keys
108 while remaining > 0 do
109 local n,data = remaining, nil
110 if remaining > 85 then n = 85 end
111 local data = table.concat(keys,"",start,n)
112 print(("Testing block %d, keytype %d, with %d keys"):format(blockNo, keyType, n))
113 local command = Command:new{cmd = cmds.CMD_MIFARE_CHKKEYS,
114 arg1 = OR(blockNo, LSHIFT(keyType,8) ),
118 local status = checkCommand(command)
119 if status then return status, blockNo end
121 remaining = remaining - n
126 -- A function to display the results
127 -- TODO: iceman 2016, still screws up output when a key is not found.
128 local function displayresults(results)
129 local sector, blockNo, keyA, keyB,_
131 print("|---|----------------|---|----------------|---|")
132 print("|sec|key A |res|key B |res|")
133 print("|---|----------------|---|----------------|---|")
135 for sector,_ in pairs(results) do
136 blockNo, keyA, keyB = unpack(_)
137 print(("|%03d| %s | 1 | %s | 1 |"):format(sector, keyA, keyB ))
139 print("|---|----------------|---|----------------|---|")
142 -- A little helper to place an item first in the list
143 local function placeFirst(akey, list)
145 if list[1] == akey then
146 -- Already at pole position
149 local result = {akey}
150 --print(("Putting '%s' first"):format(akey))
151 for i,v in ipairs(list) do
153 result[#result+1] = v
158 local function dumptofile(results)
159 local sector, blockNo, keyA, keyB,_
161 if utils.confirm("Do you wish to save the keys to dumpfile?") then
162 local destination = utils.input("Select a filename to store to", "dumpkeys.bin")
163 local file = io.open(destination, "w")
165 print("Could not write to file ", destination)
172 for sector,_ in pairs(results) do
173 blockNo, keyA, keyB = unpack(_)
174 key_a = key_a .. bin.pack("H",keyA);
175 key_b = key_b .. bin.pack("H",keyB);
183 local function main( args)
185 -- Arguments for the script
186 for o, a in getopt.getopt(args, 'h') do
187 if o == "h" then return help() end
190 result, err = reader.read1443a()
196 print(("Found a %s tag"):format(result.name))
198 core.clearCommandBuffer()
200 local keyType = 0 -- A=0, B=1
201 local numSectors = 16
203 if 0x18 == result.sak then --NXP MIFARE Classic 4k | Plus 4k
204 -- IFARE Classic 4K offers 4096 bytes split into forty sectors,
205 -- of which 32 are same size as in the 1K with eight more that are quadruple size sectors.
207 elseif 0x08 == result.sak then -- NXP MIFARE CLASSIC 1k | Plus 2k
208 -- 1K offers 1024 bytes of data storage, split into 16 sector
210 elseif 0x09 == result.sak then -- NXP MIFARE Mini 0.3k
211 -- MIFARE Classic mini offers 320 bytes split into five sectors.
213 elseif 0x10 == result.sak then-- "NXP MIFARE Plus 2k"
216 print("I don't know how many sectors there are on this type of card, defaulting to 16")
220 for sector=1,numSectors,1 do
223 The mifare Classic 1k card has 16 sectors of 4 data blocks each.
224 The first 32 sectors of a mifare Classic 4k card consists of 4 data blocks and the remaining
225 8 sectors consist of 16 data blocks.
227 local blockNo = sector * 4 -1
230 blockNo = 32*4+ (sector-32)*16 -1
233 local keyA = checkBlock(blockNo, keys, 0)
234 if keyA then keys = placeFirst(keyA, keys) end
237 local keyB = checkBlock(blockNo, keys, 1)
238 if keyB then keys = placeFirst(keyB, keys) end
241 result[sector] = {blockNo, keyA, keyB }
243 -- Check if user aborted
244 if core.ukbhit() then
245 print("Aborted by user")
249 displayresults(result)