Login with a serial RFID / NFC reader under Windows and Linux

As a have a few RS232 and Wiegand readers on order and I intend to use them to log into my personal and work computers (login, screensaver unlocking and general PAM authentication under Linux), I decided to prepare my machines to handle those readers. I figured this might be of interested to some of you also.

Serial readers are “dumb” in the sense that, unlike PC/SC or CCID readers, they only report the UIDs of whatever chip they happen to be able to read. You can’t use them to do anything smarter.

Usually that’s good enough for cheesy unencrypted UID-based authentication (think door reader), and that’s why there are so many of these things on the market. For the purpose of logging in and unlocking screensavers with my implants on my computers in low-security environments, they’re good enough too.

There are two kind of serial readers: those that send the UID once when a chip is presented, and those that send the UID repeatedly as long as the chip is in the RF field.

The first kind is okay for isolated authentication events or for recording UIDs, but they can’t be used to assess the presence or absence of a chip on the reader continuously. So you can’t use them if you want to keep your session up only as long as the chip is present, and lock it up as soon as it disappears. You can’t use them to trigger events based on how long a chip stays on the reader either - for instance, I lock my session by presenting my implant to the reader for more than 5 seconds.

The second kind is clearly better, and since the devices I ordered work like that, that’s what I concentrated on.

So, how to determine if your reader is one-shot or repeating?

On Windows, find out what real or virtual COM port the reader sits at, fire up a serial terminal (like Putty), open the COM port at the right baudrate and format (usually 9600/8N1) and bring a chip close to the reader. If it outputs the same UID over and over as long as you keep the chip on the reader, it’s a repeating device, and that’s what you want:

On Linux, do the same thing: determine what device file corresponds to your serial reader, open a terminal and cat it:

Serial RFID reader - Test on Linux

  • Using a repeating serial reader on Linux

A while ago, I coded a set of utilities to handle simple UID-based authentication with PC/SC-compatible readers, such as the ubiquitous ACR122U and many others. There’s a PAM module, a Gnome screesaver locker / unlocker, and a couple of other minor toys.

I added a small background service to watch the output of a serial reader, and extended the other scripts to handle the UIDs coming from the serial reader as well as from PC/SC readers.

You can find it here: https://github.com/Giraut/nfcutils

It’s a bit rough on the edges: the code is clean and works well, and the documentation is complete, but it’s all in in the headers of the scripts, and you kind of need to know your way around a Linux system to install and configure things. At some point I really should make a proper package, but… well, I’m lazy. Sorry…

However, if you want to volunteer to package it up, submit it to Debian and Fedora and maintain it (which bores me no end), you’re welcome to chip in. Hint hint :stuck_out_tongue_winking_eye:

  • Using a repeating serial reader on Windows

After some experimening, it turns out my old friend Rohos Logon Key also handles dumb serial readers. Yes, I know, I keep harping on about that thing, but what can I say… again, it does the job :slight_smile:

So after installing it, go to Options and select RFID Easyident/Addimat/KCY/pcProx/Stahl:

Despite the long list, there’s nothing special about those readers: they’re all dumb serial readers that send a stream of hex-encoded, CR- or LF-terminated transponder UIDs. Any other reader that does the same thing will work equally well. The entry should really just be called “Generic serial RFID reader”.

Then in Setup authentication key, if your reader isn’t on COM1, Rohos Logon Key will complain:

Serial RFID reader - Rohos Logon Key #2

Just click OK, then select the setup icon thingy on the right, then the correct COM port, and you’re all set:

The software should tell you it sees a reader (it doesn’t see anything really, it was simply able to open the COM port), then you’ll be able to set your Windows password when you present your implant to the reader - and keep it there, which is awkward to type at the same time.

I hope this will help you get going with serial readers.

8 Likes

For the terminally lazy, here’s another cool trick you can do with a serial reader under Windows: automatically wake up Windows, kill the screensaver and login with your implant without having to touch the keyboard.

Normally, if the screensaver has kicked in, or if Windows has turned the screen off to save power, you have to hit a key to bring up the login prompt. Now with this trick, no more: just scan your hand and it’ll bring you straight to your desktop, even if the screen has turned off. Yes, this was a world-class problem that definitely needed solving! :slight_smile:

So how does it work?

When Windows starts the screensaver, it’s up to the screensaver to watch for mouse or keyboard events and terminate when something has happened. When it terminates, Windows brings up the login prompt and Rohos Logon Key can then log you in. Problem: you need to hit a key to terminate the screensaver.

So what we need is something that kills the screensaver when some data is received from the serial port. Problem: under Windows, COM ports are exclusive use - and since Rohos Logon Key has already opened it for its own use, you can’t make a program that watches the same serial port and kills the screensaver when something comes through.

