Notebook entry

Reverse engineering a Windows Hello fingerprint reader

  • Reverse engineering
  • Linux
  • I²C
  • USB
  • Rust
Status
Working prototype
Platform
Linux
Language
Rust
Hardware
I²C / USB

The problem

My laptop has a fingerprint reader that works beautifully in Windows Hello and not at all in Linux. There’s no driver, no libfprint support, and no documentation. I wanted to know what it would take to change that — which mostly meant finding out what the device actually does when Windows talks to it.

Looking at the hardware

The sensor shows up as a USB device, but internally it bridges to an I²C part. That split matters: the interesting traffic is I²C wrapped inside USB control transfers, so a naive USB capture shows framing, not payloads.

$ lsusb -d 27c6:
Bus 003 Device 006: ID 27c6:5395 Fingerprint Sensor
bInterfaceClass  255 Vendor Specific
bNumEndpoints      2  (1 bulk-in, 1 interrupt-in)
lsusb, once the sensor enumerates. The bridge sits behind this single USB interface.

Capturing I²C traffic

With the sensor initialised, the first exchange looks like this — a short init packet, then a status reply. Hover a byte to line it up with its ASCII:

0000
SF:.............
0010
key=3F..
24 bytes · hover or focus a byte
Init request (bytes 0x00–0x0F) followed by the device's status reply.

The 53 46 3A prefix (SF:) shows up on every frame, so it’s almost certainly a magic header rather than data. That gave me a frame boundary to work from.

What I got wrong

Once I stopped trusting the timing and started trusting the frame header, the protocol fell out quickly.

Where it stands

There’s a small Rust program that can initialise the sensor, request a scan, and pull back a raw image. Enrollment and matching aren’t done, and I haven’t touched libfprint integration yet.

What’s next

  • Wire the raw image path into libfprint so the desktop actually uses it.
  • Work out enrollment, which needs multiple captures stitched together.
  • Figure out what the last four bytes of each frame are. I think it’s a checksum. I’ve been wrong before.