--[[ 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 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) 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 --- -- 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) 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() 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)) 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)) 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 return oops("tag is no table in: bytesToTag ("..type(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)) 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 return bytes end return oops("tag is no table in tagToBytes ("..type(tag)..")") 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 save "..infile) tag=readFile(infile) 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 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' 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") else bytes = getInputBytes(filename) if (bytes == false) then return oops('couldnt get input bytes') else -- 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) 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 --- 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)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 (string.len(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])) then 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 end --- -- raw 3rd-party function print3rdPartyCash1(tag, x) if (istable(tag.SEG[x])) then 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 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) 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 --- -- 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) local lc=check4LegicCash(data) io.write("\n") if (istable(data)) then 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 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 --- -- 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 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(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)) -- or try to find match else 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)) -- 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, --- -- 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)) -- or try to find match else 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)) -- 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 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) 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 --- -- script start main(args)