Dangerous NFC app issues

Right. So…

First of all, before I go any further: FUCK TAGWRITER WITH A BROOMSTICK. More on that later.

Secondly: I recovered access to all my fucked-up tags - those that were fucked up right off the bat, and those that were fucked up after Tagwriter went over it. The password was 12345678 in all of them. No idea why or where that password comes from. But it doesn’t come from Tagwriter.

I’m still convinced the chips were (or still are and I’m claiming victory too soon?) weirded out by the DT app’s writing BD in E2, simply because unless they were programmed like that from the factory, which is virtually impossible, I did nothing else but run the DT app on them to fuck them up - or rather, set that password somehow. So, that’s pretty clear.

But at least the chips were not dead. They just silently refused to answer if the wrong password was supplied. Another thing I learned incidentally: when you supply the wrong password, they go to HALT on their own. You need to turn off the field and re-select if you want to try again.

Now then, why should Tagwriter be fucked with a broomstick:

Reason #1: when you ask it to set the password, it sets the password, but also a whole bunch of other stuff without telling you - particularly the dynamic lock bits. Who the hell told Tagwriter to do that? Jesus H. Christ on a spit! The E1 value in AUTH0 comes from it also.

Reason #2: when you ask it to “remove the password”, it does NOT set the password to FFFFFFFF, as I assumed. What it does in fact is authenticate with whatever password you supply, then goof around with the dynamic lock bits, but it leaves the password in place.

It even tells you so when it’d done doing its thing: it says “Password protection removed”. Notice the subtle difference: the password protection is removed, but not the password itself, as the drop-down menu selection suggests.

Best guess: they didn’t have enough characters in the drop-down menu, so instead of calling the option “Remove password protection”, they shortened it to “Remove password”. Confusing as hell!

Anyhow, that’s what I was able to find out. I’m still not confident using the DT app on my doNExT, because that 12345678 password cannot come from anywhere else but something goofy happening as a result of running the DT app. But at least there’s a way to recover the chips. At least until I go to bed and try again tomorrow and it doesn’t work once more :slight_smile:

Here’s the script to bruteforce the password, if you need it. It’s a Python script that drives the Proxmark3 client (easier and more flexible than a LUA script):

#!/usr/bin/python3

### Parameters
pm3_client = "/usr/local/bin/proxmark3"
pm3_dev_file = "/dev/ttyACM0"



### Modules
import re
import os
import sys
from pty import openpty
from select import select
from subprocess import Popen, DEVNULL, PIPE



# "Obvious" passwords dictionary
pwd_dict=(
0x00000001, 0x00000010, 0x00000100, 0x00001000, 0x00010000, 0x00100000,
0x01000000, 0x10000000, 0x00000002, 0x00000020, 0x00000200, 0x00002000,
0x00020000, 0x00200000, 0x02000000, 0x20000000, 0x00000003, 0x00000030,
0x00000300, 0x00003000, 0x00030000, 0x00300000, 0x03000000, 0x30000000,
0x00000004, 0x00000040, 0x00000400, 0x00004000, 0x00040000, 0x00400000,
0x04000000, 0x40000000, 0x00000005, 0x00000050, 0x00000500, 0x00005000,
0x00050000, 0x00500000, 0x05000000, 0x50000000, 0x00000006, 0x00000060,
0x00000600, 0x00006000, 0x00060000, 0x00600000, 0x06000000, 0x60000000,
0x00000007, 0x00000070, 0x00000700, 0x00007000, 0x00070000, 0x00700000,
0x07000000, 0x70000000, 0x00000008, 0x00000080, 0x00000800, 0x00008000,
0x00080000, 0x00800000, 0x08000000, 0x80000000, 0x00000009, 0x00000090,
0x00000900, 0x00009000, 0x00090000, 0x00900000, 0x09000000, 0x90000000,
0x0000000a, 0x000000a0, 0x00000a00, 0x0000a000, 0x000a0000, 0x00a00000,
0x0a000000, 0xa0000000, 0x0000000b, 0x000000b0, 0x00000b00, 0x0000b000,
0x000b0000, 0x00b00000, 0x0b000000, 0xb0000000, 0x0000000c, 0x000000c0,
0x00000c00, 0x0000c000, 0x000c0000, 0x00c00000, 0x0c000000, 0xc0000000,
0x0000000d, 0x000000d0, 0x00000d00, 0x0000d000, 0x000d0000, 0x00d00000,
0x0d000000, 0xd0000000, 0x0000000e, 0x000000e0, 0x00000e00, 0x0000e000,
0x000e0000, 0x00e00000, 0x0e000000, 0xe0000000, 0x0000000f, 0x000000f0,
0x00000f00, 0x0000f000, 0x000f0000, 0x00f00000, 0x0f000000, 0xf0000000,
0x00000000, 0x11111111, 0x22222222, 0x33333333, 0x44444444, 0x55555555,
0x66666666, 0x77777777, 0x88888888, 0x99999999, 0xaaaaaaaa, 0xbbbbbbbb,
0xcccccccc, 0xdddddddd, 0xeeeeeeee, 0xffffffff, 0x12345678, 0x23456789,
0x3456789a, 0x456789ab, 0x56789abc, 0x6789abcd, 0x789abcde, 0x89abcdef,
0xfedcba98, 0xedcba987, 0xdcba9876, 0xcba98765, 0xba987654, 0xa9876543,
0x98765432, 0x87654321)



