Files
proxmark3/client/scripts/legic.lua

1226 lines
38 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|
+----+----+----+
--]]
example = "script run legic"
author = "Mosci"
version = "1.0"
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 File I/O
------------------ -------------------- ---------------
rt => read Tag ds => dump Segments lf => load File
wt => write Tag as => add Segment sf => save File
es => edit Segment xf => xor File
ct => copy io Tag ed => edit Data
tc => copy oi Tag rs => remove Segment
di => dump inTag cc => check Segment-CRC
do => dump outTag ck => check KGH
tk => toggle KGH-Flag
mt => make Token
q => quit et => edit Token h => this Help
Data I/O
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'
Segment Manipulation
(all manipulations happens only in the 'virtual inTAG' - they need to be written with 'wt' to take effect)
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
Input/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)
]]
---
-- 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
---
-- 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 MSN2 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 (Stamp):\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) then
res = res .."\nRemaining write protected area (Stamp):\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)
if (istable(data)) then
for i=0, #data-1 do
data[i]=input("Data"..i..": ", data[i])
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
---
-- 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 File I/O
------------------ -------------------- ------------------
rt => read Tag ds => dump Segments lf => load File
wt => write Tag as => add Segment sf => save File
es => edit Segment xf => xor to File
ct => copy io Tag ed => edit Data
tc => copy oi Tag rs => remove Segment
cc => check Segment-CRC
di => dump inTag ck => check KGH
do => dump outTag tk => toggle KGH-Flag
mt => make Token
q => quit et => edit Token h => this Help
]]
return t
end
---
-- modify Tag (interactive)
function modifyMode()
local i, outTAG, inTAG, outfile, infile, sel, segment, bytes, outbytes
actions = {
["h"] = function(x)
print(modifyHelp().."\n".."tags im Memory:"..(istable(inTAG) and " inTAG" or "")..(istable(outTAG) and " outTAG" 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
-- 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)
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,
["ct"] = function(x)
print("copy virtual input-TAG to output-TAG")
outTAG=inTAG
end,
["tc"] = function(x)
print("copy virtual output-TAG to input-TAG")
inTAG=outTAG
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 print("\n"..dumpTag(inTAG).."\n") end end,
["do"] = function(x) if (istable(outTAG)) then print("\n"..dumpTag(outTAG).."\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,
["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,
["cc"] = function(x) if (istable(inTAG)) then checkAllSegCrc(inTAG) 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,1))])=='function') then
actions[string.lower(string.sub(ic,0,1))](string.sub(ic,3))
elseif (type(actions[string.lower(string.sub(ic,0,2))])=='function') then
actions[string.lower(string.sub(ic,0,2))](string.sub(ic,4))
else actions.h('') end
until (string.sub(ic,0,1)=="q")
end
--- main function
function main(args)
if (#args == 0 ) then modifyMode() end
--- variables
local inTAG, 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
--- start
main(args)