Skip to content
Permalink
Fetching contributors…
Cannot retrieve contributors at this time
2717 lines (2541 sloc) 87 KB
--[[
if it don't works with you tag-layout - be so kind and let me know ;-)
Tested on Tags with those Layouts:
(example) Legic-Prime Layout with 'Kaba Group Header'
+----+----+----+----+----+----+----+----+
0x00|MCD |MSN0|MSN1|MSN2|MCC | 60 | ea | 9f |
+----+----+----+----+----+----+----+----+
0x08| ff | 00 | 00 | 00 | 11 |Bck0|Bck1|Bck2|
+----+----+----+----+----+----+----+----+
0x10|Bck3|Bck4|Bck5|BCC | 00 | 00 |Seg0|Seg1|
+----+----+----+----+----+----+----+----+
0x18|Seg2|Seg3|SegC|Stp0|Stp1|Stp2|Stp3|UID0|
+----+----+----+----+----+----+----+----+
0x20|UID1|UID2|kghC|
+----+----+----+
MCD = Manufacturer ID
MSN = Manufacturer SerialNumber
60 ea = DCF Low + DCF high
9f = raw byte which holds the bits for OLE,WRP,WRC,RD
ff = unknown but important
00 = unimportant
11 = unknown but important
Bck = header-backup-area
00 00 = Year (00 = 2000) & Week (not important)
Seg = Segment Header
SegC = Crc8 over the Segment Header
Stp = Stamp (could be more as 4 - up to 7)
UID = dec User-ID for online-Mapping
kghC = crc8 over MCD + MSN0..MSN2 + UID
(example) Legic-Cash on MIM256/1024 tag' (37 bytes)
+----+----+----+----+----+----+----+----+
0x00|Seg0|Seg1|Seg2|Seg3|SegC|STP0|STP1|STP2|
+----+----+----+----+----+----+----+----+
0x08|STP3|STP4|STP5|STP6| 01 |CURh|CURl|LIMh|
+----+----+----+----+----+----+----+----+
0x10|LIMm|LIMl|CHKh|CHKl|BALh|BALm|BALl|LRBh|
+----+----+----+----+----+----+----+----+
0x18|LRBm|LRBl|CHKh|CHKl|SHDh|SHDm|SHDl|LRSh|
+----+----+----+----+----+----+----+----+
0x20|LRSm|LRSl| CV |CHKh|CHKl|
+----+----+----+----+----+
STP = Stamp (seems to be always 7 bytes)
01 = unknown but important
CUR = currency in HEX (ISO 4217)
LIM = Cash-Limit
CHK = crc16 over byte-addr 0x05..0x12
BAL = Balance
LRB = ID of the reader that changed the balance
CHK = crc16 over BAL + LRB
SHD = shadow Balance
LRS = ID of the reader that changed the shadow balance (?? should be always the same as LRB)
CV = Counter value for transactions
CHK = crc16 over SHD + LRS + CV
(example) Legic-Prime Layout 'gantner unsegmented user-credential'
+----+----+----+----+----+----+----+----+
0x00|MCD |MSN0|MSN1|MSN2|MCC | 60 | ea | 08 |
+----+----+----+----+----+----+----+----+
0x08|Stp0|Stp1|Stp2|Stp3|Stp4|Dat0|Dat1|uCRC| <- addr 0x08..0x0f is WRP
+----+----+----+----+----+----+----+----+
0x10|emb0| <- this is only within wrp if addr 0x07==09
+----+
MCD = Manufacturer ID
MSN = Manufacturer SerialNumber
60 ea = DCF Low + DCF high
08 = raw byte which holds the bits for OLE,WRP,WRC,RD
Stp = Stamp (could be more as 4 - up to 7)
Dat = Online-Mapping Data
uCRC = crc8 over addr 0x00..0x03+0x07..0x0E
(example) Legic-Prime Layout 'gantner unsegmented Master-Token (IAM) with a stamp_len of 4'
+----+----+----+----+----+----+----+----+
0x00|MCD |MSN0|MSN1|MSN2|MCC | 20 | f8 | 08 |
+----+----+----+----+----+----+----+----+
0x08|Stp0|Stp1|Stp2|Stp3| 00 | 00 | 00 |CRC1|
+----+----+----+----+----+----+----+----+
0x10| 00 | 00 | 00 | 00 | 00 |CRC2|
+----+----+----+----+----+----+
MCD = Manufacturer ID
MSN = Manufacturer SerialNumber
60 ea = DCF Low + DCF high
08 = raw byte which holds the bits for OLE,WRP,WRC,RD
Stp = Stamp (could be more as 4 - up to 7)
Dat = Online-Mapping Data
CRC1 = crc8 over addr 0x00..0x03+0x07..0x0E (special 'gantner crc8')
CRC2 = MCD + MSB0..2+ addr 0x06 + addr 0x05 + addr 0x07 + Stamp (regular Master-Token-CRC)
--]]
example = "script run legic"
author = "Mosci"
version = "1.0.3"
desc =
[[
This script helps you to read, create and modify Legic Prime Tags (MIM22, MIM256, MIM1024)
it's kinda interactive with following commands in three categories:
Data I/O Segment Manipulation Token-Data
----------------- -------------------- -----------------
rt => read Tag as => add Segment mt => make Token
wt => write Tag es => edit Segment Header et => edit Token data
ed => edit Segment Data tk => toggle KGH-Flag
File I/O rs => remove Segment
----------------- cc => check Segment-CRC
lf => load File ck => check KGH
sf => save File ds => dump Segments
xf => xor to File
(partially) known Segments Virtual Tags Script Output
--------------------------- ------------------------------- ------------------------
dlc => dump Legic-Cash ct => copy mainTag to backupTag tac => toggle ansicolors
elc => edit Legic-Cash tc => copy backupTag to mainTag
d3p => dump 3rd-Party-Cash tt => switch mainTag & backupTag
e3p => edit 3rd-Party-Cash di => dump mainTag
do => dump backupTag
rt: 'read tag' - reads a tag placed near to the PM3
wt: 'write tag' - writes the content of the 'virtual inTag' to a tag placed near to th PM3
without the need of changing anything - MCD,MSN,MCC will be read from the tag
before and applied to the output.
lf: 'load file' - load a (xored) file from the local Filesystem into the 'virtual inTag'
sf: 'save file' - saves the 'virtual inTag' to the local Filesystem (xored with Tag-MCC)
xf: 'xor file' - saves the 'virtual inTag' to the local Filesystem (xored with choosen MCC - use '00' for plain values)
ct: 'copy tag' - copy the 'virtual Tag' to a second 'virtual TAG' - not usefull yet, but inernally needed
tc: 'copy tag' - copy the 'second virtual Tag' to 'virtual TAG' - not usefull yet, but inernally needed
tt: 'toggle tag' - copy mainTag to BackupTag and backupTag to mainTag
di: 'dump mainTag' - shows the current content of the 'virtual Tag'
do: 'dump backupTag' - shows the current content of the 'virtual outTag'
ds: 'dump Segments' - will show the content of a selected Segment
as: 'add Segment' - will add a 'empty' Segment to the inTag
es: 'edit Segment' - edit the Segment-Header of a selected Segment (len, WRP, WRC, RD, valid)
all other Segment-Header-Values are either calculated or not needed to edit (yet)
ed: 'edit data' - edit the Data of a Segment (ADF-Aera / Stamp & Payload specific Data)
et: 'edit Token' - edit Data of a Token (CDF-Area / SAM, SAM64, SAM63, IAM, GAM specific Data)
mt: 'make Token' - create a Token 'from scratch' (guided)
rs: 'remove segment' - removes a Segment (except Segment 00, but this can be set to valid=0 for Master-Token)
cc: 'check Segment-CRC'- checks & calculates (if check failed) the Segment-CRC of all Segments
ck: 'check KGH-CRC' - checks the and calculates a 'Kaba Group Header' if one was detected
'Kaba Group Header CRC calculation'
tk: 'toggle KGH' - toglle the (script-internal) flag for kgh-calculation for a segment
xc: 'etra c' - show string that was used to calculate the kgh-crc of a segment
dlc: 'dump Legic-Cash' - show balance and checksums of a Legic-Cash Segment
elc: 'edit Legic-Cash' - edit values of a Legic-Cash Segment
d3p: 'dump 3rd Party' - show balance, history and checksums of a (yet) unknown 3rd-Party Cash Segment
e3p: 'edit 3rd Party' - edit Data in 3rd-Party Cash Segment
tac: 'toggle ansicolors'- switch on and off the colored text-output of this script
default can be changed by setting the variable 'colored_output' to false
]]
currentTag="inTAG"
---
-- requirements
local utils = require('utils')
local getopt = require('getopt')
local ansicolors = require('ansicolors')
---
-- global variables / defines
local bxor = bit32.bxor
local bbit = bit32.extract
local input = utils.input
local confirm = utils.confirm
---
-- init ansicolor-values & ansicolors switch
local colored_output = true
local acoff = ""
local acgreen= ""
local accyan = ""
local acred = ""
local acyellow = ""
local acblue = ""
local acmagenta = ""
--- Helper ---
---
-- default colors (change to whatever you want)
function load_colors(onoff)
if (onoff) then
-- colors
acgreen = ansicolors.green
accyan = ansicolors.cyan
acred = ansicolors.red
acyellow= ansicolors.yellow
acblue = ansicolors.blue
acmagenta= ansicolors.magenta
acoff = ansicolors.reset
else
-- 'no color'
acgreen = ""
accyan = ""
acred = ""
acyellow= ""
acblue = ""
acmagenta= ""
acoff = ""
end
end
---
-- curency-codes for Legic-Cash-Segments (ISO 4217)
local currency = {
["03d2"]="EUR",
["0348"]="USD",
["033A"]="GBP",
["02F4"]="CHF"
}
---
-- This is only meant to be used when errors occur
function oops(err)
print(acred.."ERROR: "..acoff ,err)
return nil, err
end
---
-- Usage help
function help()
print(desc)
print("Version: "..version)
print("Example usage: "..example)
end
---
-- table check helper
function istable(t)
return type(t) == 'table'
end
---
-- creates a 'deep copy' of a table (a=b only references)
function deepCopy(object)
local lookup_table = {}
local function _copy(object)
if type(object) ~= "table" then
return object
elseif lookup_table[object] then
return lookup_table[object]
end
local new_table = {}
lookup_table[object] = new_table
for index, value in pairs(object) do
new_table[_copy(index)] = _copy(value)
end
return setmetatable(new_table, getmetatable(object))
end
return _copy(object)
end
---
-- xor single byte
function xorme(hex, xor, index)
if ( index >= 23 ) then
return ('%02x'):format(bxor( tonumber(hex,16) , tonumber(xor,16) ))
else
return hex
end
end
---
-- (de)obfuscate bytes
function xorBytes(inBytes, crc)
local bytes = {}
for index = 1, #inBytes do
bytes[index] = xorme(inBytes[index], crc, index)
end
if (#inBytes == #bytes) then
-- replace crc
bytes[5] = string.sub(crc,-2)
return bytes
else
print("error: byte-count missmatch")
return false
end
end
---
-- check availability of file
function file_check(file_name)
local file_found=io.open(file_name, "r")
if file_found==nil then
return false
else
file_found:close()
return true
end
end
---
-- split csv-string into table
local function split(str, sep)
local sep = sep or ','
local fields={}
local matchfunc = string.gmatch(str, "([^"..sep.."]+)")
if not matchfunc then return {str} end
for str in matchfunc do
table.insert(fields, str)
end
return fields
end
---
-- put a string into a bytes-table
function str2bytes(s)
if (string.len(s)%2 ~= 0) then
return print("stamp should be a even hexstring e.g.: deadbeef or 0badc0de")
end
local res={}
for i=1, string.len(s), 2 do
table.insert(res, string.sub(s,i,(i+1)))
end
return res
end
---
-- put certain bytes into a new table
function bytesToTable(bytes, bstart, bend)
local t={}
for i=0, (bend-bstart) do
t[i]=bytes[bstart+i]
end
return t
end
---
-- read file into table
function getInputBytes(infile)
local line
local bytes = {}
local fhi,err = io.open(infile,"rb")
if err then oops("faild to read from file ".. infile); return false; end
file_data = fhi:read("*a");
for i = 1, #file_data do
bytes[i] = string.format("%x",file_data:byte(i))
end
fhi:close()
if (bytes[7]=='00') then return false end
print(#bytes .. " bytes from "..infile.." loaded")
return bytes
end
---
-- create tag-table helper
function createTagTable()
local t={
['MCD'] = '00',
['MSN0']= '11',
['MSN1']= '22',
['MSN2']= '33',
['MCC'] = 'cc',
['DCFl']= 'ff',
['DCFh']= 'ff',
['Type']= 'GAM',
['OLE'] = 0,
['Stamp_len']= 18,
['WRP'] = '00',
['WRC'] = '00',
['RD'] = '00',
['raw'] = '9f',
['SSC'] = 'ff',
['data']= {},
['bck'] = {},
['MTC'] = {},
['SEG'] = {}
}
return t
end
---
-- put bytes into tag-table
function bytesToTag(bytes, tag)
if istable(tag) == false then return oops("tag is no table in: bytesToTag ("..type(tag)..")") end
tag.MCD =bytes[1];
tag.MSN0=bytes[2];
tag.MSN1=bytes[3];
tag.MSN2=bytes[4];
tag.MCC =bytes[5];
tag.DCFl=bytes[6];
tag.DCFh=bytes[7];
tag.raw =bytes[8];
tag.SSC =bytes[9];
tag.Type=getTokenType(tag.DCFl);
tag.OLE=bbit("0x"..tag.DCFl,7,1)
tag.WRP=("%d"):format(bbit("0x"..bytes[8],0,4))
tag.WRC=("%d"):format(bbit("0x"..bytes[8],4,3))
tag.RD=("%d"):format(bbit("0x"..bytes[8],7,1))
if (tag.Type=="SAM" and tag.raw=='9f') then
tag.Stamp_len=(tonumber(0xfc,10)-tonumber(bbit("0x"..tag.DCFh,0,8),10))
elseif (tag.Type=="SAM" and (tag.raw=='08' or tag.raw=='09')) then
tag.Stamp_len = tonumber(tag.raw,10)
end
tag.data=bytesToTable(bytes, 10, 13)
tag.Bck=bytesToTable(bytes, 14, 20)
tag.MTC=bytesToTable(bytes, 21, 22)
print(acgreen.."Tag-Type: ".. tag.Type..acoff)
if (tag.Type=="SAM" and #bytes>23) then
tag=segmentsToTag(bytes, tag)
print(acgreen..(#tag.SEG+1).." Segment(s) found"..acoff)
-- unsegmented Master-Token
-- only tag-data
else
for i=0, #tag.Bck do
table.insert(tag.data, tag.Bck[i])
end
tag.data[#tag.data]=tag.MTC[0]
tag.Bck=nil
--tag.MTC[0]=tag.MTC[1]
--tag.MTC[1]=nil
end
print(accyan..#bytes.." bytes for Tag processed"..acoff)
return tag
end
---
-- put segments from byte-table to tag-table
function segmentsToTag(bytes, tag)
if(#bytes>23) then
local start=23
local i=-1
if (istable(tag)) then
repeat
i=i+1
tag.SEG[i]=getSegmentData(bytes, start, ("%02d"):format(i))
if (tag.Type=="SAM") then
if (checkKghCrc(tag, i)) then tag.SEG[i].kgh=true end
end
start=start+tag.SEG[i].len
until ((tag.SEG[i].valid==0) or tag.SEG[i].last==1 or i==126)
return tag
else return oops("tag is no table in: segmentsToTag ("..type(tag)..")") end
else print("no Segments: must be a MIM22") end
end
---
-- read Tag-Table in bytes-table
function tagToBytes(tag)
if istable(tag) == false then return oops("tag is no table in tagToBytes ("..type(tag)..")") end
local bytes = {}
local i, i2
-- main token-data
table.insert(bytes, tag.MCD)
table.insert(bytes, tag.MSN0)
table.insert(bytes, tag.MSN1)
table.insert(bytes, tag.MSN2)
table.insert(bytes, tag.MCC)
table.insert(bytes, tag.DCFl)
table.insert(bytes, tag.DCFh)
table.insert(bytes, tag.raw)
table.insert(bytes, tag.SSC)
-- raw token data
for i=0, #tag.data do
table.insert(bytes, tag.data[i])
end
-- backup data
if(istable(tag.Bck)) then
for i=0, #tag.Bck do
table.insert(bytes, tag.Bck[i])
end
end
-- token-create-time / master-token crc
for i=0, #tag.MTC do
table.insert(bytes, tag.MTC[i])
end
-- process segments
if (type(tag.SEG[0])=='table') then
for i=0, #tag.SEG do
for i2=1, #tag.SEG[i].raw+1 do
table.insert(bytes, #bytes+1, tag.SEG[i].raw[i2])
end
table.insert(bytes, #bytes+1, tag.SEG[i].crc)
for i2=0, #tag.SEG[i].data-1 do
table.insert(bytes, #bytes+1, tag.SEG[i].data[i2])
end
end
end
-- fill with zeros
for i=#bytes+1, 1024 do
table.insert(bytes, i, '00')
end
return bytes
end
--- PM3 I/O ---
---
-- read from pm3 into virtual-tag
function readFromPM3()
local tag, bytes, infile
infile="legic.temp"
core.console("hf legic reader")
core.console("hf legic esave "..infile)
tag=readFile(infile..".bin")
return tag
end
---
-- write virtual Tag to real Tag
function writeToTag(tag)
local bytes
local filename='MylegicClone.hex'
local taglen=22
if(utils.confirm(acred.."\nplace the (empty) Tag onto the PM3\nand confirm writing to this Tag: "..acoff) == false) then
return
end
-- get used bytes / tag-len
if(istable(tag.SEG)) then
if (istable(tag.Bck)) then
for i=0, #tag.SEG do
taglen=taglen+tag.SEG[i].len+5
end
end
local uid_old=tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2
-- read new tag into memory so we can xor the new data with the new MCC
outTAG=readFromPM3()
outbytes=tagToBytes(outTAG)
-- copy 'inputbuffer' to 'outputbuffer'
tag.MCD = outbytes[1]
tag.MSN0 = outbytes[2]
tag.MSN1 = outbytes[3]
tag.MSN2 = outbytes[4]
tag.MCC = outbytes[5]
-- recheck all segments-crc/kghcrc (only on a credential)
if(istable(tag.Bck)) then
checkAllSegCrc(tag)
checkAllKghCrc(tag)
local uid_new=tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2
for i=0, #tag.SEG do
if (check43rdPartyCash1(uid_old, tag.SEG[i].data)) then
io.write(accyan.."\nfixing known checksums"..acoff.." ... ")
if (fix3rdPartyCash1(uid_new, tag.SEG[i].data)) then
io.write(acgreen.." done\n"..acoff)
else oops("\nsomething went wrong at the repair of the 3rd-party-cash-segment") end
end
end
end
bytes=tagToBytes(tag)
-- master-token-crc
if (tag.Type ~= "SAM") then bytes[22] = calcMtCrc(bytes) end
if (bytes) then
print("write temp-file '"..filename.."'")
print(accyan)
writeFile(bytes, filename)
--writeToTag(bytes, taglen, 'MylegicClone.hex')
print(acoff)
end
end
-- write data to file
if (taglen > 0) then
WriteBytes = utils.input(acyellow.."enter number of bytes to write?"..acoff, taglen)
-- load file into pm3-buffer
if (type(filename) ~= "string") then filename=input(acyellow.."filename to load to pm3-buffer?"..acoff,"legic.temp") end
cmd = 'hf legic load '..filename
core.console(cmd)
-- write pm3-buffer to Tag
for i=0, WriteBytes do
if ( i<5 or i>6) then
cmd = ('hf legic write o 0x%02x d 0x01'):format(i)
core.console(cmd)
--print(cmd)
elseif (i == 6) then
-- write DCF in reverse order (requires 'mosci-patch')
cmd = 'hf legic write o 0x05 d 0x02'
print(acgreen..cmd..acoff)
core.console(cmd)
--print(cmd)
else
print(acgreen.."skip byte 0x05 - will be written next step"..acoff)
end
utils.Sleep(0.2)
end
end
end
--- File I/O ---
---
-- read file into virtual-tag
function readFile(filename)
print(accyan)
local bytes = {}
local tag = {}
if file_check(filename) == false then return oops("input file: "..filename.." not found") end
bytes = getInputBytes(filename)
if bytes == false then return oops('couldnt get input bytes') end
-- make plain bytes
bytes = xorBytes(bytes,bytes[5])
print("create virtual tag from ".. #bytes .. " bytes")
-- create Tag for plain bytes
tag=createTagTable()
-- load plain bytes to tag-table
print(acoff)
tag=bytesToTag(bytes, tag)
return tag
end
---
-- write bytes to file
function writeFile(bytes, filename)
if (filename ~= 'MylegicClone.hex') then
if (file_check(filename)) then
local answer = confirm("\nthe output-file "..filename.." alredy exists!\nthis will delete the previous content!\ncontinue?")
if (answer==false) then return print("user abort") end
end
end
local line
local bcnt=0
local fho,err = io.open(filename, "w")
if err then oops("OOps ... faild to open output-file ".. filename) end
bytes=xorBytes(bytes, bytes[5])
for i = 1, #bytes do
if (bcnt == 0) then
line = bytes[i]
elseif (bcnt <= 7) then
line = line.." "..bytes[i]
end
if (bcnt == 7) then
-- write line to new file
fho:write(line.."\n")
-- reset counter & line
bcnt = -1
line = ""
end
bcnt = bcnt + 1
end
fho:close()
print("\nwrote ".. #bytes .." bytes to " .. filename)
return true
end
--- Map related ---
---
-- make tagMap
function makeTagMap()
local tagMap={}
if (#tagMap == 0) then
tagMap['name'] = input(accyan.."enter Name for this Map: "..acoff , "newTagMap")
tagMap['mappings']={}
tagMap['crc8']={}
-- insert fixed Tag-CRC
table.insert(tagMap.crc8, {name='TAG-CRC', pos=5, seq={1, 4}})
tagMap['crc16']={}
end
print(accyan.."new tagMap created"..acoff)
return tagMap
end
---
-- save mapping to file
function saveTagMap(map, filename)
if (string.len(filename)>0) then
if (file_check(filename)) then
local answer = confirm("\nthe output-file "..filename.." alredy exists!\nthis will delete the previous content!\ncontinue?")
if (answer==false) then return print("user abort") end
end
end
local line
local fho,err = io.open(filename, "w")
if err then oops("OOps ... faild to open output-file ".. filename) end
-- write line to new file
for k, v in pairs(map) do
if (istable(v)) then
for k2, v2 in pairs(v) do
if (k=='mappings') then
fho:write(k..","..k2..","..v2['name']..","..v2['start']..","..v2['end']..","..((v2['highlight']) and "1" or "0").."\n")
elseif (k=="crc8") then
local tmp=""
tmp=k..","..k2..","..v2['name']..","..v2['pos']..","
tmp=tmp..tbl2seqstr(v2['seq'])
fho:write(tmp.."\n")
end
end
else
fho:write(k..","..v.."\n")
end
end
fho:close()
return true
end
---
-- toggle higligh
function toggleHighlight(tbl)
if (tbl['highlight']) then
tbl['highlight'] = false
else
tbl['highlight'] = true
end
return tbl
end
---
-- return table od seqence-string
function seqstr2tbl(seqstr)
local s=split(seqstr)
local res={}
if (#s>=1) then
for sk, sv in pairs(s) do
s2=split(sv, '-')
if(#s2==2) then
table.insert(res, s2[1])
table.insert(res, s2[2])
end
end
end
return res
end
---
-- return sequence-string from table
function tbl2seqstr(seqtbl)
local res=""
if (istable(seqtbl)) then
for sk, sv in pairs(seqtbl) do
res=res..sv..((sk%2==0) and "," or "-")
end
if (string.sub(res, string.len(res))==",") then
res=string.sub(res, 1, string.len(res)-1)
end
end
return res
end
---
-- read map-file into map
function loadTagMap(filename)
local map={mappings={}, crc8={}, crc16={}}
local m=0
local c=0
local line, fields
local temp={}
local offset=0
if (file_check(filename)==false) then
return oops("input file: "..filename.." not found")
else
local fhi,err = io.open(filename)
while true do
line = fhi:read()
if line == nil then
break
else
fields=split(line)
end
if (#fields==2) then
if (fields[1]=='offset') then
offset=tonumber(fields[2],10)
end
-- map-name
map[fields[1]]=fields[2]
elseif (fields[1]=='mappings') then
m=m+1
temp={}
-- mapping
temp['name']=fields[3]
temp['start']=tonumber(fields[4], 10)
temp['end']=tonumber(fields[5], 10)
if(temp['start']>22) then
temp['start']=temp['start']+offset
temp['end']=temp['end']+offset
end
if (tonumber(fields[6], 10)==1) then temp['highlight']= true
else temp['highlight']= false end
table.insert(map['mappings'], m, temp)
elseif (fields[1]=='crc8') then
c=c+1
temp={}
-- crc8
temp['name']=fields[3]
temp['pos']=tonumber(fields[4], 10)+offset
local s=string.sub(line, string.len(fields[1]..","..fields[2]..","..fields[3]..",")+1, string.len(line))
temp['seq']=seqstr2tbl(s)
for k, v in pairs(temp['seq']) do
if(tonumber(v, 10)>22) then v=tonumber(v, 10)+offset end
temp['seq'][k]=tonumber(v, 10)
end
table.insert(map.crc8, temp)
end
end
fhi:close()
end
return map
end
---
-- dump tagMap (mappings only)
function dumpTagMap(tag, tagMap)
if(#tagMap.mappings>0) then
bytes=tagToBytes(tag)
local temp
local lastend=0
-- start display mappings
for k, v in pairs(tagMap.mappings) do
if ((lastend+1)<v['start']) then
print("...")
end
if (isPosCrc8(tagMap, v['start'])>0) then
if ( checkMapCrc8(tagMap, bytes, isPosCrc8(tagMap, v['start']) ) ) then
io.write("("..("%04d"):format(v['start']).."-"..("%04d"):format(v['end'])..") "..acgreen..v['name']..acoff..":")
else
io.write("("..("%04d"):format(v['start']).."-"..("%04d"):format(v['end'])..") "..acred..v['name']..acoff..":")
end
else
io.write("("..("%04d"):format(v['start']).."-"..("%04d"):format(v['end'])..") "..((v['highlight']) and acmagenta or acyellow)..v['name']..acoff..":")
end
temp=""
for i=((string.len(v['name']))/10), 2 do
temp=temp.."\t"
end
for i=v['start'], v['end'] do
temp=temp..bytes[i].." "
end
print(temp)
lastend=v['end']
end
end
end
---
--
function isPosCrc8(tagMap, pos)
local res=0
if (#tagMap.crc8>0) then
for k, v in pairs(tagMap.crc8) do
if(v['pos']==pos) then res=k end
end
end
return res
end
---
-- check mapped crc
function checkMapCrc8(tagMap, bytes, n)
local res=false
if (#tagMap.crc8>0) then
if(istable(tagMap.crc8[n])) then
temp=""
for k2, v2 in pairs(tagMap.crc8[n]) do
if (istable(v2)) then
temp=temp..tbl2seqstr(v2)
end
end
local tempres=""
local tempres=getSequences(bytes, temp)
tempres=("%02x"):format(utils.Crc8Legic(tempres))
if (bytes[tagMap.crc8[n]['pos']]==tempres) then
res=true
end
end
end
return res
end
---
-- edit existing Map
function editTagMap(tag, tagMap)
local t = [[
Data: dm = show dr = dump raw
Mappings: im = insert am = add rm = remove
CRC8: ac8 = add sc8 = show rc8 = remove
: q = exit h = Help
]]
--if(#tagMap.mappings==0) then oops("no mappings in tagMap"); return tagMap end
print("tagMap edit-mode submenu")
repeat
x=input('tagMap submenu:', 'h')
if (x=='h') then print(t)
elseif (x=='dm') then tagMmap=dumpTagMap(tag, tagMap)
elseif (x=='dr') then tagMmap=dumpMap(tag, tagMap)
elseif (x=='rc8') then
if (istable(tagMap.crc8)) then
local x1 = selectTableEntry(tagMap.crc8, "select number of CRC8 to remove:")
if (istable(tagMap.crc8[x1])) then
table.remove(tagMap.crc8, x1)
end
end
elseif (x=='ac8') then
local p=tonumber(input("enter byte-addr of crc8", '0'),10)
if (p>0) then
local i1=input("enter comma-seperated byte-sequences (e.g.: '1-4,23-26')", '1-4,23-26')
local s1=split(i1, ',')
if (#s1>0) then
local temp={seq={}}
for k, v in pairs(s1) do
v1=split(v, '-')
if(#v1==2) then
table.insert(temp.seq, v1[1])
table.insert(temp.seq, v1[2])
end
end
temp['pos']=p
temp['name']=input("enter a name for the CRC8", "CRC "..(#tagMap.crc8+1))
table.insert(tagMap.crc8, temp)
end
end
elseif (string.sub(x, 1, 3)=='sc8') then
local bytes=tagToBytes(tag)
local res, pos
-- trigger manually by sc8 <'4digit' checkadd> <'seqeuence-string'>
-- e.g.: sc8 0027 1-4,23-36
if (string.len(x)>=9) then
pos=tonumber(string.sub(x, 5, 8), 10)
x=string.sub(x, 9, string.len(x))
print("x: "..x.." - pos:"..pos)
else
x=selectTableEntry(tagMap.crc8, "select CRC:")
if(istable(tagMap.crc8[x])) then
pos=tagMap.crc8[x]['pos']
x=tbl2seqstr(tagMap.crc8[x]['seq'])
end
end
if (type(x)=='string') then
res=("%02x"):format(utils.Crc8Legic(getSequences(bytes, x)))
print(accyan.."Sequence:\t"..acoff..x)
print(accyan.."Bytes:\t\t"..acoff..getSequences(bytes, x))
print(accyan.."calculated: "..acoff..res..accyan.." bytes["..pos.."]: "..acoff..bytes[pos].." ("..compareCrc(utils.Crc8Legic(getSequences(bytes, x)), bytes[pos])..")")
end
elseif (x=="tm") then
x=selectTableEntry(tagMap.mappings, "select number of Mapping:")
tagMap.mappings[x]=toggleHighlight(tagMap.mappings[x])
elseif (x=='am') then tagMap=addMapping(tag, tagMap)
elseif (x=='im') then tagMap=addMapping(tag, tagMap, selectTableEntry(tagMap.mappings, "select List-Position for insert:"))
elseif (x=='rm') then tagMap=deleteMapping(tag, tagMap)
elseif (x=='mas') then tagMap=mapTag(tagMap); tagMap=mapAllSegments(tag, tagMap)
elseif (type(actions[string.sub(x, 3)])=='function') then actions[string.sub(x, 3)]()
end
until x=='q'
print("exit sub-Menu")
return tagMap
end
---
-- dump raw mapped and unmapped
function dumpMap(tag, tagMap)
local dstart=1
local dend, cnt
local bytes = tagToBytes(tag)
local stats = getSegmentStats(bytes)
dend=stats[#stats]['end']
print(accyan.."Tag uses "..dend.." bytes:"..acoff)
for i=dstart, dend do
if (check4MappedByte(i, tagMap) and not check4MapCrc8(i, tagMap) and not check4Highlight(i, tagMap)) then io.write(""..acyellow)
elseif (check4MapCrc8(i, tagMap)) then
if ( checkMapCrc8(tagMap, bytes, isPosCrc8(tagMap, i) ) ) then
io.write(""..acgreen)
else
io.write(""..acred)
end
else
io.write(""..acoff)
end
-- highlighted mapping
if (check4Highlight(i, tagMap)) then io.write(""..acmagenta) end
io.write(bytes[i])
if (i%8==0) then io.write("\n")
else io.write(" ") end
end
io.write("\n"..acoff)
end
---
-- show bytes used for crc-calculation
function getSequences(bytes, seqstr)
if (type(seqstr) ~= "string") then seqstr = input("enter comma-seperated sequences (e.g.: '1-4,23-26')", '1-4,23-26') end
local seqs = split(seqstr, ',')
local res = ""
if(#seqs>0) then
for k, v in pairs(seqs) do
local seq = split(v,'-')
if (#seq >= 2) then
for i = seq[1], seq[2] do
res = res..bytes[i].." "
end
end
if(string.len(res)>0) then res = res.." " end
end
else
oops("no sequence found in '"..seqstr.."'")
end
return res
end
---
-- check if byte-addr is a know crc
function check4MapCrc8(addr, tagMap)
local res=false
for i=1, #tagMap.crc8 do
if (addr == tagMap.crc8[i]['pos']) then
res=true
end
end
return res
end
---
-- check if byte-addr is a know crc
function check4MapCrc16(addr, tagMap)
local res=false
for i=1, #tagMap.crc16 do
if (addr == tagMap.crc16[i]['pos']) then
res=true
end
end
return res
end
---
-- check if byte is mapped or not
function check4MappedByte(addr, tagMap)
local res=false
for _, v in pairs(tagMap.mappings) do
if (addr >= v['start'] and addr <= v['end'] ) then
res= true
end
end
return res
end
---
-- check if byte is highlighted or not
function check4Highlight(addr, tagMap)
local res=false
for _, v in pairs(tagMap.mappings) do
if (addr >= v['start'] and addr <= v['end'] ) then
res= v['highlight']
end
end
return res
end
---
-- add interactive mapping
function addMapping(tag, tagMap, x)
if (type(x) ~= "number") then x = #tagMap.mappings + 1 end
local bytes = tagToBytes(tag)
local myMapping={}
myMapping['name'] = input(accyan.."enter Maping-Name:"..acoff, string.format("mapping %d", #tagMap.mappings+1))
myMapping['start'] = tonumber(input(accyan.."enter start-addr:"..acoff, '1'), 10)
myMapping['end'] = tonumber(input(accyan.."enter end-addr:"..acoff, #bytes), 10)
myMapping['highlight'] = confirm("set highlighted")
table.insert(tagMap.mappings, x, myMapping)
return tagMap
end
---
-- delete mapping
function deleteMapping(tag, tagMap)
if(#tagMap.mappings>0) then
local d = selectTableEntry(tagMap.mappings, "select number of Mapping to remove:")
if (type(d)=='number') then
table.remove(tagMap.mappings, d)
else oops("deleteMapping: got type = "..type(d).." - expected type = 'number'")
end
end
return tagMap
end
---
-- select a mapping from a tagmap
function selectTableEntry(table, action)
if (type(action) ~= "string") then action = "select number of item:" end
for k, v in pairs(table) do
print(accyan..k..acoff.."\t-> "..accyan..v['name']..acoff)
end
local res = tonumber(input(action , 0), 10)
if (istable(table[res])) then
return res
else
return false
end
end
---
-- map all segments
function mapAllSegments(tag, tagMap)
local bytes=tagToBytes(tag)
local WRP,WRC,WRPC
segs=getSegmentStats(bytes)
if (istable(segs)) then
for k, v in pairs(segs) do
-- wrp (write proteted) = byte 2
WRP = tonumber(bytes[v['start']+2],16)
-- wrc (write control) - bit 4-6 of byte 3
WRC = tonumber(bbit("0x"..bytes[v['start']+3],4,3),16)
--tagMap=mapTokenData(tagMap, 'Segment '..("%02d"):format(v['index']).." HDR", v['start'], v['start']+3)
tagMap=mapTokenData(tagMap, 'Segment '..("%02d"):format(v['index']).." CRC", v['start']+4, v['start']+4, true)
table.insert(tagMap.crc8, {name = 'Segment '..("%02d"):format(v['index']).." CRC", pos=v['start']+4, seq={1,4,v['start'],v['start']+3}} )
if(WRC>WRP) then
WRPC=WRC
tagMap=mapTokenData(tagMap, 'Segment '..("%02d"):format(v['index']).." WRC", v['start']+5, v['start']+5+WRC-1, true)
elseif (WRP>WRC and WRC>0) then
WRPC=WRP
tagMap=mapTokenData(tagMap, 'Segment '..("%02d"):format(v['index']).." WRC", v['start']+5, v['start']+5+WRC-1, true)
tagMap=mapTokenData(tagMap, 'Segment '..("%02d"):format(v['index']).." WRP", v['start']+WRC+5, v['start']+5+WRP-1, true)
else
WRPC=WRP
tagMap=mapTokenData(tagMap, 'Segment '..("%02d"):format(v['index']).." WRP", v['start']+5, v['start']+5+WRP-1, true)
end
tagMap=mapTokenData(tagMap, 'Segment '..("%02d"):format(v['index']).." data", v['start']+5+WRPC, v['end'], false)
end
print(#segs.." Segments mapped")
else
oops("autoMapSegments failed: no Segments found")
end
return tagMap
end
---
-- map all token data
function mapTokenData(tagMap, mname, mstart, mend, mhigh)
--if ( not mhigh ) then mhigh=false end
local myMapping={}
myMapping['name'] =mname
myMapping['start']=mstart
myMapping['end'] =mend
myMapping['highlight']=mhigh
table.insert(tagMap.mappings, myMapping)
return tagMap
end
---
-- map a map
function mapTag(tagMap)
tagMap=makeTagMap()
tagMap=mapTokenData(tagMap, 'Tag-ID', 1, 4, true)
tagMap=mapTokenData(tagMap, 'Tag-CRC', 5, 5, false)
tagMap=mapTokenData(tagMap, 'DCF', 6, 7, true)
tagMap=mapTokenData(tagMap, 'THDR-Raw/Stamp-Len', 8, 8, true)
tagMap=mapTokenData(tagMap, 'SSC', 9, 9, true)
tagMap=mapTokenData(tagMap, 'Header', 10, 13, false)
tagMap=mapTokenData(tagMap, 'Backup', 14, 19, true)
tagMap=mapTokenData(tagMap, 'Bck-CRC', 20, 20, false)
tagMap=mapTokenData(tagMap, 'TokenTime', 21, 22, false)
return tagMap
end
--- Dump Data ---
---
-- dump virtual Tag-Data
function dumpTag(tag)
local i, i2
local res
local dp=0
local raw=""
-- sytstem area
res =acyellow.."\nCDF: System Area"..acoff
res= res.."\n"..dumpCDF(tag)
-- segments (user-token area)
if(tag.Type=="SAM" and tag.raw=='9f') then
res = res..acyellow.."\n\nADF: User Area"..acoff
for i=0, #tag.SEG do
res=res.."\n"..dumpSegment(tag, i).."\n"
end
end
return res
end
---
-- dump tag-system area
function dumpCDF(tag)
local res=""
local i=0
local raw=""
local bytes
if (istable(tag)) then
res = res..accyan.."MCD: "..acoff..tag.MCD..accyan.." MSN: "..acoff..tag.MSN0.." "..tag.MSN1.." "..tag.MSN2..accyan.." MCC: "..acoff..tag.MCC.."\n"
res = res.."DCF: "..tag.DCFl.." "..tag.DCFh..", Token_Type="..tag.Type.." (OLE="..tag.OLE.."), Stamp_len="..tag.Stamp_len.."\n"
res = res.."WRP="..tag.WRP..", WRC="..tag.WRC..", RD="..tag.RD..", raw="..tag.raw..((tag.raw=='9f') and (", SSC="..tag.SSC.."\n") or "\n")
-- credential (end-user tag)
if (tag.Type=="SAM" and tag.raw=='9f') then
res = res.."Remaining Header Area\n"
for i=0, (#tag.data) do
res = res..tag.data[i].." "
end
res = res.."\nBackup Area\n"
for i=0, (#tag.Bck) do
res = res..tag.Bck[i].." "
end
res = res.."\nTime Area\n"
for i=0, (#tag.MTC) do
res = res..tag.MTC[i].." "
end
-- Master Token specific
elseif (tag.Type~="SAM") then
res = res .."Master-Token Area\nStamp: "
res= res..tag.SSC.." "
for i=0, tag.Stamp_len-2 do
res = res..tag.data[i].." "
end
res=res.."\nunused payload\n"
for i=0, (#tag.data-tag.Stamp_len-1) do
res = res..tag.data[i].." "
end
bytes=tagToBytes(tag)
local mtcrc=calcMtCrc(bytes)
res=res.."\nMaster-Token CRC: "
res = res ..tag.MTC[1].." ("..((tag.MTC[1]==mtcrc) and "valid" or "error")..")"
-- 'Gantner User-Credential' specific
elseif (tag.Type=="SAM" and (tag.raw=='08' or tag.raw=='09')) then
print(acgreen.."Gantner Detected"..acoff)
end
return res
else print("no valid Tag in dumpCDF") end
end
---
-- dump single segment
function dumpSegment(tag, index)
local i=index
local i2
local dp=0 --data-position in table
local res="" --result
local raw="" --raw-header
-- segment
if ( (istable(tag.SEG[i])) and tag.Type=="SAM" and tag.raw=="9f") then
if (istable(tag.SEG[i].raw)) then
for k,v in pairs(tag.SEG[i].raw) do
raw=raw..v.." "
end
end
-- segment header
res = res..accyan.."Segment "..("%02d"):format(tag.SEG[i].index)..acoff..": "
res = res .."raw header: "..string.sub(raw,0,-2)..", flag="..tag.SEG[i].flag..", (valid="..("%x"):format(tag.SEG[i].valid)..", last="..("%x"):format(tag.SEG[i].last).."), "
res = res .."len="..("%04d"):format(tag.SEG[i].len)..", WRP="..("%02x"):format(tag.SEG[i].WRP)..", WRC="..("%02x"):format(tag.SEG[i].WRC)..", "
res = res .."RD="..("%02x"):format(tag.SEG[i].RD)..", CRC="..tag.SEG[i].crc.." "
res = res .."("..(checkSegmentCrc(tag, i) and acgreen.."valid" or acred.."error") ..acoff..")"
raw=""
-- WRC protected
if ((tag.SEG[i].WRC>0)) then
res = res .."\nWRC protected area:\n"
for i2=dp, dp+tag.SEG[i].WRC-1 do
res = res..tag.SEG[i].data[i2].." "
dp=dp+1
end
end
-- WRP mprotected
if (tag.SEG[i].WRP>tag.SEG[i].WRC) then
res = res .."\nRemaining write protected area:\n"
for i2=dp, dp+(tag.SEG[i].WRP-tag.SEG[i].WRC)-1 do
res = res..tag.SEG[i].data[i2].." "
dp=dp+1
end
end
-- payload
if (#tag.SEG[i].data-dp>0) then
res = res .."\nRemaining segment payload:\n"
for i2=dp, #tag.SEG[i].data-2 do
res = res..tag.SEG[i].data[dp].." "
dp=dp+1
end
if (tag.SEG[i].kgh) then
res = res..tag.SEG[i].data[dp].." (KGH: "..(checkKghCrc(tag, i) and acgreen.."valid" or acred.."error") ..acoff..")"
else res = res..tag.SEG[i].data[dp] end
end
dp=0
return res
else
return print("Segment not found")
end
end
---
-- return bytes 'sstrat' to 'send' from a table
function dumpTable(tab, header, tstart, tend)
res=""
for i=tstart, tend do
res=res..tab[i].." "
end
if (#header == 0) then
return res
else
return (header.." #"..(tend-tstart+1).."\n"..res)
end
end
---
-- dump 3rd Party Cash
function dump3rdPartyCash1(tag , seg)
local uid=tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2
local stamp=tag.SEG[seg].data[0].." "..tag.SEG[seg].data[1].." "..tag.SEG[seg].data[2]
local datastamp=tag.SEG[seg].data[20].." "..tag.SEG[seg].data[21].." "..tag.SEG[seg].data[22]
local balance=tonumber(tag.SEG[seg].data[32]..tag.SEG[seg].data[33] ,16)
local balancecrc=utils.Crc8Legic(uid..tag.SEG[seg].data[32]..tag.SEG[seg].data[33])
local mirror=tonumber(tag.SEG[seg].data[35]..tag.SEG[seg].data[36] ,16)
local mirrorcrc=utils.Crc8Legic(uid..tag.SEG[seg].data[35]..tag.SEG[seg].data[36])
local lastbal0=tonumber(tag.SEG[seg].data[39]..tag.SEG[seg].data[40] ,16)
local lastbal1=tonumber(tag.SEG[seg].data[41]..tag.SEG[seg].data[42] ,16)
local lastbal2=tonumber(tag.SEG[seg].data[43]..tag.SEG[seg].data[44] ,16)
test=""
-- display decoded/known stuff
print("\n------------------------------")
print("Tag-ID:\t\t "..uid)
print("Stamp:\t\t "..stamp)
print("UID-Mapping: \t\t"..("%06d"):format(tonumber(tag.SEG[seg].data[46]..tag.SEG[seg].data[47]..tag.SEG[seg].data[48], 16)))
print("------------------------------")
print("checksum 1:\t\t "..tag.SEG[seg].data[31].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[seg].data, "", 19, 30)), tag.SEG[seg].data[31])..")")
print("checksum 2:\t\t "..tag.SEG[seg].data[34].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[seg].data, "", 32, 33)), tag.SEG[seg].data[34])..")")
print("checksum 3:\t\t "..tag.SEG[seg].data[37].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[seg].data, "", 35, 36)), tag.SEG[seg].data[37])..")")
print("checksum 4:\t\t "..tag.SEG[seg].data[55].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[seg].data, "", 46, 54)), tag.SEG[seg].data[55])..")")
print("checksum 5:\t\t "..tag.SEG[seg].data[62].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[seg].data, "", 56, 61)), tag.SEG[seg].data[62])..")")
print("checksum 6:\t\t "..tag.SEG[seg].data[73].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[seg].data, "", 63, 72)), tag.SEG[seg].data[73])..")")
print("checksum 7:\t\t "..tag.SEG[seg].data[89].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[seg].data, "", 74, 88)), tag.SEG[seg].data[89])..")")
print("------------------------------")
print(string.format("Balance:\t\t %3.2f", balance/100).." ".."("..compareCrc(balancecrc, tag.SEG[seg].data[34])..")")
print(string.format("Shadow:\t\t\t %3.2f", mirror/100).." ".."("..compareCrc(balancecrc, tag.SEG[seg].data[37])..")")
print("------------------------------")
print(string.format("History 1:\t\t %3.2f", lastbal0/100))
print(string.format("History 2:\t\t %3.2f", lastbal1/100))
print(string.format("History 3:\t\t %3.2f", lastbal2/100))
print("------------------------------\n")
end
---
-- dump Legic-Cash data
function dumpLegicCash(tag, x)
if istable(tag.SEG[x]) == false then return end
io.write("in Segment "..tag.SEG[x].index.." :\n")
print("--------------------------------\n\tLegic-Cash Values\n--------------------------------")
local limit, curr, balance, rid, tcv
-- currency of balance & limit
curr=currency[tag.SEG[x].data[8]..tag.SEG[x].data[9]]
-- maximum balance
limit=string.format("%4.2f", tonumber(tag.SEG[x].data[10]..tag.SEG[x].data[11]..tag.SEG[x].data[12], 16)/100)
-- current balance
balance=string.format("%4.2f", tonumber(tag.SEG[x].data[15]..tag.SEG[x].data[16]..tag.SEG[x].data[17], 16)/100)
-- reader-id who wrote last transaction
rid=tonumber(tag.SEG[x].data[18]..tag.SEG[x].data[19]..tag.SEG[x].data[20], 16)
-- transaction counter value
tcv=tonumber(tag.SEG[x].data[29], 16)
print("Currency:\t\t "..curr)
print("Limit:\t\t\t "..limit)
print("Balance:\t\t "..balance)
print("Transaction Counter:\t "..tcv)
print("Reader-ID:\t\t "..rid.."\n--------------------------------\n")
end
---
-- raw 3rd-party
function print3rdPartyCash1(tag, x)
if istable(tag.SEG[x]) == false then return end
local uid=tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2
print("\n\t\tStamp : "..dumpTable(tag.SEG[x].data, "", 0 , 2))
print("\t\tBlock 0: "..dumpTable(tag.SEG[x].data, "", 3 , 18))
print()
print("\t\tBlock 1: "..dumpTable(tag.SEG[x].data, "", 19, 30))
print("checksum 1: Tag-ID .. Block 1 => LegicCrc8 = "..tag.SEG[x].data[31].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[x].data, "", 19, 30)), tag.SEG[x].data[31])..")")
print()
print("\t\tBlock 2: "..dumpTable(tag.SEG[x].data, "", 32, 33))
print("checksum 2: Block 2 => LegicCrc8 = "..tag.SEG[x].data[34].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[x].data, "", 32, 33)), tag.SEG[x].data[34])..")")
print()
print("\t\tBlock 3: "..dumpTable(tag.SEG[x].data, "", 35, 36))
print("checksum 3: Block 3 => LegicCrc8 = "..tag.SEG[x].data[37].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[x].data, "", 35, 36)), tag.SEG[x].data[37])..")")
print()
print("\t\tyet unknown: "..tag.SEG[x].data[38])
print()
print("\t\tHisatory 1: "..dumpTable(tag.SEG[x].data, "", 39, 40))
print("\t\tHisatory 2: "..dumpTable(tag.SEG[x].data, "", 41, 42))
print("\t\tHisatory 3: "..dumpTable(tag.SEG[x].data, "", 43, 44))
print()
print("\t\tyet unknown: "..tag.SEG[x].data[45])
print()
print("\t\tKGH-UID HEX: "..dumpTable(tag.SEG[x].data, "", 46, 48))
print("\t\tBlock 4: "..dumpTable(tag.SEG[x].data, "", 49, 54))
print("checksum 4: Tag-ID .. KGH-UID .. Block 4 => LegicCrc8 = "..tag.SEG[x].data[55].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[x].data, "", 46, 54)), tag.SEG[x].data[55])..")")
print()
print("\t\tBlock 5: "..dumpTable(tag.SEG[x].data, "", 56, 61))
print("checksum 5: Tag-ID .. Block 5 => LegicCrc8 = "..tag.SEG[x].data[62].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[x].data, "", 56, 61)), tag.SEG[x].data[62])..")")
print()
print("\t\tBlock 6: "..dumpTable(tag.SEG[x].data, "", 63, 72))
print("checksum 6: Tag-ID .. Block 6 => LegicCrc8 = "..tag.SEG[x].data[73].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[x].data, "", 63, 72)), tag.SEG[x].data[73])..")")
print()
print("\t\tBlock 7: "..dumpTable(tag.SEG[x].data, "", 74, 88))
print("checksum 7: Tag-ID .. Block 7 => LegicCrc8 = "..tag.SEG[x].data[89].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(tag.SEG[x].data, "", 74, 88)), tag.SEG[x].data[89])..")")
print()
print("\t\tBlock 8: "..dumpTable(tag.SEG[x].data, "", 90, 94))
end
--- Token related --
---
-- make token
function makeToken()
local mt={
['Type'] = {"SAM", "SAM63", "SAM64", "IAM", "GAM"},
['DCF'] = {"60ea", "31fa", "30fa", "80fa", "f0fa"},
['WRP'] = {"15", "2", "2", "2", "2"},
['WRC'] = {"01", "02", "02", "00", "00"},
['RD'] = {"01", "00", "00", "00", "00"},
['Stamp'] = {"00", "00", "00", "00", "00"},
['Segment'] = {"0d", "c0", "04", "00", "be", "01", "02", "03", "04", "01", "02", "03", "04"}
}
ttype=""
for k, v in pairs(mt.Type) do
ttype = ttype..k..") "..v.." "
end
mtq = tonumber(input("select number for Token-Type\n"..ttype, '1'), 10)
if (type(mtq) ~= "number") then return print("selection invalid!")
elseif (mtq > #mt.Type) then return print("selection invalid!")
else print("Token-Type '"..mt.Type[mtq].."' selected") end
local raw = calcHeaderRaw(mt.WRP[mtq], mt.WRC[mtq], mt.RD[mtq])
local mtCRC = "00"
bytes = {"01", "02", "03", "04", "cb", string.sub(mt.DCF[mtq], 0, 2), string.sub(mt.DCF[mtq], 3), raw,
"00", "00", "00", "00", "00", "00", "00", "00",
"00", "00", "00", "00", "00", "00"}
if (mtq == 1) then
for i = 0, #mt.Segment do
table.insert(bytes, mt.Segment[i])
end
bytes[9] = "ff"
end
-- fill bytes
for i = #bytes, 1023 do table.insert(bytes, "00") end
-- if Master-Token -> calc Master-Token-CRC
if (mtq>1) then bytes[22] = calcMtCrc(bytes) end
local tempTag = createTagTable()
-- remove segment if MasterToken
if (mtq>1) then tempTag.SEG[0] = nil end
return bytesToTag(bytes, tempTag)
end
---
-- edit token-data
function editTag(tag)
-- for simulation it makes sense to edit everything
local edit_sim = "MCD MSN0 MSN1 MSN2 MCC DCFl DCFh WRP WRC RD"
-- on real tags it makes only sense to edit DCF, WRP, WRC, RD
local edit_real = "DCFl DCFh WRP WRC RD"
if (confirm(acyellow.."do you want to edit non-writeable values (e.g. for simulation)?"..acoff)) then
edit_tag = edit_sim
else edit_tag = edit_real end
if(istable(tag)) then
for k,v in pairs(tag) do
if(type(v) ~= "table" and type(v) ~= "boolean" and string.find(edit_tag, k)) then
tag[k] = input("value for: "..accyan..k..acoff, v)
end
end
if (tag.Type == "SAM") then ttype = "Header"; else ttype = "Stamp"; end
if (confirm(acyellow.."do you want to edit "..ttype.." Data?"..acoff)) then
-- master-token specific
if(istable(tag.Bck) == false) then
-- stamp-data length=(0xfc-DCFh)
-- on MT: SSC holds the Starting Stamp Character (Stamp0)
tag.SSC=input(ttype.."0: ", tag.SSC)
-- rest of stamp-bytes are in tag.data 0..n
for i=0, (tonumber(0xfc ,10)-("%d"):format('0x'..tag.DCFh))-2 do
tag.data[i] = input(ttype.. i+1 ..": ", tag.data[i])
end
else
--- on credentials byte7 should always be 9f and byte8 ff
-- on Master-Token not (even on SAM63/64 not)
-- tag.SSC=input(ttype.."0: ", tag.SSC)
for i=0, #tag.data do
tag.data[i] = input(ttype.. i ..": ", tag.data[i])
end
end
end
bytes = tagToBytes(tag)
--- check data-consistency (calculate tag.raw)
bytes[8] = calcHeaderRaw(tag.WRP, tag.WRC, tag.RD)
--- Master-Token specific
-- should be triggered if a SAM was converted to a non-SAM (user-Token to Master-Token)
-- or a Master-Token has being edited (also SAM64 & SAM63 - which are in fact Master-Token)
if(tag.Type ~= "SAM" or bytes[6]..bytes[7] ~= "60ea") then
-- calc new Master-Token crc
bytes[22] = calcMtCrc(bytes)
else
-- ensure tag.SSC set to 'ff' on credential-token (SAM)
bytes[9] = 'ff'
-- if a Master-Token was converted to a Credential-Token
-- lets unset the Time-Area to 00 00 (will contain Stamp-Data on MT)
bytes[21] = '00'
bytes[22] = '00'
end
tag = bytesToTag(bytes, tag)
end
end
---
-- calculates header-byte (addr 0x07)
function calcHeaderRaw(wrp, wrc, rd)
wrp = ("%02x"):format(tonumber(wrp, 10))
rd = tonumber(rd, 16)
local res = ("%02x"):format(tonumber(wrp, 16)+tonumber(wrc.."0", 16)+((rd>0) and tonumber("8"..(rd-1), 16) or 0))
return res
end
---
-- determine TagType (bits 0..6 of DCFlow)
function getTokenType(DCFl)
--[[
0x00–0x2f IAM
0x30–0x6f SAM
0x70–0x7f GAM
]]--
local tt = tonumber(bbit("0x"..DCFl,0,7),10)
if (tt >= 0 and tt <= 47) then tt = "IAM"
elseif (tt == 49) then tt = "SAM63"
elseif (tt == 48) then tt = "SAM64"
elseif (tt >= 50 and tt <= 111) then tt = "SAM"
elseif (tt >= 112 and tt <= 127) then tt = "GAM"
else tt = "???" end
return tt
end
---
-- clear beackup-area of a virtual tag
function clearBackupArea(tag)
for i=1, #tag.Bck do
tag.Bck[i]='00'
end
return tag
end
--- Segment related --
---
-- get segmemnt-data from byte-table
function getSegmentData(bytes, start, index)
local segment={
['index'] = '00',
['flag'] = 'c',
['valid'] = 0,
['last'] = 0,
['len'] = 13,
['raw'] = {'00', '00', '00', '00'},
['WRP'] = 4,
['WRC'] = 0,
['RD'] = 0,
['crc'] = '00',
['data'] = {},
['kgh'] = false
}
if (bytes[start]) then
local i
-- #index
segment.index = index
-- flag = high nibble of byte 1
segment.flag = string.sub(bytes[start+1],0,1)
-- valid = bit 6 of byte 1
segment.valid = bbit("0x"..bytes[start+1],6,1)
-- last = bit 7 of byte 1
segment.last = bbit("0x"..bytes[start+1],7,1)
-- len = (byte 0)+(bit0-3 of byte 1)
segment.len = tonumber(bbit("0x"..bytes[start+1],0,4)..bytes[start],16)
-- raw segment header
segment.raw = {bytes[start], bytes[start+1], bytes[start+2], bytes[start+3]}
-- wrp (write proteted) = byte 2
segment.WRP = tonumber(bytes[start+2],16)
-- wrc (write control) - bit 4-6 of byte 3
segment.WRC = tonumber(bbit("0x"..bytes[start+3],4,3),16)
-- rd (read disabled) - bit 7 of byte 3
segment.RD = tonumber(bbit("0x"..bytes[start+3],7,1),16)
-- crc byte 4
segment.crc = bytes[start+4]
-- segment-data starts at segment.len - segment.header - segment.crc
for i=0, (segment.len-5) do
segment.data[i]=bytes[start+5+i]
end
return segment
else return false;
end
end
---
-- get index, start-aadr, length and content
function getSegmentStats(bytes)
local sStats = {}
local sValid, sLast, sLen, sStart, x
sStart=23
x=0
repeat
local s={}
-- valid = bit 6 of byte 1
sValid = bbit("0x"..bytes[sStart+1],6,1)
-- last = bit 7 of byte 1
sLast = bbit("0x"..bytes[sStart+1],7,1)
-- len = (byte 0)+(bit0-3 of byte 1)
sLen = tonumber(bbit("0x"..bytes[sStart+1],0,4)..bytes[sStart],16)
--print("index: "..("%02d"):format(x).." Len: "..sLen.." start:"..sStart.." end: "..(sStart+sLen-1))
s['index']=x
s['start']=sStart
s['end']=sStart+sLen-1
s['len']=sLen
if ( (sStart+sLen-1)>sStart ) then
table.insert(sStats, s)
end
sStart=sStart+sLen
x=x+1
until (sLast==1 or sValid==0 or x==126)
if (#sStats>0 ) then return sStats
else return false; end
end
---
-- regenerate segment-header (after edit)
function regenSegmentHeader(segment)
local seg=segment
local raw = segment.raw
local i
-- len bit0..7 | len=12bit=low nibble of byte1..byte0
raw[1]=("%02x"):format(bbit("0x"..("%03x"):format(seg.len),0,8))
-- high nibble of len bit6=valid , bit7=last of byte 1 | ?what are bit 5+6 for? maybe kgh?
raw[2]=("%02x"):format(bbit("0x"..("%03x"):format(seg.len),8,4)+bbit("0x"..("%02x"):format((seg.valid*64)+(seg.last*128)),0,8))
-- WRP
raw[3]=("%02x"):format(bbit("0x"..("%02x"):format(seg.WRP),0,8))
-- WRC + RD
raw[4]=("%02x"):format(tonumber((seg.WRC*16)+(seg.RD*128),10))
-- flag
seg.flag=string.sub(raw[2],0,1)
--print(raw[1].." "..raw[2].." "..raw[3].." "..raw[4])
if(#seg.data>(seg.len-5)) then
print("current payload: ".. #seg.data .." - desired payload: ".. seg.len-5)
print(acyellow.."Data-Length has being reduced:"..acgreen.." auto-removing "..acyellow.. #seg.data-(seg.len-5) ..acgreen .." bytes from Payload!"..acoff);
for i=(seg.len-5), #seg.data-1 do
table.remove(seg.data)
end
elseif (#seg.data<(seg.len-5)) then
print("current payload: ".. #seg.data .." - desired payload: ".. seg.len-5)
print(acyellow.."Data-Length has being extended:"..acgreen.." auto-adding "..acyellow..(seg.len-5)-#seg.data ..acgreen .." bytes to Payload!"..acoff);
for i=#seg.data, (seg.len-5-1) do
table.insert(seg.data, '00')
end
end
return seg
end
---
-- edit segment helper
function editSegment(tag, index)
local k,v
local edit_possible="valid len RD WRP WRC Stamp Payload"
if (istable(tag.SEG[index])) then
for k,v in pairs(tag.SEG[index]) do
if(string.find(edit_possible,k)) then
tag.SEG[index][k]=tonumber(input(accyan..k..acoff..": ", v),10)
end
end
else print("Segment with Index: "..("%02d"):format(index).." not found in Tag")
return false
end
regenSegmentHeader(tag.SEG[index])
print("\n"..dumpSegment(tag, index).."\n")
return tag
end
---
-- edit Segment Data
function editSegmentData(data)
io.write("\n")
if istable(data) == false then print("no Segment-Data found") end
local lc = check4LegicCash(data)
for i=0, #data-1 do
data[i]=input(accyan.."Data"..i..acoff..": ", data[i])
end
if (lc) then
data = fixLegicCash(data)
end
return data
end
---
-- list available segmets in virtual tag
function segmentList(tag)
local i
local res = ""
if (istable(tag.SEG[0])) then
for i=0, #tag.SEG do
res = res .. tag.SEG[i].index .. " "
end
return res
else print("no Segments found in Tag")
return false
end
end
---
-- helper to selecting a segment
function selectSegment(tag)
local sel
if (istable(tag.SEG[0])) then
print("availabe Segments:\n"..segmentList(tag))
sel=input("select Segment: ", '00')
sel=tonumber(sel,10)
if (sel) then return sel
else return '0' end
else
print("\nno Segments found")
return false
end
end
---
-- add segment
function addSegment(tag)
local i
local segment={
['index'] = '00',
['flag'] = 'c',
['valid'] = 1,
['last'] = 1,
['len'] = 13,
['raw'] = {'0d', '40', '04', '00'},
['WRP'] = 4,
['WRC'] = 0,
['RD'] = 0,
['crc'] = '00',
['data'] = {},
['kgh'] = false
}
if (istable(tag.SEG[0])) then
tag.SEG[#tag.SEG].last=0
table.insert(tag.SEG, segment)
for i=0, 8 do
tag.SEG[#tag.SEG].data[i]=("%02x"):format(i)
end
tag.SEG[#tag.SEG].index=("%02d"):format(#tag.SEG)
return tag
else
print("no Segment-Table found")
end
end
---
-- get only the stamp-bytes of a segment
function getSegmentStamp(seg, bytes)
local stamp=""
local stamp_len=7
--- the 'real' stamp on MIM is not really easy to tell for me since the 'data-block' covers stamp0..stampn+data0..datan
-- there a no stamps longer than 7 bytes & they are write-protected by default , and I have not seen user-credntials
-- with stamps smaller 3 bytes (except: Master-Token)
-- WRP -> Read/Write Protection
-- WRC -> Read/Write Condition
-- RD depends on WRC - if WRC > 0 and RD=1: only reader with matching #WRC of Stamp-bytes in thier Database have Read-Access to the Tag
if (seg.WRP<7) then stamp_len=(seg.WRP) end
for i=1, (stamp_len) do
stamp=stamp..seg.data[i-1]
end
if (bytes) then
stamp=str2bytes(stamp)
return stamp
else return stamp end
end
---
-- edit stamp of a segment
function editStamp(new_stamp, uid, data)
local stamp=str2bytes(new_stamp)
for i=0, #stamp-1 do
data[i]=stamp[i+1]
end
-- now fill in stamp
for i=0, (string.len(new_stamp)/2)-1 do
data[i]=stamp[i+1]
end
return fix3rdPartyCash1(uid, data)
end
---
-- autoselect special/known segments
function autoSelectSegment(tag, s)
local uid=tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2
local x=#tag.SEG+1
local res = false
io.write("autoSelect ")
--- search for desired segment-type
-- 3rd Party Segment
if (s=="3rdparty") then
repeat
io.write(". ")
x=x-1
res=check43rdPartyCash1(uid, tag.SEG[x].data)
until ( res or x==0 )
end
-- Legic-Cash Segment
if (s=="legiccash") then
repeat
io.write(". ")
x=x-1
res=check4LegicCash(tag.SEG[x].data)
until ( res or x==0 )
end
---
-- segment found
if (res) then
io.write("\nautoselected Index: "..string.format("%02d", x).."\n")
return x
end
---
-- nothing found
io.write(acyellow.."no Segment found\n"..acoff)
return -1
end
---
-- delete segment (except segment 00)
function delSegment(tag, index)
if (istable(tag.SEG[0])) then
local i
if (type(index)=="string") then index=tonumber(index,10) end
if (index > 0) then
table.remove(tag.SEG, index)
for i=0, #tag.SEG do
tag.SEG[i].index=("%02d"):format(i)
end
end
if(istable(tag.SEG[#tag.SEG])) then tag.SEG[#tag.SEG].last=1 end
return tag
end
end
---
-- edit uid 3rd party cash
function edit3rdUid(mapid, uid, data)
mapid=("%06x"):format(tonumber(mapid, 10))
data[46]=string.sub(mapid, 1 ,2)
data[47]=string.sub(mapid, 3 ,4)
data[48]=string.sub(mapid, 5 ,6)
return fix3rdPartyCash1(uid, data)
end
---
-- edit balance 3rd party cash
function edit3rdCash(new_cash, uid, data)
new_cash=("%04x"):format(new_cash)
data[32]=string.sub(new_cash, 0,2)
data[33]=string.sub(new_cash, 3,4)
data[34]=("%02x"):format(utils.Crc8Legic(uid..new_cash))
data[35]=string.sub(new_cash, 0,2)
data[36]=string.sub(new_cash, 3,4)
data[37]=("%02x"):format(utils.Crc8Legic(uid..new_cash))
data[39]=string.sub(new_cash, 0,2)
data[40]=string.sub(new_cash, 3,4)
data[41]='00'
data[42]='00'
data[43]='00'
data[44]='00'
return fix3rdPartyCash1(uid, data)
end
---
-- edit 3rd-party cash
function edit3rdPartyCash1(tag, x)
local uid=tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2
if (confirm("\nedit Balance?")) then
local new_cash=input("enter new Balance≤\nwithout comma and without currency-sign! (0-65535)", "100")
tag.SEG[x].data=edit3rdCash(new_cash, uid, tag.SEG[x].data)
end
-- change User-ID (used for online-account-mapping)
if (confirm("\nedit UserID-Mapping?")) then
local new_mapid=input("enter new UserID (6-digit value)", "012345")
tag.SEG[x].data=edit3rdUid(new_mapid, uid, tag.SEG[x].data)
end
if (confirm("\nedit Stamp?")) then
local new_stamp=input("enter new Stamp", getSegmentStamp(tag.SEG[x]))
tag.SEG[x].data=editStamp(new_stamp, uid, tag.SEG[x].data)
new_stamp=getSegmentStamp(tag.SEG[x], 'true')
print("stamp_bytes: "..#new_stamp)
-- replace stamp in 'block 1' also
io.write("editing stamp in Block 1 also ")
for i=20, (20+#new_stamp-1) do
tag.SEG[x].data[i]=new_stamp[i-19]
io.write(".");
end
print(" done")
-- fix known checksums
tag.SEG[x].data=fix3rdPartyCash1(uid, tag.SEG[x].data)
end
return tag
end
---
-- edit Legic Cash
function editLegicCash(data)
local limit, curr, balance, rid, tcv
-- currency of balance & limit
curr=currency[data[8]..data[9]]
-- maximum balance
limit=string.format("%4.2f", tonumber(data[10]..data[11]..data[12], 16)/100)
-- current balance
balance=string.format("%4.2f", tonumber(data[15]..data[16]..data[17], 16)/100)
-- reader-id who wrote last transaction
rid=tonumber(data[18]..data[19]..data[20], 16)
-- transaction counter value
tcv=tonumber(data[29], 16)
-- edit currency
if (confirm(accyan.."change Currency?"..acoff)) then
for k, v in pairs(currency) do io.write(k .. " = " .. v .. "\n") end
curr=input(accyan.."enter the 4-digit Hex for the new Currency:"..acoff, data[8]..data[9])
data[8]=string.sub(curr, 1, 2)
data[9]=string.sub(curr, 3, 4)
end
-- edit limit
if (confirm(accyan.."change Limit?"..acoff)) then
limit=string.format("%06x", input(accyan.."enter the Decimal for the new Limit:"..acoff, limit))
data[10]=string.sub(limit, 1, 2)
data[11]=string.sub(limit, 3, 4)
data[12]=string.sub(limit, 5, 6)
end
-- edit balance
if (confirm(accyan.."change Balance?"..acoff)) then
balance=string.format("%06x", input(accyan.."enter the Decimal for the new Balance:"..acoff, balance))
print("Balance: "..balance)
data[15]=string.sub(balance, 1, 2)
data[16]=string.sub(balance, 3, 4)
data[17]=string.sub(balance, 5, 6)
end
-- edit transaction-counter
if (confirm(accyan.."change Transaction-Counter?"..acoff)) then
tcv=string.format("%02x", input(accyan.."enter the 4-digit Hex for the new Currency:"..acoff, data[29]))
data[29]=tcv
end
-- edit reader.id
if (confirm(accyan.."change Last-Reader-ID?"..acoff)) then
rid=string.format("%06x", input(accyan.."enter the Decimal for the new Balance:"..acoff, rid))
print("Balance: "..balance)
data[18]=string.sub(rid, 1, 2)
data[19]=string.sub(rid, 3, 4)
data[20]=string.sub(rid, 5, 6)
end
return fixLegicCash(data)
end
---
-- chack for signature of a 'Legic-Cash-Segment'
function check4LegicCash(data)
if(#data==32) then
local stamp_len=(#data-25)
local stamp=""
for i=0, stamp_len-1 do
stamp=stamp..data[i].." "
end
if (data[7]=="01") then
if (("%04x"):format(utils.Crc16(dumpTable(data, "", 0, 12))) == data[13]..data[14]) then
if (("%04x"):format(utils.Crc16(dumpTable(data, "", 15, 20))) == data[21]..data[22]) then
if (("%04x"):format(utils.Crc16(dumpTable(data, "", 23, 29))) == data[30]..data[31]) then
io.write(accyan.."Legic-Cash Segment detected "..acoff)
return true
end
end
end
end
end
return false
end
---
-- chack for signature of a '3rd Party Cash-Segment' - not all bytes know until yet !!
function check43rdPartyCash1(uid, data)
if(#data==95) then
-- too explicit checking will avoid fixing ;-)
if (string.find(compareCrc(utils.Crc8Legic(uid..dumpTable(data, "", 19, 30)), data[31]),"valid")) then
--if (compareCrc(utils.Crc8Legic(uid..data[32]..data[33]), data[34])=="valid") then
--if (compareCrc(utils.Crc8Legic(uid..data[35]..data[36]), data[37])=="valid") then
--if (compareCrc(utils.Crc8Legic(uid..dumpTable(data, "", 56, 61)), data[62])=="valid") then
--if (compareCrc(utils.Crc8Legic(uid..dumpTable(data, "", 74, 88)), data[89])=="valid") then
io.write(accyan.."3rd Party Cash-Segment detected "..acoff)
return true
--end
--end
--end
--end
end
end
return false
end
--- CRC related ---
---
-- build segmentCrc credentials
function segmentCrcCredentials(tag, segid)
if (istable(tag.SEG[0])) then
local cred = tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2
cred = cred ..tag.SEG[segid].raw[1]..tag.SEG[segid].raw[2]..tag.SEG[segid].raw[3]..tag.SEG[segid].raw[4]
return cred
else return print(acyellow.."Master-Token / unsegmented Tag!"..acoff) end
end
---
-- build kghCrc credentials
function kghCrcCredentials(tag, segid)
if (istable(tag) and istable(tag.SEG[0])) then
local x='00'
if (type(segid)=="string") then segid=tonumber(segid,10) end
if (segid>0) then x='93' end
local cred = tag.MCD..tag.MSN0..tag.MSN1..tag.MSN2..("%02x"):format(tag.SEG[segid].WRP)
cred = cred..("%02x"):format(tag.SEG[segid].WRC)..("%02x"):format(tag.SEG[segid].RD)..x
for i=0, #tag.SEG[segid].data-2 do
cred = cred..tag.SEG[segid].data[i]
end
return cred
end
end
---
-- compare two bytes
function compareCrc(calc, guess)
calc=("%02x"):format(calc)
if (calc==guess) then return acgreen.."valid"..acoff
else return acred.."error "..acoff..calc.."!="..guess end
end
---
-- compare 4 bytes
function compareCrc16(calc, guess)
calc=("%04x"):format(calc)
if (calc==guess) then return acgreen.."valid"..acoff
else return acred.."error "..acoff..calc.."!="..guess end
end
---
-- repair / fix crc's of a 'Legic-Cash-Segment'
function fixLegicCash(data)
if(#data==32 and data[7]=="01") then
local crc1, crc2, crc3
-- set shadow-balance equal to balance
data[23]=data[15]
data[24]=data[16]
data[25]=data[17]
-- set shadow-last-reader to last-reader
data[26]=data[18]
data[27]=data[19]
data[28]=data[20]
-- calculate all crc's
crc1=("%04x"):format(utils.Crc16(dumpTable(data, "", 0, 12)))
crc2=("%04x"):format(utils.Crc16(dumpTable(data, "", 15, 20)))
crc3=("%04x"):format(utils.Crc16(dumpTable(data, "", 23, 29)))
-- set crc's
data[13]=string.sub(crc1, 1, 2)
data[14]=string.sub(crc1, 3, 4)
data[21]=string.sub(crc2, 1, 2)
data[22]=string.sub(crc2, 3, 4)
data[30]=string.sub(crc3, 1, 2)
data[31]=string.sub(crc3, 3, 4)
return data
end
end
---
-- repair / fix (yet known) crc's of a '3rd Party Cash-Segment' - not all bytes know until yet !!
function fix3rdPartyCash1(uid, data)
if(#data==95) then
-- checksum 1
data[31]=("%02x"):format(utils.Crc8Legic(uid..dumpTable(data, "", 19, 30)))
-- checksum 2
data[34]=("%02x"):format(utils.Crc8Legic(uid..data[32]..data[33]))
-- checksum 3
data[37]=("%02x"):format(utils.Crc8Legic(uid..data[35]..data[36]))
-- checksum 4
data[55]=("%02x"):format(utils.Crc8Legic(uid..dumpTable(data, "", 46, 54)))
-- checksum 5
data[62]=("%02x"):format(utils.Crc8Legic(uid..dumpTable(data, "", 56, 61)))
-- checksum 6
data[73]=("%02x"):format(utils.Crc8Legic(uid..dumpTable(data, "", 63, 72)))
-- checksum 7
data[89]=("%02x"):format(utils.Crc8Legic(uid..dumpTable(data, "", 74, 88)))
return data
end
end
---
-- calculate Master-Token crc
function calcMtCrc(bytes)
--print(#bytes)
local cmd=bytes[1]..bytes[2]..bytes[3]..bytes[4]..bytes[7]..bytes[6]..bytes[8]
local len=(tonumber(0xfc ,10)-("%d"):format('0x'..bytes[7]))
for i=1, len do
cmd=cmd..bytes[8+i]
end
local res=("%02x"):format(utils.Crc8Legic(cmd))
return res
end
---
-- calculate segmentCRC for a given segment
function calcSegmentCrc(tag, segid)
if (istable(tag.SEG[0])) then
-- check if a 'Kaber Group Header' exists
local data=segmentCrcCredentials(tag, segid)
return ("%02x"):format(utils.Crc8Legic(data))
end
end
---
-- calcuate kghCRC for a given segment
function calcKghCrc(tag, segid)
if (istable(tag.SEG[0])) then
-- check if a 'Kaber Group Header' exists
local i
local data=kghCrcCredentials(tag, segid)
return ("%02x"):format(utils.Crc8Legic(data))
end
end
---
-- check all segment-crc
function checkAllSegCrc(tag)
if (istable(tag.SEG[0])) then
for i=0, #tag.SEG do
crc=calcSegmentCrc(tag, i)
tag.SEG[i].crc=crc
end
else return print(acyellow.."Master-Token / unsegmented Tag"..acoff) end
end
---
-- check all segmnet-crc
function checkAllKghCrc(tag)
if (istable(tag.SEG[0])) then
for i=0, #tag.SEG do
crc=calcKghCrc(tag, i)
if (tag.SEG[i].kgh) then
tag.SEG[i].data[#tag.SEG[i].data-1]=crc
end
end
end
end
---
-- validate segmentCRC for a given segment
function checkSegmentCrc(tag, segid)
local data=segmentCrcCredentials(tag, segid)
if (("%02x"):format(utils.Crc8Legic(data))==tag.SEG[segid].crc) then
return true
end
return false
end
---
-- validate kghCRC to segment in tag-table
function checkKghCrc(tag, segid)
if (type(tag.SEG[segid])=='table') then
if (tag.data[3]=="11" and tag.raw=="9f" and tag.SSC=="ff") then
local data=kghCrcCredentials(tag, segid)
if (("%02x"):format(utils.Crc8Legic(data))==tag.SEG[segid].data[tag.SEG[segid].len-5-1]) then return true; end
else return false; end
else oops(acred.."'Kaba Group header' detected but no Segment-Data found"..ansocolors.reset) end
end
---
-- helptext for modify-mode
function modifyHelp()
local t=[[
Data I/O Segment Manipulation Token-Data
----------------- -------------------- ---------------------
rt => read Tag as => add Segment mt => make Token
wt => write Tag es => edit Segment Header et => edit Token data
ed => edit Segment Data tk => toggle KGH-Flag
File I/O rs => remove Segment
----------------- cc => check Segment-CRC
lf => load File ck => check KGH
sf => save File ds => dump Segments
xf => xor to File
Virtual Tags tagMap (partial) known Segments
-------------------------------- --------------------- ---------------------------
ct => copy mainTag to backupTag mm => make (new) Map dlc => dump Legic-Cash
tc => copy backupTag to mainTag em => edit Map submenu elc => edit Legic-Cash
tt => switch mainTag & backupTag lm => load map from file d3p => dump 3rd-Party-Cash
di => dump mainTag sm => save map to file e3p => edit 3rd-Party-Cash
do => dump backupTag
h => this help q => quit
]]
return t
end
---
-- modify Tag (interactive)
function modifyMode()
local i, backupTAG, outTAG, inTAG, outfile, infile, sel, segment, bytes, outbytes, tagMap
actions = {
---
-- helptext
["h"] = function(x)
print(" Version: "..version);
print(modifyHelp().."\n".."tags im Memory: "..(istable(inTAG) and ((currentTag=='inTAG') and acgreen.."*mainTAG"..acoff or "mainTAG") or "").." "..(istable(backupTAG) and ((currentTag=='backupTAG') and acgreen.."*backupTAG"..acoff or "backupTAG") or ""))
end,
---
-- read real Tag with PM3 into virtual 'mainTAG'
["rt"] = function(x)
inTAG=readFromPM3();
--actions.di()
end,
---
-- write content of virtual 'mainTAG' to real Tag with PM3
["wt"] = function(x)
writeToTag(inTAG)
end,
---
-- copy mainTAG to backupTAG
["ct"] = function(x)
print(accyan.."copy mainTAG to backupTAG"..acoff)
backupTAG=deepCopy(inTAG)
end,
---
-- copy backupTAG to mainTAG
["tc"] = function(x)
print(accyan.."copy backupTAG to mainTAG"..acoff)
inTAG=deepCopy(backupTAG)
end,
---
-- toggle between mainTAG and backupTAG
["tt"] = function(x)
-- copy main to temp
outTAG=deepCopy(inTAG)
-- copy backup to main
inTAG=deepCopy(backupTAG)
print(accyan.."toggle to "..accyan..((currentTag=='inTAG') and "backupTAG" or "mainTAG")..acoff)
if(currentTag=="inTAG") then currentTag='backupTAG'
else currentTag='inTAG' end
-- copy temp (main) to backup
backupTAG=deepCopy(outTAG)
end,
---
-- load file into mainTAG
["lf"] = function(x)
if (type(x)=='string' and file_check(x)) then
filename = x
else
filename = input("enter filename: ", "legic.temp")
end
inTAG=readFile(filename)
-- check for existing tagMap
if (file_check(filename..".map")) then
if(confirm(accyan.."Mapping-File for "..acoff..filename..accyan.." found - load it also?"..acoff)) then
tagMap=loadTagMap(filename..".map")
end
end
end,
---
-- save values of mainTAG to a file (xored with MCC of mainTAG)
["sf"] = function(x)
if(istable(inTAG)) then
outfile=input("enter filename:", "legic.temp")
bytes=tagToBytes(inTAG)
--bytes=xorBytes(bytes, inTAG.MCC)
if (bytes) then
writeFile(bytes, outfile)
end
end
end,
---
-- save values of mainTAG to a file (xored with 'specific' MCC)
["xf"] = function(x)
if(istable(inTAG)) then
outfile=input("enter filename:", "legic.temp")
crc=input("enter new crc: ('00' for a plain dump)", inTAG.MCC)
print("obfuscate witth: "..crc)
bytes=tagToBytes(inTAG)
bytes[5]=crc
if (bytes) then
writeFile(bytes, outfile)
end
end
end,
---
-- dump mainTAG (and all Segments)
["di"] = function(x)
if (istable(inTAG)) then
local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
if(istable(inTAG.SEG[0])) then
for i=0, #inTAG.SEG do
if(check43rdPartyCash1(uid, inTAG.SEG[i].data)) then
io.write(accyan.."in Segment index: "..inTAG.SEG[i].index ..acoff.. "\n")
elseif(check4LegicCash(inTAG.SEG[i].data)) then
io.write(accyan.."in Segment index: "..inTAG.SEG[i].index..acoff.."\n")
lc=true;
lci=inTAG.SEG[i].index;
end
end
end
print("\n"..dumpTag(inTAG).."\n")
if (lc) then actions["dlc"](lci) end
lc=false
end
end,
---
-- dump backupTAG (and all Segments)
["do"] = function(x) if (istable(backupTAG)) then print("\n"..dumpTag(backupTAG).."\n") end end,
---
-- create a empty tagMap
["mm"] = function(x)
-- clear existing tagMap and init
if (istable(inTAG)) then
tagMap=makeTagMap()
end
end,
---
-- edit a tagMap
["em"] = function(x)
if (istable(inTAG)==false) then
if (confirm("no mainTAG in memory!\nread from PM3?")) then
actions['rt']()
elseif (confirm("load from File?")) then
actions['lf']()
else return
end
end
if (istable(tagMap)==false) then actions['mm']() end
-- edit
tagMap=editTagMap(inTAG, tagMap)
end,
---
-- save a tagMap
["sm"] = function(x)
if (istable(tagMap)) then
if (istable(tagMap) and #tagMap.mappings>0) then
print(accyan.."Map contains "..acoff..#tagMap..accyan.." mappings"..acoff)
saveTagMap(tagMap, input(accyan.."enter filename:"..acoff, "Legic.map"))
else
print(acyellow.."no mappings in tagMap!"..acoff)
end
end
end,
---
-- load a tagMap
["lm"] = function(x)
tagMap=loadTagMap(input(accyan.."enter filename:"..acoff, "Legic.map"))
end,
---
-- dump single segment
["ds"] = function(x)
if (type(x)=="string" and string.len(x)>0) then
sel = tonumber(x,10)
else
sel = selectSegment(inTAG)
end
if (sel) then print("\n"..(dumpSegment(inTAG, sel) or acred.."no Segments available") ..acoff.."\n") end
end,
---
-- edit segment header
["es"] = function(x)
if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
else sel=selectSegment(inTAG) end
if (sel) then
if(istable(inTAG.SEG[0])) then
inTAG=editSegment(inTAG, sel)
inTAG.SEG[sel]=regenSegmentHeader(inTAG.SEG[sel])
else
print(acyellow.."no Segments in Tag"..acoff)
end
end
end,
---
-- add segment
["as"] = function(x)
if (istable(inTAG.SEG[0])) then
inTAG=addSegment(inTAG)
inTAG.SEG[#inTAG.SEG-1]=regenSegmentHeader(inTAG.SEG[#inTAG.SEG-1])
inTAG.SEG[#inTAG.SEG]=regenSegmentHeader(inTAG.SEG[#inTAG.SEG])
else print(accyan.."Master-Token / unsegmented Tag!"..acoff)
end
end,
---
-- remove segment
["rs"] = function(x)
if (istable(inTAG.SEG[0])) then
if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
else sel=selectSegment(inTAG) end
inTAG=delSegment(inTAG, sel)
for i=0, #inTAG.SEG do
inTAG.SEG[i]=regenSegmentHeader(inTAG.SEG[i])
end
end
end,
---
-- edit data-portion of single segment
["ed"] = function(x)
if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
else sel=selectSegment(inTAG) end
if (istable(inTAG.SEG[sel])) then
inTAG.SEG[sel].data=editSegmentData(inTAG.SEG[sel].data)
end
end,
---
-- edit Tag (MCD, MSN, MCC etc.)
["et"] = function(x)
if (istable(inTAG)) then
editTag(inTAG)
end
end,
---
-- make (dummy) Token
["mt"] = function(x) inTAG=makeToken(); actions.di() end,
---
-- fix segment-crc on single segment
["ts"] = function(x)
if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
else sel=selectSegment(inTAG) end
regenSegmentHeader(inTAG.SEG[sel])
end,
---
-- toggle kgh-crc-flag on a single segment
["tk"] = function(x)
if (istable(inTAG) and istable(inTAG.SEG[0])) then
if (type(x)=="string" and string.len(x)>0) then
sel = tonumber(x,10)
else
sel = selectSegment(inTAG)
end
if(inTAG.SEG[sel].kgh) then
inTAG.SEG[sel].kgh = false
else
inTAG.SEG[sel].kgh = true
end
end
end,
---
-- calculate LegicCrc8
["k"] = function(x)
if (type(x)=="string" and string.len(x)>0) then
print(("%02x"):format(utils.Crc8Legic(x)))
end
end,
---
-- noop
["xb"] = function(x)
end,
---
-- print string for LegicCrc8-calculation about single segment
["xc"] = function(x)
if (istable(inTAG) and istable(inTAG.SEG[0])) then
if (type(x)=="string" and string.len(x)>0) then
sel = tonumber(x,10)
else
sel = selectSegment(inTAG)
end
print("k "..kghCrcCredentials(inTAG, sel))
end
end,
---
-- fix legic-cash checksums
["flc"] = function(x)
if (type(x)=="string" and string.len(x)>0) then
x = tonumber(x,10)
else
x = selectSegment(inTAG)
end
inTAG.SEG[x].data=fixLegicCash(inTAG.SEG[x].data)
end,
---
-- edit legic-cash values fixLegicCash(data)
["elc"] = function(x)
x=autoSelectSegment(inTAG, "legiccash")
inTAG.SEG[x].data=editLegicCash(inTAG.SEG[x].data)
end,
---
-- dump legic-cash human-readable
["dlc"] = function(x)
-- if segment index was user defined
if (type(x)=="string" and string.len(x)>0) then
x=tonumber(x,10)
print(string.format("User-Selected Index %02d", x))
else
-- or try to find match
x = autoSelectSegment(inTAG, "legiccash")
end
-- dump it
dumpLegicCash(inTAG, x)
end,
---
-- dump 3rd-party-cash-segment
["d3p"] = function(x)
local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
-- if segment index was user defined
if (type(x)=="string" and string.len(x)>0) then
x=tonumber(x,10)
print(string.format("User-Selected Index %02d", x))
else
-- or try to find match
x = autoSelectSegment(inTAG, "3rdparty")
end
if (istable(inTAG) and istable(inTAG.SEG[x]) and inTAG.SEG[x].len == 100) then
uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
if (check43rdPartyCash1(uid, inTAG.SEG[x].data)) then
dump3rdPartyCash1(inTAG, x)
end
end
end,
---
-- dump 3rd-party-cash-segment (raw blocks and checksums over 'known areas')
["r3p"] = function(x)
local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
-- if segment index was user defined
if (type(x)=="string" and string.len(x)>0) then
x=tonumber(x,10)
print(string.format("User-Selected Index %02d", x))
else
-- or try to find match
x = autoSelectSegment(inTAG, "3rdparty")
end
print3rdPartyCash1(inTAG, x)
end,
---
-- edit 3rd-party-cash-segment values (Balance, Mapping-UID, Stamp)
["e3p"] = function(x)
local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
-- if segment index was user defined
if (type(x)=="string" and string.len(x)>0) then
x=tonumber(x,10)
print(string.format("User-Selected Index %02d", x))
else
-- or try to find match
x = autoSelectSegment(inTAG, "3rdparty")
end
if (istable(inTAG) and istable(inTAG.SEG[x]) and inTAG.SEG[x].len == 100) then
inTAG=edit3rdPartyCash1(inTAG, x)
dump3rdPartyCash1(inTAG, x)
end
end,
---
-- force fixing 3rd-party-checksums
["f3p"] = function(x)
if(type(x)=="string" and string.len(x)>=2) then
x = tonumber(x, 10)
else
x = selectSegment(inTAG)
end
if (istable(inTAG.SEG[x])) then
local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
inTAG.SEG[x].data=fix3rdPartyCash1(uid, inTAG.SEG[x].data)
end
end,
---
-- get stamp from single segment
["gs"] = function(x)
if(type(x)=="string" and string.len(x)>=2) then
x = tonumber(x, 10)
else
x = selectSegment(inTAG)
end
local stamp=getSegmentStamp(inTAG.SEG[x])
print("Stamp : "..stamp)
stamp=str2bytes(stamp)
print("lenght: "..#stamp)
end,
---
-- calculate crc16
["c6"] = function(x) local crc16=string.format("%4.04x", utils.Crc16(x))
print(string.sub(crc16, 0,2).." "..string.sub(crc16, 3,4))
end,
---
-- check & fix segments-crc of all segments
["cc"] = function(x) if (istable(inTAG)) then checkAllSegCrc(inTAG) end end,
---
-- set backup-area-bytes to '00'
["cb"] = function(x)
if (istable(inTAG)) then
print(accyan.."purge BackupArea"..acoff)
inTAG=clearBackupArea(inTAG)
end
end,
---
-- check and fix all segments inTAG.SEG[x].kgh toggled 'on'
["ck"] = function(x) if (istable(inTAG)) then checkAllKghCrc(inTAG) end end,
---
-- check and fix all segments inTAG.SEG[x].kgh toggled 'on'
["tac"] = function(x)
if (colored_output) then
colored_output=false
else
colored_output=true
end
load_colors(colored_output)
end,
}
repeat
-- defualt message / prompt
ic=input("Legic command? ('h' for help - 'q' for quit)", "h")
-- command actions decisions (first match, longer commands before shorter)
if (type(actions[string.lower(string.sub(ic,0,3))])=='function') then
actions[string.lower(string.sub(ic,0,3))](string.sub(ic,5))
elseif (type(actions[string.lower(string.sub(ic,0,2))])=='function') then
actions[string.lower(string.sub(ic,0,2))](string.sub(ic,4))
elseif (type(actions[string.lower(string.sub(ic,0,1))])=='function') then
actions[string.lower(string.sub(ic,0,1))](string.sub(ic,3))
else
actions.h('')
end
until (string.sub(ic,0,1)=="q")
end
---
-- main function
function main(args)
-- set init colors/switch (can be toggled with 'tac' => 'toggle ansicolors')
load_colors(colored_output)
if (#args == 0 ) then modifyMode() end
--- variables
local inTAG, backupTAG, outTAG, outfile, interactive, crc, ofs, cfs, dfs
-- just a spacer for better readability
print()
--- parse arguments
for o, a in getopt.getopt(args, 'hrmi:do:c:') do
-- display help
if o == "h" then return help(); end
-- read tag from PM3
if o == "r" then inTAG=readFromPM3() end
-- input file
if o == "i" then inTAG=readFile(a) end
-- dump virtual-Tag
if o == "d" then dfs=true end
-- interacive modifying
if o == "m" then
interactive = true
modifyMode()
end
-- xor (e.g. for clone or plain file)
if o == "c" then
cfs = true
crc = a
end
-- output file
if o == "o" then
outfile = a
ofs = true
end
end
-- file conversion (output to file)
if ofs == false then return end
-- dump infile / tag-read
if (dfs) then
print("-----------------------------------------")
print(dumpTag(inTAG))
end
bytes=tagToBytes(inTAG)
if (cfs) then
-- xor willl be done in function writeFile
-- with the value of byte[5]
bytes[5]=crc
end
-- write to outfile
if (bytes) then
writeFile(bytes, outfile)
--- read real tag into virtual tag
-- inTAG=readFromPM3() end
--- or simply use the bytes that where wriiten
inTAG=bytesToTag(bytes, inTAG)
-- show new content
if (dfs) then
print("-----------------------------------------")
print(dumpTag(inTAG))
end
end
end
---
-- script start
main(args)
You can’t perform that action at this time.