mirror of
				https://github.com/RfidResearchGroup/proxmark3.git
				synced 2025-10-30 07:57:15 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			300 lines
		
	
	
	
		
			9.3 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			300 lines
		
	
	
	
		
			9.3 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
| ---
 | |
| -- This Lua script is designed to run with Iceman/RRG Proxmark3 fork
 | |
| -- Just copy luxeodump.lua to client/luascripts/
 | |
| -- and run "script run luxeodump"
 | |
| 
 | |
| -- requirements
 | |
| local cmds = require('commands')
 | |
| local getopt = require('getopt')
 | |
| local utils = require('utils')
 | |
| local lib14a = require('read14a')
 | |
| local ansicolors  = require('ansicolors')
 | |
| 
 | |
| copyright = ''
 | |
| author = '0xdrrb'
 | |
| version = 'v0.1.1'
 | |
| desc = [[
 | |
| This is a script that tries to dump and decrypt the data of a specific type of Mifare laundromat token.  OBS! Tag must be on the antenna.
 | |
| ]]
 | |
| example = [[
 | |
|     script run luxeodump
 | |
| ]]
 | |
| usage = [[
 | |
| script run luxeodump
 | |
| ]]
 | |
| arguments = [[
 | |
|     -h              This help
 | |
| ]]
 | |
| local PM3_SUCCESS = 0
 | |
| 
 | |
| -- Some shortcuts
 | |
| local band = bit32.band
 | |
| local bor = bit32.bor
 | |
| local bnot = bit32.bnot
 | |
| local bxor = bit32.bxor
 | |
| local lsh = bit32.lshift
 | |
| local rsh = bit32.rshift
 | |
| 
 | |
| local acgreen   = ansicolors.bright..ansicolors.green
 | |
| local accyan    = ansicolors.bright..ansicolors.cyan
 | |
| local acred     = ansicolors.red
 | |
| local acyellow  = ansicolors.bright..ansicolors.yellow
 | |
| local acblue    = ansicolors.bright..ansicolors.blue
 | |
| local acmagenta = ansicolors.bright..ansicolors.magenta
 | |
| local acoff     = ansicolors.reset
 | |
| 
 | |
| 
 | |
| -- This is only meant to be used when errors occur
 | |
| local function oops(err)
 | |
|     print('ERROR: ', err)
 | |
|     core.clearCommandBuffer()
 | |
|     return nil, err
 | |
| end
 | |
| ---
 | |
| -- Usage help
 | |
| local function help()
 | |
|     print(copyright)
 | |
|     print(author)
 | |
|     print(version)
 | |
|     print(desc)
 | |
|     print(ansicolors.cyan..'Usage'..ansicolors.reset)
 | |
|     print(usage)
 | |
|     print(ansicolors.cyan..'Arguments'..ansicolors.reset)
 | |
|     print(arguments)
 | |
|     print(ansicolors.cyan..'Example usage'..ansicolors.reset)
 | |
|     print(example)
 | |
| end
 | |
| ---
 | |
| --
 | |
| local function setdevicedebug( status )
 | |
|     local c = 'hw dbg '
 | |
|     if status then
 | |
|         c = c..'1'
 | |
|     else
 | |
|         c = c..'0'
 | |
|     end
 | |
|     core.console(c)
 | |
| end
 | |
| 
 | |
| local function xteaCrypt(num_rounds, v, key)
 | |
| 	local v0 = v[0]
 | |
| 	local v1 = v[1]
 | |
| 	local delta = 0x9E3779B9
 | |
| 	local sum = 0
 | |
| 
 | |
| 	for i = 0, num_rounds-1 do
 | |
| 		-- v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
 | |
| 		v0 = band(bxor(bxor(lsh(v1,4), rsh(v1,5)) + v1, sum + key[band(sum,3)]) + v0, 0xFFFFFFFF)
 | |
| 		sum = band(sum + delta, 0xFFFFFFFF)
 | |
| 		-- v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]);
 | |
| 		v1 = band(bxor(bxor(lsh(v0,4), rsh(v0,5)) + v0, sum + key[band(rsh(sum,11),3)]) + v1, 0xFFFFFFFF)
 | |
