Skip to content

Add RBR850 support#15

Open
chrisbraddock wants to merge 1 commit into
Fysac:masterfrom
chrisbraddock:rbr850-support
Open

Add RBR850 support#15
chrisbraddock wants to merge 1 commit into
Fysac:masterfrom
chrisbraddock:rbr850-support

Conversation

@chrisbraddock

Copy link
Copy Markdown

Summary

Adds support for the NETGEAR Orbi RBR850 (firmware V7.2.x). The RBR850 uses a different cipher than the existing musl/uclibc XOR scheme, reversed from FUN_001392ec in /usr/sbin/httpd:

  • 131096-byte fixed file: 24-byte big-endian header + 131072-byte body
  • Entire buffer is DES-ECB encrypted in 8-byte blocks, different key per block
  • Keys derived from a rolling 32-bit counter seeded at 0x72677456 ("Ntgr" LE + 8) and the constant "NtgrBak\0", incremented by 8 per block
  • Header is itself ciphertext, so Decrypt dispatches by file size rather than plaintext magic
  • 16-bit IP-style checksum in the header, self-validating to 0xffff over the plaintext

Closes #10.

Changes

  • cfg/rbr850.go (new): cipher, key derivation, header codec, checksum, decrypt/encrypt helpers. Uses crypto/des from the stdlib; no new module deps.
  • cfg/cfg.go: new RngRBR850 sentinel (follows the loose Rng convention from overrides.go); file-size gate in Decrypt; cipher branch in Encrypt.
  • Metadata.RawPrefix []byte (new, json:",omitempty"): preserves the 32-byte firmware-internal prefix that sits at the start of the decrypted RBR850 body so re-encrypted backups round-trip cleanly. Zero-impact on other devices (omitempty).
  • ToJSON bugfix: switch bytes.Split on =bytes.SplitN(..., 2). The old behavior errored on any config value containing = (which exists in the wild — e.g., opaque token strings). Single-line change, benefits all devices.
  • cfg/rbr850_test.go (new): known-answer tests for block-key derivation, header codec round-trip, checksum self-validation, full encrypt/decrypt round-trip, and dispatch-by-size.
  • cfg/testdata/RBR850/ (new): synthesized fixture (plausible NVRAM-shaped keys with fake values, no real-device secrets) following the existing RBR50/RBR760 layout.
  • cfg_test.go: RBR850 added to the devices slice; TestChecksum skips RBR850 since it splices plaintext Header.Bytes() into the ciphertext — incompatible with an encrypted-header cipher.
  • README.md: new "RBR850" subsection under "Encryption Scheme" documenting the DES-rolling-key scheme and the raw_prefix metadata field.

Test plan

  • go test ./... — all existing devices (RBR50, RBR760) plus RBR850 pass
  • go vet ./... and gofmt -l . clean
  • Round-trip test (TestEncryptDecrypt) exercises RBR850 via the shared devices table
  • Verified on a real RBR850 backup (fw V7.2.8.5): decrypts to 1757 config entries, decrypt → encrypt → decrypt produces byte-identical JSON output

Notes

  • Re-encrypted .cfg files are ~99.99% byte-identical to the device's original (3 plaintext bytes differ: 2 in the header checksum because the 3rd byte at the k=v/0xff-fill boundary shifts by 1 NUL — the device uses its own padding convention rather than the existing chunkSize-aligned one). The JSON, the config map, and the cleaned body bytes all round-trip losslessly. Device should accept the re-encrypted file since the 1-byte shift is in the NAND-erase padding region.
  • HW-version → magic derivation (FUN_00138ee4) isn't reversed; users re-encrypting should keep real_magic from the decrypted JSON unchanged.

🤖 Generated with Claude Code

The RBR850 (firmware V7.2.x) uses a different scheme than the existing
musl/uclibc XOR cipher, reversed from FUN_001392ec in /usr/sbin/httpd.
The .cfg file is always exactly 131096 bytes: a 24-byte big-endian
header followed by a 131072-byte body. The whole buffer (header
included) is DES-ECB encrypted in 8-byte blocks with a different key
per block, derived from a rolling 32-bit counter seeded at 0x72677456
("Ntgr" LE + 8) and the string "NtgrBak\0", incremented by 8 per block.
The header carries a 16-bit IP-style checksum self-validating to 0xffff
over the plaintext, a HW-version-derived magic, and a version field.

Since the header is ciphertext we can't peek at a plaintext magic to
dispatch. Decrypt routes to the new path by file size.

The decrypted body has a 32-byte firmware-internal prefix and trailing
0xff NAND-erase padding. The prefix is stashed in a new optional
Metadata.RawPrefix field so re-encrypted backups are byte-compatible
with the device; the suffix is reconstructed from the fixed file size.
Existing decrypted.json fixtures for other devices parse unchanged
since RawPrefix is json:",omitempty".

Also fixes a latent bug in ToJSON: bytes.Split on '=' errored on any
value containing '='. Switch to SplitN(..., 2) so the key is the prefix
up to the first '=' and the rest is the value. Closes Fysac#10.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@twolven

twolven commented Apr 26, 2026

Copy link
Copy Markdown

Confirmed working on RBR840 (firmware V7.2.x). File is the same 131096-byte DES-ECB format — decrypted cleanly to 1700+ config entries including DHCP reservations, port forwarding rules, and DDNS credentials. The dispatch-by-file-size approach handles the RBR840 without any additional changes needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RBR850 Decrypt Issue

2 participants