Proxmark scripting

In this post, I will go through the details about writing a Lua-script for the Proxmark device.

Background - mifare crack’n’dump

First, some background. In order to crack and dump a mifare classic, there are a number of steps involved:

First of all, hf 14a reader reads some info about the card that is placed by the antenna.

proxmark3> hf 14a reader
ATQA : 04 00           
 UID : 92 c0 45 6b           
 SAK : 08 [2]          
TYPE : NXP MIFARE CLASSIC 1k | Plus 2k          
proprietary non iso14443a-4 card found, RATS not supported          
proxmark3> 

The information that is received from this is often required for later steps, especially the type (in this case that it is a 1K mifare classic).

The next step is to crack a key. Quite straightforward:

proxmark3> hf mf mifare
-------------------------------------------------------------------------
Executing command. Expected execution time: 25sec on average  :-)
Press the key on the proxmark3 device to abort both proxmark3 and client.
-------------------------------------------------------------------------
..........................



uid(92c0456b) nt(096f522a) par(b981d12931e9e9d1) ks(0d0b0e030f0e0f09)

          
|diff|{nr}    |ks3|ks3^5|parity         |
+----+--------+---+-----+---------------+
| 00 |00000000| d |  8  |1,0,0,1,1,1,0,1|
| 20 |00000020| b |  e  |1,0,0,0,0,0,0,1|
| 40 |00000040| e |  b  |1,0,0,0,1,0,1,1|
| 60 |00000060| 3 |  6  |1,0,0,1,0,1,0,0|
| 80 |00000080| f |  a  |1,0,0,0,1,1,0,0|
| a0 |000000a0| e |  b  |1,0,0,1,0,1,1,1|
| c0 |000000c0| f |  a  |1,0,0,1,0,1,1,1|
| e0 |000000e0| 9 |  c  |1,0,0,0,1,0,1,1|
key_count:1
------------------------------------------------------------------
Key found:ffffffffffff 
          
Found valid key:ffffffffffff

This gives us the A-key to sector 0. Using this, we can proceed with a nested attack. Usage:

proxmark3> hf mf nested
Usage:          
 all sectors:  hf mf nested  <card memory> <block number> <key A/B> <key (12 hex symbols)> [t,d]          
 one sector:   hf mf nested  o <block number> <key A/B> <key (12 hex symbols)>          
               <target block number> <target key A/B> [t]          
card memory - 0 - MINI(320 bytes), 1 - 1K, 2 - 2K, 4 - 4K, <other> - 1K          
t - transfer keys into emulator memory          
d - write keys to binary file          
           
      sample1: hf mf nested 1 0 A FFFFFFFFFFFF           
      sample1: hf mf nested 1 0 A FFFFFFFFFFFF t           
      sample1: hf mf nested 1 0 A FFFFFFFFFFFF d           
      sample2: hf mf nested o 0 A FFFFFFFFFFFF 4 A    

Since I am going to want to save the keys, I want to create a dump-file with the keys, implying the use of the d switch. Also, I need to know the card type, which as revealed earlier was 1k - 1 as card memory. Adding in the key that was extracted previously, I wind up with the following:

proxmark3> hf mf nested 1 0 A FFFFFFFFFFFF d
--block no:00 key type:00 key:ff ff ff ff ff ff  etrans:0          
Block shift=0          
Testing known keys. Sector count=16          
nested...          
Time in nested: 20.000 (inf sec per key)

-----------------------------------------------
Iterations count: 0

          
|---|----------------|---|----------------|---|          
|sec|key A           |res|key B           |res|          
|---|----------------|---|----------------|---|          
|000|  ffffffffffff  | 1 |  ffffffffffff  | 1 |          
|001|  ffffffffffff  | 1 |  ffffffffffff  | 1 |          
|002|  ffffffffffff  | 1 |  ffffffffffff  | 1 |          
|003|  ffffffffffff  | 1 |  ffffffffffff  | 1 |          
|004|  ffffffffffff  | 1 |  ffffffffffff  | 1 |          
|005|  ffffffffffff  | 1 |  ffffffffffff  | 1 |          
|006|  ffffffffffff  | 1 |  ffffffffffff  | 1 |          
|007|  ffffffffffff  | 1 |  ffffffffffff  | 1 |          
|008|  ffffffffffff  | 1 |  ffffffffffff  | 1 |          
|009|  ffffffffffff  | 1 |  ffffffffffff  | 1 |          
|010|  ffffffffffff  | 1 |  ffffffffffff  | 1 |          
|011|  ffffffffffff  | 1 |  ffffffffffff  | 1 |          
|012|  ffffffffffff  | 1 |  ffffffffffff  | 1 |          
|013|  ffffffffffff  | 1 |  ffffffffffff  | 1 |          
|014|  ffffffffffff  | 1 |  ffffffffffff  | 1 |          
|015|  ffffffffffff  | 1 |  ffffffffffff  | 1 |          
|---|----------------|---|----------------|---|          
Printing keys to bynary file dumpkeys.bin...   

So, now we have the keys dumped:

