Theft Protection and the CAN Gateway - krcenov/RNS510-Map-Viewer GitHub Wiki

Theft Protection, the CAN Gateway, and TpPvVerifyPin

This page covers the RNS510's real anti-theft/immobilizer mechanism — spanning 2 separate processors (the VUCI/gateway ST10F276E chip and the APPS PowerPC application) — and everything found investigating it: a real ST10/C166 disassembler, a byte-level-confirmed EEPROM state machine, and the real vehicle-pairing security design VW calls "component protection." See SWL Disc: Boot Chain and Build System for how GATEWAY.FLI gets flashed, and HOST BSP and Vehicle Coding for the separate SetCoding/SetRegData coding-array mechanism this is related to but distinct from. Reader/analysis code: research/ swl_5238_reader.py.

Status: real, complete picture at the STRING and (for one specific sub-mechanism) DISASSEMBLY level on both processors; the actual live PIN-comparison code path itself was searched for extensively and not located — recorded honestly as a real, multi-technique-confirmed wall, not a gap nobody tried to close.

GATEWAY.FLI — a complete, real immobilizer state machine (string level)

VUCI\B101\RNSMIDEC\PROG\GATEWAY.FLI (884,736 bytes, the real ST10F276E MOST-bus/CAN gateway firmware) is genuinely DIFFERENT compiled content per market/disc (same size, different SHA256 across builds — real per-vehicle-platform CAN routing tables baked in alongside the shared logic, consistent with the per-platform GWTABLE.FLI naming on the boot-chain page).

TheftProtection_* — not just a bare function name, a complete real state machine: TheftProtection_Startup() (registers a MOST-bus "Shadow" callback + a timer), TheftProtection_Timer(), TheftProtection_Eeprom_Init()/_Update() (persists its own state to EEPROM behind a verified signature — see below), TheftProtection_Shadow_Callback_Button()/_Handle_FBlockAvail(), TheftProtection_PrintStatus, setTheftProtection(), and a real, complete one-line status dump:

THP: St=%d, t_cc=%ds, t_b=%ds, t_tu=%ds, c_r=%2d, c_uk=%2d, c_ud=%2d, last_st=0x%2x

(St=state, t_cc="ComfortCoding" timer, t_b=blocking timer, t_tu=temp-unlock timer, c_r/c_uk/c_ud=3 counters, plausibly Retry/UnlockKey/UnlockDiag). A parallel diagnostic OVERRIDE interface exposes every one of these as a settable field — confirming the guessed roles are real, not speculation from the format string alone: "THP: Diag: Override CounterPINRetry %d", "...Override Timer TempUnlock %d[s]", "...Override Timer Blocking %d[s]", "...Override Timer ConfortCoding %d[s]" (sic, a real typo preserved in the firmware), "...Override Counter UnlockKey: %d", "...Override Counter Diag %d", "...Override CurrentState %d", "...Override ps_PIC_THP (theft detection) %d", "...Override ps_PIC_SFL (safe line cut) %d". A real retry-limit message: "THP: Cnt=%d, Limit=%d REACHED". A real persistent EEPROM record layout, also dumped as one line: "THP: EEPROM: CS:0x%02x LS:0x%02x BT:%u UT:%u FUL:%2d/%2d PUL:%2d/%2d IP:%2d/%2d" (checksum, last-state, block-time, unlock-time, 3 current/max counter pairs — exact field meanings not decoded further, but the shape, a signed, versioned, counter-limited persistent security record, is unambiguous).

ITD_TheftProtection_Unlock sits inside a much larger real ITD_* ("Interactive Test/Diagnostic", inferred) function family — ~50 real names covering hardware test/calibration AND coding/security (ITD_setCoding/ITD_readCoding/ITD_getTPFlags [TP=Theft Protection]), gated by a real DiagS_SetAccessLayer function — a real access-LAYER concept at the diagnostic-dispatch level, a plausible real home for the vehicle SecurityAccess check this project searched for and did not find in FHDD6.FLI's own 0-24MB region (see HOST BSP and Vehicle Coding). DiagS_SVDO_Key sits in the SAME service-name table as EELS_ ApplDiagReadDataByCommonIdentifier — a literal UDS/KWP2000 ReadDataByCommonIdentifier service-name convention, confirming this whole area is a real diagnostic-service dispatch table. Its exact role was not determined — no digit-count/PIN-format string was found confirming or refuting a link to a 5-digit VCDS-style code.

