Part 6: Building a Signed SSH OTA Package

The current RG2100 firmware did not ship with a ready-to-run SSH daemon. The goal here was a small, signed OTA that installs Dropbear and a systemd service, without flashing boot, modem, or recovery partitions.

Borrowing Dropbear from older firmware

An older R717 firmware included Dropbear and a compatible glibc runtime. The useful files were copied from:

R717F21.FR.1311_ota_update_all_sm/ota_update_all/system

The working runtime set:

dropbearmulti
dropbear_rsa_host_key
ld-2.22.so
libc-2.22.so
libcrypt-2.22.so
libnsl-2.22.so
libnss_compat-2.22.so
libnss_files-2.22.so
libutil-2.22.so
libz.so.1.2.8

Required symlinks at runtime:

dropbear -> dropbearmulti
dropbearkey -> dropbearmulti
ld-linux.so.3 -> ld-2.22.so
libc.so.6 -> libc-2.22.so
libcrypt.so.1 -> libcrypt-2.22.so
libnsl.so.1 -> libnsl-2.22.so
libnss_compat.so.2 -> libnss_compat-2.22.so
libnss_files.so.2 -> libnss_files-2.22.so
libutil.so.1 -> libutil-2.22.so
libz.so.1 -> libz.so.1.2.8

Manually, this worked as a systemd service. Modern OpenSSH rejects old ssh-rsa host keys by default, so the client needs:

ssh -o HostKeyAlgorithms=+ssh-rsa [email protected]

The original R717 root password hash cracked to frk9x07. For the final neutral package below, I used a different password:

franklinroot

The SHA-256 crypt hash used in /etc/shadow:

$5$FwRoot9x07$PSqLxmZNQiVergpuBSwjjOsBpsgjoMzVpw1rQjHvsg7

Package layout

The first test package used obviously lab-specific names. Once the mechanism was proven, I built a cleaner version with no lab references in paths, filenames, scripts, UI messages, or package strings.

Final neutral package paths:

/data/dropbear
/etc/systemd/system/dropbear.service
/tmp/dropbear-postinst.sh
/tmp/dropbear-remove.sh
/etc/shadow.fw-pre-ssh

The updater-script

The install updater-script is intentionally minimal:

ui_print("Installing remote access service");
mount("ubifs", "UBI", "system", "/system", "");
mount("ubifs", "UBI", "usrfs", "/data", "");
package_extract_file("tmp/dropbear-postinst.sh", "/tmp/dropbear-postinst.sh");
package_extract_dir("system", "/system");
package_extract_dir("data", "/data");
set_perm(0, 0, 0755, "/tmp/dropbear-postinst.sh");
run_program("/bin/sh", "/tmp/dropbear-postinst.sh");
ui_print("Remote access service installed");
unmount("/data");
unmount("/system");

The package contains only:

META-INF/com/android/metadata
META-INF/com/google/android/update-binary
META-INF/com/google/android/updater-script
data/dropbear/dropbear_rsa_host_key
data/dropbear/dropbearmulti
data/dropbear/ld-2.22.so
data/dropbear/libc-2.22.so
data/dropbear/libcrypt-2.22.so
data/dropbear/libnsl-2.22.so
data/dropbear/libnss_compat-2.22.so
data/dropbear/libnss_files-2.22.so
data/dropbear/libutil-2.22.so
data/dropbear/libz.so.1.2.8
system/etc/systemd/system/dropbear.service
tmp/dropbear-postinst.sh

There are no destructive updater commands:

write_raw_image
format
delete
delete_recursive
apply_patch
block_image_update

Post-install script behaviour

The postinstall script:

  • backs up /system/etc/shadow to /system/etc/shadow.fw-pre-ssh
  • changes only the root password hash
  • creates Dropbear symlinks under /data/dropbear
  • enables /system/etc/systemd/system/dropbear.service
  • removes older generic *dropbear.service units and old /data/*/dropbear runtime directories to avoid port conflicts
  • runs sync

It does not flash boot, modem, recovery, firmware, or any raw partition.

The AppleDouble trap

The first signed test package passed web UI validation and rebooted into recovery, but the update failed before the updater-script ran.

Recovery logs showed:

._full_update_all.zip
full_update_all.zip
._fti_sw_ver
fti_sw_ver
._filesize
filesize
._zip_sign.bin
zip_sign.bin
There is no decrypted zip file. Exit PrepareRecovery.

macOS had added AppleDouble sidecar files to the inner tar. Franklin’s recovery script does roughly:

zipfile=`find /cache -name "*.zip"`
if [[ -f "$zipfile" ]]; then
    ...
fi

With both ._full_update_all.zip and full_update_all.zip present, zipfile becomes a multi-line value and the -f test fails.

The fix was to disable resource-fork/AppleDouble generation during packaging:

export COPYFILE_DISABLE=1
xattr -cr "$workdir"

After rebuilding, the decrypted inner tar contained only:

full_update_all.zip
fti_sw_ver
filesize
zip_sign.bin

Final packages

The final neutral install PAC:

analysis/franklin-remote-access-ota/RG2100.FR.SVC.0001.pac

The removal PAC:

analysis/franklin-remote-access-ota-remove/RG2100.FR.SVC.REMOVE.0001.pac

SHA-256 hashes:

bf45fcd18786bfd189316b21a35cc158863d8e46f4bcf06eab2342d9517b9817  RG2100.FR.SVC.0001.pac
33943626b6d41811b24fb3edeb84526a1533358817d6268220e1fd1208df7977  RG2100.FR.SVC.REMOVE.0001.pac

Audit checks before flashing:

old-name-scan-install: no matches
old-name-scan-remove: no matches
destructive-check: no risky updater commands
enc signature: Verified OK
zip signature: OK

Successful install

The proof-of-concept package using the same install mechanism applied successfully. The device rebooted into recovery, showed the progress bar, rebooted back into normal mode, and came up with Dropbear active:

dropbear.service enabled
dropbear.service active
tcp 0.0.0.0:22 LISTEN

Connect with:

ssh -o HostKeyAlgorithms=+ssh-rsa [email protected]

Password:

franklinroot

What actually changed

The successful recovery log from the proof-of-concept package showed extraction of only the intended files. The final neutral package uses different names and paths, but the same narrow update mechanism. The audited file set:

/system/etc/systemd/system/dropbear.service
/data/dropbear/dropbear_rsa_host_key
/data/dropbear/dropbearmulti
/data/dropbear/ld-2.22.so
/data/dropbear/libc-2.22.so
/data/dropbear/libcrypt-2.22.so
/data/dropbear/libnsl-2.22.so
/data/dropbear/libnss_compat-2.22.so
/data/dropbear/libnss_files-2.22.so
/data/dropbear/libutil-2.22.so
/data/dropbear/libz.so.1.2.8

The web UI can be overdramatic about update status. The important evidence is the recovery log and the resulting filesystem state. In this case, the package did not replace the full system, it merged a small number of files into the existing rootfs and usrfs volumes.

Continue to Part 7: Security and Wrap-Up.

Leave a Reply