#xxd dumpkeys.bin 
0000000: ffff ffff ffff ffff ffff ffff ffff ffff  ................
0000010: ffff ffff ffff ffff ffff ffff ffff ffff  ................
0000020: ffff ffff ffff ffff ffff ffff ffff ffff  ................
0000030: ffff ffff ffff ffff ffff ffff ffff ffff  ................
0000040: ffff ffff ffff ffff ffff ffff ffff ffff  ................
0000050: ffff ffff ffff ffff ffff ffff ffff ffff  ................
0000060: ffff ffff ffff ffff ffff ffff ffff ffff  ................
0000070: ffff ffff ffff ffff ffff ffff ffff ffff  ................
0000080: ffff ffff ffff ffff ffff ffff ffff ffff  ................
0000090: ffff ffff ffff ffff ffff ffff ffff ffff  ................
00000a0: ffff ffff ffff ffff ffff ffff ffff ffff  ................
00000b0: ffff ffff ffff ffff ffff ffff ffff ffff  ................

Let’s also dump the card data. That is done via the hf mf dump-command. A thing which I didn’t understand until I read the source code, was that this command reads the keys from dumpkeys.bin.

proxmark3> hf mf dump
|-----------------------------------------|          
|------ Reading sector access bits...-----|          
|-----------------------------------------|          
#db# READ BLOCK FINISHED                 
#db# READ BLOCK FINISHED                 
#db# READ BLOCK FINISHED                 
#db# READ BLOCK FINISHED                 
#db# READ BLOCK FINISHED                 
#db# READ BLOCK FINISHED                 
#db# READ BLOCK FINISHED                 
#db# READ BLOCK FINISHED                 
#db# READ BLOCK FINISHED                 
#db# READ BLOCK FINISHED                 
#db# READ BLOCK FINISHED                 
#db# READ BLOCK FINISHED                 
#db# READ BLOCK FINISHED                 
#db# READ BLOCK FINISHED                 
#db# READ BLOCK FINISHED                 
#db# READ BLOCK FINISHED                 
|-----------------------------------------|          
|----- Dumping all blocks to file... -----|          
|-----------------------------------------|          
#db# READ BLOCK FINISHED                 
Dumped card data into 'dumpdata.bin'          
#db# READ BLOCK FINISHED                 
Dumped card data into 'dumpdata.bin'          
[...]
proxmark3> 

So, now the complete dump (including keys) is within dumpdata.bin

