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')
8 local pre = require('precalc')
12 2. script run tnp3sim -m
13 3. script run tnp3sim -m -i myfile
16 usage = "script run tnp3sim -h -m -i <filename>"
18 This script will try to load a binary datadump of a Mifare TNP3xxx card.
19 It vill try to validate all checksums and view some information stored in the dump
20 For an experimental mode, it tries to manipulate some data.
21 At last it sends all data to the PM3 device memory where it can be used in the command "hf mf sim"
25 -m : Maxed out items (experimental)
26 -i : filename for the datadump to read (bin)
30 local TIMEOUT = 2000 -- Shouldn't take longer than 2 seconds
31 local DEBUG = true -- the debug flag
32 local RANDOM = '20436F707972696768742028432920323031302041637469766973696F6E2E20416C6C205269676874732052657365727665642E20'
34 local band = bit32.band
36 local lshift = bit32.lshift
37 local rshift = bit32.rshift
38 local byte = string.byte
39 local char = string.char
40 local sub = string.sub
41 local format = string.format
44 -- A debug printout-function
46 if not DEBUG then return end
48 if type(args) == "table" then
59 -- 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 SetAllCheckSum(blocks)
110 print('Updating all checksums')
111 SetCheckSum(blocks, 3)
112 SetCheckSum(blocks, 2)
113 SetCheckSum(blocks, 1)
114 SetCheckSum(blocks, 0)
117 local function SetCheckSum(blocks, chksumtype)
119 if blocks == nil then return nil, 'Argument \"blocks\" nil' end
124 if chksumtype == 0 then
125 newcrc = ('%04X'):format(CalcCheckSum(blocks,1,0))
126 blocks[1] = blocks[1]:sub(1,28)..newcrc:sub(3,4)..newcrc:sub(1,2)
127 elseif chksumtype == 1 then
128 newcrc = ('%04X'):format(CalcCheckSum(blocks,1,1))
129 blocks[area1] = blocks[area1]:sub(1,28)..newcrc:sub(3,4)..newcrc:sub(1,2)
130 newcrc = ('%04X'):format(CalcCheckSum(blocks,2,1))
131 blocks[area2] = blocks[area2]:sub(1,28)..newcrc:sub(3,4)..newcrc:sub(1,2)
132 elseif chksumtype == 2 then
133 newcrc = ('%04X'):format(CalcCheckSum(blocks,1,2))
134 blocks[area1] = blocks[area1]:sub(1,24)..newcrc:sub(3,4)..newcrc:sub(1,2)..blocks[area1]:sub(29,32)
135 newcrc = ('%04X'):format(CalcCheckSum(blocks,2,2))
136 blocks[area2] = blocks[area2]:sub(1,24)..newcrc:sub(3,4)..newcrc:sub(1,2)..blocks[area2]:sub(29,32)
137 elseif chksumtype == 3 then
138 newcrc = ('%04X'):format(CalcCheckSum(blocks,1,3))
139 blocks[area1] = blocks[area1]:sub(1,20)..newcrc:sub(3,4)..newcrc:sub(1,2)..blocks[area1]:sub(25,32)
140 newcrc = ('%04X'):format(CalcCheckSum(blocks,2,3))
141 blocks[area2] = blocks[area2]:sub(1,20)..newcrc:sub(3,4)..newcrc:sub(1,2)..blocks[area2]:sub(25,32)
145 function CalcCheckSum(blocks, dataarea, chksumtype)
147 if dataarea == 1 then
151 if chksumtype == 0 then
152 data = blocks[0]..blocks[1]:sub(1,28)
153 elseif chksumtype == 1 then
154 data = blocks[area]:sub(1,28)..'0500'
155 elseif chksumtype == 2 then
156 data = blocks[area+1]..blocks[area+2]..blocks[area+4]
157 elseif chksumtype == 3 then
158 data = blocks[area+5]..blocks[area+6]..blocks[area+8]..string.rep('00',0xe0)
160 return utils.Crc16(data)
163 local function ValidateCheckSums(blocks)
164 print(' Validating checksums')
166 local isOk, crc, calc
168 crc = GetCheckSum(blocks,1,0)
169 calc = CalcCheckSum(blocks, 1, 0)
170 if crc == calc then isOk='Ok' else isOk = 'Error' end
171 io.write( ('TYPE 0 : %04x = %04x -- %s\n'):format(crc,calc,isOk))
173 -- Checksum Type 1 (DATAAREAHEADER 1)
174 crc = GetCheckSum(blocks,1,1)
175 calc = CalcCheckSum(blocks,1,1)
176 if crc == calc then isOk='Ok' else isOk = 'Error' end
177 io.write( ('TYPE 1 area 1: %04x = %04x -- %s\n'):format(crc,calc,isOk))
179 -- Checksum Type 1 (DATAAREAHEADER 2)
180 crc = GetCheckSum(blocks,2,1)
181 calc = CalcCheckSum(blocks,2,1)
182 if crc == calc then isOk='Ok' else isOk = 'Error' end
183 io.write( ('TYPE 1 area 2: %04x = %04x -- %s\n'):format(crc,calc,isOk))
185 -- Checksum Type 2 (DATAAREA 1)
186 crc = GetCheckSum(blocks,1,2)
187 calc = CalcCheckSum(blocks,1,2)
188 if crc == calc then isOk='Ok' else isOk = 'Error' end
189 io.write( ('TYPE 2 area 1: %04x = %04x -- %s\n'):format(crc,calc,isOk))
191 -- Checksum Type 2 (DATAAREA 2)
192 crc = GetCheckSum(blocks,2,2)
193 calc = CalcCheckSum(blocks,2,2)
194 if crc == calc then isOk='Ok' else isOk = 'Error' end
195 io.write( ('TYPE 2 area 2: %04x = %04x -- %s\n'):format(crc,calc,isOk))
197 -- Checksum Type 3 (DATAAREA 1)
198 crc = GetCheckSum(blocks,1,3)
199 calc = CalcCheckSum(blocks,1,3)
200 if crc == calc then isOk='Ok' else isOk = 'Error' end
201 io.write( ('TYPE 3 area 1: %04x = %04x -- %s\n'):format(crc,calc,isOk))
203 -- Checksum Type 3 (DATAAREA 2)
204 crc = GetCheckSum(blocks,2,3)
205 calc = CalcCheckSum(blocks,2,3)
206 if crc == calc then isOk='Ok' else isOk = 'Error' end
207 io.write( ('TYPE 3 area 2: %04x = %04x -- %s\n'):format(crc,calc,isOk))
211 local function AddKey(keys, blockNo, data)
212 local pos = (math.floor( blockNo / 4 ) * 12)+1
213 local key = keys:sub(pos, pos + 11 )
214 return key..data:sub(13)
217 local function LoadEmulator(uid, blocks)
218 print('Sending dumpdata to emulator memory')
219 local keys = pre.GetAll(uid)
221 for _,b in pairs(blocks) do
226 if (_ >= 8 and _<=21) or (_ >= 36 and _<=49) then
227 local base = ('%s%s%02x%s'):format(blocks[0], blocks[1], _ , RANDOM)
228 local baseStr = utils.ConvertHexToAscii(base)
229 local key = md5.sumhexa(baseStr)
230 local enc = core.aes128_encrypt(key, blockdata)
231 blockdata = utils.ConvertAsciiToHex(enc)
235 -- add keys if not existing..
236 if ( blockdata:sub(1,12) == '000000000000' ) then
237 blockdata = AddKey(keys, _, blockdata)
240 core.clearCommandBuffer()
241 cmd = Command:new{cmd = cmds.CMD_MIFARE_EML_MEMSET, arg1 = _ ,arg2 = 1,arg3 = 16, data = blockdata}
242 local err = core.SendCommand(cmd:getBytes())
243 if err then return err end
248 local function Num2Card(m, l)
251 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,0x42, 0x43, 0x44, 0x46, 0x47, 0x48, 0x4A, 0x4B,
252 0x4C, 0x4D, 0x4E, 0x50, 0x51, 0x52, 0x53, 0x54,0x56, 0x57, 0x58, 0x59, 0x5A, 0x00
254 local msw = tonumber(utils.SwapEndiannessStr(m,32),16)
255 local lsw = tonumber(utils.SwapEndiannessStr(l,32),16)
257 if msw > 0x17ea1 then
261 if msw == 0x17ea1 and lsw > 0x8931fee8 then
262 return "out of range"
268 index, msw, lsw = DivideByK( msw, lsw)
269 if ( index <= 1 ) then
270 s = char(k[index]) .. s
272 s = char(k[index-1]) .. s
274 print (index-1, msw, lsw)
290 function DivideByK(msw, lsw)
297 --local num = 0 | band( rshift(msw,16), 0xffff)
298 local num = band( rshift(msw, 16), 0xffff)
300 --highLSW = 0 | lshift( (num / RADIX) , 16)
301 highLSW = lshift( (num / RADIX) , 16)
302 remainder = num % RADIX
304 num = bor( lshift(remainder,16), band(msw, 0xffff))
306 --highLSW |= num / RADIX
307 highLSW = highLSW or (num / RADIX)
308 remainder = num % RADIX
310 num = bor( lshift(remainder,16), ( band(rshift(lsw,16), 0xffff)))
312 --lowLSW = 0 | (num / RADIX) << 16
313 lowLSW = 0 or (lshift( (num / RADIX), 16))
314 remainder = num % RADIX
316 num = bor( lshift(remainder,16) , band(lsw, 0xffff) )
318 lowLSW = bor(lowLSW, (num / RADIX))
319 remainder = num % RADIX
320 return remainder, highLSW, lowLSW
322 -- uint num = 0 | (msw >> 16) & 0xffff;
324 -- highLSW = 0 | (num / RADIX) << 16;
325 -- remainder = num % RADIX;
327 -- num = (remainder << 16) | (msw & 0xffff);
329 -- highLSW |= num / RADIX;
330 -- remainder = num % RADIX;
332 -- num = (remainder << 16) | ((lsw >> 16) & 0xffff);
334 -- lowLSW = 0 | (num / RADIX) << 16;
335 -- remainder = num % RADIX;
337 -- num = (remainder << 16) | (lsw & 0xffff);
339 -- lowLSW |= num / RADIX;
340 -- remainder = num % RADIX;
344 local function main(args)
346 print( string.rep('--',20) )
347 print( string.rep('--',20) )
349 local result, err, hex
351 local inputTemplate = "dumpdata.bin"
352 local outputTemplate = os.date("toydump_%Y-%m-%d_%H%M");
354 -- Arguments for the script
355 for o, a in getopt.getopt(args, 'hmi:o:') do
356 if o == "h" then return help() end
357 if o == "m" then maxed = true end
358 if o == "o" then outputTemplate = a end
359 if o == "i" then inputTemplate = a end
363 local cmdSetDbgOff = "hf mf dbg 0"
364 core.console( cmdSetDbgOff)
366 -- Load dump.bin file
367 print( (' Load data from %s'):format(inputTemplate))
368 hex, err = utils.ReadDumpFile(inputTemplate)
369 if not hex then return oops(err) end
373 for i = 1, #hex, 32 do
374 blocks[blockindex] = hex:sub(i,i+31)
375 blockindex = blockindex + 1
378 if DEBUG then ValidateCheckSums(blocks) end
381 print( string.rep('--',20) )
382 print(' Gathering info')
383 local uid = blocks[0]:sub(1,8)
384 local toytype = blocks[1]:sub(1,4)
385 local cardidLsw = blocks[1]:sub(9,16)
386 local cardidMsw = blocks[1]:sub(17,24)
387 local subtype = blocks[1]:sub(25,28)
390 print( string.rep('--',20) )
392 local item = toys.Find( toytype, subtype)
394 local itemStr = ('%s - %s (%s)'):format(item[6],item[5], item[4])
395 print(' ITEM TYPE : '..itemStr )
397 print( (' ITEM TYPE : 0x%s 0x%s'):format(toytype, subtype) )
400 print( (' UID : 0x%s'):format(uid) )
401 print( (' CARDID : 0x%s %s [%s]'):format(
403 --Num2Card(cardidMsw, cardidLsw))
406 print( string.rep('--',20) )
409 -- Experience should be:
410 local experience = blocks[8]:sub(1,6)
411 print(('Experience : %d'):format(utils.SwapEndianness(experience,16)))
413 local money = blocks[8]:sub(7,10)
414 print(('Money : %d'):format(utils.SwapEndianness(money,16)))
419 local seqnum = blocks[8]:sub(18,19)
420 print(('Sequence number : %d'):format( tonumber(seqnum,16)))
422 local fairy = blocks[9]:sub(1,8)
423 --FD0F = Left, FF0F = Right
424 local path = 'not choosen'
425 if fairy:sub(2,2) == 'D' then
427 elseif fairy:sub(2,2) == 'F' then
430 print(('Fairy : %d [Path: %s] '):format(utils.SwapEndianness(fairy,24),path))
432 local hat = blocks[9]:sub(8,11)
433 print(('Hat : %d'):format(utils.SwapEndianness(hat,16)))
435 local level = blocks[13]:sub(27,28)
436 print(('LEVEL : %d'):format( tonumber(level,16)))
438 --local health = blocks[]:sub();
439 --print(('Health : %d'):format( tonumber(health,16))
441 --0x0D 0x29 0x0A 0x02 16-bit hero points value. Maximum 100.
442 local heropoints = blocks[13]:sub(20,23)
443 print(('Hero points : %d'):format(utils.SwapEndianness(heropoints,16)))
445 --0x10 0x2C 0x0C 0x04 32 bit flag value indicating heroic challenges completed.
446 local challenges = blocks[16]:sub(25,32)
447 print(('Finished hero challenges : %d'):format(utils.SwapEndianness(challenges,32)))
450 local name1 = blocks[10]:sub(1,32)
451 local name2 = blocks[12]:sub(1,32)
452 print('Custom name : '..utils.ConvertHexToAscii(name1..name2))
455 print('Lets try to max out some values')
456 -- max out money, experience
458 blocks[8] = 'FFFFFF'..'FFFF'..blocks[8]:sub(11,32)
459 blocks[36] = 'FFFFFF'..'FFFF'..blocks[36]:sub(11,32)
462 -- max out hero challenges
464 blocks[16] = blocks[16]:sub(1,24)..'FFFFFFFF'
465 blocks[44] = blocks[44]:sub(1,24)..'FFFFFFFF'
468 -- max out heropoints
470 blocks[13] = blocks[13]:sub(1,19)..'0064'..blocks[13]:sub(24,32)
471 blocks[41] = blocks[41]:sub(1,19)..'0064'..blocks[41]:sub(24,32)
475 SetAllCheckSum(blocks)
477 -- Validate Checksums
478 ValidateCheckSums(blocks)
481 --Load dumpdata to emulator memory
483 err = LoadEmulator(uid, blocks)
484 if err then return oops(err) end
485 core.clearCommandBuffer()
486 print('The simulation is now prepared.\n --> run \"hf mf sim u '..uid..'\" <--')