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 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')
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
)
25 1. script run hf_mf_keycheck
28 script run hf_mf_keycheck [-p]
35 local PM3_SUCCESS
= 0 -- needs to be refactored into own like pm3_cmd
37 local TIMEOUT
= 10000 -- 10 seconds
39 -- This is only meant to be used when errors occur
40 local function oops(err
)
42 core
.clearCommandBuffer()
52 print(ansicolors
.cyan
..'Usage'..ansicolors
.reset
)
54 print(ansicolors
.cyan
..'Arguments'..ansicolors
.reset
)
56 print(ansicolors
.cyan
..'Example usage'..ansicolors
.reset
)
60 -- waits for answer from pm3 device
61 local function checkCommand(response
)
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"
67 if response
.Status
== PM3_SUCCESS
then
69 key
= response
.Data
:sub(1, 12)
70 found
= tonumber(response
.Data
:sub(13,14))
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
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
113 -- A function to display the results
114 local function display_results(keys
)
115 local sector
, keyA
, keyB
, succA
, succB
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
)
131 if list
[1] == akey
then
132 -- Already at pole position
135 local result
= {akey
}
136 --print(("Putting '%s' first"):format(akey))
137 for i
,v
in ipairs(list
) do
139 result
[#result
+1] = v
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.
149 local function get_blockno(s
)
153 if type(s
) == 'string' then
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')
174 print('Could not write to file ', destination
)
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
);
194 local function printkeys()
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
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')
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()
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)
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.
261 elseif 0x08 == tag.sak
then -- NXP MIFARE CLASSIC 1k | Plus 2k
262 -- 1K offers 1024 bytes of data storage, split into 16 sector
264 elseif 0x09 == tag.sak
then -- NXP MIFARE Mini 0.3k
265 -- MIFARE Classic mini offers 320 bytes split into five sectors.
267 elseif 0x10 == tag.sak
then-- "NXP MIFARE Plus 2k"
270 print("I don't know how many sectors there are on this type of card, defaulting to 16")
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
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
)