To install SSH persistently without hand-editing the live filesystem, I needed to understand how Franklin packages and applies updates. This part covers the FOTA check-in flow, recovery behaviour, and PAC container format.
FOTA check-in
The OTA endpoint follows this pattern:
https://fota.pintracview.com/fota/RG2100/check_update.php?carrier=<CARRIER>&rev=<CURRENTVERSION>&imei=<IMEI>
The device checks in with a carrier, current revision, and IMEI. Supplying an older real revision can return an update. Supplying arbitrary lower numbers does not necessarily work, the revision must be one the server recognises.
Local tooling involved in the update process:
/usr/bin/fota_app
/usr/bin/fota_cli
/usr/bin/packer
How recovery applies updates
Recovery analysis showed several important properties.
First, the update package is not a blind raw flash by default. Recovery uses an Android-style updater-script. The stock delta update mounts volumes, applies patches, extracts selected files, and only writes raw partitions where the script explicitly calls:
write_raw_image(...)
Second, recovery dynamically builds its mount table. On this device:
rootfs -> /system
usrfs -> /data
cachefs -> /cache
modem -> /firmware
Live mounts looked like:
ubi0:rootfs on / type ubifs
ubi0:usrfs on /data type ubifs
ubi0:cachefs on /cache type ubifs
ubi0:systemrw on /systemrw type ubifs
/dev/ubi1_0 on /firmware type ubifs
In recovery, the table was:
0 /system ubifs /dev/ubi1_0
1 /data ubifs /dev/ubi1_1
2 /cache ubifs /dev/ubi1_2
3 /firmware ubifs /dev/ubi2_0
That means an OTA can mount /system and /data and extract a handful of files without replacing the whole root filesystem. This is the property we exploit in Part 6.
Franklin PAC container structure
The RG2100 update package is a Franklin PAC container. Useful local scripts produced during this research:
analysis/tools/split_franklin_pac.sh
analysis/tools/decrypt_recovery_payload.sh
analysis/tools/build_franklin_remote_access_pac.sh
The outer PAC contains:
full_update_all.zip.enc
enc_sign.bin
model
fti_sw_ver
encrypted header
encrypted title
The encrypted payload decrypts to a tar containing:
full_update_all.zip
fti_sw_ver
filesize
zip_sign.bin
Decryption constants
Recovery decrypts with AES-256-CBC using constants from:
/etc/init.d/find_recovery_partitions.sh
The key and IV:
KEY = 41676d364459766a6175374463764e7179677a525644343034656f6f57614f64
IV = 7a5138366e6f35344d7a4d74436d3265
The original script had a trailing CR/LF-looking extra byte pair on the IV string. OpenSSL prints:
hex string is too long, ignoring excess
Using the first 16 bytes of the IV is sufficient.
Signatures
Two signatures matter:
enc_sign.bin = RSA/SHA256 signature of full_update_all.zip.enc
zip_sign.bin = RSA/SHA256 signature of full_update_all.zip
Both are produced with the private key embedded in recovery:
/res/mepyek
In the extracted recovery filesystem:
analysis/recovery/mtd25/extracted/106018209/rootfs/res/mepyek
The signing commands:
openssl dgst -sha256 -sign mepyek \
-out zip_sign.bin \
full_update_all.zip
openssl dgst -sha256 -sign mepyek \
-out enc_sign.bin \
full_update_all.zip.enc
The web updater validates enc_sign.bin before accepting the upload. Recovery validates zip_sign.bin after decrypting and untarring the inner payload.
If the private signing key is extractable from the device, and on this firmware it is, the signature is not a meaningful trust boundary against someone who already has filesystem access.
Continue to Part 6: Building an SSH OTA.