b915fda3 |
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') |
411105e0 |
7 | local toys = require('default_toys') |
b915fda3 |
8 | |
9 | example =[[ |
10 | 1. script run tnp3sim |
11 | 2. script run tnp3sim -m |
12 | 3. script run tnp3sim -m -i myfile |
13 | ]] |
14 | author = "Iceman" |
15 | usage = "script run tnp3sim -h -m -i <filename>" |
16 | desc =[[ |
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" |
21 | |
22 | Arguments: |
23 | -h : this help |
24 | -m : Maxed out items (experimental) |
25 | -i : filename for the datadump to read (bin) |
ab7fdfcb |
26 | |
27 | ]] |
b915fda3 |
28 | |
29 | local TIMEOUT = 2000 -- Shouldn't take longer than 2 seconds |
ab7fdfcb |
30 | local DEBUG = false -- the debug flag |
31 | local RANDOM = '20436F707972696768742028432920323031302041637469766973696F6E2E20416C6C205269676874732052657365727665642E20' |
32 | |
33 | local band = bit32.band |
34 | local bor = bit32.bor |
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 |
41 | |
224ce36e |
42 | |
43 | |
44 | local band = bit32.band |
45 | local bor = bit32.bor |
46 | local lshift = bit32.lshift |
47 | local rshift = bit32.rshift |
48 | local byte = string.byte |
49 | local char = string.char |
50 | local sub = string.sub |
51 | local format = string.format |
52 | |
b915fda3 |
53 | --- |
54 | -- A debug printout-function |
55 | function dbg(args) |
56 | if not DEBUG then |
57 | return |
58 | end |
59 | |
60 | if type(args) == "table" then |
61 | local i = 1 |
62 | while result[i] do |
63 | dbg(result[i]) |
64 | i = i+1 |
65 | end |
66 | else |
67 | print("###", args) |
68 | end |
69 | end |
70 | --- |
71 | -- This is only meant to be used when errors occur |
72 | function oops(err) |
73 | print("ERROR: ",err) |
74 | end |
75 | --- |
76 | -- Usage help |
77 | function help() |
78 | print(desc) |
79 | print("Example usage") |
80 | print(example) |
81 | end |
82 | -- |
83 | -- Exit message |
84 | function ExitMsg(msg) |
85 | print( string.rep('--',20) ) |
86 | print( string.rep('--',20) ) |
87 | print(msg) |
88 | print() |
89 | end |
90 | |
b915fda3 |
91 | local function writedumpfile(infile) |
92 | t = infile:read("*all") |
93 | len = string.len(t) |
94 | local len,hex = bin.unpack(("H%d"):format(len),t) |
95 | return hex |
96 | end |
97 | -- blocks with data |
98 | -- there are two dataareas, in block 8 or block 36, ( 1==8 , |
99 | -- checksum type = 0, 1, 2, 3 |
100 | local function GetCheckSum(blocks, dataarea, chksumtype) |
101 | |
102 | local crc |
103 | local area = 36 |
104 | if dataarea == 1 then |
105 | area = 8 |
106 | end |
107 | |
108 | if chksumtype == 0 then |
109 | crc = blocks[1]:sub(29,32) |
110 | elseif chksumtype == 1 then |
111 | crc = blocks[area]:sub(29,32) |
112 | elseif chksumtype == 2 then |
113 | crc = blocks[area]:sub(25,28) |
114 | elseif chksumtype == 3 then |
115 | crc = blocks[area]:sub(21,24) |
116 | end |
117 | return utils.SwapEndianness(crc,16) |
118 | end |
119 | |
120 | local function SetCheckSum(blocks, chksumtype) |
121 | |
122 | if blocks == nil then return nil, 'Argument \"blocks\" nil' end |
123 | local newcrc |
124 | local area1 = 8 |
125 | local area2 = 36 |
126 | |
127 | if chksumtype == 0 then |
128 | newcrc = ('%04X'):format(CalcCheckSum(blocks,1,0)) |
129 | blocks[1] = blocks[1]:sub(1,28)..newcrc:sub(3,4)..newcrc:sub(1,2) |
130 | elseif chksumtype == 1 then |
131 | newcrc = ('%04X'):format(CalcCheckSum(blocks,1,1)) |
132 | blocks[area1] = blocks[area1]:sub(1,28)..newcrc:sub(3,4)..newcrc:sub(1,2) |
133 | newcrc = ('%04X'):format(CalcCheckSum(blocks,2,1)) |
134 | blocks[area2] = blocks[area2]:sub(1,28)..newcrc:sub(3,4)..newcrc:sub(1,2) |
135 | elseif chksumtype == 2 then |
136 | newcrc = ('%04X'):format(CalcCheckSum(blocks,1,2)) |
137 | blocks[area1] = blocks[area1]:sub(1,24)..newcrc:sub(3,4)..newcrc:sub(1,2)..blocks[area1]:sub(29,32) |
138 | newcrc = ('%04X'):format(CalcCheckSum(blocks,2,2)) |
139 | blocks[area2] = blocks[area2]:sub(1,24)..newcrc:sub(3,4)..newcrc:sub(1,2)..blocks[area2]:sub(29,32) |
140 | elseif chksumtype == 3 then |
141 | newcrc = ('%04X'):format(CalcCheckSum(blocks,1,3)) |
142 | blocks[area1] = blocks[area1]:sub(1,20)..newcrc:sub(3,4)..newcrc:sub(1,2)..blocks[area1]:sub(25,32) |
143 | newcrc = ('%04X'):format(CalcCheckSum(blocks,2,3)) |
144 | blocks[area2] = blocks[area2]:sub(1,20)..newcrc:sub(3,4)..newcrc:sub(1,2)..blocks[area2]:sub(25,32) |
145 | end |
146 | end |
147 | |
148 | function CalcCheckSum(blocks, dataarea, chksumtype) |
149 | local area = 36 |
150 | if dataarea == 1 then |
151 | area = 8 |
152 | end |
153 | |
154 | if chksumtype == 0 then |
155 | data = blocks[0]..blocks[1]:sub(1,28) |
156 | elseif chksumtype == 1 then |
157 | data = blocks[area]:sub(1,28)..'0500' |
158 | elseif chksumtype == 2 then |
159 | data = blocks[area+1]..blocks[area+2]..blocks[area+4] |
160 | elseif chksumtype == 3 then |
161 | data = blocks[area+5]..blocks[area+6]..blocks[area+8]..string.rep('00',0xe0) |
162 | end |
163 | return utils.Crc16(data) |
164 | end |
165 | |
166 | local function ValidateCheckSums(blocks) |
167 | |
168 | local isOk, crc, calc |
169 | -- Checksum Type 0 |
170 | crc = GetCheckSum(blocks,1,0) |
171 | calc = CalcCheckSum(blocks, 1, 0) |
172 | if crc == calc then isOk='Ok' else isOk = 'Error' end |
173 | io.write( ('TYPE 0 : %04x = %04x -- %s\n'):format(crc,calc,isOk)) |
174 | |
175 | -- Checksum Type 1 (DATAAREAHEADER 1) |
176 | crc = GetCheckSum(blocks,1,1) |
177 | calc = CalcCheckSum(blocks,1,1) |
178 | if crc == calc then isOk='Ok' else isOk = 'Error' end |
179 | io.write( ('TYPE 1 area 1: %04x = %04x -- %s\n'):format(crc,calc,isOk)) |
180 | |
181 | -- Checksum Type 1 (DATAAREAHEADER 2) |
182 | crc = GetCheckSum(blocks,2,1) |
183 | calc = CalcCheckSum(blocks,2,1) |
184 | if crc == calc then isOk='Ok' else isOk = 'Error' end |
185 | io.write( ('TYPE 1 area 2: %04x = %04x -- %s\n'):format(crc,calc,isOk)) |
186 | |
187 | -- Checksum Type 2 (DATAAREA 1) |
188 | crc = GetCheckSum(blocks,1,2) |
189 | calc = CalcCheckSum(blocks,1,2) |
190 | if crc == calc then isOk='Ok' else isOk = 'Error' end |
191 | io.write( ('TYPE 2 area 1: %04x = %04x -- %s\n'):format(crc,calc,isOk)) |
192 | |
193 | -- Checksum Type 2 (DATAAREA 2) |
194 | crc = GetCheckSum(blocks,2,2) |
195 | calc = CalcCheckSum(blocks,2,2) |
196 | if crc == calc then isOk='Ok' else isOk = 'Error' end |
197 | io.write( ('TYPE 2 area 2: %04x = %04x -- %s\n'):format(crc,calc,isOk)) |
198 | |
199 | -- Checksum Type 3 (DATAAREA 1) |
200 | crc = GetCheckSum(blocks,1,3) |
201 | calc = CalcCheckSum(blocks,1,3) |
202 | if crc == calc then isOk='Ok' else isOk = 'Error' end |
203 | io.write( ('TYPE 3 area 1: %04x = %04x -- %s\n'):format(crc,calc,isOk)) |
204 | |
205 | -- Checksum Type 3 (DATAAREA 2) |
206 | crc = GetCheckSum(blocks,2,3) |
207 | calc = CalcCheckSum(blocks,2,3) |
208 | if crc == calc then isOk='Ok' else isOk = 'Error' end |
209 | io.write( ('TYPE 3 area 2: %04x = %04x -- %s\n'):format(crc,calc,isOk)) |
210 | end |
211 | |
b915fda3 |
212 | local cmd |
213 | local blockdata |
214 | for _,b in pairs(blocks) do |
215 | |
216 | blockdata = b |
217 | |
218 | if _%4 ~= 3 then |
219 | if (_ >= 8 and _<=21) or (_ >= 36 and _<=49) then |
ab7fdfcb |
220 | local base = ('%s%s%02x%s'):format(blocks[0], blocks[1], _ , RANDOM) |
b915fda3 |
221 | local baseStr = utils.ConvertHexToAscii(base) |
222 | local key = md5.sumhexa(baseStr) |
ab7fdfcb |
223 | local enc = core.aes128_encrypt(key, blockdata) |
b915fda3 |
224 | local hex = utils.ConvertAsciiToBytes(enc) |
225 | hex = utils.ConvertBytesToHex(hex) |
226 | |
227 | blockdata = hex |
228 | io.write( _..',') |
229 | end |
230 | end |
231 | |
232 | cmd = Command:new{cmd = cmds.CMD_MIFARE_EML_MEMSET, arg1 = _ ,arg2 = 1,arg3 = 0, data = blockdata} |
233 | local err = core.SendCommand(cmd:getBytes()) |
234 | if err then |
235 | return err |
236 | end |
237 | end |
238 | io.write('\n') |
239 | end |
240 | |
224ce36e |
241 | local function Num2Card(m, l) |
242 | |
243 | local k = { |
244 | 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,0x42, 0x43, 0x44, 0x46, 0x47, 0x48, 0x4A, 0x4B, |
245 | 0x4C, 0x4D, 0x4E, 0x50, 0x51, 0x52, 0x53, 0x54,0x56, 0x57, 0x58, 0x59, 0x5A, 0x00 |
246 | } |
247 | local msw = tonumber(utils.SwapEndiannessStr(m,32),16) |
248 | local lsw = tonumber(utils.SwapEndiannessStr(l,32),16) |
249 | |
250 | if msw > 0x17ea1 then |
251 | return "too big" |
252 | end |
253 | |
254 | if msw == 0x17ea1 and lsw > 0x8931fee8 then |
255 | return "out of range" |
256 | end |
257 | |
258 | local s = "" |
259 | local index |
260 | for i = 1,10 do |
261 | index, msw, lsw = DivideByK( msw, lsw) |
262 | if ( index <= 1 ) then |
263 | s = char(k[index]) .. s |
264 | else |
265 | s = char(k[index-1]) .. s |
266 | end |
267 | print (index-1, msw, lsw) |
268 | end |
269 | return s |
270 | end |
271 | --33LRT-LM9Q9 |
272 | --7, 122, 3474858630 |
273 | --20, 4, 1008436634 |
274 | --7, 0, 627182959 |
275 | --17, 0, 21626998 |
276 | --16, 0, 745758 |
277 | --23, 0, 25715 |
278 | --21, 0, 886 |
279 | --16, 0, 30 |
280 | --1, 0, 1 |
281 | --1, 0, 0 |
282 | |
283 | function DivideByK(msw, lsw) |
284 | |
285 | local lowLSW |
286 | local highLSW |
287 | local remainder = 0 |
288 | local RADIX = 29 |
289 | |
290 | --local num = 0 | band( rshift(msw,16), 0xffff) |
291 | local num = band( rshift(msw, 16), 0xffff) |
292 | |
293 | --highLSW = 0 | lshift( (num / RADIX) , 16) |
294 | highLSW = lshift( (num / RADIX) , 16) |
295 | remainder = num % RADIX |
296 | |
297 | num = bor( lshift(remainder,16), band(msw, 0xffff)) |
298 | |
299 | --highLSW |= num / RADIX |
300 | highLSW = highLSW or (num / RADIX) |
301 | remainder = num % RADIX |
302 | |
303 | num = bor( lshift(remainder,16), ( band(rshift(lsw,16), 0xffff))) |
304 | |
305 | --lowLSW = 0 | (num / RADIX) << 16 |
306 | lowLSW = 0 or (lshift( (num / RADIX), 16)) |
307 | remainder = num % RADIX |
308 | |
309 | num = bor( lshift(remainder,16) , band(lsw, 0xffff) ) |
310 | |
311 | lowLSW = bor(lowLSW, (num / RADIX)) |
312 | remainder = num % RADIX |
313 | return remainder, highLSW, lowLSW |
314 | |
315 | -- uint num = 0 | (msw >> 16) & 0xffff; |
316 | |
317 | -- highLSW = 0 | (num / RADIX) << 16; |
318 | -- remainder = num % RADIX; |
319 | |
320 | -- num = (remainder << 16) | (msw & 0xffff); |
321 | |
322 | -- highLSW |= num / RADIX; |
323 | -- remainder = num % RADIX; |
324 | |
325 | -- num = (remainder << 16) | ((lsw >> 16) & 0xffff); |
326 | |
327 | -- lowLSW = 0 | (num / RADIX) << 16; |
328 | -- remainder = num % RADIX; |
329 | |
330 | -- num = (remainder << 16) | (lsw & 0xffff); |
331 | |
332 | -- lowLSW |= num / RADIX; |
333 | -- remainder = num % RADIX; |
334 | |
335 | end |
336 | |
b915fda3 |
337 | local function main(args) |
338 | |
339 | print( string.rep('--',20) ) |
340 | print( string.rep('--',20) ) |
341 | |
342 | local result, err, hex |
343 | local maxed = false |
344 | local inputTemplate = "dumpdata.bin" |
345 | local outputTemplate = os.date("toydump_%Y-%m-%d_%H%M"); |
346 | |
347 | -- Arguments for the script |
348 | for o, a in getopt.getopt(args, 'hmi:o:') do |
349 | if o == "h" then return help() end |
350 | if o == "m" then maxed = true end |
351 | if o == "o" then outputTemplate = a end |
352 | if o == "i" then inputTemplate = a end |
353 | end |
354 | |
355 | -- Turn off Debug |
356 | local cmdSetDbgOff = "hf mf dbg 0" |
357 | core.console( cmdSetDbgOff) |
358 | |
b915fda3 |
359 | -- Load dump.bin file |
360 | print( (' Load data from %s'):format(inputTemplate)) |
361 | hex, err = utils.ReadDumpFile(inputTemplate) |
362 | if not hex then return oops(err) end |
363 | |
364 | local blocks = {} |
365 | local blockindex = 0 |
366 | for i = 1, #hex, 32 do |
367 | blocks[blockindex] = hex:sub(i,i+31) |
368 | blockindex = blockindex + 1 |
369 | end |
370 | |
371 | if DEBUG then |
ab7fdfcb |
372 | print(' Validating checksums') |
b915fda3 |
373 | ValidateCheckSums(blocks) |
374 | end |
375 | |
376 | -- |
377 | print( string.rep('--',20) ) |
378 | print(' Gathering info') |
379 | local uid = blocks[0]:sub(1,8) |
411105e0 |
380 | local toytype = blocks[1]:sub(1,4) |
224ce36e |
381 | local cardidLsw = blocks[1]:sub(9,16) |
382 | local cardidMsw = blocks[1]:sub(17,24) |
411105e0 |
383 | local subtype = blocks[1]:sub(25,28) |
b915fda3 |
384 | |
385 | -- Show info |
386 | print( string.rep('--',20) ) |
411105e0 |
387 | |
388 | local item = toys.Find( toytype, subtype) |
389 | if item then |
390 | local itemStr = ('%s - %s (%s)'):format(item[6],item[5], item[4]) |
ab7fdfcb |
391 | print(' ITEM TYPE : '..itemStr ) |
411105e0 |
392 | else |
393 | print( (' ITEM TYPE : 0x%s 0x%s'):format(toytype, subtype) ) |
394 | end |
395 | |
b915fda3 |
396 | print( (' UID : 0x%s'):format(uid) ) |
224ce36e |
397 | print( (' CARDID : 0x%s %s [%s]'):format( |
398 | cardidMsw,cardidLsw, |
399 | --Num2Card(cardidMsw, cardidLsw)) |
400 | '') |
401 | ) |
b915fda3 |
402 | print( string.rep('--',20) ) |
403 | |
224ce36e |
404 | |
ab7fdfcb |
405 | -- Experience should be: |
b915fda3 |
406 | local experience = blocks[8]:sub(1,6) |
ab7fdfcb |
407 | print(('Experience : %d'):format(utils.SwapEndianness(experience,16))) |
408 | |
b915fda3 |
409 | local money = blocks[8]:sub(7,10) |
410 | print(('Money : %d'):format(utils.SwapEndianness(money,16))) |
ab7fdfcb |
411 | |
412 | -- |
413 | |
414 | -- Sequence number |
415 | local seqnum = blocks[8]:sub(18,19) |
416 | print(('Sequence number : %d'):format( tonumber(seqnum,16))) |
417 | |
b915fda3 |
418 | local fairy = blocks[9]:sub(1,8) |
419 | --FD0F = Left, FF0F = Right |
420 | local path = 'not choosen' |
421 | if fairy:sub(2,2) == 'D' then |
422 | path = 'Left' |
423 | elseif fairy:sub(2,2) == 'F' then |
424 | path = 'Right' |
425 | end |
426 | print(('Fairy : %d [Path: %s] '):format(utils.SwapEndianness(fairy,24),path)) |
427 | |
428 | local hat = blocks[9]:sub(8,11) |
429 | print(('Hat : %d'):format(utils.SwapEndianness(hat,16))) |
ab7fdfcb |
430 | |
431 | local level = blocks[13]:sub(27,28) |
432 | print(('LEVEL : %d'):format( tonumber(level,16))) |
433 |