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) ..
36 local TIMEOUT = 10000 -- 10 seconds
41 print("Example usage")
45 --[[This may be moved to a separate library at some point]]
49 -- Asks the user for Yes or No
50 confirm = function(message, ...)
52 message = message .. " [y]/[n] ?"
57 if answer == 'Y' or answer == "y" then
59 elseif answer == 'N' or answer == 'n' then
65 -- Asks the user for input
66 input = function (message , default)
68 if default ~= nil then
69 message = message .. " (default: ".. default.. " )"
71 message = message .." \n > "
75 if answer == '' then answer = default end
82 local function checkCommand(command)
84 --print("Sending this command : " .. tostring(command))
85 local usbcommand = command:getBytes()
86 core.SendCommand(usbcommand)
87 local result = core.WaitForResponseTimeout(cmds.CMD_ACK,TIMEOUT)
89 local count,cmd,arg0 = bin.unpack('LL',result)
91 local count,arg1,arg2,data = bin.unpack('LLH511',result,count)
95 --print("Key not found...")
99 print("Timeout while waiting for response. Increase TIMEOUT in keycheck.lua to wait longer")
100 return nil, "Timeout while waiting for device to respond"
104 function checkBlock(blockNo, keys, keyType)
105 -- The command data is only 512 bytes, each key is 6 bytes, meaning that we can send max 85 keys in one go.
106 -- If there's more, we need to split it up
107 local start, remaining= 1, #keys
109 while remaining > 0 do
110 local n,data = remaining, nil
111 if remaining > 85 then n = 85 end
112 local data = table.concat(keys,"",start,n)
113 print(("Testing block %d, keytype %d, with %d keys"):format(blockNo, keyType, n))
114 local command = Command:new{cmd = cmds.CMD_MIFARE_CHKKEYS,
115 arg1 = OR(blockNo, LSHIFT(keyType,8) ),
119 local status = checkCommand(command)
120 if status then return status, blockNo end
122 remaining = remaining - n
127 -- A function to display the results
128 -- TODO: iceman 2016, still screws up output when a key is not found.
129 local function displayresults(results)
130 local sector, blockNo, keyA, keyB,_
132 print("|---|----------------|---|----------------|---|")
133 print("|sec|key A |res|key B |res|")
134 print("|---|----------------|---|----------------|---|")
136 for sector,_ in pairs(results) do
137 blockNo, keyA, keyB = unpack(_)
138 print(("|%03d| %s | 1 | %s | 1 |"):format(sector, keyA, keyB ))
140 print("|---|----------------|---|----------------|---|")
143 -- A little helper to place an item first in the list
144 local function placeFirst(akey, list)
146 if list[1] == akey then
147 -- Already at pole position
150 local result = {akey}
151 --print(("Putting '%s' first"):format(akey))
152 for i,v in ipairs(list) do
154 result[#result+1] = v
159 local function dumptofile(results)
160 local sector, blockNo, keyA, keyB,_
162 if utils.confirm("Do you wish to save the keys to dumpfile?") then
163 local destination = utils.input("Select a filename to store to", "dumpkeys.bin")
164 local file = io.open(destination, "w")
166 print("Could not write to file ", destination)
173 for sector,_ in pairs(results) do
174 blockNo, keyA, keyB = unpack(_)
175 key_a = key_a .. bin.pack("H",keyA);
176 key_b = key_b .. bin.pack("H",keyB);
183 local function printkeys()
188 print ('Number of keys: '..#keys)
191 local function main( args)
193 -- Arguments for the script
194 for o, a in getopt.getopt(args, 'hp') do
195 if o == "h" then return help() end
196 if o == "p" then return printkeys() end
199 result, err = reader.read1443a()
205 print(("Found a %s tag"):format(result.name))
207 core.clearCommandBuffer()
209 local keyType = 0 -- A=0, B=1
210 local numSectors = 16
212 if 0x18 == result.sak then --NXP MIFARE Classic 4k | Plus 4k
213 -- IFARE Classic 4K offers 4096 bytes split into forty sectors,
214 -- of which 32 are same size as in the 1K with eight more that are quadruple size sectors.
216 elseif 0x08 == result.sak then -- NXP MIFARE CLASSIC 1k | Plus 2k
217 -- 1K offers 1024 bytes of data storage, split into 16 sector
219 elseif 0x09 == result.sak then -- NXP MIFARE Mini 0.3k
220 -- MIFARE Classic mini offers 320 bytes split into five sectors.
222 elseif 0x10 == result.sak then-- "NXP MIFARE Plus 2k"
225 print("I don't know how many sectors there are on this type of card, defaulting to 16")
229 for sector=1,numSectors,1 do
232 The mifare Classic 1k card has 16 sectors of 4 data blocks each.
233 The first 32 sectors of a mifare Classic 4k card consists of 4 data blocks and the remaining
234 8 sectors consist of 16 data blocks.
236 local blockNo = sector * 4 -1
239 blockNo = 32*4+ (sector-32)*16 -1
242 local keyA = checkBlock(blockNo, keys, 0)
243 if keyA then keys = placeFirst(keyA, keys) end
246 local keyB = checkBlock(blockNo, keys, 1)
247 if keyB then keys = placeFirst(keyB, keys) end
250 result[sector] = {blockNo, keyA, keyB }
252 -- Check if user aborted
253 if core.ukbhit() then
254 print("Aborted by user")
258 displayresults(result)