### Main routine
def main():
  """Main routine
  """

  # Possible Proxmark3 console prompts
  pm3_prompts_regex=re.compile("^(proxmark3>|\[.*\] pm3 -->)$")

  # Console command to send the PWD_AUTH authentication command to the NTAG
  authcmd = "hf 14a raw -c -s 1b{pwd:08x}"

  # Create a PTY pair to fool the Proxmark3 client into working interactively
  pty_master, pty_slave = openpty()

  # Spawn the Proxmark3 client
  pm3_proc = Popen([pm3_client, pm3_dev_file], bufsize=0, env={},
			stdin=pty_slave, stdout=PIPE, stderr=DEVNULL)

  # Start with the first password in the dictionary
  pwd_dict_i = 0
  pwd = pwd_dict[pwd_dict_i]

  # Interact with the Proxmark3 client
  recvbuf = ""
  sendcmd = True

  expect_reply_bytes = 0

  while True:

    # Read lines from the Proxmark3 client
    rlines = []

    for c in pm3_proc.stdout.read(256).decode("ascii"):

      if c == "\n" or c == "\r" or pm3_prompts_regex.match(recvbuf):
        rlines.append(recvbuf)
        recvbuf = ""

      elif len(recvbuf)<256 and c.isprintable():
        recvbuf += c

    # Process the lines from the client
    for l in rlines:

      # If we detect a fatal error from the client, exit
      if re.search("(proxmark failed|offline|OFFLINE|unknown command)", l):
        return(-1)

      # Do we have a prompt
      if pm3_prompts_regex.match(l):

        # Stop if we're going to overflow
        if pwd > 0xffffffff:
          sys.stdout.write("PWD_AUTH unsuccessful\n")
          return(0)

        # Issue another PWD_AUTH command
        os.write(pty_master, (authcmd.format(pwd=pwd) + "\r").encode("ascii"))

        # Inform the user
        sys.stdout.write("\rTrying password {:08x}... ".format(pwd))

        # Next password in dictionary, or iterate after running out
        if pwd_dict_i < len(pwd_dict) - 1:
          pwd_dict_i += 1
          pwd = pwd_dict[pwd_dict_i]

        elif pwd_dict_i == len(pwd_dict) -1:
          pwd_dict_i += 1
          pwd = 0x00000000
          while pwd in pwd_dict:
            pwd += 1

        else:
          pwd += 1
          while pwd in pwd_dict:
            pwd += 1

      # Are we expecting 4 bytes (indicating a PACK + CRC was returned)?
      elif expect_reply_bytes == 4:

        reply_bytes_ascii = l.split()
        pack = (int(reply_bytes_ascii[0], 16) << 8) + \
		int(reply_bytes_ascii[1], 16)

        sys.stdout.write("PWD_AUTH successful: PACK = {:04x}\n".format(pack))
        return(0)

      else:

        # Did we get a "received x bytes" line?
        m = re.findall("received ([0-9]+) bytes", l)

        if m:
          expect_reply_bytes = int(m[0])
          if expect_reply_bytes != 4:
            expect_reply_bytes = 0

      

### Jump to the main routine
if __name__ == "__main__":
  sys.exit(main())
2 Likes