What you can do however is connect Rohos Logon Key to a “fake” COM port, and make a “wedge” program that reads the real serial port, kills the screensaver upon receiving serial data, then transfers the data to Rohos Logon Key through the fake COM port.

To achieve this, first you need to download and install com0com. This gem of a utility is a null modem cable emulator: you can create a pair of virtual COM ports that can be used to make two serial port applications talk to each other without physical serial ports or a cable.

So let’s do that: create a pair of COM ports (port class on both sides). Here I create COM8 and COM9:

com0com - creating a pair of fake COM ports

Then connect Rohos Logon Key to one end of the fake serial cable. Here COM8:

Rohos Logon Key thinks it’s connected to a reader, but in fact it’s connected to a dangling virtual null modem cable. Now what we need is a program to connect the real RFID reader’s COM port and the other end of the fake null modem cable, that also kills the screensaver:

To do this, first install the Python3 language interpreter. Then in the command line, download the PySerial module with pip:

Python3 - install PySerial module

Then open a file called, for example, screensaver_killer.py somewhere in your home directory and paste the following script in it:

#!/usr/bin/python3

import os
import time
import serial

incomport="COM4"
outcomport="COM9"
screensaver="ribbons.scr"

while True:

  uid=None

  # Read a UID from the serial RFID reader
  try:
    with serial.Serial(incomport) as s:
      uid=s.readline().strip().decode("ascii")
    print("UID read in:", uid)
  except:
    print("Read error")
    uid=None
    time.sleep(0.2)

  # Pass on the UID to Rohos Logon Key
  if uid:
    try:
      with serial.Serial(outcomport) as s:
        s.write((uid+"\n").encode("ascii"))
      print("  UID sent out")
    except:
      print("  Write error")

  # Kill the screensaver
  if uid:
    try:
      os.system("taskkill /f /t /im {}".format(screensaver))
    except:
      pass

In the file, replace the incomport variable with the COM port corresponding to your RFID reader, outcomport to the virtual COM port corresponding to the dangling end of the virtual serial cable created by com0com, and screensaver by the name of the screensaver you normally run. Then save the file.

At this point you can run it to try it if you want: as long as it runs, it’ll pass stuff it receives from the serial RFID reader on to Rohos Logon Keys, and kill the screensaver specified in the script. But if you want to run it at boot time in the background, you can start it with the Task Scheduler:

And here’s a video to show it works: I log in directly from the screensaver with my EM4xxx implant, then with the computer in power saving mode:

5 Likes

Sweet, this’ll come in handy. Thanks!

1 Like

@fraggersparks are you taking notes? Wake up call :slight_smile:

I was going to see if I can find or make something to decrypt my LUKS partition during boot. Have a yubi key + a password combo right now.

1 Like

Not terribly difficult to do, unless your LUKS partition is your boot partition, in which case you’ll probably have to make an initrd image that loads the appropriate drivers, handles reading your implant and recovers the LUKS key before doing the chroot. If your LUKS partition is something else, like /home, you can do all that in the regular system, which is easier.

It’s my boot partition. So yeah what you said.

Maybe I might get cracking on something like that, if I have some time :slight_smile:

GnuPG i believe has a solid foundation for this to work. I’ve been swamped (and the Rona’s impacted my work - busy!) but this is next on the list.

1 Like

If you do I’d definitely be interested. I was going to wait for the Apex line because it’s an actual secure element

Bleh… Why wait for the Apex line when we already have s00per secure Mifare Classics eh? :slight_smile:

More seriously, what I was thinking of is a generic mechanism to wait for input from a contactless smartcard / implant at boot time to decrypt the root partition. Once that’s done, you could plug in a shite UID-based authentication, or a more advance challenge-response thing, whatever you fancy. Maybe it even already exists, I haven’t checked.

Oh well then, have at it. I don’t have time either :slight_smile:

I have a challenge response slot on my yubi key yeah.

This is already implemented, interestingly enough. YubiKey login has a PAM and i think a similar mechanism can be used to decrypt the root partition with LUKS.

1 Like

Yes, I use yubikey-luks which adds a 2nd factor using a challenge response in slot 2 of the YubiKey.

I’ve added provisions in SiRFIDaL to read a UID and pass it to cryptsetup. So you would do something like this to set it up (as root):

  • Get the UID you want to use as a crypto key for the volume with sirfidal_getuids.py
  • Do cryptsetup luksAddKey /dev/<your encrypted volume> and paste the UID when it prompts for a key
  • Then to open it at boot time with your implant: sirfidal_getuids.py -q -s '' | cryptsetup -d- luksOpen /dev/<your encrypted volume> <mapper>