A separate, real coding-synchronization protocol, distinct from both TheftProtection_* and the APPS-side SetCoding/byte-array mechanism: CodingShadow_* functions (CodingShadow_Check_Coding, CodingShadow_Received_VZE_VZA/_SW_ID/_MFL_ONOFF_ID/_NAV_EXISTS/ _CAR_TYPE) driven by a real CONFIG_FKTID_* enum (CONFIG_FKTID_AUTHENTICATE — a literal "authenticate" function ID — plus _REGCHANGE/_REGDATA) and named CODING_*/CL_CODING_* items (CODING_SW_ID, CODING_NAV_EXISTS, CODING_MFL_ONOFF_ID, CODING_CAR_TYPE, CODING_VZE_VZA_ID — "VZE"/"VZA" not confidently translated). A 2nd, parallel, gateway-side coding representation.

The APPS-side half of CONFIG_FKTID_AUTHENTICATE is now found and disassembled, closing the cross-reference: WA/CTEST.OUT's real configLogin() function builds a fixed-format record (a string pointer/length, the real m_wHostDeviceId global, 4 literal constant bytes, and the real address of a local callback, configTestShadowSink, embedded as a function pointer) and transmits it via cc_tla_most_send() — a genuine MOST-bus login/authenticate message, not a local-only check. See RNS510 Serial Console: Command Reference for the full disassembly and the other 26 real, shell-callable CTEST.OUT functions found alongside it (most previously undocumented anywhere in this project).

Confirmed the EU and both North-America builds' GATEWAY.FLI all carry the IDENTICAL set of 37 THP/TheftProtection strings — no market-specific variation in this mechanism.

A real ST10/C166 disassembler, found and validated

Capstone has no support for this chip's instruction set (CS_ARCH_* lists PPC/ARM/x86/MIPS/etc, nothing C166/ST10/8051-family), and no local objdump/binutils target exists either — but a real, open-source tool does: hn/c166-dis (Perl, GPLv3; a 3rd-party external tool, not vendored into this repo, same treatment as pip install capstone). perl c166-dis.pl <file> <start_offset_hex> <length_hex>.

Immediately validated against known structure: disassembling from file offset 0 decodes the file's own AA55AA55 magic (the SAME magic already known from FHDD6.FLI and PQTABLE.FLI/SKTABLE.FLI — see the boot-chain and HOST BSP pages — confirming .FLI is a shared generic Continental firmware wrapper across BOTH the PowerPC processors AND this ST10 gateway chip, not PowerPC-specific) as non-code, correctly, immediately followed by a real, textbook C166 interrupt vector table at file offset 0x20.

Address mapping confirmed SIMPLE — no unknown load-base recovery needed, unlike FHDD6.FLI's own PowerPC region: file_offset = segment * 0x10000 + offset, verified by disassembling a real vector- table target and getting back coherent, non-garbage C166 code: a clean bit-extraction loop (MOVB/SHR/AND) feeding sequential constant IDs into one shared reporting function via CALLS 0x05,0x8B24 — exactly a "for each status bit, report signal N" diagnostic loop. That shared helper itself disassembles to a long, coherent function using EXTP (C166's real paged-memory-extension prefix) for indexed array lookups — real, generic, shared code, not noise.

The string-to-code cross-reference — a real dispatcher found, an EEPROM signature confirmed at the disassembly level

Method that worked, after 2 that didn't: searching for "ITD_TheftProtection_Unlock"'s own file offset as a literal pointer value found nothing (0 hits one byte order, 23 hits the other — all confirmed text coincidences inside unrelated strings). What worked: target short, distinctive DEBUG/FORMAT strings instead of a bare job name (loaded as a direct immediate right before a print call, the same pattern already proven for PowerPC printf sites in CTEST.OUT) — searching for MOV Rx,#imm16 (opcode 0xE6) loading the exact file offset of 4 different "THP:"/"TheftProtection:" strings found real hits, tightly clustered in one ~3.6KB region (file offset 0xA6480-0xA7100).

Disassembling that region (coherent throughout, no illegal-opcode breaks) reveals a real diagnostic OVERRIDE-command dispatcher: each handler loads its own name/format string via the same shared-reporter pattern above — matching, by exact file-offset arithmetic, the "CounterPINRetry"/"Timer TempUnlock"/etc. override names found by string-scanning — then applies the override directly to internal state (a single bit via BMOV, or a full EEPROM-style record write).

