RRG-Proxmark3/client/luascripts/hf_mfu_ultra.lua
iceman1001 84b565bec4 style
2025-05-19 22:31:41 +02:00

358 lines
12 KiB
Lua

local ansicolors = require('ansicolors')
local cmds = require('commands')
local getopt = require('getopt')
local lib14a = require('read14a')
local utils = require('utils')
-- globals
copyright = ''
author = 'Dmitry Malenok'
version = 'v1.0.0'
desc = [[
The script provides functionality for writing Mifare Ultralight Ultra/UL-5 tags.
]]
example = [[
-- restpre (write) dump to tag
]]..ansicolors.yellow..[[script run hf_mfu_ultra -f hf-mfu-3476FF1514D866-dump.bin -k ffffffff -r]]..ansicolors.reset..[[
-- wipe tag (]]..ansicolors.red..[[Do not use it with UL-5!]]..ansicolors.reset..[[)
]]..ansicolors.yellow..[[script run hf_mfu_ultra -k 1d237f76 -w ]]..ansicolors.reset..[[
]]
usage = [[
script run hf_mfu_ultra -h -f <dump filename> -k <passwd> -w -r
]]
arguments = [[
-h this help
-f filename for the datadump to read (bin)
-k pwd to use with the restore and wipe operations
-r restore a binary dump to tag
-w wipe tag (]]..ansicolors.red..[[Do not use it with UL-5!]]..ansicolors.reset..[[)
]]
local _password = nil
local _defaultPassword = 'FFFFFFFF'
local _dumpstart = 0x38*2 + 1
---
--- Handles errors
local function error(err)
print(ansicolors.red.."ERROR:"..ansicolors.reset, err)
core.clearCommandBuffer()
return nil, err
end
---
-- sets the global password variable
local function setPassword(password)
if password == nil or #password == 0 then
_password = nil;
elseif #password ~= 8 then
return false, 'Password must be 4 hex bytes'
else
_password = password
end
return true, 'Sets'
end
--- Parses response data
local function parseResponse(rawResponse)
local resp = Command.parse(rawResponse)
local len = tonumber(resp.arg1) * 2
return string.sub(tostring(resp.data), 0, len);
end
---
--- Sends raw data to PM3 and returns raw response if any
local function sendRaw(rawdata, options)
local flags = lib14a.ISO14A_COMMAND.ISO14A_RAW
if options.keep_signal then
flags = flags + lib14a.ISO14A_COMMAND.ISO14A_NO_DISCONNECT
end
if options.connect then
flags = flags + lib14a.ISO14A_COMMAND.ISO14A_CONNECT
end
if options.no_select then
flags = flags + lib14a.ISO14A_COMMAND.ISO14A_NO_SELECT
end
if options.append_crc then
flags = flags + lib14a.ISO14A_COMMAND.ISO14A_APPEND_CRC
end
local arg2 = #rawdata / 2
if options.bits7 then
arg2 = arg2 | tonumber(bit32.lshift(7, 16))
end
local command = Command:newMIX{cmd = cmds.CMD_HF_ISO14443A_READER,
arg1 = flags,
arg2 = arg2,
data = rawdata}
return command:sendMIX(options.ignore_response)
end
---
--- Sends raw data to PM3 and returns parsed response
local function sendWithResponse(payload, options)
local opts;
if options then
opts = options
else
opts = {ignore_response = false, keep_signal = true, append_crc = true}
end
local rawResp, err = sendRaw(payload, opts)
if err then return err end
return parseResponse(rawResp)
end
---
-- Authenticates if password is provided
local function authenticate(password)
if password then
local resp, err = sendWithResponse('1B'..password)
if err then return err end
-- looking for 2 bytes (4 symbols) of PACK and 2 bytes (4 symbols) of CRC
if not resp or #resp ~=8 then return false, 'It seems that password is wrong' end
return true
end
return true
end
--
-- selects tag and authenticates if password is provided
local function connect()
core.clearCommandBuffer()
local info, err = lib14a.read(true, true)
if err then
lib14a.disconnect()
return false, err
end
core.clearCommandBuffer()
return authenticate(_password)
end
--
-- reconnects and selects tag again
local function reconnect()
lib14a.disconnect()
utils.Sleep(1)
local info, err = connect()
if not info then return false, "Unable to select tag: "..err end
return true
end
--
-- checks tag version
local function checkTagVersion()
local resp, err = sendWithResponse('60');
if err or resp == nil then return false, err end
if string.find(resp, '0034210101000E03') ~= 1 then return false, 'Wrong tag version: '..string.sub(resp,1,-5) end
return true
end
--
-- sends magic wakeup command
local function magicWakeup()
io.write('Sending magic wakeup command...')
local resp, err = sendRaw('5000', {ignore_response = false, append_crc = true})
if err or resp == nil then return false, "Unable to send first magic wakeup command: "..err end
resp, err = sendRaw('40', {connect = true, no_select = true, ignore_response = false, keep_signal = true, append_crc = false, bits7 = true})
if err or resp == nil then return false, "Unable to send first magic wakeup command: "..err end
resp, err = sendRaw('43', {ignore_response = false, keep_signal = true, append_crc = false})
if err or resp == nil then return false, "Unable to send second magic wakeup command: "..err end
print(ansicolors.green..'done'..ansicolors.reset..'.')
return true
end
--
-- Writes dump to tag
local function writeDump(filename)
print(string.rep('--',20))
local info, err = connect()
if not info then return false, "Unable to select tag: "..err end
info, err = checkTagVersion()
if not info then return info, err end
-- load dump from file
if not filename then return false, 'No dump filename provided' end
io.write('Loading dump from file '..filename..'...')
local dump
dump, err = utils.ReadDumpFile(filename)
if not dump then return false, err end
if #dump ~= _dumpstart - 1 + 0xa4*2 then return false, 'Invalid dump file' end
print(ansicolors.green..'done'..ansicolors.reset..'.')
local resp
for i = 3, 0x23 do
local blockStart = i * 8 + _dumpstart
local block = string.sub(dump, blockStart, blockStart + 7)
local cblock = string.format('%02x',i)
io.write('Writing block 0x'..cblock..'...')
resp, err = sendWithResponse('A2'..cblock..block)
if err ~= nil then return false, err end
print(ansicolors.green..'done'..ansicolors.reset..'.')
end
-- set password
io.write('Setting password and pack ')
info, err = reconnect()
if not info then return false, err end
local passwordStart = 0x27*8 + _dumpstart
local password = string.sub(dump, passwordStart, passwordStart + 7)
local packBlock = string.sub(dump, passwordStart+8, passwordStart + 15)
io.write('(password: '..password..') (pack block: '..packBlock..')...')
resp, err = sendWithResponse('A227'..password)
if err ~= nil then return false, err end
resp, err = sendWithResponse('A228'..packBlock)
if err ~= nil then return false, err end
if not setPassword(password) then return false, 'Unable to set password' end
info, err = reconnect()
if not info then return false, err end
print(ansicolors.green..'done'..ansicolors.reset..'.')
-- set configs and locks
for i = 0x24, 0x26 do
local blockStart = i * 8 + _dumpstart
local block = string.sub(dump, blockStart, blockStart + 7)
local cblock = string.format('%02x',i)
io.write('Writing block 0x'..cblock..'...')
resp, err = sendWithResponse('A2'..cblock..block)
if err ~= nil then return false, err end
info, err = reconnect()
if not info then return false, err end
print(ansicolors.green..'done'..ansicolors.reset..'.')
end
info, err = magicWakeup()
if not info then return false, err end
-- set uid and locks
for i = 0x2, 0x0, -1 do
local blockStart = i * 8 + _dumpstart
local block = string.sub(dump, blockStart, blockStart + 7)
local cblock = string.format('%02x',i)
io.write('Writing block 0x'..cblock..'...')
resp, err = sendWithResponse('A2'..cblock..block, {connect = i == 0x2, ignore_response = false, keep_signal = i ~= 0, append_crc = true})
if err ~= nil then return false, err end
print(ansicolors.green..'done'..ansicolors.reset..'.')
end
print(ansicolors.green..'The dump has been written to the tag.'..ansicolors.reset)
return true
end
--
-- Wipes tag
local function wipe()
print(string.rep('--',20))
print('Wiping tag')
local info, err = connect()
if not info then return false, "Unable to select tag: "..err end
info, err = checkTagVersion()
if not info then return info, err end
local resp
-- clear lock bytes on page 0x02
resp, err = sendWithResponse('3000')
if err or resp == nil then return false, err end
local currentLowLockPage = string.sub(resp,17,24)
if(string.sub(currentLowLockPage,5,8) ~= '0000') then
info, err = magicWakeup()
if not info then return false, err end
local newLowLockPage = string.sub(currentLowLockPage,1,4)..'0000'
io.write('Clearing lock bytes on page 0x02...')
resp, err = sendWithResponse('A202'..newLowLockPage, {connect = true, ignore_response = false, keep_signal = true, append_crc = true})
if err ~= nil then return false, err end
print(ansicolors.green..'done'..ansicolors.reset..'.')
end
-- clear lock bytes on page 0x24
io.write('Clearing lock bytes on page 0x24...')
info, err = reconnect()
if not info then return false, err end
resp, err = sendWithResponse('A224000000BD')
if err ~= nil then return false, err end
print(ansicolors.green..'done'..ansicolors.reset..'.')
-- clear configs
io.write('Clearing cfg0 and cfg1...')
resp, err = sendWithResponse('A225000000FF')
if err ~= nil then return false, err end
resp, err = sendWithResponse('A22600050000')
if err ~= nil then return false, err end
print(ansicolors.green..'done'..ansicolors.reset..'.')
-- clear password
io.write('Reseting password (and pack) to default ('.._defaultPassword..') and 0000...')
info, err = reconnect()
if not info then return false, err end
resp, err = sendWithResponse('A227'.._defaultPassword)
if err ~= nil then return false, err end
resp, err = sendWithResponse('A22800000000')
if err ~= nil then return false, err end
if not setPassword(_defaultPassword) then return false, 'Unable to set password' end
info, err = reconnect()
if not info then return false, err end
print(ansicolors.green..'done'..ansicolors.reset..'.')
-- clear other blocks
for i = 3, 0x23 do
local cblock = string.format('%02x',i)
io.write('Clearing block 0x'..cblock..'...')
resp, err = sendWithResponse('A2'..cblock..'00000000')
if err ~= nil then return false, err end
print(ansicolors.green..'done'..ansicolors.reset..'.')
end
print(ansicolors.green..'The tag has been wiped.'..ansicolors.reset)
lib14a.disconnect()
return true
end
--
-- Prints help
local function help()
print(copyright)
print(author)
print(version)
print(desc)
print(ansicolors.cyan..'Usage'..ansicolors.reset)
print(usage)
print(ansicolors.cyan..'Arguments'..ansicolors.reset)
print(arguments)
print(ansicolors.cyan..'Example usage'..ansicolors.reset)
print(example)
end
---
-- The main entry point
local function main(args)
if #args == 0 then return help() end
local dumpFilename = nil
for opt, value in getopt.getopt(args, 'hf:k:rw') do
local res, err
res = true
if opt == "h" then return help() end
if opt == "f" then dumpFilename = value end
if opt == 'k' then res, err = setPassword(value) end
if opt == 'r' then res, err = writeDump(dumpFilename) end
if opt == 'w' then res, err = wipe() end
if not res then return error(err) end
end
end
main(args)