proxmark3/client/luascripts/hf_mf_keycheck.lua

296 lines
8.2 KiB
Lua
Raw Normal View History

2013-06-07 17:26:56 +08:00
--[[
2019-05-18 23:53:08 +08:00
This is an example of Lua-scripting within Proxmark3. This is a lua-side
2019-03-09 17:34:43 +08:00
implementation of hf mf chk
2013-06-07 17:26:56 +08:00
2019-03-09 17:34:43 +08:00
This code is licensed to you under the terms of the GNU GPL, version 2 or,
at your option, any later version. See the LICENSE.txt file for the text of
the license.
2019-03-09 17:34:43 +08:00
Copyright (C) 2013 m h swende <martin at swende.se>
--]]
2013-06-07 17:26:56 +08:00
local cmds = require('commands')
2019-08-23 04:16:33 +08:00
local keylist = require('mfc_default_keys')
local lib14a = require('read14a')
local getopt = require('getopt')
local utils = require('utils')
2020-04-05 18:41:38 +08:00
local ansicolors = require('ansicolors')
2019-04-29 07:51:00 +08:00
copyright = ''
author = "Holiman"
2020-04-05 18:41:38 +08:00
version = 'v1.0.2'
desc = ("This script implements Mifare check keys.\
It utilises a large list of default keys (currently %d keys).\
2019-08-23 04:16:33 +08:00
If you want to add more, just put them inside /lualibs/mfc_default_keys.lua\n"):format(#keylist)
2019-04-29 07:51:00 +08:00
example = [[
1. script run hf_mf_keycheck
2019-04-29 07:51:00 +08:00
]]
usage = [[
script run hf_mf_keycheck [-p]
2020-04-05 18:41:38 +08:00
]]
arguments = [[
2019-03-09 17:34:43 +08:00
-h : this help
-p : print keys
]]
2013-06-07 17:26:56 +08:00
2019-05-08 17:17:14 +08:00
local PM3_SUCCESS = 0 -- needs to be refactored into own like pm3_cmd
2013-06-07 17:26:56 +08:00
local TIMEOUT = 10000 -- 10 seconds
2019-03-09 17:34:43 +08:00
---
2017-12-21 17:15:10 +08:00
-- This is only meant to be used when errors occur
local function oops(err)
2019-04-29 07:51:00 +08:00
print('ERROR:', err)
core.clearCommandBuffer()
return nil, err
2017-12-21 17:15:10 +08:00
end
2019-03-09 17:34:43 +08:00
---
-- Usage help
2017-12-21 17:15:10 +08:00
local function help()
2019-04-29 07:51:00 +08:00
print(copyright)
print(author)
print(version)
2019-03-09 17:34:43 +08:00
print(desc)
2020-04-05 18:41:38 +08:00
print(ansicolors.cyan..'Usage'..ansicolors.reset)
2019-04-29 07:51:00 +08:00
print(usage)
2020-04-05 18:41:38 +08:00
print(ansicolors.cyan..'Arguments'..ansicolors.reset)
print(arguments)
print(ansicolors.cyan..'Example usage'..ansicolors.reset)
print(example)
end
--
-- waits for answer from pm3 device
2019-04-29 07:51:00 +08:00
local function checkCommand(response)
if not response then
2020-09-23 06:11:11 +08:00
print("Timeout while waiting for response. Increase TIMEOUT in hf_mf_keycheck.lua to wait longer")
2019-03-09 17:34:43 +08:00
return nil, "Timeout while waiting for device to respond"
end
2019-04-30 04:41:28 +08:00
if response.Status == PM3_SUCCESS then
--decode data array
key = response.Data:sub(1, 12)
found = tonumber(response.Data:sub(13,14))
if found == 1 then
return key
end
2019-04-29 07:51:00 +08:00
end
return nil
2013-06-07 17:26:56 +08:00
end
local function checkBlock(blockno, testkeys, keytype)
-- The command data is only 512 bytes,
-- each key is 6 bytes,
-- NG args inside dataarray is 4 bytes. That give us (512-4)/6 or max 84 keys in one go.
2019-03-09 17:34:43 +08:00
-- If there's more, we need to split it up
local start, remaining = 1, #testkeys
local maxchunk = math.floor((512-4)/6)
2019-03-09 17:34:43 +08:00
local chunksize = remaining
if remaining > maxchunk then chunksize = maxchunk end
2019-03-09 17:34:43 +08:00
local n = chunksize
2019-04-30 04:41:28 +08:00
2019-03-09 17:34:43 +08:00
while remaining > 0 do
local d0 = ('%02X%02X00%02X'):format(keytype, blockno, chunksize)
2019-03-09 17:34:43 +08:00
local d1 = table.concat(testkeys, "", start, n)
2019-04-30 04:41:28 +08:00
core.clearCommandBuffer()
2019-03-09 17:34:43 +08:00
print(("Testing block %d, keytype %d, with %d keys"):format(blockno, keytype, chunksize))
2019-04-29 07:51:00 +08:00
local c = Command:newNG{cmd = cmds.CMD_HF_MIFARE_CHKKEYS, data = d0..d1}
key, err = checkCommand(c:sendNG(false))
2019-04-30 04:41:28 +08:00
if key then return key, blockno end
2019-03-09 17:34:43 +08:00
start = start + chunksize
remaining = remaining - chunksize
if remaining < maxchunk then chunksize = remaining end
2019-03-09 17:34:43 +08:00
n = n + chunksize
end
return nil
2013-06-07 17:26:56 +08:00
end
2019-04-29 07:51:00 +08:00
---
2013-06-07 17:26:56 +08:00
-- A function to display the results
local function display_results(keys)
2019-03-09 17:34:43 +08:00
local sector, keyA, keyB, succA, succB
print('')
print('|---|----------------|---|----------------|---|')
print('|sec|key A |res|key B |res|')
print('|---|----------------|---|----------------|---|')
for sector = 0, #keys do
succA, succB, keyA, keyB = unpack(keys[sector])
print(('|%03d| %s | %s | %s | %s |'):format(sector, keyA, succA, keyB, succB))
end
print('|---|----------------|---|----------------|---|')
2013-06-07 17:26:56 +08:00
end
2019-04-29 07:51:00 +08:00
---
2013-06-07 17:26:56 +08:00
-- A little helper to place an item first in the list
local function placeFirst(akey, list)
2019-03-09 17:34:43 +08:00
akey = akey:lower()
if list[1] == akey then
-- Already at pole position
return list
end
local result = {akey}
--print(("Putting '%s' first"):format(akey))
for i,v in ipairs(list) do
if v ~= akey then
result[#result+1] = v
end
end
return result
2013-06-07 17:26:56 +08:00
end
--[[
2019-03-09 17:34:43 +08:00
The mifare Classic 1k card has 16 sectors of 4 data blocks each.
The first 32 sectors of a mifare Classic 4k card consists of 4 data blocks and the remaining
2019-03-09 17:34:43 +08:00
8 sectors consist of 16 data blocks.
--]]
local function get_blockno(s)
2013-06-07 17:26:56 +08:00
2019-03-09 17:34:43 +08:00
local b, sector
2019-03-09 17:34:43 +08:00
if type(s) == 'string' then
sector = tonumber(s)
else
sector = s
end
2019-03-09 17:34:43 +08:00
if sector < 32 then
b = sector * 4
else
b = 32 * 4 + (sector - 32) * 16
end
2019-03-09 17:34:43 +08:00
return ('%02x'):format(b)
end
--
-- dumps all keys to file
local function dumptofile(uid, keys)
2019-03-09 17:34:43 +08:00
if utils.confirm('Do you wish to save the keys to dumpfile?') then
local filename = ('hf-mf-%s-key.bin'):format(uid);
local destination = utils.input('Select a filename to store to', filename)
2019-03-09 17:34:43 +08:00
local file = io.open(destination, 'wb')
if file == nil then
print('Could not write to file ', destination)
return
end
local key_a = ''
local key_b = ''
--for sector,_ in pairs(keys) do
for sector = 0, #keys do
local succA, succB, keyA, keyB = unpack(keys[sector])
key_a = key_a .. bin.pack('H', keyA);
key_b = key_b .. bin.pack('H', keyB);
end
file:write(key_a)
file:write(key_b)
file:close()
end
end
2019-04-29 07:51:00 +08:00
---
--
2019-03-09 17:34:43 +08:00
local function printkeys()
for i=1, #keylist do
print(i, keylist[i])
end
print ('Number of keys: '..#keylist)
end
2019-04-29 07:51:00 +08:00
---
--
local function perform_check(uid, numsectors)
2019-03-09 17:34:43 +08:00
local keyType = 0 -- A=0, B=1
-- empty list of found keys
local keys = {}
for i = 0, numsectors-1 do
keys[i] = {0,0,'',''}
end
core.fast_push_mode(true)
local start_time = os.time()
2019-05-01 07:38:52 +08:00
2019-03-09 17:34:43 +08:00
for sector = 0, #keys do
-- Check if user aborted
if core.kbd_enter_pressed() then
2019-03-09 17:34:43 +08:00
print('Aborted by user')
break
end
local targetblock = tonumber(get_blockno(sector), 16)
local succA, succB, keyA, keyB = unpack(keys[sector])
local keyA = checkBlock(targetblock, keylist, 0)
if keyA then succA = 1; keylist = placeFirst(keyA, keylist) end
keyA = keyA or '------------'
local keyB = checkBlock(targetblock, keylist, 1)
if keyB then succB = 1; keylist = placeFirst(keyB, keylist) end
keyB = keyB or '------------'
keys[sector] = {succA, succB, keyA, keyB}
end
2019-05-01 07:38:52 +08:00
local end_time = os.time()
print('')
2020-09-23 06:11:11 +08:00
print('[+] hf_mf_keycheck - Checkkey execution time: '..os.difftime(end_time, start_time)..' sec')
2019-05-01 07:38:52 +08:00
core.fast_push_mode(false)
2019-05-01 07:38:52 +08:00
2019-03-09 17:34:43 +08:00
display_results(keys)
2019-05-01 07:38:52 +08:00
2019-03-09 17:34:43 +08:00
-- save to dumpkeys.bin
dumptofile(uid, keys)
end
--
-- shows tag information
local function taginfo(tag)
2019-03-09 17:34:43 +08:00
local sectors = 16
-- Show tag info
print((' Found tag %s'):format(tag.name))
if 0x18 == tag.sak then --NXP MIFARE Classic 4k | Plus 4k
-- MIFARE Classic 4K offers 4096 bytes split into forty sectors,
-- of which 32 are same size as in the 1K with eight more that are quadruple size sectors.
sectors = 40
elseif 0x08 == tag.sak then -- NXP MIFARE CLASSIC 1k | Plus 2k
-- 1K offers 1024 bytes of data storage, split into 16 sector
sectors = 16
elseif 0x09 == tag.sak then -- NXP MIFARE Mini 0.3k
-- MIFARE Classic mini offers 320 bytes split into five sectors.
sectors = 5
elseif 0x10 == tag.sak then-- "NXP MIFARE Plus 2k"
sectors = 32
else
print("I don't know how many sectors there are on this type of card, defaulting to 16")
end
return sectors
end
---
-- The main entry point
local function main(args)
2019-03-09 17:34:43 +08:00
-- Arguments for the script
for o, a in getopt.getopt(args, 'hp') do
2019-04-29 07:51:00 +08:00
if o == 'h' then return help() end
if o == 'p' then return printkeys() end
2019-03-09 17:34:43 +08:00
end
-- identify tag
tag, err = lib14a.read(false, true)
if not tag then return oops(err) end
2019-05-01 07:38:52 +08:00
2020-04-05 18:41:38 +08:00
local numSectors = 16
2019-03-09 17:34:43 +08:00
-- detect sectors and print taginfo
numsectors = taginfo(tag)
perform_check(tag.uid, numsectors)
2013-06-07 17:26:56 +08:00
end
2019-03-12 07:12:26 +08:00
main( args)