| 	end
 | |
| 	v[0] = v0
 | |
| 	v[1] = v1
 | |
| end
 | |
| 
 | |
| local function xteaDecrypt(num_rounds, v, key)
 | |
| 	local v0 = v[0]
 | |
| 	local v1 = v[1]
 | |
| 	local delta = 0x9E3779B9
 | |
| 	local sum = band(delta * num_rounds, 0xFFFFFFFF)
 | |
| 
 | |
| 	for i = 0, num_rounds-1 do
 | |
| 		-- v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]);
 | |
| 		v1 = band(v1 - bxor(bxor(lsh(v0,4), rsh(v0,5)) + v0, sum + key[band(rsh(sum,11),3)]), 0xFFFFFFFF)
 | |
| 		sum = band(sum - delta, 0xFFFFFFFF)
 | |
| 		-- v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
 | |
| 		v0 = band(v0 - bxor(bxor(lsh(v1,4), rsh(v1,5)) + v1, sum + key[band(sum,3)]), 0xFFFFFFFF)
 | |
| 	end
 | |
| 	v[0] = v0
 | |
| 	v[1] = v1
 | |
| end
 | |
| 
 | |
| local function createxteakey(mfuid)
 | |
| 	local xteakey = {}
 | |
| 	local buid = {}
 | |
| 	local tmpkey = {}
 | |
| 	local uid = {}
 | |
| 
 | |
| 	-- Warning ! "it is customary in Lua to START ARRAYS WITH ONE"
 | |
| 	buid = utils.ConvertHexToBytes(mfuid)
 | |
| 	uid[0] = bor(buid[1], lsh(buid[2], 8))
 | |
| 	uid[1] = bor(buid[3], lsh(buid[4], 8))
 | |
| 
 | |
| 	tmpkey[0] = 0x198B
 | |
| 	tmpkey[1] = uid[0]
 | |
| 	tmpkey[2] = 0x46D8
 | |
| 	tmpkey[3] = uid[1]
 | |
| 	tmpkey[4] = 0x5310
 | |
| 	tmpkey[5] = bxor(uid[0], 0xA312)
 | |
| 	tmpkey[6] = 0xFFCB
 | |
| 	tmpkey[7] = bxor(uid[1], 0x55AA)
 | |
| 
 | |
| 	xteakey[0] = bor(lsh(tmpkey[1], 16), tmpkey[0])
 | |
| 	xteakey[1] = bor(lsh(tmpkey[3], 16), tmpkey[2])
 | |
| 	xteakey[2] = bor(lsh(tmpkey[5], 16), tmpkey[4])
 | |
| 	xteakey[3] = bor(lsh(tmpkey[7], 16), tmpkey[6])
 | |
| 
 | |
| 	return xteakey
 | |
| end
 | |
| 
 | |
| local function getblockdata(response)
 | |
|     if not response then
 | |
|         return nil, 'No response from device'
 | |
|     end
 | |
|     if response.Status == PM3_SUCCESS then
 | |
|         return response.Data
 | |
|     else
 | |
|         return nil, "Couldn't read block.. ["..response.Status.."]"
 | |
|     end
 | |
| end
 | |
| 
 | |
| local function readblock(blockno, key)
 | |
|     -- Read block N
 | |
|     local keytype = '01'  -- key B
 | |
|     local data = ('%02x%s%s'):format(blockno, keytype, key)
 | |
|     local c = Command:newNG{cmd = cmds.CMD_HF_MIFARE_READBL, data = data}
 | |
|     local b, err = getblockdata(c:sendNG(false))
 | |
|     if not b then return oops(err) end
 | |
|     return b
 | |
| end
 | |
| 
 | |
| local function readtag(mfkey,xteakey)
 | |
| 	local tagdata = {}
 | |
| 	local cleardata = {}
 | |
| 	local v = {}
 | |
| 	local vv = {}
 | |
| 
 | |
| 	-- Read 4 sectors and build table
 | |
| 	for sect = 8, 11 do
 | |
