Files
proxmark3/client/lualibs/commands.lua
T

229 lines
7.3 KiB
Lua

--[[
Handle Proxmark Communication Commands
--]]
local _commands = require('pm3_cmd')
local util = require('utils')
local TIMEOUT = 2000
local _reverse_lookup,k,v = {}
for k, v in pairs(_commands) do
_reverse_lookup[v] = k
end
_commands.tostring = function(command)
if(type(command) == 'number') then
return ("%s (%d)"):format(_reverse_lookup[command]or "ERROR UNDEFINED!", command)
end
return ("Error, numeric argument expected, got : %s"):format(tostring(command))
end
Command = {
new = function(self, o)
local o = o or {} -- create object if user does not provide one
setmetatable(o, self) -- DIY inheritance a'la javascript
self.__index = self
o.cmd = o.cmd or _commands.CMD_UNKNOWN
o.arg1 = o.arg1 or 0
o.arg2 = o.arg2 or 0
o.arg3 = o.arg3 or 0
local data = o.data or "0"
if (type(data) == 'string') then
-- We need to check if it is correct length, otherwise pad it
local len = #data
if (len < 1024) then
--Should be 1024 hex characters to represent 512 bytes of data
data = data .. string.rep("0",1024 - len )
end
if (len > 1024) then
-- OOps, a bit too much data here
print( ( "WARNING: data size too large, was %s chars, will be truncated "):format(len) )
--
data = data:sub(1, 1024)
end
else
print(("WARNING; data was NOT a (hex-) string, but was %s"):format(type(data)))
end
o.data = data
return o
end,
newNG = function(self, o)
local o = o or {} -- create object if user does not provide one
setmetatable(o, self) -- DIY inheritance a'la javascript
self.__index = self
o.cmd = o.cmd or _commands.CMD_UNKNOWN
local data = o.data or ''
if (type(data) == 'string') then
if (#data > 1024) then
-- OOps, a bit too much data here
print( ( "WARNING: data size too large, was %s chars, will be truncated "):format( #data) )
--
data = data:sub(1, 1024)
end
end
o.data = data
return o
end,
}
-- commented out, not used.
function Command:__tostring()
local output = ("%s\r\nargs : (%s, %s, %s)\r\ndata:\r\n%s\r\n"):format(
_commands.tostring(self.cmd),
tostring(self.arg1),
tostring(self.arg2),
tostring(self.arg3),
tostring(self.data))
return output
end
--- Raw command support, uniform across card technologies.
--
-- The device takes a different struct per technology, but the Lua side is the
-- same for all of them. Build with Command:newRaw{} using these named fields:
--
-- tech '14a' | '14b' | '15' which technology
-- flags transport flag bitmask (ISO14A_* / ISO14B_* / ISO15_*)
-- data payload as a hex string optional, defaults to empty
-- timeout device-side timeout 14a and 14b only, ignored for 15
-- wait_us pre-transmit delay 14a only, ignored elsewhere
-- lenbits send this many bits 14a only, ignored elsewhere
--
-- The payload length is always derived from `data`, never passed separately.
--
-- On the wire these become, little endian:
-- 14a iso14a_raw_cmd_t u32 flags, u32 timeout, u32 wait_us, u16 len, u16 lenbits, u8 data[]
-- 14b iso14b_raw_cmd_t u16 flags, u32 timeout, u16 rawlen, u8 raw[]
-- 15 iso15_raw_cmd_t u8 flags, u16 rawlen, u8 raw[]
--- hex string -> raw byte string
function fromhex(hexstr)
return (hexstr:gsub('%x%x', function(cc) return string.char(tonumber(cc, 16)) end))
end
--- raw byte string -> uppercase hex string
function tohex(raw)
return (raw:gsub('.', function(c) return string.format('%02X', string.byte(c)) end))
end
-- little endian hex for a value of n bytes
local function le(value, nbytes)
local out = ''
local v = value or 0
for _ = 1, nbytes do
out = out .. string.format('%02X', v % 256)
v = math.floor(v / 256)
end
return out
end
local RAW_CMD = {
['14a'] = _commands.CMD_HF_ISO14443A_READER,
['14b'] = _commands.CMD_HF_ISO14443B_COMMAND,
['15'] = _commands.CMD_HF_ISO15693_COMMAND,
}
--- Build a raw command for any of 14a / 14b / 15.
-- @return a Command ready for :sendNG()
function Command:newRaw(o)
o = o or {}
local tech = o.tech or '14a'
local cmd = RAW_CMD[tech]
if cmd == nil then
error('Command:newRaw - unknown tech "'..tostring(tech)..'", expected 14a, 14b or 15')
end
local data = o.data or ''
local len = #data / 2
local hdr
if tech == '14a' then
hdr = le(o.flags, 4) .. le(o.timeout, 4) .. le(o.wait_us, 4) .. le(len, 2) .. le(o.lenbits, 2)
elseif tech == '14b' then
hdr = le(o.flags, 2) .. le(o.timeout, 4) .. le(len, 2)
else
hdr = le(o.flags, 1) .. le(len, 2)
end
local c = Command:newNG{ cmd = cmd, data = hdr .. data }
c.tech = tech
return c
end
--- Unpack the reply to a raw command, uniform across technologies.
-- @param tech '14a' | '14b' | '15'
-- @param resp the table returned by :sendNG()
-- @return len, extra, data
-- len number of payload bytes
-- extra 14a: the select status on a CONNECT, otherwise nil
-- data payload as a raw byte string (use tohex() to print it)
function parseRaw(tech, resp)
if resp == nil or type(resp) ~= 'table' or resp.Data == nil then
return nil, nil, nil
end
if tech == '14a' then
-- iso14a_raw_resp_t: u16 len, u8 sel, u8 rfu, u8 data[]
if #resp.Data < 8 then return nil, nil, nil end
local len = tonumber(resp.Data:sub(1, 2), 16) + (tonumber(resp.Data:sub(3, 4), 16) * 256)
local sel = tonumber(resp.Data:sub(5, 6), 16)
return len, sel, fromhex(resp.Data:sub(9, 8 + (len * 2)))
end
-- 14b and 15 answer with the payload only
return resp.Length, nil, fromhex(resp.Data:sub(1, resp.Length * 2))
end
function Command:sendNG( ignore_response, timeout )
if timeout == nil then timeout = TIMEOUT end
local data = self.data
local cmd = self.cmd
local err, msg = core.SendCommandNG(cmd, data)
if err == nil then return nil, msg end
if ignore_response then return true, nil end
local response, msg = core.WaitForResponseTimeout(cmd, timeout)
if response == nil then
return nil, 'Error, waiting for response timed out :: '..msg
end
data = nil
cmd = nil
local count, length, magic, status, reason, crc
count, cmd, length, magic, status, reason, crc = bin.unpack('SSIccS', response)
count, data, ng = bin.unpack('H'..length..'C', response, count)
--[[ uncomment if you want to debug
print('NG package received')
print('CMD ::', tostring(cmd))
print('Length ::', tostring(length))
print('Magic ::', string.format("0x%08X", magic), util.ConvertHexToAscii(string.format("0x%08X", magic)))
print('Status ::', tostring(status))
print('crc ::', string.format("0x%02X", crc))
print('NG ::', ng)
print('Data ::', data)
--]]
return { Cmd = cmd,
Length = length,
Magic = magic,
Status = status,
Reason = reason,
Crc = crc,
Data = data,
Ng = ng
}
end
return _commands