mirror of
https://github.com/RfidResearchGroup/proxmark3.git
synced 2026-09-26 18:08:08 +00:00
229 lines
7.3 KiB
Lua
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
|