1 local cmds = require('commands')
2 local getopt = require('getopt')
3 local bin = require('bin')
4 local lib14a = require('read14a')
5 local utils = require('utils')
6 local md5 = require('md5')
7 local toys = require('default_toys')
11 2. script run tnp3sim -m
12 3. script run tnp3sim -m -i myfile
15 usage = "script run tnp3sim -h -m -i <filename>"
17 This script will try to load a binary datadump of a Mifare TNP3xxx card.
18 It vill try to validate all checksums and view some information stored in the dump
19 For an experimental mode, it tries to manipulate some data.
20 At last it sends all data to the PM3 device memory where it can be used in the command "hf mf sim"
24 -m : Maxed out items (experimental)
25 -i : filename for the datadump to read (bin)
28 local TIMEOUT = 2000 -- Shouldn't take longer than 2 seconds
29 local DEBUG = true -- the debug flag
32 local band = bit32.band
34 local lshift = bit32.lshift
35 local rshift = bit32.rshift
36 local byte = string.byte
37 local char = string.char
38 local sub = string.sub
39 local format = string.format
42 -- A debug printout-function
48 if type(args) == "table" then
59 -- This is only meant to be used when errors occur
67 print("Example usage")
73 print( string.rep('--',20) )
74 print( string.rep('--',20) )
79 local function writedumpfile(infile)
80 t = infile:read("*all")
82 local len,hex = bin.unpack(("H%d"):format(len),t)
86 -- there are two dataareas, in block 8 or block 36, ( 1==8 ,
87 -- checksum type = 0, 1, 2, 3
88 local function GetCheckSum(blocks, dataarea, chksumtype)
96 if chksumtype == 0 then
97 crc = blocks[1]:sub(29,32)
98 elseif chksumtype == 1 then
99 crc = blocks[area]:sub(29,32)
100 elseif chksumtype == 2 then
101 crc = blocks[area]:sub(25,28)
102 elseif chksumtype == 3 then
103 crc = blocks[area]:sub(21,24)
105 return utils.SwapEndianness(crc,16)
108 local function SetCheckSum(blocks, chksumtype)
110 if blocks == nil then return nil, 'Argument \"blocks\" nil' end
115 if chksumtype == 0 then
116 newcrc = ('%04X'):format(CalcCheckSum(blocks,1,0))
117 blocks[1] = blocks[1]:sub(1,28)..newcrc:sub(3,4)..newcrc:sub(1,2)
118 elseif chksumtype == 1 then
119 newcrc = ('%04X'):format(CalcCheckSum(blocks,1,1))
120 blocks[area1] = blocks[area1]:sub(1,28)..newcrc:sub(3,4)..newcrc:sub(1,2)
121 newcrc = ('%04X'):format(CalcCheckSum(blocks,2,1))
122 blocks[area2] = blocks[area2]:sub(1,28)..newcrc:sub(3,4)..newcrc:sub(1,2)
123 elseif chksumtype == 2 then
124 newcrc = ('%04X'):format(CalcCheckSum(blocks,1,2))
125 blocks[area1] = blocks[area1]:sub(1,24)..newcrc:sub(3,4)..newcrc:sub(1,2)..blocks[area1]:sub(29,32)
126 newcrc = ('%04X'):format(CalcCheckSum(blocks,2,2))
127 blocks[area2] = blocks[area2]:sub(1,24)..newcrc:sub(3,4)..newcrc:sub(1,2)..blocks[area2]:sub(29,32)
128 elseif chksumtype == 3 then
129 newcrc = ('%04X'):format(CalcCheckSum(blocks,1,3))
130 blocks[area1] = blocks[area1]:sub(1,20)..newcrc:sub(3,4)..newcrc:sub(1,2)..blocks[area1]:sub(25,32)
131 newcrc = ('%04X'):format(CalcCheckSum(blocks,2,3))
132 blocks[area2] = blocks[area2]:sub(1,20)..newcrc:sub(3,4)..newcrc:sub(1,2)..blocks[area2]:sub(25,32)
136 function CalcCheckSum(blocks, dataarea, chksumtype)
138 if dataarea == 1 then
142 if chksumtype == 0 then
143 data = blocks[0]..blocks[1]:sub(1,28)
144 elseif chksumtype == 1 then
145 data = blocks[area]:sub(1,28)..'0500'
146 elseif chksumtype == 2 then
147 data = blocks[area+1]..blocks[area+2]..blocks[area+4]
148 elseif chksumtype == 3 then
149 data = blocks[area+5]..blocks[area+6]..blocks[area+8]..string.rep('00',0xe0)
151 return utils.Crc16(data)
154 local function ValidateCheckSums(blocks)
156 local isOk, crc, calc
158 crc = GetCheckSum(blocks,1,0)
159 calc = CalcCheckSum(blocks, 1, 0)
160 if crc == calc then isOk='Ok' else isOk = 'Error' end
161 io.write( ('TYPE 0 : %04x = %04x -- %s\n'):format(crc,calc,isOk))
163 -- Checksum Type 1 (DATAAREAHEADER 1)
164 crc = GetCheckSum(blocks,1,1)
165 calc = CalcCheckSum(blocks,1,1)
166 if crc == calc then isOk='Ok' else isOk = 'Error' end
167 io.write( ('TYPE 1 area 1: %04x = %04x -- %s\n'):format(crc,calc,isOk))
169 -- Checksum Type 1 (DATAAREAHEADER 2)
170 crc = GetCheckSum(blocks,2,1)
171 calc = CalcCheckSum(blocks,2,1)
172 if crc == calc then isOk='Ok' else isOk = 'Error' end
173 io.write( ('TYPE 1 area 2: %04x = %04x -- %s\n'):format(crc,calc,isOk))
175 -- Checksum Type 2 (DATAAREA 1)
176 crc = GetCheckSum(blocks,1,2)
177 calc = CalcCheckSum(blocks,1,2)
178 if crc == calc then isOk='Ok' else isOk = 'Error' end
179 io.write( ('TYPE 2 area 1: %04x = %04x -- %s\n'):format(crc,calc,isOk))
181 -- Checksum Type 2 (DATAAREA 2)
182 crc = GetCheckSum(blocks,2,2)
183 calc = CalcCheckSum(blocks,2,2)
184 if crc == calc then isOk='Ok' else isOk = 'Error' end
185 io.write( ('TYPE 2 area 2: %04x = %04x -- %s\n'):format(crc,calc,isOk))
187 -- Checksum Type 3 (DATAAREA 1)
188 crc = GetCheckSum(blocks,1,3)
189 calc = CalcCheckSum(blocks,1,3)
190 if crc == calc then isOk='Ok' else isOk = 'Error' end
191 io.write( ('TYPE 3 area 1: %04x = %04x -- %s\n'):format(crc,calc,isOk))
193 -- Checksum Type 3 (DATAAREA 2)
194 crc = GetCheckSum(blocks,2,3)
195 calc = CalcCheckSum(blocks,2,3)
196 if crc == calc then isOk='Ok' else isOk = 'Error' end
197 io.write( ('TYPE 3 area 2: %04x = %04x -- %s\n'):format(crc,calc,isOk))
200 local function LoadEmulator(blocks)
201 local HASHCONSTANT = '20436F707972696768742028432920323031302041637469766973696F6E2E20416C6C205269676874732052657365727665642E20'
204 for _,b in pairs(blocks) do
209 if (_ >= 8 and _<=21) or (_ >= 36 and _<=49) then
210 local base = ('%s%s%02x%s'):format(blocks[0], blocks[1], _ , HASHCONSTANT)
211 local baseStr = utils.ConvertHexToAscii(base)
212 local key = md5.sumhexa(baseStr)
213 local enc = core.aes(key, blockdata)
214 local hex = utils.ConvertAsciiToBytes(enc)
215 hex = utils.ConvertBytesToHex(hex)
222 cmd = Command:new{cmd = cmds.CMD_MIFARE_EML_MEMSET, arg1 = _ ,arg2 = 1,arg3 = 0, data = blockdata}
223 local err = core.SendCommand(cmd:getBytes())
231 local function Num2Card(m, l)
234 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,0x42, 0x43, 0x44, 0x46, 0x47, 0x48, 0x4A, 0x4B,
235 0x4C, 0x4D, 0x4E, 0x50, 0x51, 0x52, 0x53, 0x54,0x56, 0x57, 0x58, 0x59, 0x5A, 0x00
237 local msw = tonumber(utils.SwapEndiannessStr(m,32),16)
238 local lsw = tonumber(utils.SwapEndiannessStr(l,32),16)
240 if msw > 0x17ea1 then
244 if msw == 0x17ea1 and lsw > 0x8931fee8 then
245 return "out of range"
251 index, msw, lsw = DivideByK( msw, lsw)
252 if ( index <= 1 ) then
253 s = char(k[index]) .. s
255 s = char(k[index-1]) .. s
257 print (index-1, msw, lsw)
273 function DivideByK(msw, lsw)
280 --local num = 0 | band( rshift(msw,16), 0xffff)
281 local num = band( rshift(msw, 16), 0xffff)
283 --highLSW = 0 | lshift( (num / RADIX) , 16)
284 highLSW = lshift( (num / RADIX) , 16)
285 remainder = num % RADIX
287 num = bor( lshift(remainder,16), band(msw, 0xffff))
289 --highLSW |= num / RADIX
290 highLSW = highLSW or (num / RADIX)
291 remainder = num % RADIX
293 num = bor( lshift(remainder,16), ( band(rshift(lsw,16), 0xffff)))
295 --lowLSW = 0 | (num / RADIX) << 16
296 lowLSW = 0 or (lshift( (num / RADIX), 16))
297 remainder = num % RADIX
299 num = bor( lshift(remainder,16) , band(lsw, 0xffff) )
301 lowLSW = bor(lowLSW, (num / RADIX))
302 remainder = num % RADIX
303 return remainder, highLSW, lowLSW
305 -- uint num = 0 | (msw >> 16) & 0xffff;
307 -- highLSW = 0 | (num / RADIX) << 16;
308 -- remainder = num % RADIX;
310 -- num = (remainder << 16) | (msw & 0xffff);
312 -- highLSW |= num / RADIX;
313 -- remainder = num % RADIX;
315 -- num = (remainder << 16) | ((lsw >> 16) & 0xffff);
317 -- lowLSW = 0 | (num / RADIX) << 16;
318 -- remainder = num % RADIX;
320 -- num = (remainder << 16) | (lsw & 0xffff);
322 -- lowLSW |= num / RADIX;
323 -- remainder = num % RADIX;
327 local function main(args)
329 print( string.rep('--',20) )
330 print( string.rep('--',20) )
332 local result, err, hex
334 local inputTemplate = "dumpdata.bin"
335 local outputTemplate = os.date("toydump_%Y-%m-%d_%H%M");
337 -- Arguments for the script
338 for o, a in getopt.getopt(args, 'hmi:o:') do
339 if o == "h" then return help() end
340 if o == "m" then maxed = true end
341 if o == "o" then outputTemplate = a end
342 if o == "i" then inputTemplate = a end
346 local cmdSetDbgOff = "hf mf dbg 0"
347 core.console( cmdSetDbgOff)
349 -- if not loadFromDump then
350 -- -- Look for tag present on reader,
351 -- result, err = lib14a.read1443a(false)
352 -- if not result then return oops(err) end
354 -- core.clearCommandBuffer()
356 -- if 0x01 ~= result.sak then -- NXP MIFARE TNP3xxx
357 -- return oops('This is not a TNP3xxx tag. aborting.')
361 -- print((' Found tag : %s'):format(result.name))
364 -- Load dump.bin file
365 print( (' Load data from %s'):format(inputTemplate))
366 hex, err = utils.ReadDumpFile(inputTemplate)
367 if not hex then return oops(err) end
371 for i = 1, #hex, 32 do
372 blocks[blockindex] = hex:sub(i,i+31)
373 blockindex = blockindex + 1
377 print('Validating checksums in the loaded datadump')
378 ValidateCheckSums(blocks)
382 print( string.rep('--',20) )
383 print(' Gathering info')
384 local uid = blocks[0]:sub(1,8)
385 local toytype = blocks[1]:sub(1,4)
386 local cardidLsw = blocks[1]:sub(9,16)
387 local cardidMsw = blocks[1]:sub(17,24)
388 local subtype = blocks[1]:sub(25,28)
391 print( string.rep('--',20) )
393 local item = toys.Find( toytype, subtype)
395 local itemStr = ('%s - %s (%s)'):format(item[6],item[5], item[4])
396 print(' ITEM TYPE :'..itemStr )
398 print( (' ITEM TYPE : 0x%s 0x%s'):format(toytype, subtype) )
401 print( (' UID : 0x%s'):format(uid) )
402 print( (' CARDID : 0x%s %s [%s]'):format(
404 --Num2Card(cardidMsw, cardidLsw))
407 print( string.rep('--',20) )
410 -- lets do something.
412 local experience = blocks[8]:sub(1,6)
413 print(('Experience : %d'):format(utils.SwapEndianness(experience,24)))
414 local money = blocks[8]:sub(7,10)
415 print(('Money : %d'):format(utils.SwapEndianness(money,16)))
416 local fairy = blocks[9]:sub(1,8)
417 --FD0F = Left, FF0F = Right
418 local path = 'not choosen'
419 if fairy:sub(2,2) == 'D' then
421 elseif fairy:sub(2,2) == 'F' then
424 print(('Fairy : %d [Path: %s] '):format(utils.SwapEndianness(fairy,24),path))
426 local hat = blocks[9]:sub(8,11)
427 print(('Hat : %d'):format(utils.SwapEndianness(hat,16)))
429 --0x0D 0x29 0x0A 0x02 16-bit hero points value. Maximum 100.
430 local heropoints = blocks[13]:sub(20,23)
431 print(('Hero points : %d'):format(utils.SwapEndianness(heropoints,16)))
433 --0x10 0x2C 0x0C 0x04 32 bit flag value indicating heroic challenges completed.
434 local challenges = blocks[16]:sub(25,32)
435 print(('Finished hero challenges : %d'):format(utils.SwapEndianness(challenges,32)))
438 print('Lets try to max out some values')
439 -- max out money, experience
441 blocks[8] = 'FFFFFF'..'FFFF'..blocks[8]:sub(11,32)
442 blocks[36] = 'FFFFFF'..'FFFF'..blocks[36]:sub(11,32)
445 -- max out hero challenges
447 blocks[16] = blocks[16]:sub(1,24)..'FFFFFFFF'
448 blocks[44] = blocks[44]:sub(1,24)..'FFFFFFFF'
451 -- max out heropoints
453 blocks[13] = blocks[13]:sub(1,19)..'0064'..blocks[13]:sub(24,32)
454 blocks[41] = blocks[41]:sub(1,19)..'0064'..blocks[41]:sub(24,32)
458 print('Updating all checksums')
459 SetCheckSum(blocks, 3)
460 SetCheckSum(blocks, 2)
461 SetCheckSum(blocks, 1)
462 SetCheckSum(blocks, 0)
464 print('Validating all checksums')
465 ValidateCheckSums(blocks)
468 --Load dumpdata to emulator memory
470 print('Sending dumpdata to emulator memory')
471 err = LoadEmulator(blocks)
472 if err then return oops(err) end
473 core.clearCommandBuffer()
474 print('The simulation is now prepared.\n --> run \"hf mf sim u '..uid..'\" <--')