text
[RRG-proxmark3.git] / client / luascripts / hf_mf_dump_luxeo.lua
blobf645d3d7badd00f13e076c3d3e6a3b3d8b43dbfa
1 ---
2 -- This Lua script is designed to run with Iceman/RRG Proxmark3 fork
3 -- Just copy hf_mf_dump-luxeo.lua to client/luascripts/
4 -- and run "script run hf_mf_dump_luxeo"
6 -- requirements
7 local cmds = require('commands')
8 local getopt = require('getopt')
9 local utils = require('utils')
10 local lib14a = require('read14a')
11 local ansicolors = require('ansicolors')
13 copyright = ''
14 author = '0xdrrb'
15 version = 'v0.1.3'
16 desc = [[
17 This is a script that tries to dump and decrypt the data of a specific type of Mifare laundromat token.
18 OBS! Tag must be on the antenna.
20 example = [[
21 script run hf_mf_dump_luxeo
23 usage = [[
24 script run hf_mf_dump_luxeo
26 arguments = [[
27 -h This help
29 local PM3_SUCCESS = 0
31 -- Some shortcuts
32 local band = bit32.band
33 local bor = bit32.bor
34 local bnot = bit32.bnot
35 local bxor = bit32.bxor
36 local lsh = bit32.lshift
37 local rsh = bit32.rshift
39 local acgreen = ansicolors.bright..ansicolors.green
40 local accyan = ansicolors.bright..ansicolors.cyan
41 local acred = ansicolors.red
42 local acyellow = ansicolors.bright..ansicolors.yellow
43 local acblue = ansicolors.bright..ansicolors.blue
44 local acmagenta = ansicolors.bright..ansicolors.magenta
45 local acoff = ansicolors.reset
48 -- This is only meant to be used when errors occur
49 local function oops(err)
50 print('ERROR: ', err)
51 core.clearCommandBuffer()
52 return nil, err
53 end
54 ---
55 -- Usage help
56 local function help()
57 print(copyright)
58 print(author)
59 print(version)
60 print(desc)
61 print(ansicolors.cyan..'Usage'..ansicolors.reset)
62 print(usage)
63 print(ansicolors.cyan..'Arguments'..ansicolors.reset)
64 print(arguments)
65 print(ansicolors.cyan..'Example usage'..ansicolors.reset)
66 print(example)
67 end
68 ---
70 local function setdevicedebug( status )
71 local c = 'hw dbg '
72 if status then
73 c = c..'-1'
74 else
75 c = c..'-0'
76 end
77 core.console(c)
78 end
80 local function xteaCrypt(num_rounds, v, key)
81 local v0 = v[0]
82 local v1 = v[1]
83 local delta = 0x9E3779B9
84 local sum = 0
86 for i = 0, num_rounds-1 do
87 -- v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
88 v0 = band(bxor(bxor(lsh(v1,4), rsh(v1,5)) + v1, sum + key[band(sum,3)]) + v0, 0xFFFFFFFF)
89 sum = band(sum + delta, 0xFFFFFFFF)
90 -- v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]);
91 v1 = band(bxor(bxor(lsh(v0,4), rsh(v0,5)) + v0, sum + key[band(rsh(sum,11),3)]) + v1, 0xFFFFFFFF)
92 end
93 v[0] = v0
94 v[1] = v1
95 end
97 local function xteaDecrypt(num_rounds, v, key)
98 local v0 = v[0]
99 local v1 = v[1]
100 local delta = 0x9E3779B9
101 local sum = band(delta * num_rounds, 0xFFFFFFFF)
103 for i = 0, num_rounds-1 do
104 -- v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]);
105 v1 = band(v1 - bxor(bxor(lsh(v0,4), rsh(v0,5)) + v0, sum + key[band(rsh(sum,11),3)]), 0xFFFFFFFF)
106 sum = band(sum - delta, 0xFFFFFFFF)
107 -- v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
108 v0 = band(v0 - bxor(bxor(lsh(v1,4), rsh(v1,5)) + v1, sum + key[band(sum,3)]), 0xFFFFFFFF)
110 v[0] = v0
111 v[1] = v1
114 local function createxteakey(mfuid)
115 local xteakey = {}
116 local buid = {}
117 local tmpkey = {}
118 local uid = {}
120 -- Warning ! "it is customary in Lua to START ARRAYS WITH ONE"
121 buid = utils.ConvertHexToBytes(mfuid)
122 uid[0] = bor(buid[1], lsh(buid[2], 8))
123 uid[1] = bor(buid[3], lsh(buid[4], 8))
125 tmpkey[0] = 0x198B
126 tmpkey[1] = uid[0]
127 tmpkey[2] = 0x46D8
128 tmpkey[3] = uid[1]
129 tmpkey[4] = 0x5310
130 tmpkey[5] = bxor(uid[0], 0xA312)
131 tmpkey[6] = 0xFFCB
132 tmpkey[7] = bxor(uid[1], 0x55AA)
134 xteakey[0] = bor(lsh(tmpkey[1], 16), tmpkey[0])
135 xteakey[1] = bor(lsh(tmpkey[3], 16), tmpkey[2])
136 xteakey[2] = bor(lsh(tmpkey[5], 16), tmpkey[4])
137 xteakey[3] = bor(lsh(tmpkey[7], 16), tmpkey[6])
139 return xteakey
142 local function getblockdata(response)
143 if not response then
144 return nil, 'No response from device'
146 if response.Status == PM3_SUCCESS then
147 return response.Data
148 else
149 return nil, "Couldn't read block.. ["..response.Status.."]"
153 local function readblock(blockno, key)
154 -- Read block N
155 local keytype = '01' -- key B
156 local data = ('%02x%s%s'):format(blockno, keytype, key)
157 local c = Command:newNG{cmd = cmds.CMD_HF_MIFARE_READBL, data = data}
158 local b, err = getblockdata(c:sendNG(false))
159 if not b then return oops(err) end
160 return b
163 local function readtag(mfkey,xteakey)
164 local tagdata = {}
165 local cleardata = {}
166 local v = {}
167 local vv = {}
169 -- Read 4 sectors and build table
170 for sect = 8, 11 do
171 for blockn = sect * 4, (sect * 4) + 2 do
172 local blockdata = readblock(blockn, mfkey)
173 if not blockdata then return oops('[!] failed reading block') end
174 table.insert(tagdata, blockdata)
178 -- Decrypt data and build clear table
179 for key,value in ipairs(tagdata) do
180 local clearblockdata
181 v[0] = utils.SwapEndianness(value:sub(1, 8), 32)
182 v[1] = utils.SwapEndianness(value:sub(9, 16), 32)
183 xteaDecrypt(16, v, xteakey)
184 vv[0] = utils.SwapEndianness(value:sub(17, 24), 32)
185 vv[1] = utils.SwapEndianness(value:sub(25, 32), 32)
186 xteaDecrypt(16, vv, xteakey)
187 clearblockdata=string.format("%08X%08X%08X%08X",
188 utils.SwapEndianness(string.format("%08X", v[0]), 32),
189 utils.SwapEndianness(string.format("%08X", v[1]), 32),
190 utils.SwapEndianness(string.format("%08X", vv[0]), 32),
191 utils.SwapEndianness(string.format("%08X", vv[1]), 32))
192 table.insert(cleardata, clearblockdata)
195 return tagdata,cleardata
200 local function main(args)
202 -- Arguments for the script
203 for o, a in getopt.getopt(args, 'h') do
204 if o == 'h' then return help() end
207 local xteakey = {}
208 -- local v = {}
209 local edata = {}
210 local cdata = {}
212 -- Turn off Debug
213 setdevicedebug(false)
215 -- GET TAG UID
216 tag, err = lib14a.read(false, true)
217 if err then
218 lib14a.disconnect()
219 return oops(err)
221 core.clearCommandBuffer()
223 -- simple tag check
224 if 0x08 ~= tag.sak then
225 if 0x0400 ~= tag.atqa then
226 return oops(('[fail] found tag %s :: looking for Mifare S50 1k'):format(tag.name))
230 xteakey = createxteakey(tag.uid)
231 print(acblue.."UID: "..tag.uid..acoff)
232 print(acblue..string.format("XTEA key: %08X %08X %08X %08X", xteakey[0], xteakey[1], xteakey[2], xteakey[3])..acoff)
234 local keys = {
235 "415A54454B4D",
236 "4B6A43059B64",
237 "C8BE6250C9C5",
240 for i, key in ipairs(keys) do
241 edata, cdata = readtag(key, xteakey)
242 if edata and cdata then
243 goto continue
247 if edata == nil or cdata == nil then
248 print("ERROR Reading tag!")
249 return nil
252 ::continue::
254 print("Ciphered data:")
255 for key,value in ipairs(edata) do
256 print(value)
257 if key % 3 == 0 then print("") end
260 -- compute CRC for each segment
261 crcH = utils.SwapEndianness(core.reveng_runmodel("CRC-16/ARC", cdata[1]..cdata[2]..cdata[3]:sub(1,28), false, '0'),16)
262 crcA = utils.SwapEndianness(core.reveng_runmodel("CRC-16/ARC", cdata[4]..cdata[5]..cdata[6]..cdata[7]:sub(1,28), false, '0'),16)
263 crcB = utils.SwapEndianness(core.reveng_runmodel("CRC-16/ARC", cdata[8]..cdata[9]..cdata[10]..cdata[11]:sub(1,28), false, '0'),16)
265 print("\nHeader:")
266 for key,value in ipairs(cdata) do
267 if key == 3 then
268 print(value:sub(1,28)..acmagenta..value:sub(29,32)..acoff)
269 if utils.SwapEndianness(value:sub(29,32),16) == crcH then strcrc = " OK" else strcrc = acred.." CRCERROR !!" end
270 print(acmagenta.."CRC16/ARC = "..string.format("0x%04X", crcH)..strcrc..acoff)
271 print("\nDataA:")
272 elseif key == 4 then
273 print(acgreen..value:sub(1,4)..acoff..value:sub(5,16)..accyan..value:sub(17,24)..acoff..value:sub(25,26)..accyan..value:sub(27,28)..acoff..value:sub(29,32))
274 versionA = utils.SwapEndianness(value:sub(1,4),16)
275 dateA = string.format("%d/%02d/%02d %02d:%02d", tonumber(value:sub(17,18),10)+2000, tonumber(value:sub(19,20),10),
276 tonumber(string.format("%02X", band(tonumber(value:sub(21,22),16),0x3f)),10),
277 tonumber(value:sub(23,24),10), tonumber(value:sub(27,28),10))
278 elseif key == 8 then
279 print(acgreen..value:sub(1,4)..acoff..value:sub(5,16)..accyan..value:sub(17,24)..acoff..value:sub(25,26)..accyan..value:sub(27,28)..acoff..value:sub(29,32))
280 versionB = utils.SwapEndianness(value:sub(1,4),16)
281 dateB = string.format("%d/%02d/%02d %02d:%02d", tonumber(value:sub(17,18),10)+2000, tonumber(value:sub(19,20),10),
282 tonumber(string.format("%02X", band(tonumber(value:sub(21,22),16),0x3f)),10),
283 tonumber(value:sub(23,24),10), tonumber(value:sub(27,28),10))
284 elseif key == 5 then
285 print(acyellow..value:sub(1,4)..acoff..value:sub(5,32))
286 creditA = utils.SwapEndianness(value:sub(1,4),16)/100
287 elseif key == 9 then
288 print(acyellow..value:sub(1,4)..acoff..value:sub(5,32))
289 creditB = utils.SwapEndianness(value:sub(1,4),16)/100
290 elseif key == 7 then
291 print(value:sub(1,28)..acmagenta..value:sub(29,32)..acoff)
292 print(acgreen.."Version "..string.format("0x%04X", versionA)..acoff)
293 print(acyellow.."Credit : "..creditA..acoff)
294 if utils.SwapEndianness(value:sub(29,32),16) == crcA then strcrc = " OK" else strcrc = acred.." CRCERROR !!" end
295 print(acmagenta.."CRC16/ARC = "..string.format("0x%04X", crcA)..strcrc..acoff)
296 print(accyan.."Date: "..dateA..acoff)
297 print("\nDataB:")
298 elseif key == 11 then
299 print(value:sub(1,28)..acmagenta..value:sub(29,32)..acoff)
300 print(acgreen.."Version "..string.format("0x%04X", versionB)..acoff)
301 print(acyellow.."Credit : "..creditB..acoff)
302 if utils.SwapEndianness(value:sub(29,32),16) == crcB then strcrc = " OK" else strcrc = acred.." CRCERROR !!" end
303 print(acmagenta.."CRC16/ARC = "..string.format("0x%04X", crcB)..strcrc..acoff)
304 print(accyan.."Date: "..dateB..acoff)
305 print("\nFooter:")
306 else
307 print(value)
311 return
314 main(args)