Merge pull request #1327 from Pondorasti/patch-1
[RRG-proxmark3.git] / client / luascripts / hf_mf_keycheck.lua
blob59680cbe48ebe160d60aef97216e029d03d504de
1 --[[
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
7 the license.
9 Copyright (C) 2013 m h swende <martin at swende.se>
10 --]]
11 local cmds = require('commands')
12 local keylist = require('mfc_default_keys')
13 local lib14a = require('read14a')
14 local getopt = require('getopt')
15 local utils = require('utils')
16 local ansicolors = require('ansicolors')
18 copyright = ''
19 author = "Holiman"
20 version = 'v1.0.2'
21 desc = ("This script implements Mifare check keys.\
22 It utilises a large list of default keys (currently %d keys).\
23 If you want to add more, just put them inside /lualibs/mfc_default_keys.lua\n"):format(#keylist)
24 example = [[
25 1. script run hf_mf_keycheck
27 usage = [[
28 script run hf_mf_keycheck [-p]
30 arguments = [[
31 -h : this help
32 -p : print keys
35 local PM3_SUCCESS = 0 -- needs to be refactored into own like pm3_cmd
37 local TIMEOUT = 10000 -- 10 seconds
38 ---
39 -- This is only meant to be used when errors occur
40 local function oops(err)
41 print('ERROR:', err)
42 core.clearCommandBuffer()
43 return nil, err
44 end
45 ---
46 -- Usage help
47 local function help()
48 print(copyright)
49 print(author)
50 print(version)
51 print(desc)
52 print(ansicolors.cyan..'Usage'..ansicolors.reset)
53 print(usage)
54 print(ansicolors.cyan..'Arguments'..ansicolors.reset)
55 print(arguments)
56 print(ansicolors.cyan..'Example usage'..ansicolors.reset)
57 print(example)
58 end
60 -- waits for answer from pm3 device
61 local function checkCommand(response)
62 if not response then
63 print("Timeout while waiting for response. Increase TIMEOUT in hf_mf_keycheck.lua to wait longer")
64 return nil, "Timeout while waiting for device to respond"
65 end
67 if response.Status == PM3_SUCCESS then
68 --decode data array
69 key = response.Data:sub(1, 12)
70 found = tonumber(response.Data:sub(13,14))
71 if found == 1 then
72 return key
73 end
74 end
75 return nil
76 end
78 local function checkBlock(blockno, testkeys, keytype)
80 -- The command data is only 512 bytes,
81 -- each key is 6 bytes,
82 -- NG args inside dataarray is 4 bytes. That give us (512-4)/6 or max 84 keys in one go.
83 -- If there's more, we need to split it up
84 local start, remaining = 1, #testkeys
85 local maxchunk = math.floor((512-4)/6)
86 local chunksize = remaining
87 if remaining > maxchunk then chunksize = maxchunk end
88 local n = chunksize
90 while remaining > 0 do
92 local d0 = ('%02X%02X00%02X'):format(keytype, blockno, chunksize)
93 local d1 = table.concat(testkeys, "", start, n)
95 core.clearCommandBuffer()
97 print(("Testing block %d, keytype %d, with %d keys"):format(blockno, keytype, chunksize))
99 local c = Command:newNG{cmd = cmds.CMD_HF_MIFARE_CHKKEYS, data = d0..d1}
100 key, err = checkCommand(c:sendNG(false))
102 if key then return key, blockno end
104 start = start + chunksize
105 remaining = remaining - chunksize
107 if remaining < maxchunk then chunksize = remaining end
108 n = n + chunksize
110 return nil
113 -- A function to display the results
114 local function display_results(keys)
115 local sector, keyA, keyB, succA, succB
116 print('')
117 print('|---|----------------|---|----------------|---|')
118 print('|sec|key A |res|key B |res|')
119 print('|---|----------------|---|----------------|---|')
121 for sector = 0, #keys do
122 succA, succB, keyA, keyB = unpack(keys[sector])
123 print(('|%03d| %s | %s | %s | %s |'):format(sector, keyA, succA, keyB, succB))
125 print('|---|----------------|---|----------------|---|')
128 -- A little helper to place an item first in the list
129 local function placeFirst(akey, list)
130 akey = akey:lower()
131 if list[1] == akey then
132 -- Already at pole position
133 return list
135 local result = {akey}
136 --print(("Putting '%s' first"):format(akey))
137 for i,v in ipairs(list) do
138 if v ~= akey then
139 result[#result+1] = v
142 return result
144 --[[
145 The mifare Classic 1k card has 16 sectors of 4 data blocks each.
146 The first 32 sectors of a mifare Classic 4k card consists of 4 data blocks and the remaining
147 8 sectors consist of 16 data blocks.
148 --]]
149 local function get_blockno(s)
151 local b, sector
153 if type(s) == 'string' then
154 sector = tonumber(s)
155 else
156 sector = s
159 if sector < 32 then
160 b = sector * 4
161 else
162 b = 32 * 4 + (sector - 32) * 16
164 return ('%02x'):format(b)
167 -- dumps all keys to file
168 local function dumptofile(uid, keys)
169 if utils.confirm('Do you wish to save the keys to dumpfile?') then
170 local filename = ('hf-mf-%s-key.bin'):format(uid);
171 local destination = utils.input('Select a filename to store to', filename)
172 local file = io.open(destination, 'wb')
173 if file == nil then
174 print('Could not write to file ', destination)
175 return
178 local key_a = ''
179 local key_b = ''
181 --for sector,_ in pairs(keys) do
182 for sector = 0, #keys do
183 local succA, succB, keyA, keyB = unpack(keys[sector])
184 key_a = key_a .. bin.pack('H', keyA);
185 key_b = key_b .. bin.pack('H', keyB);
187 file:write(key_a)
188 file:write(key_b)
189 file:close()
194 local function printkeys()
195 for i=1, #keylist do
196 print(i, keylist[i])
198 print ('Number of keys: '..#keylist)
202 local function perform_check(uid, numsectors)
204 local keyType = 0 -- A=0, B=1
206 -- empty list of found keys
207 local keys = {}
208 for i = 0, numsectors-1 do
209 keys[i] = {0,0,'',''}
212 core.fast_push_mode(true)
214 local start_time = os.time()
216 for sector = 0, #keys do
217 -- Check if user aborted
218 if core.kbd_enter_pressed() then
219 print('Aborted by user')
220 break
223 local targetblock = tonumber(get_blockno(sector), 16)
225 local succA, succB, keyA, keyB = unpack(keys[sector])
227 local keyA = checkBlock(targetblock, keylist, 0)
228 if keyA then succA = 1; keylist = placeFirst(keyA, keylist) end
229 keyA = keyA or '------------'
231 local keyB = checkBlock(targetblock, keylist, 1)
232 if keyB then succB = 1; keylist = placeFirst(keyB, keylist) end
233 keyB = keyB or '------------'
235 keys[sector] = {succA, succB, keyA, keyB}
238 local end_time = os.time()
239 print('')
240 print('[+] hf_mf_keycheck - Checkkey execution time: '..os.difftime(end_time, start_time)..' sec')
242 core.fast_push_mode(false)
244 display_results(keys)
246 -- save to dumpkeys.bin
247 dumptofile(uid, keys)
250 -- shows tag information
251 local function taginfo(tag)
253 local sectors = 16
254 -- Show tag info
255 print((' Found tag %s'):format(tag.name))
257 if 0x18 == tag.sak then --NXP MIFARE Classic 4k | Plus 4k
258 -- MIFARE Classic 4K offers 4096 bytes split into forty sectors,
259 -- of which 32 are same size as in the 1K with eight more that are quadruple size sectors.
260 sectors = 40
261 elseif 0x08 == tag.sak then -- NXP MIFARE CLASSIC 1k | Plus 2k
262 -- 1K offers 1024 bytes of data storage, split into 16 sector
263 sectors = 16
264 elseif 0x09 == tag.sak then -- NXP MIFARE Mini 0.3k
265 -- MIFARE Classic mini offers 320 bytes split into five sectors.
266 sectors = 5
267 elseif 0x10 == tag.sak then-- "NXP MIFARE Plus 2k"
268 sectors = 32
269 else
270 print("I don't know how many sectors there are on this type of card, defaulting to 16")
272 return sectors
275 -- The main entry point
276 local function main(args)
278 -- Arguments for the script
279 for o, a in getopt.getopt(args, 'hp') do
280 if o == 'h' then return help() end
281 if o == 'p' then return printkeys() end
283 -- identify tag
284 tag, err = lib14a.read(false, true)
285 if not tag then return oops(err) end
287 local numSectors = 16
289 -- detect sectors and print taginfo
290 numsectors = taginfo(tag)
292 perform_check(tag.uid, numsectors)
295 main( args)