You would just have to setup your source of UIDs (pcscd, serial port, keyboard wedge or cellphone) and start sirfidal_server at boot time before opening the LUKS volume.

1 Like

The script I had posted above works, but it didn’t always work reliably. As I was trying to figure out why, I realized something: Rohos Logon Key really expects UIDs to come in specific data formats from the serial readers it supports nominally. The script however simply passes it raw LF-terminated ASCII UIDs.

So I modified it a bit: now it simulates an Easyident FS-2044 RFID reader (one of the readers Rohos Logon Key explicitely supports). With that new version, Rohos unlocks each and every time without fail.

Here it is:

#!/usr/bin/python3

# This program emulates an Easyident FS-2044 RFID reader with UIDs read from a
# generic, 10-digit UID, 9600 baud serial RFID reader, for use as an input
# device by Rohos Logon Key.
#
# This program requires a virtual null modem cable such as com0com, with
# Rohos Logon Key listening to one end of the null modem cable, and this
# program outputting fake Easyident frames to the other end.
#
# Additionally, this program can kill a running Windows screensaver whenever a
# UID is read, so that the Windows logon screen is automatically brought up
# before passing the UID to Rohos Logon Key, thereby alleviating the need to
# press a key to activate the logon screen.



# Parameters
incomport="COM2"		# Input COM port for the generic serial reader
outcomport="COM4"		# Output COM port for the virtual null modem
				# Rohos Logon Key is connected to the
				# other end of
screensaver="ribbons.scr"	# Set to None to bypass killing the screensaver



# Modules
import os
import time
import serial



# Main program
incomport_fd=None
outcomport_fd=None

hexdigits = "0123456789ABCDEF"

while True:

  uid=None

  if not incomport_fd:
    try:
      incomport_fd = serial.Serial(incomport)
    except:
      print("Error opening input COM port {}".format(incomport))
      incomport_fd = None
      time.sleep(0.2)
      continue

  # Read a UID from the serial RFID reader
  try:
    uid=incomport_fd.readline().strip().decode("ascii")
  except:
    print("Read error")
    incomport_fd = None
    time.sleep(0.2)
    continue

  if not outcomport_fd:
    try:
      outcomport_fd = serial.Serial(outcomport)
    except:
      print("Error opening output COM port {}".format(outcomport))
      outcomport_fd = None
      time.sleep(0.2)
      continue

  uid = "".join([c for c in uid.upper() if c in hexdigits])

  print("UID read in:", uid)

  if len(uid) != 10:
    print("  Incompatible UID (not 10 digits) - giving up")
    continue

  # Kill the screensaver
  if screensaver:
    try:
      os.system("taskkill /f /t /im {}".format(screensaver))
    except:
      pass

  # Create a simulated Easyident data frame
  csum = sum([ord(c) - ord("0" if c.isdigit() else "7") for c in uid]) & 0xf
  easyident_frame = b"\x8a" + (uid + hexdigits[csum]).encode("ascii") + b"\r\n"

  # Pass on the fake Easyident frame to Rohos Logon Key
  try:
    outcomport_fd.write(easyident_frame)
    outcomport_fd.flush()
    print("  UID sent out")
  except:
    print("  Write error")
    incomport_fd = None
    time.sleep(0.2)
2 Likes

Microsoft pushed a Windows update on Friday that breaks Rohos in serial mode.

My foot reader is now dead.

Fuck Microsoft…

Dicks!

Any legitimate reason for it?
Have you “raised a ticket”?
How long did it take you to work out the cause?

Well I suppose the legitimate reason is that my company’s IT guy is anal about keeping up with updates. I suppose he’s right too. He would be righter if he ditched Windows but this is a Microsoft shop, so…

But he retires in 1 1/2 year and I’m next in line. And I’m already installing Linux right and left and proving by example that it’s nicer when done right, so there is hope :slight_smile:

Yeah I reported it to the dev. We’ll see what he says. I’m not too hopeful though: he seems to frown upon what I’m doing with his baby - i.e. using it for insecure applications. I have a feeling he’s not too keen on supporting serial readers, from what he told me in the past. We’ll see.

It took me forever to find out the cause. The clincher was when I uninstalled everything - com2com, my Python script, Rohos, cleaned the registry squeaky clean - then reinstalled Rohos vanilla and simply configured it to listen to a real serial port to which nothing was connected, and seeing card IDs being read. That’s a dead giveaway.

I managed to get Rohos to cooperate with my RFID serial reader again. Most of the time anyway.

