RE - Fingerprint Sensor Driver
Chasing support for the Synaptics SYNA8002 / SYNA8006 fingerprint reader on Linux turned into a crash course in kernel driver prototyping and Windows driver reverse engineering. The DLL at the heart of the investigation is synaWudfBioSpi.dll, a Windows UMDF biometric driver installed via an INF that matches ACPI IDs SYNA8002 / SYNA8006.
Goal: Understand how Windows speaks SPI to the sensor well enough to reproduce the minimum viable protocol on Linux.
What this log covers
- Structure of the Windows driver stack
- Extracting high-signal breadcrumbs from strings and symbols
- Navigating Ghidra when the listing initially looks “empty”
- Separating import thunks from real call sites
- Mapping out where the crypto and key handling logic likely resides
Building a Linux SPI proto driver
To experiment quickly, a simple kernel module was built as a raw SPI pipe, letting userspace poke the fingerprint sensor directly before investing in fprint / libfprint glue.
The driver:
- Binds via an ACPI match table to
SYNA8002 - Exposes
/dev/syna8002as a character device - Caches the last userspace write as the TX buffer
- On read, performs one
spi_sync()transfer with the cached TX buffer (if any) and a fixed 512-byte RX buffer
Workflow:
- Write a command blob.
- Read back the device’s reply.
- Iterate on the protocol until it starts making sense.
Understanding the Windows UMDF driver layout
Files from the Windows driver package paint this stack:
- UMDF service:
synaWudfBioSpi.dll(SPI implementation) - WBDI / WinBio adapter:
synaBscAdapter.dll - Loader indirection (Win10):
synaDriverLoader.dll - Security components: SGX / TEE-related binaries for sensitive operations
Symbol names show a class-like layout:
CBiometricDeviceSPI::OnPrepareHardwareCBiometricDeviceSPI::OnReleaseHardwareCBiometricDeviceSPI::ReadCBiometricDeviceSPI::WriteCBiometricDeviceSPI::WriteReadCBiometricDeviceSPI::OnInterruptIsrCBiometricDeviceSPI::OnInterruptWorkItem
Together these imply a conventional lifecycle: initialise hardware, configure interrupts, then shuttle IOCTL/read/write requests.
Getting signal from strings and paths
strings synaWudfBioSpi.dll delivered actionable hints before any deep disassembly:
CBiometricDeviceSPI::*names create a mental map of capabilities.synaWudfBioSpi.pdbconfirms it was built with symbols.- Source path fragments (
palSpiProtocol.c,palWinSpiDriver.c) suggest a Platform Abstraction Layer (PAL) where shared SPI logic lives, wrapped by platform-specific glue.
The working hypothesis: find the PAL entry points, and the SPI/crypto protocol will start revealing itself.
When Ghidra looks “empty”
The DLL is a normal x86-64 PE (PE32+) binary, not obviously packed. If Ghidra feels empty, it often means:
- You’re staring at type definitions, not instantiated objects.
- You’re looking at import thunks, not call sites.
- You’re not traversing XREFs from the Imports tree into real code.
Once the navigation clicks (Symbol Tree → Imports → function → XREFs), meaningful analysis follows quickly.
Import thunks vs. real call sites
Windows imports yield three distinct views inside Ghidra:
- Thunk functions that simply
JMPto the resolved DLL export. - IAT pointers (Import Address Table) storing those resolved addresses.
- Actual call sites elsewhere in the binary that prepare parameters and jump through the thunk.
Example thunk:
BCryptImportKeyPair → JMP [->BCRYPT.DLL::BCryptImportKeyPair]
Seeing the thunk only proves the DLL calls Windows crypto. The interesting investigation happens at each XREF, where parameters—and therefore key buffers—are assembled.
Locating the ECC key import path
Following cross references to BCryptImportKeyPair leads to a call site that clearly imports an ECC private key:
LEA R8, [u_ECCPRIVATEBLOB] ; pszBlobType = "ECCPRIVATEBLOB"
...
CALL BCryptImportKeyPair
Key observations:
- The blob type string is not the key material.
- Key bytes are passed via the
pbInputstack parameter (Microsoft signature offset Stack[0x28]). cbInputcarries the blob length.
Tracing backwards shows the pbInput pointer originates from a buffer saved into locals such as local_60 / local_98, allocated and populated earlier. No static .rdata blob holds the key. Instead a helper function constructs the blob on the fly.
FUN_1800ebb70 as the crypto pivot
The helper in question, FUN_1800ebb70, appears throughout ECC import/export paths.
Parameters:
param_1: integer selector (values like0x139,0x13ahint at format variants)param_2/param_3/param_4: pointers to buffers or context structures
Behaviour inferred so far:
- Populates ECC blob headers.
- Writes key material into the workspace buffer.
- Hands the finished blob to
BCryptImportKeyPair.
Adjacent helpers worth annotating next:
FUN_1800eba80FUN_1800ebc30FUN_1800ded30FUN_1800decf0
What “reverse the crypto” really means
The objective is not to break ECC. It’s to mirror the data flow that Windows uses:
- Identify the primitives and APIs: ECC key import/generation, shared secret agreement, KDF, signing/verification.
- Understand the inputs: sensor identifiers, pairing blobs, registry-stored secrets.
- Replicate the derivation, blob formatting, and crypto steps on Linux.
Visualising the pipeline helps:
Device / sensor identity
+
Stored secrets
↓
Derived ECC key blob
↓
Encrypted protocol session
Next targets
- Annotate the crypto spine: fully decompile
FUN_1800ebb70and its adjacent helpers; rename locals/structs to capture the blob layout (headers, curve ID, private scalar length, etc.). - Track secret sources: find where registry entries, files, or ACPI-provided identifiers feed into the blob constructors.
- Compare with SGX/TEE binaries: check whether
synaTEE.signed.dllmirrors the same logic, especially if the true secret material resides inside the TEE.
Each of these should bring the Linux prototype closer to reproducing the Windows SPI and crypto handshake with confidence.