#xxd dumpdata.bin 
0000000: 92c0 456b 7c88 0400 c08e 1c96 4960 2612  ..Ek|.......I`&.
0000010: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000020: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000030: ffff ffff ffff ff07 8069 ffff ffff ffff  .........i......
0000040: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000050: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000060: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000070: ffff ffff ffff ff07 8069 ffff ffff ffff  .........i......
0000080: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000090: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000a0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000b0: ffff ffff ffff ff07 8069 ffff ffff ffff  .........i......
00000c0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000d0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000e0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000f0: ffff ffff ffff ff07 8069 ffff ffff ffff  .........i......
0000100: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000110: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000120: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000130: ffff ffff ffff ff07 8069 ffff ffff ffff  .........i......
0000140: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000150: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000160: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000170: ffff ffff ffff ff07 8069 ffff ffff ffff  .........i......
0000180: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000190: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00001a0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00001b0: ffff ffff ffff ff07 8069 ffff ffff ffff  .........i......
00001c0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00001d0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00001e0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00001f0: ffff ffff ffff ff07 8069 ffff ffff ffff  .........i......
0000200: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000210: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000220: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000230: ffff ffff ffff ff07 8069 ffff ffff ffff  .........i......
0000240: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000250: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000260: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000270: ffff ffff ffff ff07 8069 ffff ffff ffff  .........i......
0000280: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000290: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00002a0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00002b0: ffff ffff ffff ff07 8069 ffff ffff ffff  .........i......
00002c0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00002d0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00002e0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00002f0: ffff ffff ffff ff07 8069 ffff ffff ffff  .........i......
0000300: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000310: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000320: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000330: ffff ffff ffff ff07 8069 ffff ffff ffff  .........i......
0000340: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000350: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000360: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000370: ffff ffff ffff ff07 8069 ffff ffff ffff  .........i......
0000380: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000390: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00003a0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00003b0: ffff ffff ffff ff07 8069 ffff ffff ffff  .........i......
00003c0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00003d0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00003e0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00003f0: ffff ffff ffff ff07 80bc ffff ffff ffff  ................

If we want a better picture, more like MLC, there’s a trick:

proxmark3> script run htmldump
--- Executing: ./scripts/htmldump.lua, args''
Wrote a HTML dump to the file 2013-10-04_204910.html

-----Finished

It gives a bit of syntax highlightning to the dump:

htmldump

At this point, we have enough information that we don’t need the card anymore. We have the full dump, and can emulate it, analyze the contents etc.

Scripted approach

Let’s make a script which automates this. I want to put the device in auto-pwn-mode, like this

  1. Listen for mifare-cards
  2. If mifare card is found, crack it
  3. Get all keys into dumpkeys-[uid].bin
  4. Dump all data into dumpdata-[uid].bin
  5. Generate html-report into dumpdata-[uid].html
  6. Go to 1 (and ignore already cracked cards)

Let’s start with a script-skeleton

local getopt = require('getopt')
example = "script run mifare_autopwn"
author = "Martin Holst Swende"


desc =
[[
This is a which automates cracking and dumping mifare classic cards. 

Arguments:
	-d 				debug logging on
	-h 				this help

]]

-------------------------------
-- Some utilities 
-------------------------------
local DEBUG = false
--- 
-- A debug printout-function
function dbg(args)
	if DEBUG then
		print("###", args)
	end
end 
--- 
-- This is only meant to be used when errors occur
function oops(err)
	print("ERROR: ",err)
	return nil,err
end

--- 
-- Usage help
function help()
	print(desc)
	print("Example usage")
	print(example)
end

--- 
-- The main entry point
function main(args)


	local verbose = false
	-- Read the parameters
	for o, a in getopt.getopt(args, 'hd') do
		if o == "h" then help() return end
		if o == "d" then DEBUG = true end
	end
end

-- Call the main 
main(args)

Some points of interest:

local getopt = require('getopt')

This loads the getopt library into the variable getopt. A library is a lua-file placed within the client/lualibs/-folder. A library should not do anything when loaded, but, in the end, return a lua-table containing the functions that the library exposes. Much like a Javascript library (tangent: Javascript and Lua are very similar. They use the same kind of table-based object, using prototype-inheritance instead of classes. The biggest differences are syntax, exception handling and some peripherals, such as regexps - Lua regexps are a lot simpler than the PCRE-standard).

The main function is not magic, it could be named anything. The script is just executed, so it is the call main(args) which calls the main. The args is a global variable that was set from the C-side, and contains the arguments that was passed from the proxmark console when the script was invoked. It is good to use that convention, though, since in the future we may change that so the main is invoked explicitly from the calling C-code.

So, let’s add some functionality. First of all, loop and wait for a iso 14443a-tag. The functionality to read the card info is already implemented in a library (I used it in the script to check keys), so we can start by importing that

local reader14a = require('read14a')

Then define the loop:

---
-- Waits for a mifare card to be placed within the vicinity of the reader. 
-- @return if successfull: an table containing card info
-- @return if unsuccessfull : nil, error
function wait_for_mifare()
	while not core.ukbhit() do
		res, err = reader.read1443a()
		if res then return res end
		-- err means that there was no response from card
	end
	return nil, "Aborted by user"
end

Several things happen here. First of all, core.ukbhit is a functionality implemented on the C-side and which has been exposed to the Lua-environment. There are a couple of those, and they are all available through the core library.

The following are defined:

  • core.SendCommand - sends a command to the device
  • core.WaitForResponseTimeout - waits for a response from the device
  • core.foobar - just for testing
  • core.ukbhit - Utility to check if a key has been pressed by the user. This method does not block.
  • core.clearCommandBuffer - Clears any previous commands from the device which has not been handled.
  • core.console - Allows the lua-side to invoke command via the console-syntax, e.g. core.console('hf mf nested 1 0 a FFFFFFFFFFFF')

Lastly, we have two return values. Lua does not handle exceptions well, so the norm is to have one return value if all is well, and return nil + error message if something went wrong.

In our main function, we call it within a loop aswell, handling the possible return values. For now, I put a snippet to show the return values from the reader:

	res, err = wait_for_mifare()
	if err then return oops(err) end
	-- Display it
	for k,v in pairs(res) do
		print(tostring(k) .." = ".. tostring(v))
	end

Adding in some logic to ignore already seen cards, we wind up with this:

function main(args)


	local verbose, exit,res,err
	local seen_uids = {}

	-- Read the parameters
	for o, a in getopt.getopt(args, 'hd') do
		if o == "h" then help() return end
		if o == "d" then DEBUG = true end
	end

	while not exit do
		res, err = wait_for_mifare()
		if err then return oops(err) end
		-- Seen already?
		if not seen_uids[res.uid] then
			-- Store it
			seen_uids[res.uid] = res.uid
			-- Display it
			for k,v in pairs(res) do
				print(tostring(k) .." = ".. tostring(v))
			end
		end
	end
end

For testing, I put it on and swiped to cards I had at hand:

proxmark3> script run mifare_autopwn
--- Executing: ./scripts/mifare_autopwn.lua, args''
uid = 92C0456B
atqa = 0400
name = NXP MIFARE CLASSIC 1k | Plus 2k
sak = 8
uid = 2A746F7F
atqa = 0400
name = NXP MIFARE CLASSIC 1k | Plus 2k
sak = 8
 ERROR: 	Aborted by user

-----Finished
proxmark3>  

Perfect! So the next step is to perform the hf mf mifare. Unfortunately, I cannot just call it via the console - core.console('hf mf mifare') since I need to obtain the key that is extracted - not just show it to the user. So, I’ll have to implement that within Lua instead.

I’ll get to that in part II.

2013-10-07

tweets

favorites