Rohos has a really weird way of handling the serial port. I think it’s using a custom reader protocol that I don’t understand. But no matter how hard I try to reverse engineer it, I’m not really able to find out exactly what it needs to work well. Nor can I find the specs of the serial readers it’s supposed to support anywhere online. Not that it would be terribly useful: Rohos simply links to third party libraries to support those readers as far as I can tell, and those manufacturer-provided libraries are rarely fully documented.

Anyway, here’s a new script that manages to pass UIDs from a generic serial reader to Rohos. It’s seriously dirty and hacky but it seems to work more or less reliably:

#!/usr/bin/python3

# This program sends UIDs read from a generic 9600 8N1 serial RFID reader to
# Rohos Logon Key, configured to use the authentication device type
# "RFID Easyident/Addimat/KCY/pcProx/Stahl", using the weird serial protocol
# it seems to be using in that mode.
#
# It requires a virtual null modem provided by com0com, with RTS/CTS "cabled"
# in the com0com setup utility (default), "emulate baud rate" enabled",
# "enable buffer overrun" enabled and Rohos Logon Key configured to be
# listening to one end of the null modem cable.
#
# Additionally, this program can kill a running Windows screensaver whenever a
# UID is read, so that the Windows logon screen is automatically brought up
# before passing the UID to Rohos Logon Key, thereby alleviating the need to
# press a key to activate the logon screen.
#
# Please note that the way Rohos Logon Key is listening to the serial port in
# "RFID Easyident/Addimat/KCY/pcProx/Stahl" mode is totally unclear. The way
# this program sends UIDs to it is highly unusual, but it seems to be the only
# way to get Rohos Logon Key to accept UIDs somewhat reliably. This is probably
# not correct, and probably abuses the program's normal operation.



# Parameters
incomport   = "COM2"		# Input COM port for the generic serial reader
outcomport  = "COM4"		# Output COM port for the virtual null modem
							# Rohos Logon Key is connected to the
							# other end of
screensaver = "ribbons.scr"	# Set to None to bypass killing the screensaver



# Modules
import os
import time
import serial
from datetime import datetime



# Main program
incomport_fd = None
outcomport_fd = None

recbuf = b""

while True:

  uid = None

  if incomport_fd is None:
    try:
      incomport_fd = serial.Serial(incomport, timeout = .5)
    except:
      print("Error opening input COM port {}".format(incomport))
      incomport_fd = None
      time.sleep(0.2)
      continue

  # Read a UID from the serial RFID reader
  uid = b""
  b = b""
  while True:

    try:
      b = incomport_fd.read(1)
    except:
      print("Read error")
      incomport_fd = None
      time.sleep(0.2)
      break

    if b:
      if b in b"0123456789abcdefABCDEF":
        recbuf += b.upper()
      elif b == b"\n":
        uid = recbuf
        recbuf = b""
    else:
      if uid:
        break

  if incomport_fd is None:
    continue

  if outcomport_fd is None:
    try:
      outcomport_fd = serial.Serial(outcomport)
      cts = outcomport_fd.cts
    except:
      print("Error opening output COM port {}".format(outcomport))
      outcomport_fd = None
      time.sleep(0.2)
      continue

  print("UID read in:", uid.decode("ascii"))

  # Kill the screensaver
  if screensaver is not None:
    print("  Killing the screensaver")
    try:
      os.system("taskkill /f /t /im {}".format(screensaver))
    except:
      pass

  # Pass on the UID to Rohos Logon key twice, it case it misses the first time
  for i in range(2):

    # First wait for a falling edge on CTS
    now = datetime.now().timestamp()
    cts_wait_timeout = now + 2
    prev_cts = cts
    cts = outcomport_fd.cts
    while not (cts and not prev_cts) and now < cts_wait_timeout:
      prev_cts = cts
      cts = outcomport_fd.cts
      now = datetime.now().timestamp()

    # If we can send the UID, do so. Otherwise give up
    if cts and not prev_cts:
      try:
        outcomport_fd.write(uid)
        outcomport_fd.flush()
        print("  UID sent out {}".format("once" if i == 0 else "twice"))
      except:
        print("  Write error")
        outcomport_fd = None

    # If the CTS thing fails, make sure we don't end up in a tight loop
    time.sleep(.5)

EDIT:

Oh and yeah, another thing: if you have a pcProx Sonar connected to your computer, you’ll have to delete or rename C:\Windows\system32\pcProxAPI.dll and C:\Windows\SysWOW64\pcProxAPI.dll to disable the pcProx API: it turns out that library (or Rohos) only checks the VID (vendor ID) of the various USB devices listed in the system to determine the presence of a pcProx reader. Of course, the Sonar being made by the same manufacturer, it has the same VID. But it’s not a reader, so it messes everything up.

Took me forever to find that one…

1 Like