A real retry-limit constant, confirmed at the disassembly level:

MOVB  RL1,[R12]          ; load current counter byte
CMPB  RL1,#0x0032         ; compare against 50 (0x32) decimal
JMPR  CC_NC,0x000A65D8    ; if counter >= 50, take the "limit reached" path
...
ADDB  RL1,#1               ; else increment
MOVB  [R12],RL1            ; and store it back

— the "limit reached" branch leads, a few instructions later, into the exact same shared reporter loading the file offset of "THP: Cnt=%d, Limit=%d REACHED", confirmed by direct address arithmetic. 50 is a real, disassembly-confirmed retry limit (which specific counter — PIN retry, unlock-key, unlock-diag — not individually pinned down; the dispatcher services all of them through the same generic shape). A sibling handler shows a 2nd, CONDITIONAL threshold: 0x64 (100) if a helper-returned flag equals 1, else the same 0x32 (50).

The real EEPROM signature scheme, also confirmed at the disassembly level: after every state-changing write in this region, the code loads and stores a fixed 4-byte pair:

MOV  R12,#0x5A55
MOV  R13,#0xA5AA
MOV  [R0],R12
MOV  [R0+#0x0002],R13

0x5A55 XOR 0xA5AA == 0xFFFF exactly — a classic magic-value + bitwise-inverted-magic-value pair, the real, concrete implementation behind the already-known strings "TheftProtection_Eeprom_Init(): Signature invalid."/"...cannot be verified".

Honest scope: this is a real, disassembly-confirmed FACTORY/DEVELOPMENT diagnostic interface for directly overriding TheftProtection's internal counters/timers/state — genuinely useful new knowledge about this ECU's internals, but NOT the same code path as a real end user's VCDS-style Login/SecurityAccess PIN entry and comparison. That live check was not located.

The dispatcher's own caller — 7 real techniques tried, all negative; a confirmed wall

Traced one level further back and found a whole FAMILY of small, per-override handler functions (each setting one internal constant/flag, then calling 1 of 2 shared apply-helpers) — clearly the real per-command handler family the override strings describe. Then tried, in order, to find what CALLS into this family:

  1. Literal jump-table search (consecutive handler addresses as adjacent 16-bit words, any order) — zero hits.
  2. Backward disassembly for a CMP+JMPR compare-and-branch switch chain — found MORE independent tiny handler functions, no dispatch chain.
  3. Word-address hypothesis (byte_offset/2 table entries) — zero hits.
  4. Low-byte-only table hypothesis — zero hits.
  5. A blind full-file linear-sweep disassembly, grep for every CALLI the disassembler decoded — found a promising dense cluster, tested and REFUTED: disassembling backward shows it's really a 16-bit data table (a PWM/backlight calibration curve, matching this file's own frontIllum_printEepromInformation strings), and the linear sweep had desynced upstream, decoding misaligned data bytes as plausible-looking CALLI mnemonics. A real, general lesson: a full-file blind sweep is not trustworthy on this architecture without first knowing real code/data boundaries — verify any full-sweep finding by disassembling backward from it first.
  6. Order-independent literal-address search — every raw occurrence of 16 handler addresses, scanned for any 64-byte window containing 3+ distinct ones (tolerant of table reordering) — zero clusters anywhere in the 884,736-byte file. 6 of the 16 handler addresses have ZERO raw byte occurrences anywhere in the file except their own code location — not low, exactly zero. This closes the "literal address table" hypothesis completely, in any ordering.

Conclusion, definitively closed for this investigation: whatever selects these handlers is either computed via runtime arithmetic (a base plus a value derived from an incoming diagnostic request's own sub-function byte — information this project doesn't have) or reached via code not located in this disassembly. A real recursive-descent disassembler with symbolic register tracking would be the honest next step — substantial new tooling, not a quick follow-up search.

TpPvVerifyPin — the real APPS-side PIN-check code, located (strings), and the vehicle-pairing design

