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)
29 local TIMEOUT = 2000 -- Shouldn't take longer than 2 seconds
30 local DEBUG = false -- the debug flag
31 local RANDOM = '20436F707972696768742028432920323031302041637469766973696F6E2E20416C6C205269676874732052657365727665642E20'
33 local band = bit32.band
35 local lshift = bit32.lshift
36 local rshift = bit32.rshift
37 local byte = string.byte
38 local char = string.char
39 local sub = string.sub
40 local format = string.format
43 -- A debug printout-function
49 if type(args) == "table" then
60 -- This is only meant to be used when errors occur
68 print("Example usage")
74 print( string.rep('--',20) )
75 print( string.rep('--',20) )
80 local function writedumpfile(infile)
81 t = infile:read("*all")
83 local len,hex = bin.unpack(("H%d"):format(len),t)
87 -- there are two dataareas, in block 8 or block 36, ( 1==8 ,
88 -- checksum type = 0, 1, 2, 3
89 local function GetCheckSum(blocks, dataarea, chksumtype)
97 if chksumtype == 0 then
98 crc = blocks[1]:sub(29,32)
99 elseif chksumtype == 1 then
100 crc = blocks[area]:sub(29,32)
101 elseif chksumtype == 2 then
102 crc = blocks[area]:sub(25,28)
103 elseif chksumtype == 3 then
104 crc = blocks[area]:sub(21,24)
106 return utils.SwapEndianness(crc,16)
109 local function SetCheckSum(blocks, chksumtype)
111 if blocks == nil then return nil, 'Argument \"blocks\" nil' end
116 if chksumtype == 0 then
117 newcrc = ('%04X'):format(CalcCheckSum(blocks,1,0))
118 blocks[1] = blocks[1]:sub(1,28)..newcrc:sub(3,4)..newcrc:sub(1,2)
119 elseif chksumtype == 1 then
120 newcrc = ('%04X'):format(CalcCheckSum(blocks,1,1))
121 blocks[area1] = blocks[area1]:sub(1,28)..newcrc:sub(3,4)..newcrc:sub(1,2)
122 newcrc = ('%04X'):format(CalcCheckSum(blocks,2,1))
123 blocks[area2] = blocks[area2]:sub(1,28)..newcrc:sub(3,4)..newcrc:sub(1,2)
124 elseif chksumtype == 2 then
125 newcrc = ('%04X'):format(CalcCheckSum(blocks,1,2))
126 blocks[area1] = blocks[area1]:sub(1,24)..newcrc:sub(3,4)..newcrc:sub(1,2)..blocks[area1]:sub(29,32)
127 newcrc = ('%04X'):format(CalcCheckSum(blocks,2,2))
128 blocks[area2] = blocks[area2]:sub(1,24)..newcrc:sub(3,4)..newcrc:sub(1,2)..blocks[area2]:sub(29,32)
129 elseif chksumtype == 3 then
130 newcrc = ('%04X'):format(CalcCheckSum(blocks,1,3))
131 blocks[area1] = blocks[area1]:sub(1,20)..newcrc:sub(3,4)..newcrc:sub(1,2)..blocks[area1]:sub(25,32)
132 newcrc = ('%04X'):format(CalcCheckSum(blocks,2,3))
133 blocks[area2] = blocks[area2]:sub(1,20)..newcrc:sub(3,4)..newcrc:sub(1,2)..blocks[area2]:sub(25,32)
137 function CalcCheckSum(blocks, dataarea, chksumtype)
139 if dataarea == 1 then
143 if chksumtype == 0 then
144 data = blocks[0]..blocks[1]:sub(1,28)
145 elseif chksumtype == 1 then
146 data = blocks[area]:sub(1,28)..'0500'
147 elseif chksumtype == 2 then
148 data = blocks[area+1]..blocks[area+2]..blocks[area+4]
149 elseif chksumtype == 3 then
150 data = blocks[area+5]..blocks[area+6]..blocks[area+8]..string.rep('00',0xe0)
152 return utils.Crc16(data)
155 local function ValidateCheckSums(blocks)
157 local isOk, crc, calc
159 crc = GetCheckSum(blocks,1,0)
160 calc = CalcCheckSum(blocks, 1, 0)
161 if crc == calc then isOk='Ok' else isOk = 'Error' end
162 io.write( ('TYPE 0 : %04x = %04x -- %s\n'):format(crc,calc,isOk))
164 -- Checksum Type 1 (DATAAREAHEADER 1)
165 crc = GetCheckSum(blocks,1,1)
166 calc = CalcCheckSum(blocks,1,1)
167 if crc == calc then isOk='Ok' else isOk = 'Error' end
168 io.write( ('TYPE 1 area 1: %04x = %04x -- %s\n'):format(crc,calc,isOk))
170 -- Checksum Type 1 (DATAAREAHEADER 2)
171 crc = GetCheckSum(blocks,2,1)
172 calc = CalcCheckSum(blocks,2,1)
173 if crc == calc then isOk='Ok' else isOk = 'Error' end
174 io.write( ('TYPE 1 area 2: %04x = %04x -- %s\n'):format(crc,calc,isOk))
176 -- Checksum Type 2 (DATAAREA 1)
177 crc = GetCheckSum(blocks,1,2)
178 calc = CalcCheckSum(blocks,1,2)
179 if crc == calc then isOk='Ok' else isOk = 'Error' end
180 io.write( ('TYPE 2 area 1: %04x = %04x -- %s\n'):format(crc,calc,isOk))
182 -- Checksum Type 2 (DATAAREA 2)
183 crc = GetCheckSum(blocks,2,2)
184 calc = CalcCheckSum(blocks,2,2)
185 if crc == calc then isOk='Ok' else isOk = 'Error' end
186 io.write( ('TYPE 2 area 2: %04x = %04x -- %s\n'):format(crc,calc,isOk))
188 -- Checksum Type 3 (DATAAREA 1)
189 crc = GetCheckSum(blocks,1,3)
190 calc = CalcCheckSum(blocks,1,3)
191 if crc == calc then isOk='Ok' else isOk = 'Error' end
192 io.write( ('TYPE 3 area 1: %04x = %04x -- %s\n'):format(crc,calc,isOk))
194 -- Checksum Type 3 (DATAAREA 2)
195 crc = GetCheckSum(blocks,2,3)
196 calc = CalcCheckSum(blocks,2,3)
197 if crc == calc then isOk='Ok' else isOk = 'Error' end
198 io.write( ('TYPE 3 area 2: %04x = %04x -- %s\n'):format(crc,calc,isOk))
201 local function LoadEmulator(blocks)
205 for _,b in pairs(blocks) do
210 if (_ >= 8 and _<=21) or (_ >= 36 and _<=49) then
211 local base = ('%s%s%02x%s'):format(blocks[0], blocks[1], _ , RANDOM)
212 local baseStr = utils.ConvertHexToAscii(base)
213 local key = md5.sumhexa(baseStr)
214 local enc = core.aes128_encrypt(key, blockdata)
215 local hex = utils.ConvertAsciiToBytes(enc)
216 hex = utils.ConvertBytesToHex(hex)
223 cmd = Command:new{cmd = cmds.CMD_MIFARE_EML_MEMSET, arg1 = _ ,arg2 = 1,arg3 = 0, data = blockdata}
224 local err = core.SendCommand(cmd:getBytes())
232 local function Num2Card(m, l)
235 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,0x42, 0x43, 0x44, 0x46, 0x47, 0x48, 0x4A, 0x4B,
236 0x4C, 0x4D, 0x4E, 0x50, 0x51, 0x52, 0x53, 0x54,0x56, 0x57, 0x58, 0x59, 0x5A, 0x00
238 local msw = tonumber(utils.SwapEndiannessStr(m,32),16)
239 local lsw = tonumber(utils.SwapEndiannessStr(l,32),16)
241 if msw > 0x17ea1 then
245 if msw == 0x17ea1 and lsw > 0x8931fee8 then
246 return "out of range"
252 index, msw, lsw = DivideByK( msw, lsw)
253 if ( index <= 1 ) then
254 s = char(k[index]) .. s
256 s = char(k[index-1]) .. s
258 print (index-1, msw, lsw)
274 function DivideByK(msw, lsw)
281 --local num = 0 | band( rshift(msw,16), 0xffff)
282 local num = band( rshift(msw, 16), 0xffff)
284 --highLSW = 0 | lshift( (num / RADIX) , 16)
285 highLSW = lshift( (num / RADIX) , 16)
286 remainder = num % RADIX
288 num = bor( lshift(remainder,16), band(msw, 0xffff))
290 --highLSW |= num / RADIX
291 highLSW = highLSW or (num / RADIX)
292 remainder = num % RADIX
294 num = bor( lshift(remainder,16), ( band(rshift(lsw,16), 0xffff)))
296 --lowLSW = 0 | (num / RADIX) << 16
297 lowLSW = 0 or (lshift( (num / RADIX), 16))
298 remainder = num % RADIX
300 num = bor( lshift(remainder,16) , band(lsw, 0xffff) )
302 lowLSW = bor(lowLSW, (num / RADIX))
303 remainder = num % RADIX
304 return remainder, highLSW, lowLSW
306 -- uint num = 0 | (msw >> 16) & 0xffff;
308 -- highLSW = 0 | (num / RADIX) << 16;
309 -- remainder = num % RADIX;
311 -- num = (remainder << 16) | (msw & 0xffff);
313 -- highLSW |= num / RADIX;
314 -- remainder = num % RADIX;
316 -- num = (remainder << 16) | ((lsw >> 16) & 0xffff);
318 -- lowLSW = 0 | (num / RADIX) << 16;
319 -- remainder = num % RADIX;
321 -- num = (remainder << 16) | (lsw & 0xffff);
323 -- lowLSW |= num / RADIX;
324 -- remainder = num % RADIX;
328 local function main(args)
330 print( string.rep('--',20) )
331 print( string.rep('--',20) )
333 local result, err, hex
335 local inputTemplate = "dumpdata.bin"
336 local outputTemplate = os.date("toydump_%Y-%m-%d_%H%M");
338 -- Arguments for the script
339 for o, a in getopt.getopt(args, 'hmi:o:') do
340 if o == "h" then return help() end
341 if o == "m" then maxed = true end
342 if o == "o" then outputTemplate = a end
343 if o == "i" then inputTemplate = a end
347 local cmdSetDbgOff = "hf mf dbg 0"
348 core.console( cmdSetDbgOff)
350 -- Load dump.bin file
351 print( (' Load data from %s'):format(inputTemplate))
352 hex, err = utils.ReadDumpFile(inputTemplate)
353 if not hex then return oops(err) end
357 for i = 1, #hex, 32 do
358 blocks[blockindex] = hex:sub(i,i+31)
359 blockindex = blockindex + 1
363 print(' Validating checksums')
364 ValidateCheckSums(blocks)
368 print( string.rep('--',20) )
369 print(' Gathering info')
370 local uid = blocks[0]:sub(1,8)
371 local toytype = blocks[1]:sub(1,4)
372 local cardidLsw = blocks[1]:sub(9,16)
373 local cardidMsw = blocks[1]:sub(17,24)
374 local subtype = blocks[1]:sub(25,28)
377 print( string.rep('--',20) )
379 local item = toys.Find( toytype, subtype)
381 local itemStr = ('%s - %s (%s)'):format(item[6],item[5], item[4])
382 print(' ITEM TYPE : '..itemStr )
384 print( (' ITEM TYPE : 0x%s 0x%s'):format(toytype, subtype) )
387 print( (' UID : 0x%s'):format(uid) )
388 print( (' CARDID : 0x%s %s [%s]'):format(
390 --Num2Card(cardidMsw, cardidLsw))
393 print( string.rep('--',20) )
396 -- Experience should be:
397 local experience = blocks[8]:sub(1,6)
398 print(('Experience : %d'):format(utils.SwapEndianness(experience,16)))
400 local money = blocks[8]:sub(7,10)
401 print(('Money : %d'):format(utils.SwapEndianness(money,16)))
406 local seqnum = blocks[8]:sub(18,19)
407 print(('Sequence number : %d'):format( tonumber(seqnum,16)))
409 local fairy = blocks[9]:sub(1,8)
410 --FD0F = Left, FF0F = Right
411 local path = 'not choosen'
412 if fairy:sub(2,2) == 'D' then
414 elseif fairy:sub(2,2) == 'F' then
417 print(('Fairy : %d [Path: %s] '):format(utils.SwapEndianness(fairy,24),path))
419 local hat = blocks[9]:sub(8,11)
420 print(('Hat : %d'):format(utils.SwapEndianness(hat,16)))
422 local level = blocks[13]:sub(27,28)
423 print(('LEVEL : %d'):format( tonumber(level,16)))
425 --local health = blocks[]:sub();
426 --print(('Health : %d'):format( tonumber(health,16))
428 --0x0D 0x29 0x0A 0x02 16-bit hero points value. Maximum 100.
429 local heropoints = blocks[13]:sub(20,23)
430 print(('Hero points : %d'):format(utils.SwapEndianness(heropoints,16)))
432 --0x10 0x2C 0x0C 0x04 32 bit flag value indicating heroic challenges completed.
433 local challenges = blocks[16]:sub(25,32)
434 print(('Finished hero challenges : %d'):format(utils.SwapEndianness(challenges,32)))
437 local name1 = blocks[10]:sub(1,32)
438 local name2 = blocks[12]:sub(1,32)
439 print('Custom name : '..utils.ConvertHexToAscii(name1..name2))
442 print('Lets try to max out some values')
443 -- max out money, experience
445 blocks[8] = 'FFFFFF'..'FFFF'..blocks[8]:sub(11,32)
446 blocks[36] = 'FFFFFF'..'FFFF'..blocks[36]:sub(11,32)
449 -- max out hero challenges
451 blocks[16] = blocks[16]:sub(1,24)..'FFFFFFFF'
452 blocks[44] = blocks[44]:sub(1,24)..'FFFFFFFF'
455 -- max out heropoints
457 blocks[13] = blocks[13]:sub(1,19)..'0064'..blocks[13]:sub(24,32)
458 blocks[41] = blocks[41]:sub(1,19)..'0064'..blocks[41]:sub(24,32)
462 print('Updating all checksums')
463 SetCheckSum(blocks, 3)
464 SetCheckSum(blocks, 2)
465 SetCheckSum(blocks, 1)
466 SetCheckSum(blocks, 0)
468 print('Validating all checksums')
469 ValidateCheckSums(blocks)
472 --Load dumpdata to emulator memory
474 print('Sending dumpdata to emulator memory')
475 err = LoadEmulator(blocks)
476 if err then return oops(err) end
477 core.clearCommandBuffer()
478 print('The simulation is now prepared.\n --> run \"hf mf sim u '..uid..'\" <--')