RRG-Proxmark3/client/scripts/legic.lua
2016-03-06 13:26:13 +01:00

1779 lines
No EOL
62 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

--[[
(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 = "script run legic"
author = "Mosci"
version = "1.0.1"
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
ct => copy io Tag ed => edit Segment Data tk => toggle KGH-Flag
tc => copy oi Tag rs => remove Segment
cc => check Segment-CRC File I/O
di => dump inTag ck => check KGH -----------------
do => dump outTag lf => load File
ds => dump Segments sf => save File
lc => dump Legic-Cash xf => xor to File
d3p => dump 3rd Party Cash
r3p => raw 3rd Party Cash
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.
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
di: 'dump inTag' - shows the current content of the 'virtual Tag'
do: 'dump outTag' - 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
d3p: 'dump 3rd Party' - show balance, history and checksums of a (yet) unknown 3rd Party Cash-Segment
r3p: 'raw 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
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)
]]
currentTag="inTAG"
---
-- requirements
local utils = require('utils')
local getopt = require('getopt')
---
-- global variables / defines
local bxor = bit32.bxor
local bbit = bit32.extract
local input = utils.input
local confirm = utils.confirm
---
-- 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("ERROR: ",err)
return nil, err
end
---
-- Usage help
function help()
print(desc)
print(version)
print("Example usage")
print(example)
end
---
-- table check helper
function istable(t)
return type(t) == 'table'
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
return true
end
end
---
-- read file into virtual-tag
function readFile(filename)
local bytes = {}
local tag = {}
if (file_check(filename)==false) then
return oops("input file: "..filename.." not found")
else
bytes = getInputBytes(filename)
if (bytes == false) then return oops('couldnt get input bytes')
else
-- make plain bytes
bytes = xorBytes(bytes,bytes[5])
-- create Tag for plain bytes
tag=createTagTable()
-- load plain bytes to tag-table
tag=bytesToTag(bytes, tag)
end
end
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
---
-- 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 from pm3 into virtual-tag
function readFromPM3()
local tag, bytes, infile
--if (confirm("is the Tag placed onto the Proxmak3 and ready for reading?")) then
--print("reading Tag ...")
--infile=input("input a name for the temp-file:", "legic.temp")
--if (file_check(infile)) then
-- local answer = confirm("\nthe output-file "..infile.." alredy exists!\nthis will delete the previous content!\ncontinue?")
-- if (answer==false) then return print("user abort") end
--end
infile="legic.temp"
core.console("hf legic reader")
core.console("hf legic save "..infile)
--print("read temp-file into virtual Tag ...")
tag=readFile(infile)
return tag
--else return print("user abort"); end
end
---
-- write virtual Tag to real Tag
function writeToTag(plainBytes, taglen, filename)
local bytes
if(utils.confirm("\nplace your empty tag onto the PM3 to read & write\n") == false) then
return
end
-- write data to file
if (taglen > 0) then
WriteBytes = utils.input("enter number of bytes to write?", taglen)
-- load file into pm3-buffer
if (type(filename)~="string") then filename=input("filename to load to pm3-buffer?","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 0x%02x 0x01'):format(i)
core.console(cmd)
--print(cmd)
elseif (i == 6) then
-- write DCF in reverse order (requires 'mosci-patch')
cmd = 'hf legic write 0x05 0x02'
core.console(cmd)
--print(cmd)
else
print("skipping byte 0x05 - will be written next step")
end
utils.Sleep(0.2)
end
end
end
---
-- read file into table
function getInputBytes(infile)
local line
local bytes = {}
local fhi,err = io.open(infile)
if err then oops("faild to read from file ".. infile); return false; end
while true do
line = fhi:read()
if line == nil then break end
for byte in line:gmatch("%w+") do
table.insert(bytes, byte)
end
end
fhi:close()
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)) then
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))
tag.Stamp_len=(tonumber(0xfc,10)-tonumber(bbit("0x"..tag.DCFh,0,8),10))
tag.data=bytesToTable(bytes, 10, 13)
tag.Bck=bytesToTable(bytes, 14, 20)
tag.MTC=bytesToTable(bytes, 21, 22)
print("Tag-Type: ".. tag.Type)
if (tag.Type=="SAM" and #bytes>23) then
tag=segmentsToTag(bytes, tag)
print((#tag.SEG+1).." Segment(s) found")
-- 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(#bytes.." bytes for Tag processed")
return tag
end
return oops("tag is no table in: bytesToTag ("..type(tag)..")")
end
---
-- read Tag-Table in bytes-table
function tagToBytes(tag)
if (istable(tag)) then
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
print(#bytes.." bytes of Tag dumped")
return bytes
end
return oops("tag is no table in tagToBytes ("..type(tag)..")")
end
---
-- 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("do you want to edit non-writeable values (e.g. for simulation)?")) 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: "..k, v)
end
end
if (tag.Type=="SAM") then ttype="Header"; else ttype="Stamp"; end
if (confirm("do you want to edit "..ttype.." Data?")) 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)
local res
wrp=("%02x"):format(tonumber(wrp, 10))
rd=tonumber(rd, 16)
res=("%02x"):format(tonumber(wrp, 16)+tonumber(wrc.."0", 16)+((rd>0) and tonumber("8"..(rd-1), 16) or 0))
return res
end
---
-- dump virtual Tag-Data
function dumpTag(tag)
local i, i2
local res
local dp=0
local raw=""
-- sytstem area
res ="\nCDF: System Area"
res= res.."\n"..dumpCDF(tag)
-- segments (user-token area)
if(tag.Type=="SAM") then
res = res.."\n\nADF: User Area"
for i=0, #tag.SEG do
res=res.."\n"..dumpSegment(tag, i).."\n"
end
end
return res
end
---
-- 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
---
-- 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
---
-- 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),4.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(bbit("0x"..("%03x"):format(seg.WRC),4,3)..bbit("0x"..("%02x"):format(seg.RD*128),0,8))
-- 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("Data-Length has being reduced: removing ".. #seg.data-(seg.len-5) .." bytes from Payload");
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("Data-Length has being extended: adding "..(seg.len-5)-#seg.data.." bytes to Payload");
for i=#seg.data, (seg.len-5)-1 do
table.insert(seg.data, '00')
end
end
return seg
end
---
-- determine TagType (bits 0..6 of DCFlow)
function getTokenType(DCFl)
--[[
0x000x2f IAM
0x300x6f SAM
0x700x7f 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
---
-- dump tag-system area
function dumpCDF(tag)
local res=""
local i=0
local raw=""
local bytes
if (istable(tag)) then
res = res.."MCD: "..tag.MCD..", MSN: "..tag.MSN0.." "..tag.MSN1.." "..tag.MSN2..", MCC: "..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") 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
else
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")..")"
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") 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.."Segment "..("%02d"):format(tag.SEG[i].index)..": "
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 "valid" or "error")..")"
raw=""
-- WRC protected
if (tag.SEG[i].WRC>0) then
res = res .."\nWRC protected area:\n"
for i2=dp, tag.SEG[i].WRC-1 do
res = res..tag.SEG[i].data[dp].." "
dp=dp+1
end
end
-- WRP mprotected
if ((tag.SEG[i].WRP-tag.SEG[i].WRC)>0) then
res = res .."\nRemaining write protected area:\n"
for i2=dp, (tag.SEG[i].WRP-tag.SEG[i].WRC)-1 do
res = res..tag.SEG[i].data[dp].." "
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 "valid" or "error")..")"
else res = res..tag.SEG[i].data[dp] end
end
dp=0
return res
else
return print("Segment not found")
end
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(k..": ", 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)
local lc=check4LegicCash(data)
io.write("\n")
if (istable(data)) then
for i=0, #data-1 do
data[i]=input("Data"..i..": ", data[i])
end
if (lc) then data=fixLegicCash(data) end
return data
else
print("no Segment-Data found")
end
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
---
-- 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
---
-- 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 (string.len(header)==0) then return res
else return (header.." #"..(tend-tstart+1).."\n"..res) end
end
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
---
-- compare two bytes
function compareCrc(calc, guess)
calc=("%02x"):format(calc)
if (calc==guess) then return "valid"
else return "error "..calc.."!="..guess end
end
---
-- compare 4 bytes
function compareCrc16(calc, guess)
calc=("%04x"):format(calc)
if (calc==guess) then return "valid"
else return "error "..calc.."!="..guess 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
---
-- edit uid 3rd party cash
function edit3rdUid(mapid, uid, data)
mapid=("%06x"):format(tonumber(mapid, 10))
data[46]=string.sub(mapid, 0 ,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
---
-- repair / fix crc's of a 'Legic-Cash-Segment'
function fixLegicCash(data)
if(#data==32 and data[7]=="01") then
local crc1, crc2, crc3
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)))
data[13]=string.sub(crc1, 0, 2)
data[14]=string.sub(crc1, 3, 4)
data[21]=string.sub(crc2, 0, 2)
data[22]=string.sub(crc2, 3, 4)
data[30]=string.sub(crc3, 0, 2)
data[31]=string.sub(crc3, 3, 4)
return data
end
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 (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("3rd Party Cash-Segment detected ")
return true
--end
--end
--end
--end
end
end
return false
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("Legic-Cash Segment detected ")
return true
end
end
end
end
end
return false
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
---
-- check all segmnet-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("Matser-Token / unsegmented Tag") 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
---
-- 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
---
-- 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("'Kaba Group header' detected but no Segment-Data found") 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
---
-- 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("Master-Token / unsegmented Tag!") 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
---
-- 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
---
-- 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
ct => copy io Tag ed => edit Segment Data tk => toggle KGH-Flag
tc => copy oi Tag rs => remove Segment
tt => toggle Tag cc => check Segment-CRC File I/O
di => dump inTag ck => check KGH -----------------
do => dump outTag e3p => edit 3rd Party Cash lf => load File
ds => dump Segment sf => save File
dlc => dump Legic-Cash xf => xor to File
d3p => dump 3rd Party Cash
r3p => raw 3rd Party Cash
q => quit
]]
return t
end
---
-- modify Tag (interactive)
function modifyMode()
local i, backupTAG, outTAG, inTAG, outfile, infile, sel, segment, bytes, outbytes
actions = {
["h"] = function(x)
print(" Version: "..version);
print(modifyHelp().."\n".."tags im Memory: "..(istable(inTAG) and ((currentTag=='inTAG') and "*mainTAG" or "mainTAG") or "").." "..(istable(backupTAG) and ((currentTag=='backupTAG') and "*backupTAG" or "backupTAG") or ""))
end,
["rt"] = function(x)
inTAG=readFromPM3();
--actions.di()
end,
["wt"] = function(x)
if(istable(inTAG.SEG)) then
local taglen=22
if (istable(inTAG.Bck)) then
for i=0, #inTAG.SEG do
taglen=taglen+inTAG.SEG[i].len+5
end
end
local uid_old=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
-- read new tag (output tag)
outTAG=readFromPM3()
outbytes=tagToBytes(outTAG)
-- copy 'inputbuffer' to 'outputbuffer'
inTAG.MCD = outbytes[1]
inTAG.MSN0 = outbytes[2]
inTAG.MSN1 = outbytes[3]
inTAG.MSN2 = outbytes[4]
inTAG.MCC = outbytes[5]
-- recheck all segments-crc/kghcrc (only on a credential)
if(istable(inTAG.Bck)) then
checkAllSegCrc(inTAG)
checkAllKghCrc(inTAG)
local uid_new=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
for i=0, #inTAG.SEG do
if (check43rdPartyCash1(uid_old, inTAG.SEG[i].data)) then
io.write(" - fixing known checksums ... ")
inTAG.SEG[i].data=fix3rdPartyCash1(uid_new, inTAG.SEG[i].data)
io.write(" done\n")
end
end
end
--get bytes from ready outTAG
bytes=tagToBytes(inTAG)
-- mater-token-crc
if (inTAG.Type~="SAM") then bytes[22]=calcMtCrc(bytes) end
if (bytes) then
writeFile(bytes, 'MylegicClone.hex')
writeToTag(bytes, taglen, 'MylegicClone.hex')
actions.rt('')
end
end
end,
---
-- switich and copy virtual tags
["ct"] = function(x)
print("copy mainTAG to backupTAG")
outTAG=deepCopy(inTAG)
backupTAG=deepCopy(inTAG)
end,
["tc"] = function(x)
print("copy backupTAG to mainTAG")
inTAG=deepCopy(backupTAG)
end,
["tt"] = function(x)
print("toggle to "..((currentTag=='inTAG') and "backupTAG" or "mainTAG"))
if(currentTag=="inTAG") then
outTAG=deepCopy(inTAG)
inTAG=deepCopy(backupTAG)
currentTag='backupTAG'
else
inTAG=deepCopy(outTAG)
currentTag='inTAG'
end
end,
["lf"] = function(x)
if (file_check(x)) then filename=x
else filename=input("enter filename: ", "legic.temp") end
inTAG=readFile(filename)
end,
["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,
["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,
["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("in Segment index: "..inTAG.SEG[i].index.."\n")
elseif(check4LegicCash(inTAG.SEG[i].data)) then
io.write("in Segment index: "..inTAG.SEG[i].index.."\n")
lc=true;
lci=inTAG.SEG[i].index;
end
end
end
print("\n"..dumpTag(inTAG).."\n")
if (lc) then actions["dlc"](lci) end
end
end,
["do"] = function(x) if (istable(backupTAG)) then print("\n"..dumpTag(backupTAG).."\n") end end,
["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 "no Segments available").."\n") end
end,
["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("no Segments in Tag") end
end
end,
["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("Master-Token / unsegmented Tag!")
end
end,
["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,
["ed"] = function(x)
if (type(x)=="string" and string.len(x)>0) then sel=tonumber(x,10)
else sel=selectSegment(inTAG) end
if (sel) then
inTAG.SEG[sel].data=editSegmentData(inTAG.SEG[sel].data)
end
end,
["et"] = function(x)
if (istable(inTAG)) then
editTag(inTAG)
end
end,
["mt"] = function(x) inTAG=makeToken(); actions.di() end,
["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,
["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,
["k"] = function(x)
if (type(x)=="string" and string.len(x)>0) then
print(("%02x"):format(utils.Crc8Legic(x)))
end
end,
["xb"] = function(x)
end,
["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,
["dlc"] = 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))
-- or try to find match
else x=autoSelectSegment(inTAG, "legiccash") end
local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
if (istable(inTAG.SEG[x])) then
io.write("in Segment "..inTAG.SEG[x].index.." :\n")
print("--------------------------------\n\tLegic-Cash Values\n--------------------------------")
local limit, curr, balance, rid, tcv
-- currency of balance & limit
curr=currency[inTAG.SEG[x].data[8]..inTAG.SEG[x].data[9]]
-- maximum balance
limit=string.format("%4.2f", tonumber(inTAG.SEG[x].data[10]..inTAG.SEG[x].data[11]..inTAG.SEG[x].data[12], 16)/100)
-- current balance
balance=string.format("%4.2f", tonumber(inTAG.SEG[x].data[15]..inTAG.SEG[x].data[16]..inTAG.SEG[x].data[17], 16)/100)
-- reader-id who wrote last transaction
rid=tonumber(inTAG.SEG[x].data[18]..inTAG.SEG[x].data[19]..inTAG.SEG[x].data[20], 16)
-- transaction counter value
tcv=tonumber(inTAG.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
--end
end,
["df"] = function(x)
actions["lf"](x)
res=""
for i=0, #inTAG.SEG[1].data do
res=res..inTAG.SEG[1].data[i]
end
print(res)
end,
["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))
-- or try to find match
else 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,
["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))
-- or try to find match
else x=autoSelectSegment(inTAG, "3rdparty") end
local uid=inTAG.MCD..inTAG.MSN0..inTAG.MSN1..inTAG.MSN2
if (istable(inTAG.SEG[x])) then
print("\n\t\tStamp : "..dumpTable(inTAG.SEG[x].data, "", 0 , 2))
print("\t\tBlock 0: "..dumpTable(inTAG.SEG[x].data, "", 3 , 18))
print()
print("\t\tBlock 1: "..dumpTable(inTAG.SEG[x].data, "", 19, 30))
print("checksum 1: Tag-ID .. Block 1 => LegicCrc8 = "..inTAG.SEG[x].data[31].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(inTAG.SEG[x].data, "", 19, 30)), inTAG.SEG[x].data[31])..")")
print()
print("\t\tBlock 2: "..dumpTable(inTAG.SEG[x].data, "", 32, 33))
print("checksum 2: Block 2 => LegicCrc8 = "..inTAG.SEG[x].data[34].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(inTAG.SEG[x].data, "", 32, 33)), inTAG.SEG[x].data[34])..")")
print()
print("\t\tBlock 3: "..dumpTable(inTAG.SEG[x].data, "", 35, 36))
print("checksum 3: Block 3 => LegicCrc8 = "..inTAG.SEG[x].data[37].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(inTAG.SEG[x].data, "", 35, 36)), inTAG.SEG[x].data[37])..")")
print()
print("\t\tyet unknown: "..inTAG.SEG[x].data[38])
print()
print("\t\tHisatory 1: "..dumpTable(inTAG.SEG[x].data, "", 39, 40))
print("\t\tHisatory 2: "..dumpTable(inTAG.SEG[x].data, "", 41, 42))
print("\t\tHisatory 3: "..dumpTable(inTAG.SEG[x].data, "", 43, 44))
print()
print("\t\tyet unknown: "..inTAG.SEG[x].data[45])
print()
print("\t\tKGH-UID HEX: "..dumpTable(inTAG.SEG[x].data, "", 46, 48))
print("\t\tBlock 4: "..dumpTable(inTAG.SEG[x].data, "", 49, 54))
print("checksum 4: Tag-ID .. KGH-UID .. Block 4 => LegicCrc8 = "..inTAG.SEG[x].data[55].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(inTAG.SEG[x].data, "", 46, 54)), inTAG.SEG[x].data[55])..")")
print()
print("\t\tBlock 5: "..dumpTable(inTAG.SEG[x].data, "", 56, 61))
print("checksum 5: Tag-ID .. Block 5 => LegicCrc8 = "..inTAG.SEG[x].data[62].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(inTAG.SEG[x].data, "", 56, 61)), inTAG.SEG[x].data[62])..")")
print()
print("\t\tBlock 6: "..dumpTable(inTAG.SEG[x].data, "", 63, 72))
print("checksum 6: Tag-ID .. Block 6 => LegicCrc8 = "..inTAG.SEG[x].data[73].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(inTAG.SEG[x].data, "", 63, 72)), inTAG.SEG[x].data[73])..")")
print()
print("\t\tBlock 7: "..dumpTable(inTAG.SEG[x].data, "", 74, 88))
print("checksum 7: Tag-ID .. Block 7 => LegicCrc8 = "..inTAG.SEG[x].data[89].." ("..compareCrc(utils.Crc8Legic(uid..dumpTable(inTAG.SEG[x].data, "", 74, 88)), inTAG.SEG[x].data[89])..")")
print()
print("\t\tBlock 8: "..dumpTable(inTAG.SEG[x].data, "", 90, 94))
end
end,
["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))
-- or try to find match
else x=autoSelectSegment(inTAG, "3rdparty") end
if (istable(inTAG) and istable(inTAG.SEG[x]) and inTAG.SEG[x].len == 100) then
--if (check43rdPartyCash1(uid, inTAG.SEG[x].data)) then
-- change Balance
if (confirm("\nedit Balance?")) then
local new_cash=input("enter new Balance without comma or currency", "100")
inTAG.SEG[x].data=edit3rdCash(new_cash, uid, inTAG.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")
inTAG.SEG[x].data=edit3rdUid(new_mapid, uid, inTAG.SEG[x].data)
end
if (confirm("\nedit Stamp?")) then
local new_stamp=input("enter new Stamp", getSegmentStamp(inTAG.SEG[x]))
inTAG.SEG[x].data=editStamp(new_stamp, uid, inTAG.SEG[x].data)
new_stamp=getSegmentStamp(inTAG.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
inTAG.SEG[x].data[i]=new_stamp[i-19]
io.write(".");
end
print(" done")
-- fix known checksums
inTAG.SEG[x].data=fix3rdPartyCash1(uid, inTAG.SEG[x].data)
end
-- print out new settings
dump3rdPartyCash1(inTAG, x)
--end
end
end,
["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,
["c6"] = function(x) local crc16=string.format("%4.04x", utils.Crc16(x))
print(string.sub(crc16, 0,2).." "..string.sub(crc16, 3,4))
end,
["cc"] = function(x) if (istable(inTAG)) then checkAllSegCrc(inTAG) end end,
["cb"] = function(x)
if (istable(inTAG)) then
print("purge BackupArea")
inTAG=clearBackupArea(inTAG)
end
end,
["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,
["ck"] = function(x) if (istable(inTAG)) then checkAllKghCrc(inTAG) end end,
}
print("modify-modus! enter 'h' for help or 'q' to quit")
repeat
ic=input("Legic command? ('h' for help - 'q' for quit)", "h")
-- command actions
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
function clearBackupArea(tag)
for i=1, #tag.Bck do
tag.Bck[i]='00'
end
return tag
end
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
function str2bytes(s)
local res={}
if (string.len(s)%2~=0) then return print("stamp should be a even hexstring e.g.: deadbeef or 0badc0de") end
for i=1, string.len(s), 2 do
table.insert(res, string.sub(s,i,(i+1)))
end
return res
end
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
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("no Segment found\n")
return -1
end
--- main function
function main(args)
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) then
-- 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
end
-- Creates a complete/deep copy of the data
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
--- start
main(args)