text
[RRG-proxmark3.git] / client / luascripts / hf_mf_tnp3_sim.lua
blobf04c8d7fa48c545e295d5f4889546d9e8dd40f70
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')
9 local ansicolors = require('ansicolors')
11 copyright = ''
12 author = 'Iceman'
13 version = 'v1.0.3'
14 desc = [[
15 This script will try to load a binary datadump of a Mifare TNP3xxx card.
16 It will try to validate all checksums and view some information stored in the dump
17 For an experimental mode, it tries to manipulate some data.
18 At last it sends all data to the PM3 device memory where it can be used in the command "hf mf sim"
20 example = [[
21 1. script run hf_mf_tnp3_sim
22 2. script run hf_mf_tnp3_sim -m
23 3. script run hf_mf_tnp3_sim -m -i myfile
25 usage = [[
26 script run hf_mf_tnp3_sim [-h] [-m] [-i <filename>]
28 arguments = [[
29 -h : this help
30 -m : Maxed out items (experimental)
31 -i : filename for the datadump to read (bin)
34 local DEBUG = true -- the debug flag
35 local RANDOM = '20436F707972696768742028432920323031302041637469766973696F6E2E20416C6C205269676874732052657365727665642E20'
37 local band = bit32.band
38 local bor = bit32.bor
39 local lshift = bit32.lshift
40 local rshift = bit32.rshift
41 local byte = string.byte
42 local char = string.char
43 local sub = string.sub
44 local format = string.format
46 ---
47 -- A debug printout-function
48 local function dbg(args)
49 if not DEBUG then return end
50 if type(args) == 'table' then
51 local i = 1
52 while result[i] do
53 dbg(result[i])
54 i = i+1
55 end
56 else
57 print('###', args)
58 end
59 end
60 ---
61 -- This is only meant to be used when errors occur
62 local function oops(err)
63 print('ERROR:', err)
64 core.clearCommandBuffer()
65 return nil, err
66 end
67 ---
68 -- Usage help
69 local function help()
70 print(copyright)
71 print(author)
72 print(version)
73 print(desc)
74 print(ansicolors.cyan..'Usage'..ansicolors.reset)
75 print(usage)
76 print(ansicolors.cyan..'Arguments'..ansicolors.reset)
77 print(arguments)
78 print(ansicolors.cyan..'Example usage'..ansicolors.reset)
79 print(example)
80 end
82 -- Exit message
83 local function ExitMsg(msg)
84 print( string.rep('--',20) )
85 print( string.rep('--',20) )
86 print(msg)
87 print()
88 end
90 -- blocks with data
91 -- there are two dataareas, in block 8 or block 36, ( 1==8 ,
92 -- checksum type = 0, 1, 2, 3
93 local function GetCheckSum(blocks, dataarea, chksumtype)
95 local crc
96 local area = 36
97 if dataarea == 1 then
98 area = 8
99 end
101 if chksumtype == 0 then
102 crc = blocks[1]:sub(29,32)
103 elseif chksumtype == 1 then
104 crc = blocks[area]:sub(29,32)
105 elseif chksumtype == 2 then
106 crc = blocks[area]:sub(25,28)
107 elseif chksumtype == 3 then
108 crc = blocks[area]:sub(21,24)
110 return utils.SwapEndianness(crc,16)
113 local function SetAllCheckSum(blocks)
114 print('Updating all checksums')
115 SetCheckSum(blocks, 3)
116 SetCheckSum(blocks, 2)
117 SetCheckSum(blocks, 1)
118 SetCheckSum(blocks, 0)
121 local function SetCheckSum(blocks, chksumtype)
123 if blocks == nil then return nil, 'Argument \"blocks\" nil' end
124 local newcrc
125 local area1 = 8
126 local area2 = 36
128 if chksumtype == 0 then
129 newcrc = ('%04X'):format(CalcCheckSum(blocks,1,0))
130 blocks[1] = blocks[1]:sub(1,28)..newcrc:sub(3,4)..newcrc:sub(1,2)
131 elseif chksumtype == 1 then
132 newcrc = ('%04X'):format(CalcCheckSum(blocks,1,1))
133 blocks[area1] = blocks[area1]:sub(1,28)..newcrc:sub(3,4)..newcrc:sub(1,2)
134 newcrc = ('%04X'):format(CalcCheckSum(blocks,2,1))
135 blocks[area2] = blocks[area2]:sub(1,28)..newcrc:sub(3,4)..newcrc:sub(1,2)
136 elseif chksumtype == 2 then
137 newcrc = ('%04X'):format(CalcCheckSum(blocks,1,2))
138 blocks[area1] = blocks[area1]:sub(1,24)..newcrc:sub(3,4)..newcrc:sub(1,2)..blocks[area1]:sub(29,32)
139 newcrc = ('%04X'):format(CalcCheckSum(blocks,2,2))
140 blocks[area2] = blocks[area2]:sub(1,24)..newcrc:sub(3,4)..newcrc:sub(1,2)..blocks[area2]:sub(29,32)
141 elseif chksumtype == 3 then
142 newcrc = ('%04X'):format(CalcCheckSum(blocks,1,3))
143 blocks[area1] = blocks[area1]:sub(1,20)..newcrc:sub(3,4)..newcrc:sub(1,2)..blocks[area1]:sub(25,32)
144 newcrc = ('%04X'):format(CalcCheckSum(blocks,2,3))
145 blocks[area2] = blocks[area2]:sub(1,20)..newcrc:sub(3,4)..newcrc:sub(1,2)..blocks[area2]:sub(25,32)
149 function CalcCheckSum(blocks, dataarea, chksumtype)
150 local area = 36
151 if dataarea == 1 then
152 area = 8
155 if chksumtype == 0 then
156 data = blocks[0]..blocks[1]:sub(1,28)
157 elseif chksumtype == 1 then
158 data = blocks[area]:sub(1,28)..'0500'
159 elseif chksumtype == 2 then
160 data = blocks[area+1]..blocks[area+2]..blocks[area+4]
161 elseif chksumtype == 3 then
162 data = blocks[area+5]..blocks[area+6]..blocks[area+8]..string.rep('00',0xe0)
164 return utils.Crc16(data)
167 local function ValidateCheckSums(blocks)
168 print(' Validating checksums')
170 local isOk, crc, calc
171 -- Checksum Type 0
172 crc = GetCheckSum(blocks,1,0)
173 calc = CalcCheckSum(blocks, 1, 0)
174 if crc == calc then isOk='Ok' else isOk = 'Error' end
175 io.write( ('TYPE 0 : %04x = %04x -- %s\n'):format(crc,calc,isOk))
177 -- Checksum Type 1 (DATAAREAHEADER 1)
178 crc = GetCheckSum(blocks,1,1)
179 calc = CalcCheckSum(blocks,1,1)
180 if crc == calc then isOk='Ok' else isOk = 'Error' end
181 io.write( ('TYPE 1 area 1: %04x = %04x -- %s\n'):format(crc,calc,isOk))
183 -- Checksum Type 1 (DATAAREAHEADER 2)
184 crc = GetCheckSum(blocks,2,1)
185 calc = CalcCheckSum(blocks,2,1)
186 if crc == calc then isOk='Ok' else isOk = 'Error' end
187 io.write( ('TYPE 1 area 2: %04x = %04x -- %s\n'):format(crc,calc,isOk))
189 -- Checksum Type 2 (DATAAREA 1)
190 crc = GetCheckSum(blocks,1,2)
191 calc = CalcCheckSum(blocks,1,2)
192 if crc == calc then isOk='Ok' else isOk = 'Error' end
193 io.write( ('TYPE 2 area 1: %04x = %04x -- %s\n'):format(crc,calc,isOk))
195 -- Checksum Type 2 (DATAAREA 2)
196 crc = GetCheckSum(blocks,2,2)
197 calc = CalcCheckSum(blocks,2,2)
198 if crc == calc then isOk='Ok' else isOk = 'Error' end
199 io.write( ('TYPE 2 area 2: %04x = %04x -- %s\n'):format(crc,calc,isOk))
201 -- Checksum Type 3 (DATAAREA 1)
202 crc = GetCheckSum(blocks,1,3)
203 calc = CalcCheckSum(blocks,1,3)
204 if crc == calc then isOk='Ok' else isOk = 'Error' end
205 io.write( ('TYPE 3 area 1: %04x = %04x -- %s\n'):format(crc,calc,isOk))
207 -- Checksum Type 3 (DATAAREA 2)
208 crc = GetCheckSum(blocks,2,3)
209 calc = CalcCheckSum(blocks,2,3)
210 if crc == calc then isOk='Ok' else isOk = 'Error' end
211 io.write( ('TYPE 3 area 2: %04x = %04x -- %s\n'):format(crc,calc,isOk))
215 local function AddKey(keys, blockNo, data)
216 local pos = (math.floor( blockNo / 4 ) * 12)+1
217 local key = keys:sub(pos, pos + 11 )
218 return key..data:sub(13)
221 local function LoadEmulator(uid, blocks)
222 print('Sending dumpdata to emulator memory')
223 local keys = pre.GetAll(uid)
224 local cmd, blockdata
225 for _,b in pairs(blocks) do
227 blockdata = b
229 if _%4 ~= 3 then
230 if (_ >= 8 and _<=21) or (_ >= 36 and _<=49) then
231 local base = ('%s%s%02x%s'):format(blocks[0], blocks[1], _ , RANDOM)
232 local baseStr = utils.ConvertHexToAscii(base)
233 local key = md5.sumhexa(baseStr)
234 local enc = core.aes128_encrypt(key, blockdata)
235 blockdata = utils.ConvertAsciiToHex(enc)
237 else
238 -- add keys if not existing..
239 if ( blockdata:sub(1,12) == '000000000000' ) then
240 blockdata = AddKey(keys, _, blockdata)
244 io.write( _..',')
245 io.flush()
246 core.clearCommandBuffer()
247 cmd = Command:newMIX{cmd = cmds.CMD_HF_MIFARE_EML_MEMSET, arg1 = _ ,arg2 = 1,arg3 = 16, data = blockdata}
248 local err, msg = cmd:sendMIX(true)
249 if err == nil then return err, msg end
251 io.write('\n')
254 local function Num2Card(m, l)
256 local k = {
257 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,0x42, 0x43, 0x44, 0x46, 0x47, 0x48, 0x4A, 0x4B,
258 0x4C, 0x4D, 0x4E, 0x50, 0x51, 0x52, 0x53, 0x54,0x56, 0x57, 0x58, 0x59, 0x5A, 0x00
260 local msw = tonumber(utils.SwapEndiannessStr(m,32),16)
261 local lsw = tonumber(utils.SwapEndiannessStr(l,32),16)
263 if msw > 0x17ea1 then
264 return "too big"
267 if msw == 0x17ea1 and lsw > 0x8931fee8 then
268 return "out of range"
271 local s = ""
272 local index
273 for i = 1,10 do
274 index, msw, lsw = DivideByK( msw, lsw)
275 if ( index <= 1 ) then
276 s = char(k[index]) .. s
277 else
278 s = char(k[index-1]) .. s
280 print (index-1, msw, lsw)
282 return s
284 --33LRT-LM9Q9
285 --7, 122, 3474858630
286 --20, 4, 1008436634
287 --7, 0, 627182959
288 --17, 0, 21626998
289 --16, 0, 745758
290 --23, 0, 25715
291 --21, 0, 886
292 --16, 0, 30
293 --1, 0, 1
294 --1, 0, 0
296 function DivideByK(msw, lsw)
298 local lowLSW
299 local highLSW
300 local remainder = 0
301 local RADIX = 29
303 --local num = 0 | band( rshift(msw,16), 0xffff)
304 local num = band( rshift(msw, 16), 0xffff)
306 --highLSW = 0 | lshift( (num / RADIX) , 16)
307 highLSW = lshift( (num / RADIX) , 16)
308 remainder = num % RADIX
310 num = bor( lshift(remainder,16), band(msw, 0xffff))
312 --highLSW |= num / RADIX
313 highLSW = highLSW or (num / RADIX)
314 remainder = num % RADIX
316 num = bor( lshift(remainder,16), ( band(rshift(lsw,16), 0xffff)))
318 --lowLSW = 0 | (num / RADIX) << 16
319 lowLSW = 0 or (lshift( (num / RADIX), 16))
320 remainder = num % RADIX
322 num = bor( lshift(remainder,16) , band(lsw, 0xffff) )
324 lowLSW = bor(lowLSW, (num / RADIX))
325 remainder = num % RADIX
326 return remainder, highLSW, lowLSW
328 -- uint num = 0 | (msw >> 16) & 0xffff;
330 -- highLSW = 0 | (num / RADIX) << 16;
331 -- remainder = num % RADIX;
333 -- num = (remainder << 16) | (msw & 0xffff);
335 -- highLSW |= num / RADIX;
336 -- remainder = num % RADIX;
338 -- num = (remainder << 16) | ((lsw >> 16) & 0xffff);
340 -- lowLSW = 0 | (num / RADIX) << 16;
341 -- remainder = num % RADIX;
343 -- num = (remainder << 16) | (lsw & 0xffff);
345 -- lowLSW |= num / RADIX;
346 -- remainder = num % RADIX;
350 local function main(args)
352 print( string.rep('--',20) )
353 print( string.rep('--',20) )
355 local result, err, hex
356 local maxed = false
357 local inputTemplate = 'dumpdata.bin'
358 local outputTemplate = os.date('toydump_%Y-%m-%d_%H%M');
360 -- Arguments for the script
361 for o, a in getopt.getopt(args, 'hmi:o:') do
362 if o == 'h' then return help() end
363 if o == 'm' then maxed = true end
364 if o == 'o' then outputTemplate = a end
365 if o == 'i' then inputTemplate = a end
368 -- Turn off Debug
369 local cmdSetDbgOff = 'hw dbg -0'
370 core.console( cmdSetDbgOff)
371 utils.Sleep(0.5)
373 -- Load dump.bin file
374 print( ('Load data from %s'):format(inputTemplate))
375 hex, err = utils.ReadDumpFile(inputTemplate)
376 if not hex then return oops(err) end
378 local blocks = {}
379 local blockindex = 0
380 for i = 1, #hex, 32 do
381 blocks[blockindex] = hex:sub(i, i+31)
382 blockindex = blockindex + 1
385 if DEBUG then ValidateCheckSums(blocks) end
388 print( string.rep('--',20) )
389 print(' Gathering info')
390 local uid = blocks[0]:sub(1,8)
391 local toytype = blocks[1]:sub(1,4)
392 local cardidLsw = blocks[1]:sub(9,16)
393 local cardidMsw = blocks[1]:sub(17,24)
394 local subtype = blocks[1]:sub(25,28)
396 -- Show info
397 print( string.rep('--',20) )
399 local item = toys.Find( toytype, subtype)
400 if item then
401 local itemStr = ('%s - %s (%s)'):format(item[6],item[5], item[4])
402 print(' ITEM TYPE : '..itemStr )
403 else
404 print( (' ITEM TYPE : 0x%s 0x%s'):format(toytype, subtype) )
407 print( (' UID : %s'):format(uid) )
408 print( (' CARDID : %s %s [%s]'):format(
409 cardidMsw,cardidLsw,
410 --Num2Card(cardidMsw, cardidLsw))
413 print( string.rep('--',20) )
416 -- Experience should be:
417 local experience = blocks[8]:sub(1,6)
418 print(('Experience : %d'):format(utils.SwapEndianness(experience,16)))
420 local money = blocks[8]:sub(7,10)
421 print(('Money : %d'):format(utils.SwapEndianness(money,16)))
425 -- Sequence number
426 local seqnum = blocks[8]:sub(18,19)
427 print(('Sequence number : %d'):format( tonumber(seqnum,16)))
429 local fairy = blocks[9]:sub(1,8)
430 --FD0F = Left, FF0F = Right
431 local path = 'not chosen'
432 if fairy:sub(2,2) == 'D' then
433 path = 'Left'
434 elseif fairy:sub(2,2) == 'F' then
435 path = 'Right'
437 print(('Fairy : %d [Path: %s] '):format(utils.SwapEndianness(fairy,24),path))
439 local hat = blocks[9]:sub(8,11)
440 print(('Hat : %d'):format(utils.SwapEndianness(hat,16)))
442 local level = blocks[13]:sub(27,28)
443 print(('LEVEL : %d'):format( tonumber(level,16)))
445 --local health = blocks[]:sub();
446 --print(('Health : %d'):format( tonumber(health,16))
448 --0x0D 0x29 0x0A 0x02 16-bit hero points value. Maximum 100.
449 local heropoints = blocks[13]:sub(20,23)
450 print(('Hero points : %d'):format(utils.SwapEndianness(heropoints,16)))
452 --0x10 0x2C 0x0C 0x04 32 bit flag value indicating heroic challenges completed.
453 local challenges = blocks[16]:sub(25,32)
454 print(('Finished hero challenges : %d'):format(utils.SwapEndianness(challenges,32)))
456 -- Character Name
457 local name1 = blocks[10]:sub(1,32)
458 local name2 = blocks[12]:sub(1,32)
459 print('Custom name : '..utils.ConvertHexToAscii(name1..name2))
461 if maxed then
462 print('Lets try to max out some values')
463 -- max out money, experience
464 --print (blocks[8])
465 blocks[8] = 'FFFFFF'..'FFFF'..blocks[8]:sub(11,32)
466 blocks[36] = 'FFFFFF'..'FFFF'..blocks[36]:sub(11,32)
467 --print (blocks[8])
469 -- max out hero challenges
470 --print (blocks[16])
471 blocks[16] = blocks[16]:sub(1,24)..'FFFFFFFF'
472 blocks[44] = blocks[44]:sub(1,24)..'FFFFFFFF'
473 --print (blocks[16])
475 -- max out heropoints
476 --print (blocks[13])
477 blocks[13] = blocks[13]:sub(1,19)..'0064'..blocks[13]:sub(24,32)
478 blocks[41] = blocks[41]:sub(1,19)..'0064'..blocks[41]:sub(24,32)
479 --print (blocks[13])
481 -- Update Checksums
482 SetAllCheckSum(blocks)
484 -- Validate Checksums
485 ValidateCheckSums(blocks)
488 --Load dumpdata to emulator memory
489 if DEBUG then
490 err = LoadEmulator(uid, blocks)
491 if err then return oops(err) end
492 core.clearCommandBuffer()
493 print('The simulation is now prepared.\n --> run \"hf mf sim -u '..uid..'\" <--')
496 main(args)