| 		for blockn = sect * 4, (sect * 4) + 2 do
 | |
| 			local blockdata = readblock(blockn, mfkey)
 | |
| 			if not blockdata then return oops('[!] failed reading block') end
 | |
| 			table.insert(tagdata, blockdata)
 | |
| 		end
 | |
| 	end
 | |
| 
 | |
| 	-- Decrypt data and build clear table
 | |
| 	for key,value in ipairs(tagdata) do
 | |
| 		local clearblockdata
 | |
| 		v[0] = utils.SwapEndianness(value:sub(1, 8), 32)
 | |
| 		v[1] = utils.SwapEndianness(value:sub(9, 16), 32)
 | |
| 		xteaDecrypt(16, v, xteakey)
 | |
| 		vv[0] = utils.SwapEndianness(value:sub(17, 24), 32)
 | |
| 		vv[1] = utils.SwapEndianness(value:sub(25, 32), 32)
 | |
| 		xteaDecrypt(16, vv, xteakey)
 | |
| 		clearblockdata=string.format("%08X%08X%08X%08X",
 | |
| 			utils.SwapEndianness(string.format("%08X", v[0]), 32),
 | |
| 			utils.SwapEndianness(string.format("%08X", v[1]), 32),
 | |
| 			utils.SwapEndianness(string.format("%08X", vv[0]), 32),
 | |
| 			utils.SwapEndianness(string.format("%08X", vv[1]), 32))
 | |
| 		table.insert(cleardata, clearblockdata)
 | |
| 	end
 | |
| 
 | |
| 	return tagdata,cleardata
 | |
| 
 | |
| end
 | |
| 
 | |
| 
 | |
| local function main(args)
 | |
| 
 | |
|     -- Arguments for the script
 | |
|     for o, a in getopt.getopt(args, 'h') do
 | |
|         if o == 'h' then return help() end
 | |
|     end
 | |
| 
 | |
| 	local xteakey = {}
 | |
| 	-- local v = {}
 | |
| 	local edata = {}
 | |
| 	local cdata = {}
 | |
| 
 | |
| 	-- Turn off Debug
 | |
| 	setdevicedebug(false)
 | |
| 
 | |
| 	-- GET TAG UID
 | |
| 	tag, err = lib14a.read(false, true)
 | |
| 	if err then
 | |
| 		lib14a.disconnect()
 | |
| 		return oops(err)
 | |
| 	end
 | |
| 	core.clearCommandBuffer()
 | |
| 
 | |
| 	-- simple tag check
 | |
| 	if 0x08 ~= tag.sak then
 | |
| 		if 0x0400 ~= tag.atqa then
 | |
| 			return oops(('[fail] found tag %s :: looking for Mifare S50 1k'):format(tag.name))
 | |
| 		end
 | |
| 	end
 | |
| 
 | |
| 	xteakey = createxteakey(tag.uid)
 | |
| 	print(acblue.."UID: "..tag.uid..acoff)
 | |
| 	print(acblue..string.format("XTEA key: %08X %08X %08X %08X", xteakey[0], xteakey[1], xteakey[2], xteakey[3])..acoff)
 | |
| 
 | |
| 	edata, cdata = readtag("415A54454B4D", xteakey)
 | |
| 
 | |
| 	if edata == nil or cdata == nil then
 | |
| 		print("ERROR Reading tag!")
 | |
| 		return nil
 | |
| 	end
 | |
| 
 | |
| 	print("Ciphered data:")
 | |
| 	for key,value in ipairs(edata) do
 | |
| 		print(value)
 | |
| 		if key % 3 == 0 then print("") end
 | |
| 	end
 | |
| 
 | |
| 	-- compute CRC for each segment
 | |
|     crcH = utils.SwapEndianness(core.reveng_runmodel("CRC-16/ARC", cdata[1]..cdata[2]..cdata[3]:sub(1,28), false, '0'),16)
 | |
|     crcA = utils.SwapEndianness(core.reveng_runmodel("CRC-16/ARC", cdata[4]..cdata[5]..cdata[6]..cdata[7]:sub(1,28), false, '0'),16)
 | |