Confirmed, this project's own FHDD6.FLI (85,658,916 bytes) contains TpPvVerifyPin verbatim, at file offset 82,555,321 (~82.6MB) — inside the already-known 76-85.6MB VxWorks kernel/BSP region (see Firmware Reversing), NOT the load-base-recovered 0-9MB native map/navicore code — real, linked, callable shell/console code, just outside the region this project can currently disassemble (a different linking unit; FHDD6_LOAD_BASE does not apply there, and no separate base for this region was recovered). A wide net of strings within ±15KB of this hit gives a complete, coherent picture:

  • "gsVuCTheftProtectionState : %02X" — the APPS processor mirrors the SAME TheftProtection state already reverse-engineered in GATEWAY.FLI above (VuC = the gateway chip). "LOCKED STATE FOR THEFT PROTECTION RECEIVED. PIN INPUT IS REQUESTED" — the real trigger: when the gateway reports its theft-protection state as locked, APPS asks the user for a PIN. Directly connects the 2 investigations — TpPvVerifyPin IS the real live PIN-check path.
  • "comfortCoding : %d pinVerification : %d Seat target : %d" — one debug line naming both "comfortCoding" (matches GATEWAY.FLI's own t_cc/"ConfortCoding" field above) and "pinVerification" together — independent cross-confirmation of one distributed state machine spanning both chips, via the same MOST-bus "Shadow"/FBlock framework on both sides.
  • The real hash-verification mechanism, named directly: HWInfoGetPinHash() (a REFERENCE hash from "HWInfo" storage) and NFGetNandFlashId() (the unit's own NAND flash chip ID) are both called and logged around the verify path. "entered PIN/Hash v[alid]"/"...sh inval[id]" are the real strings — "Hash valid" is a literal substring of the real "entered PIN/Hash valid" message (matching the community brute-force tool's own paraphrase, see below). HWInfo is the same NOR-flash structured data area already found in the HOST BSP (see HOST BSP and Vehicle Coding).
  • A more specific, real security mechanism: CLUSTER-ID PAIRING. A dense string cluster — cCurrentClusterIdHash=, "hash written in NAND", "ClusterID hash writing in NAND failed", CopyClusterIDFromNANDToNOR(), "Copying ClusterID from NAND to NOR" — shows the unit stores a HASH of the vehicle's INSTRUMENT CLUSTER's own unique ID, synced between NAND and NOR flash. This is the real, concrete implementation of VW Group's documented "component protection" anti-theft design: the head unit is cryptographically bound to a SPECIFIC vehicle's cluster, not just protected by a standalone PIN — moving the radio to a different car (different cluster ID) is what triggers the locked state in the first place, independent of whether the PIN itself is ever guessed.
  • Real evidence of factory/regional default PINs: ENABLE_DEFAULT_PIN_WITH_INITIAL_HASH (a real compile-time flag) and "Initial Hash found, Pin(JapNav) valid" (paired with TpPvJapNavPresent()/TpPvGetUniqueIdentifier()) — a real, build-specific default-PIN-with-pre-known-hash mechanism for at least the Japan-Navigation variant, not decoded further.

Not disassembled: the exact hash algorithm (PIN alone? PIN + NAND ID? PIN + ClusterID hash? some combination?) remains unknown at the byte/instruction level — only the real function/data names and their call relationships were established from strings.

Confirmed live, over a real serial console

A community Windows tool, RNS510_code_finder_ver20.exe/ver22.exe (BASE/Other/rns_code_finders, BASE/), turned out to be a real .NET/C# binary — decompiled cleanly with ilspycmd (pin version 9.1.0.7988; the tool's own latest-version install path is currently broken upstream due to bad NuGet package metadata). A 2nd, independent, Python implementation of the same idea, rns510-code-finder-main/rns510_code_finder.py (github.com/SlashGordon/rns510-code-finder, MIT-licensed), was found separately and names the same real command directly in its own source. Both confirm, from a working tool's own behavior rather than static analysis, that TpPvVerifyPin is a real, LIVE, directly- invocable command in a VxWorks target shell:

The real mechanism: opens a real Windows COM (serial) port at 115200 baud and, for each candidate PIN in a configurable range (default/documented 0-1999 — a 2000-value keyspace, confirmed independently by both tools), writes the literal ASCII line TpPvVerifyPin(<pin>) directly to the port, then watches the response for "Hash valid"/"Hash invalid" (the real, literal substrings of FHDD6.FLI's own "entered PIN/Hash valid" message, above). A separate "version" command's response is parsed for "Made on "/"Kernel:"/"VxWorks"/"tShell" — a real VxWorks target-shell banner, confirming the shell is reachable live over this serial line.

A real, physical wiring diagram was recovered from the .NET tool itself — its "How connect RNS510?" button shows a .resx-embedded PNG (extracted by hand: ilspycmd doesn't decode resx Bitmap blobs, but the base64 blob contains a plain embedded PNG findable by its own file signature). Also saved to the main repo at research/images/rns510_serial_debug_connector.png.

RNS510 26-pin connector to DB9 serial wiring: pin 14 = GND, 2 adjacent pins carry RXD/TXD

Shows a real 26-pin connector, part number N91-10930, wired to a standard DB9: pin 14 = GND (explicitly labeled), 2 adjacent unlabeled pins (same column, one row up) carry RXD/TXD to DB9 pins 2/3 — consistent with the 115200-baud serial config above. The exact numbers of the 2 data pins are inferred from the diagram's own row-numbering convention, not read off an explicit label — worth re-verifying against real hardware before relying on them physically.

A newer build of the .NET tool (v22) adds 2 real operational facts: a startup warning, "Don't use USB TTL interface!" (implies real reliability problems with cheap USB-serial adapters on this port), and "Please wait ~1 min after boot RNS." before the shell responds — the real target shell is not available immediately at power-on.

External validation: a real commercial coding tool

BASE/Other/Setconfig-for-rns510.pdf is a real manual for "SetConfig for RNS510", a commercial third-party coding/diagnostic tool (sold with an FTDI cable). It independently corroborates this whole page from a completely different angle — a working tool's UI against real hardware:

  • Its Coding tab lists "Theft protection" and "PIN verification" as individually togglable, named coding options — almost certainly further offsets in the same SetCoding(0x400,...) byte array documented on the HOST BSP and Vehicle Coding page, alongside the already-known manufacturer/HDD/tuner/DAB/SDARS/MPEG offsets (exact offsets not identified).
  • Its Hardware tab (service-mode only) exposes a real 4-digit PIN code field with its own "Set" button — external confirmation a tool exists that can both read AND WRITE the theft-protection PIN, independent of the serial-shell mechanism above.
  • Its Monitor tab shows a real captured debug log including RTDD_PS_VuC_HB — a real "VuC" (gateway) heartbeat message, independently confirming the "VuC" terminology used throughout this page's own GATEWAY.FLI/TpPvVerifyPin string evidence.

The other North-America / BAP community tools found alongside this

Found in the same batch as the 2 PIN-finder tools, briefly (the real BAP wire protocol they implement — frame framing, CAN IDs, function-ID catalogs — is documented in full on the HOST BSP and Vehicle Coding page): rns510-online-dest-master/rns510-tv-input-master (real, working SocketCAN BAP clients), rns510_dtv-master (a standalone Qt/GStreamer DVB-T player — initially assumed to ride the same BAP mechanism, since corrected: it actually uses its own, much simpler RS232 protocol, see HOST BSP and Vehicle Coding), and rns510_1020_1022_1024 — another real, complete SWL disc, the OLDEST build this project had before the later BASE/ reorganization (see SWL Disc: Boot Chain and Build System for the even-older 900/1020_1022/1024 builds found after that reorganization). Its VERSION.TXT reads (c) Siemens VDO AG Wetzlar — NOT CONTINENTAL Wetzlar like every later disc — a direct, disc-level confirmation of the Siemens-VDO-to-Continental corporate lineage already inferred from the HOST BSP's own embedded copyright string.

Reproducing this work

# C166/ST10 disassembly (GATEWAY.FLI):
perl c166-dis.pl GATEWAY.FLI 0xA6480 0xC80    # the override dispatcher region
# file_offset = segment*0x10000 + offset; AA55AA55 magic at offset 0 is non-code

# .NET decompile (rns_code_finders):
dotnet tool install -g ilspycmd --version 9.1.0.7988
ilspycmd -p -o out/ RNS510_code_finder_ver20.exe

Full narrative and every validation number: research/ swl_5238_reader.py's own module docstring — search for "TheftProtection_* -- a complete, real", "A REAL, WORKING ST10/C166 disassembler", "The string-to-code cross-reference itself", "The real theft-protection PIN verification mechanism -- FOUND", and "proves TpPvVerifyPin is a REAL, directly-callable".

⚠️ **GitHub.com Fallback** ⚠️