|     crcB = utils.SwapEndianness(core.reveng_runmodel("CRC-16/ARC", cdata[8]..cdata[9]..cdata[10]..cdata[11]:sub(1,28), false, '0'),16)
 | |
| 
 | |
| 	print("\nHeader:")
 | |
| 	for key,value in ipairs(cdata) do
 | |
| 		if key == 3 then
 | |
| 			print(value:sub(1,28)..acmagenta..value:sub(29,32)..acoff)
 | |
| 			if utils.SwapEndianness(value:sub(29,32),16) == crcH then strcrc = " OK" else strcrc = acred.." CRCERROR !!" end
 | |
| 			print(acmagenta.."CRC16/ARC = "..string.format("0x%04X", crcH)..strcrc..acoff)
 | |
| 			print("\nDataA:")
 | |
| 		elseif key == 4 then
 | |
| 			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))
 | |
| 			versionA = utils.SwapEndianness(value:sub(1,4),16)
 | |
| 			dateA = string.format("%d/%02d/%02d %02d:%02d", tonumber(value:sub(17,18),10)+2000, tonumber(value:sub(19,20),10),
 | |
| 											 tonumber(string.format("%02X", band(tonumber(value:sub(21,22),16),0x3f)),10),
 | |
| 											 tonumber(value:sub(23,24),10), tonumber(value:sub(27,28),10))
 | |
| 		elseif key == 8 then
 | |
| 			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))
 | |
| 			versionB = utils.SwapEndianness(value:sub(1,4),16)
 | |
| 			dateB = string.format("%d/%02d/%02d %02d:%02d", tonumber(value:sub(17,18),10)+2000, tonumber(value:sub(19,20),10),
 | |
| 											 tonumber(string.format("%02X", band(tonumber(value:sub(21,22),16),0x3f)),10),
 | |
| 											 tonumber(value:sub(23,24),10), tonumber(value:sub(27,28),10))
 | |
| 		elseif key == 5 then
 | |
| 			print(acyellow..value:sub(1,4)..acoff..value:sub(5,32))
 | |
| 			creditA = utils.SwapEndianness(value:sub(1,4),16)/100
 | |
| 		elseif key == 9 then
 | |
| 			print(acyellow..value:sub(1,4)..acoff..value:sub(5,32))
 | |
| 			creditB = utils.SwapEndianness(value:sub(1,4),16)/100
 | |
| 		elseif key == 7 then
 | |
| 			print(value:sub(1,28)..acmagenta..value:sub(29,32)..acoff)
 | |
| 			print(acgreen.."Version "..string.format("0x%04X", versionA)..acoff)
 | |
| 			print(acyellow.."Credit : "..creditA..acoff)
 | |
| 			if utils.SwapEndianness(value:sub(29,32),16) == crcA then strcrc = " OK" else strcrc = acred.." CRCERROR !!" end
 | |
| 			print(acmagenta.."CRC16/ARC = "..string.format("0x%04X", crcA)..strcrc..acoff)
 | |
| 			print(accyan.."Date: "..dateA..acoff)
 | |
| 			print("\nDataB:")
 | |
| 		elseif key == 11 then
 | |
| 			print(value:sub(1,28)..acmagenta..value:sub(29,32)..acoff)
 | |
| 			print(acgreen.."Version "..string.format("0x%04X", versionB)..acoff)
 | |
| 			print(acyellow.."Credit : "..creditB..acoff)
 | |
| 			if utils.SwapEndianness(value:sub(29,32),16) == crcB then strcrc = " OK" else strcrc = acred.." CRCERROR !!" end
 | |
| 			print(acmagenta.."CRC16/ARC = "..string.format("0x%04X", crcB)..strcrc..acoff)
 | |
| 			print(accyan.."Date: "..dateB..acoff)
 | |
| 			print("\nFooter:")
 | |
| 		else
 | |
| 			print(value)
 | |
| 		end
 | |
| 	end
 | |
| 
 | |
| 	return
 | |
| end
 | |
| 
 | |
| main(args)
 |