diff --git a/.gitignore b/.gitignore index 8ab4b1bd..8c7d5974 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,4 @@ Thumbs.db # Logs *.log +/gcc_win diff --git a/LICENSE b/LICENSE index aca08f19..48360598 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Daggy +Copyright (c) 2024 dag Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index b5bb14a2..49c25236 100644 --- a/README.md +++ b/README.md @@ -36,11 +36,28 @@ A terminal-styled custom firmware for the **Orbic RCL400** hotspot with hacking - **Port Scanner**: Scan IPs for open ports - **Firewall Manager**: Block/unblock IPs with iptables +### š GPS Tracker +- Get GPS from connected devices via browser geolocation +- Chrome workaround for HTTP geolocation +- Cell tower backup (MCC/MNC/LAC/CID) +- JSON API for programmatic access + +### š¶ Wardriver +- Scan WiFi networks with GPS coordinates +- Wigle-compatible CSV export +- Continuous loop mode (scans every 5 seconds) +- Real-time GPS display on wardrive page + +### š File Explorer +- Browse `/data/` directory +- Download wardrive logs and other files +- Delete files with confirmation + ## Requirements -- Orbic RCL400 hotspot with root access (via exploit) -- ARM cross-compiler (`arm-linux-gnueabi-gcc`) -- Python 3 for deployment scripts +- Orbic RCL400 hotspot +- ARM cross-compiler (included in `gcc/` folder) +- Python 3 with `requests` module ## Building @@ -53,14 +70,23 @@ This produces `orbic_app` - a statically-linked ARM binary. ## Deploying -1. Connect to the hotspot's WiFi -2. Run the deployment script: +### Step 1: Enable Root Shell + +```powershell +python enable_shell.py YOUR_ADMIN_PASSWORD +``` + +This exploits the Orbic web API to open a shell on port 24. + +### Step 2: Deploy Firmware ```powershell python deploy_base64.py ``` -This uploads the binary to the device via the diagnostic port (24) and executes it. +This uploads and installs DagShell with **boot persistence**. + +The firmware is deployed to `/data/orbic_app` and auto-starts on reboot. ## Accessing @@ -85,5 +111,5 @@ MIT License - See LICENSE file. ## Credits -- **Daggy** - Creator +- **dag** - Creator - Built with ā¤ļø and `gcc` diff --git a/dagshell_boot.sh b/dagshell_boot.sh new file mode 100644 index 00000000..7b565f2d --- /dev/null +++ b/dagshell_boot.sh @@ -0,0 +1,13 @@ +#!/bin/sh +# DagShell Autostart - Robust +# Note: Use /bin/sh for this device (Orbic RCL400/MDM9207) + +# 1. Enable Shell (Port 24) +# Run in background, don't block +# We use busybox explicitly to avoid path issues +busybox nc -ll -p 24 -e /bin/sh & + +# 2. Start DagShell (Port 8081) +# Give network a moment, then launch +sleep 5 +/data/orbic_app & diff --git a/deploy.py b/deploy.py new file mode 100644 index 00000000..f5de80b0 --- /dev/null +++ b/deploy.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +""" +DagShell ADB Deployment Script +Deploys orbic_app to the Orbic RCL400 via ADB +""" + +import subprocess +import sys +import os + +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +BINARY_PATH = os.path.join(SCRIPT_DIR, "orbic_fw_c", "orbic_app") +REMOTE_PATH = "/data/local/tmp/orbic_app" +PORT = 8081 + +# Use local platform-tools +ADB = os.path.join(SCRIPT_DIR, "platform-tools", "adb.exe") +if not os.path.exists(ADB): + ADB = "adb" # Fallback to PATH + +def run_cmd(cmd, check=True): + """Run a command and return output""" + print(f"$ {' '.join(cmd)}") + result = subprocess.run(cmd, capture_output=True, text=True) + if check and result.returncode != 0: + print(f"Error: {result.stderr}") + return None + return result.stdout.strip() + +def main(): + # Check if binary exists + if not os.path.exists(BINARY_PATH): + print(f"Error: Binary not found at {BINARY_PATH}") + print("Run build.ps1 first!") + sys.exit(1) + + print(f"Binary: {BINARY_PATH} ({os.path.getsize(BINARY_PATH):,} bytes)") + + # Check ADB connection + print("\n[1/5] Checking ADB connection...") + devices = run_cmd([ADB, "devices"]) + if not devices or "device" not in devices.split('\n')[-1]: + print("Error: No ADB device connected!") + print("Make sure:") + print(" 1. USB cable is connected") + print(" 2. ADB is enabled on device") + print(" 3. Run: adb devices") + sys.exit(1) + print("Device connected!") + + # Kill old process + print("\n[2/5] Killing old orbic_app process...") + run_cmd([ADB, "shell", "pkill", "-f", "orbic_app"], check=False) + + # Push binary + print("\n[3/5] Pushing binary to device...") + result = run_cmd([ADB, "push", BINARY_PATH, REMOTE_PATH]) + if result is None: + sys.exit(1) + print(result) + + # Make executable + print("\n[4/5] Setting permissions...") + run_cmd([ADB, "shell", "chmod", "+x", REMOTE_PATH]) + + # Run in background + print("\n[5/5] Starting orbic_app...") + # Use nohup to keep it running after adb disconnects + run_cmd([ADB, "shell", f"nohup {REMOTE_PATH} > /dev/null 2>&1 &"], check=False) + + print("\n" + "="*50) + print("SUCCESS! DagShell is running!") + print(f"Access at: http://192.168.1.1:{PORT}/") + print("="*50) + +if __name__ == "__main__": + main() diff --git a/deploy_base64.py b/deploy_base64.py index 36ffdb22..38b61c7b 100644 --- a/deploy_base64.py +++ b/deploy_base64.py @@ -2,21 +2,26 @@ import socket import time import base64 -import os +import argparse +from pathlib import Path -FIRMWARE_DIR = r"d:\Scripts\orbic\orbic_fw_c" +# Auto-detect firmware directory relative to this script +SCRIPT_DIR = Path(__file__).parent.absolute() +FIRMWARE_DIR = SCRIPT_DIR / "orbic_fw_c" FIRMWARE_FILE = "orbic_app" -FILESYSTEM_PATH = os.path.join(FIRMWARE_DIR, FIRMWARE_FILE) -REMOTE_FILE_B64 = "/tmp/orbic_app.b64" -REMOTE_FILE = "/tmp/orbic_app" +BOOT_SCRIPT_FILE = "dagshell_boot.sh" -TARGET_IP = '192.168.1.1' -TARGET_PORT = 24 +FILESYSTEM_PATH = FIRMWARE_DIR / FIRMWARE_FILE +BOOT_SCRIPT_PATH = SCRIPT_DIR / BOOT_SCRIPT_FILE + +# Persistent locations on device (survives reboot) +REMOTE_FILE_B64 = "/data/orbic_app.b64" +REMOTE_FILE = "/data/orbic_app" +REMOTE_BOOT_SCRIPT = "/data/dagshell_boot.sh" def send_cmd(sock, cmd, wait=0.2): sock.sendall(cmd.encode() + b"\n") time.sleep(wait) - # Don't read recv every time to speed up, just let it buffer def read_response(sock): try: @@ -25,56 +30,116 @@ def read_response(sock): except: return "" -def deploy(): - # 1. Read and Encode +def setup_autostart(sock): + """ + Sets up persistent autostart by hijacking the USB composition script. + Target: /data/usb/boot_hsusb_composition + This file is executed by /etc/init.d/usb on boot for MDM9207. + """ + print("Setting up persistence (USB Hijack)...") + + # 1. Clean up old attempts (optional but good practice) + send_cmd(sock, "sed -i '/dagshell_boot.sh/d' /data/dnsmasq.conf", wait=0.5) + + # 2. Transfer boot script (Fixed Shebang: /bin/sh) + print(f"Transferring {BOOT_SCRIPT_FILE}...") + with open(BOOT_SCRIPT_PATH, "r") as f: + boot_script_content = f.read() + + # Ensure correct shebang on device + send_cmd(sock, f"echo '#!/bin/sh' > {REMOTE_BOOT_SCRIPT}") + for line in boot_script_content.splitlines(): + if line.startswith("#!"): continue + safe_line = line.replace("'", "'\\''") + send_cmd(sock, f"echo '{safe_line}' >> {REMOTE_BOOT_SCRIPT}", wait=0.05) + + send_cmd(sock, f"chmod +x {REMOTE_BOOT_SCRIPT}") + + # 3. Hijack USB Composition + WRAPPER_PATH = "/data/usb/boot_hsusb_composition" + ORIGINAL_SCRIPT = "/sbin/usb/compositions/PRJ_SLT779_9025" + + print("Hijacking USB composition script...") + + # Create wrapper that runs our script THEN configures USB + # We write directly to the persistent location (overwriting symlink/file) + # Using 'sh' explicitly to bypass execution restrictions + + # Remove existing first (to break symlink if present) + send_cmd(sock, f"rm {WRAPPER_PATH}") + + send_cmd(sock, f"echo '#!/bin/sh' > {WRAPPER_PATH}") + send_cmd(sock, f"echo '# DagShell Wrapper' >> {WRAPPER_PATH}") + send_cmd(sock, f"echo 'sh {REMOTE_BOOT_SCRIPT} &' >> {WRAPPER_PATH}") + send_cmd(sock, f"echo '# Chainload original' >> {WRAPPER_PATH}") + send_cmd(sock, f"echo '{ORIGINAL_SCRIPT} \"$@\"' >> {WRAPPER_PATH}") + + send_cmd(sock, f"chmod +x {WRAPPER_PATH}") + + print("Persistence configured! (USB Hook applied)") + +def deploy(target_ip, target_port): + print(f"Deploying DagShell firmware...") + + # Check if boot script exists locally + if not BOOT_SCRIPT_PATH.exists(): + print(f"ERROR: {BOOT_SCRIPT_FILE} not found!") + return + + # 1. Read and Encode Firmware print(f"Reading {FILESYSTEM_PATH}...") - with open(FILESYSTEM_PATH, "rb") as f: - data = f.read() + try: + with open(FILESYSTEM_PATH, "rb") as f: + data = f.read() + except FileNotFoundError: + print("Error: compiled firmware not found. Run build.ps1 first!") + return b64_data = base64.b64encode(data).decode('utf-8') print(f"Encoded size: {len(b64_data)} bytes") - chunks = [b64_data[i:i+1000] for i in range(0, len(b64_data), 1000)] - print(f"Chunks: {len(chunks)}") try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.settimeout(10) - s.connect((TARGET_IP, TARGET_PORT)) + s.connect((target_ip, target_port)) print("Connected.") # Kill old process - print("Killing old process...") send_cmd(s, "pkill -f orbic_app") time.sleep(1) - # Clean up - send_cmd(s, f"rm {REMOTE_FILE} {REMOTE_FILE_B64}") - # Send chunks - print("Sending chunks...") + print("Sending firmware chunks...") + send_cmd(s, f"echo -n '' > {REMOTE_FILE_B64}") # Clear file for i, chunk in enumerate(chunks): - if i % 10 == 0: - print(f"Sending chunk {i}/{len(chunks)}...") - # echo "chunk" >> file - send_cmd(s, f"echo -n '{chunk}' >> {REMOTE_FILE_B64}", wait=0.05) + if i % 50 == 0: print(f" {i}/{len(chunks)}", end="\r") + send_cmd(s, f"echo -n '{chunk}' >> {REMOTE_FILE_B64}", wait=0.02) + print("") - print("Decoding...") - send_cmd(s, f"base64 -d {REMOTE_FILE_B64} > {REMOTE_FILE}", wait=1) + print("Decoding & Installing...") + send_cmd(s, f"base64 -d {REMOTE_FILE_B64} > {REMOTE_FILE}", wait=2) + send_cmd(s, f"chmod +x {REMOTE_FILE}") - print("Chmoding...") - send_cmd(s, f"chmod +x {REMOTE_FILE}", wait=0.5) + # Setup autostart with the shell script + setup_autostart(s) - print("Running...") - send_cmd(s, REMOTE_FILE, wait=1) - - time.sleep(2) - print("--- OUTPUT ---") - print(read_response(s)) - print("--------------") + print("\n------------------------------------------------") + print("Deployment Complete!") + print(f"1. Firmware at: {REMOTE_FILE}") + print(f"2. Boot script at: {REMOTE_BOOT_SCRIPT}") + print("3. Autostart hooked in /etc/init.d/misc-daemon") + print("------------------------------------------------") + print("Running test instance...") + send_cmd(s, f"{REMOTE_BOOT_SCRIPT} &") except Exception as e: print(f"Error: {e}") if __name__ == "__main__": - deploy() + parser = argparse.ArgumentParser(description="Deploy DagShell firmware to Orbic device via base64") + parser.add_argument("--target-ip", default="192.168.1.1", help="Orbic device IP (default: 192.168.1.1)") + parser.add_argument("--target-port", type=int, default=24, help="Orbic shell port (default: 24)") + args = parser.parse_args() + + deploy(args.target_ip, args.target_port) diff --git a/deploy_net.py b/deploy_net.py index f2635dfb..ff1c8852 100644 --- a/deploy_net.py +++ b/deploy_net.py @@ -5,21 +5,23 @@ import socket import time import os +import argparse +from pathlib import Path -HOST = '192.168.1.143' # Your PC IP on the RNDIS interface -PORT = 8000 -FIRMWARE_DIR = r"d:\Scripts\orbic\orbic_fw_c" +# Auto-detect firmware directory relative to this script +SCRIPT_DIR = Path(__file__).parent.absolute() +FIRMWARE_DIR = SCRIPT_DIR / "orbic_fw_c" FIRMWARE_FILE = "orbic_app" -REMOTE_FILE = "/tmp/orbic_app" -TARGET_IP = '192.168.1.1' -TARGET_PORT = 24 +# Persistent locations on device (survives reboot) +REMOTE_FILE = "/data/orbic_app" +STARTUP_SCRIPT = "/data/dagshell_autostart.sh" -def start_server(): +def start_server(host_port): os.chdir(FIRMWARE_DIR) handler = http.server.SimpleHTTPRequestHandler - with socketserver.TCPServer(("", PORT), handler) as httpd: - print(f"Serving at port {PORT}") + with socketserver.TCPServer(("", host_port), handler) as httpd: + print(f"Serving at port {host_port}") httpd.serve_forever() def send_cmd(sock, cmd): @@ -34,35 +36,75 @@ def send_cmd(sock, cmd): print("Timeout receiving response (might be expected for blocking commands)") return "" -def deploy(): +def setup_autostart(sock): + """Create autostart script to run firmware on boot""" + print("Setting up autostart...") + + # Create the startup script + send_cmd(sock, f"echo '#!/system/bin/sh' > {STARTUP_SCRIPT}") + send_cmd(sock, f"echo '# DagShell Autostart Script' >> {STARTUP_SCRIPT}") + send_cmd(sock, f"echo 'sleep 15' >> {STARTUP_SCRIPT}") # Wait for system to stabilize + send_cmd(sock, f"echo '{REMOTE_FILE} &' >> {STARTUP_SCRIPT}") + send_cmd(sock, f"chmod +x {STARTUP_SCRIPT}") + + # Try multiple autostart methods (device-specific) + # Method 1: Add to rc.local if it exists + send_cmd(sock, f"grep -q '{STARTUP_SCRIPT}' /data/rc.local 2>/dev/null || echo '{STARTUP_SCRIPT}' >> /data/rc.local") + + # Method 2: Create init.d script if supported + send_cmd(sock, f"mkdir -p /data/local/init.d") + send_cmd(sock, f"echo '#!/system/bin/sh' > /data/local/init.d/99dagshell") + send_cmd(sock, f"echo '{STARTUP_SCRIPT}' >> /data/local/init.d/99dagshell") + send_cmd(sock, f"chmod +x /data/local/init.d/99dagshell") + + print("Autostart configured!") + +def deploy(host_ip, host_port, target_ip, target_port): # Start HTTP Server in background - thread = threading.Thread(target=start_server) + thread = threading.Thread(target=start_server, args=(host_port,)) thread.daemon = True thread.start() - time.sleep(2) # Wait for startup + time.sleep(2) # Wait for startup try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.settimeout(10) - s.connect((TARGET_IP, TARGET_PORT)) + s.connect((target_ip, target_port)) print("Connected to Orbic shell!") - # 1. Download - cmd_download = f"wget -O {REMOTE_FILE} http://{HOST}:{PORT}/{FIRMWARE_FILE}" + # Kill old process first + print("Killing old process...") + send_cmd(s, "pkill -f orbic_app") + + # 1. Download to persistent location + cmd_download = f"wget -O {REMOTE_FILE} http://{host_ip}:{host_port}/{FIRMWARE_FILE}" send_cmd(s, cmd_download) # 2. Chmod send_cmd(s, f"chmod +x {REMOTE_FILE}") - # 3. Run + # 3. Setup autostart for persistence across reboots + setup_autostart(s) + + # 4. Run in background print("Running firmware...") - output = send_cmd(s, REMOTE_FILE) + output = send_cmd(s, f"{REMOTE_FILE} &") print("--- FIRMWARE OUTPUT ---") print(output) print("-----------------------") + print("\nā Firmware deployed to /data/ (persistent)") + print("ā Autostart configured for boot persistence") except Exception as e: print(f"Deploy Error: {e}") if __name__ == "__main__": - deploy() + parser = argparse.ArgumentParser(description="Deploy DagShell firmware to Orbic device via HTTP download") + parser.add_argument("--host-ip", default="192.168.1.143", help="Your PC's IP on RNDIS interface (default: 192.168.1.143)") + parser.add_argument("--host-port", type=int, default=8000, help="HTTP server port (default: 8000)") + parser.add_argument("--target-ip", default="192.168.1.1", help="Orbic device IP (default: 192.168.1.1)") + parser.add_argument("--target-port", type=int, default=24, help="Orbic shell port (default: 24)") + args = parser.parse_args() + + deploy(args.host_ip, args.host_port, args.target_ip, args.target_port) + diff --git a/docs/assets/screenshot-tools.png b/docs/assets/screenshot-tools.png new file mode 100644 index 00000000..289f90a0 Binary files /dev/null and b/docs/assets/screenshot-tools.png differ diff --git a/docs/css/style.css b/docs/css/style.css new file mode 100644 index 00000000..be3bbda2 --- /dev/null +++ b/docs/css/style.css @@ -0,0 +1,479 @@ +/* DagShell Docs - Terminal Theme */ + +@import url('https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;700&display=swap'); + +:root { + --bg-dark: #0a0a0a; + --bg-card: rgba(0, 20, 0, 0.8); + --green-primary: #00ff00; + --green-dark: #003300; + --cyan: #00ffff; + --yellow: #ffff00; + --red: #ff4444; + --text-muted: #888888; +} + +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html { + scroll-behavior: smooth; +} + +body { + font-family: 'Fira Code', Consolas, 'Courier New', monospace; + background: var(--bg-dark); + color: var(--green-primary); + min-height: 100vh; + line-height: 1.6; +} + +/* Scanline Effect */ +.scanlines { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + background: repeating-linear-gradient( + 0deg, + rgba(0, 0, 0, 0.1), + rgba(0, 0, 0, 0.1) 1px, + transparent 1px, + transparent 2px + ); + z-index: 1000; +} + +/* Container */ +.container { + max-width: 1000px; + margin: 0 auto; + padding: 20px; + position: relative; +} + +/* Header */ +.header { + text-align: center; + padding: 40px 0; + border-bottom: 1px solid var(--green-primary); + margin-bottom: 30px; +} + +.ascii-logo { + font-size: 12px; + line-height: 1.15; + color: var(--green-primary); + text-shadow: 0 0 10px var(--green-primary), 0 0 20px var(--green-primary); + white-space: pre; + display: inline-block; +} + +.tagline { + color: var(--cyan); + font-size: 14px; + margin-top: 15px; + text-shadow: 0 0 5px var(--cyan); +} + +/* Navigation */ +.nav { + display: flex; + justify-content: center; + gap: 8px; + flex-wrap: wrap; + margin-bottom: 30px; + padding: 15px; + background: rgba(0, 255, 0, 0.05); + border: 1px solid var(--green-dark); +} + +.nav a { + color: var(--green-primary); + text-decoration: none; + padding: 10px 20px; + border: 1px solid var(--green-dark); + transition: all 0.3s; + font-size: 14px; +} + +.nav a:hover { + background: var(--green-dark); + box-shadow: 0 0 15px var(--green-primary); +} + +.nav a.active { + background: var(--green-primary); + color: #000; + font-weight: bold; + box-shadow: 0 0 20px var(--green-primary); +} + +/* Cards */ +.card { + background: var(--bg-card); + border: 1px solid var(--green-primary); + padding: 25px; + margin-bottom: 25px; + box-shadow: 0 0 15px rgba(0, 255, 0, 0.2); +} + +.card h1, .card h2, .card h3 { + color: var(--cyan); + text-shadow: 0 0 5px var(--cyan); + margin-bottom: 15px; +} + +.card h1 { + font-size: 28px; + text-align: center; +} + +.card h2 { + font-size: 22px; + border-bottom: 1px solid var(--green-dark); + padding-bottom: 10px; + margin-top: 20px; +} + +.card h3 { + font-size: 18px; +} + +.card p { + margin-bottom: 15px; +} + +/* Hero Section */ +.hero { + text-align: center; + padding: 60px 20px; +} + +.hero h1 { + font-size: 36px; + margin-bottom: 20px; + text-shadow: 0 0 20px var(--cyan); +} + +.hero p { + font-size: 16px; + color: var(--text-muted); + max-width: 600px; + margin: 0 auto 30px; +} + +/* Buttons */ +.btn { + display: inline-block; + padding: 12px 30px; + background: var(--green-dark); + color: var(--green-primary); + border: 1px solid var(--green-primary); + text-decoration: none; + transition: all 0.3s; + margin: 5px; +} + +.btn:hover { + background: var(--green-primary); + color: #000; + box-shadow: 0 0 20px var(--green-primary); +} + +.btn-primary { + background: var(--green-primary); + color: #000; + font-weight: bold; +} + +.btn-primary:hover { + background: var(--cyan); + border-color: var(--cyan); + box-shadow: 0 0 20px var(--cyan); +} + +/* Feature Grid */ +.feature-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 20px; + margin: 30px 0; +} + +.feature-item { + background: rgba(0, 40, 0, 0.5); + border-left: 3px solid var(--cyan); + padding: 20px; + transition: all 0.3s; +} + +.feature-item:hover { + background: rgba(0, 60, 0, 0.5); + box-shadow: 0 0 15px rgba(0, 255, 255, 0.3); +} + +.feature-item h3 { + margin-bottom: 10px; +} + +.feature-item p { + color: var(--text-muted); + font-size: 14px; +} + +.feature-icon { + font-size: 24px; + margin-bottom: 10px; +} + +/* Code Blocks */ +pre, code { + font-family: 'Fira Code', monospace; +} + +pre { + background: #000; + border-left: 3px solid var(--green-primary); + padding: 15px; + overflow-x: auto; + margin: 15px 0; + font-size: 13px; +} + +code { + background: rgba(0, 255, 0, 0.1); + padding: 2px 6px; + border-radius: 3px; +} + +pre code { + background: none; + padding: 0; +} + +/* Steps */ +.steps { + counter-reset: step; +} + +.step { + position: relative; + padding-left: 50px; + margin-bottom: 25px; +} + +.step::before { + counter-increment: step; + content: counter(step); + position: absolute; + left: 0; + top: 0; + width: 35px; + height: 35px; + background: var(--green-dark); + border: 2px solid var(--green-primary); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-weight: bold; + color: var(--green-primary); +} + +.step h3 { + color: var(--green-primary); +} + +/* FAQ */ +.faq-item { + background: rgba(0, 30, 0, 0.5); + border: 1px solid var(--green-dark); + margin-bottom: 15px; + overflow: hidden; +} + +.faq-question { + padding: 15px 20px; + cursor: pointer; + display: flex; + justify-content: space-between; + align-items: center; + transition: all 0.3s; +} + +.faq-question:hover { + background: rgba(0, 50, 0, 0.5); +} + +.faq-question h3 { + margin: 0; + font-size: 16px; +} + +.faq-toggle { + font-size: 20px; + color: var(--green-primary); + transition: transform 0.3s; +} + +.faq-item.open .faq-toggle { + transform: rotate(45deg); +} + +.faq-answer { + max-height: 0; + overflow: hidden; + transition: max-height 0.3s ease-out; + background: rgba(0, 20, 0, 0.5); +} + +.faq-item.open .faq-answer { + max-height: 500px; +} + +.faq-answer p { + padding: 15px 20px; + margin: 0; + color: var(--text-muted); +} + +/* Screenshot */ +.screenshot { + border: 2px solid var(--green-primary); + box-shadow: 0 0 30px rgba(0, 255, 0, 0.3); + max-width: 100%; + display: block; + margin: 20px auto; +} + +/* Lists */ +ul, ol { + margin: 15px 0 15px 25px; +} + +li { + margin-bottom: 8px; +} + +/* Links */ +a { + color: var(--cyan); + text-decoration: none; +} + +a:hover { + text-shadow: 0 0 10px var(--cyan); +} + +/* Tables */ +table { + width: 100%; + border-collapse: collapse; + margin: 15px 0; +} + +th, td { + padding: 12px; + text-align: left; + border: 1px solid var(--green-dark); +} + +th { + background: rgba(0, 255, 0, 0.1); + color: var(--cyan); +} + +tr:hover { + background: rgba(0, 255, 0, 0.05); +} + +/* Footer */ +.footer { + text-align: center; + padding: 30px; + border-top: 1px solid var(--green-dark); + margin-top: 40px; + color: var(--text-muted); + font-size: 13px; +} + +.footer a { + color: var(--green-primary); +} + +/* Blink Animation */ +.blink { + animation: blink 1s infinite; +} + +@keyframes blink { + 0%, 50% { opacity: 1; } + 51%, 100% { opacity: 0; } +} + +/* Glow Animation */ +.glow { + animation: glow 2s ease-in-out infinite alternate; +} + +@keyframes glow { + from { + text-shadow: 0 0 10px var(--green-primary); + } + to { + text-shadow: 0 0 20px var(--green-primary), 0 0 30px var(--green-primary); + } +} + +/* Warning/Info Boxes */ +.alert { + padding: 15px 20px; + margin: 15px 0; + border-left: 4px solid; +} + +.alert-info { + background: rgba(0, 255, 255, 0.1); + border-color: var(--cyan); +} + +.alert-warning { + background: rgba(255, 255, 0, 0.1); + border-color: var(--yellow); +} + +.alert-danger { + background: rgba(255, 68, 68, 0.1); + border-color: var(--red); +} + +/* Responsive */ +@media (max-width: 768px) { + .ascii-logo { + font-size: 8px; + } + + .nav { + flex-direction: column; + align-items: center; + } + + .nav a { + width: 100%; + text-align: center; + } + + .hero h1 { + font-size: 24px; + } + + .container { + padding: 15px; + } +} diff --git a/docs/faq.html b/docs/faq.html new file mode 100644 index 00000000..d5ea6c75 --- /dev/null +++ b/docs/faq.html @@ -0,0 +1,272 @@ + + + +
+ + + ++ ____ ____ _ _ _ +| _ \ __ _ __ / ___|| |__ ___| | | +| | | |/ _` |/ _\___ \| '_ \ / _ \ | | +| |_| | (_| | (_| |__) | | | | __/ | | +|____/ \__,_|\__, |___/|_| |_|\___|_|_| + |___/ ++
[ Orbic RCL400 Custom Firmware ]
+DagShell is a custom firmware/control panel for the Orbic RCL400 LTE hotspot. It provides a + web-based interface with hacking tools, privacy features, and direct modem access that aren't + available in the stock firmware.
+Yes! DagShell is open source software released under the MIT License. You can view, modify, and + distribute the source code freely.
+DagShell is deployed to /data/orbic_app and configured to auto-start on boot, so it
+ survives reboots. The shell enabler (enable_shell.py) doesn't modify permanent
+ storage - you can always reboot to get a fresh state if needed.
DagShell is intended for educational purposes and use on devices you own. Some features like TTL + modification or MAC spoofing may violate your carrier's terms of service. Always comply with + local laws and regulations.
+DagShell comes with enable_shell.py which exploits the Orbic's web API to open a
+ root shell on port 24. No external exploit is required - just run the script with your admin
+ password.
DagShell is specifically designed for the Orbic RCL400. Other devices have different modem
+ interfaces and may not be compatible. The modem communication specifically uses
+ /dev/smd8 which is Qualcomm-specific.
+
The ARM cross-compiler is included in the gcc/ folder. Just run
+ .\build.ps1 to compile. The firmware is compiled statically to avoid library
+ dependencies.
+
TTL (Time To Live) is a packet header value that decreases by 1 at each network hop. Carriers can + detect tethered devices because their packets have a lower TTL than direct phone traffic. + Setting TTL to 65 makes hotspot traffic appear to originate from the device itself.
+The IMSI catcher detector provides basic monitoring of cell tower information. It can detect + obvious anomalies like unknown operators or sudden signal changes. However, sophisticated IMSI + catchers that properly impersonate legitimate towers may not be detected. Consider it an + awareness tool rather than a security guarantee.
+Currently, DagShell only supports sending SMS. To read received messages, use the stock Orbic
+ portal at http://192.168.1.1/common/shortmessage.html.
No, iptables rules are stored in memory and will be lost on reboot. If you need persistent rules, + you would need to add them to a startup script on the device.
+Run python enable_shell.py YOUR_ADMIN_PASSWORD first. This opens port 24.
If the script shows "Login retcode = 102", double-check your password.
+If you see connection errors, try rebooting the Orbic and running the script immediately after. +
+DagShell is single-threaded, so it can only handle one request at a time. If the modem is busy or + slow to respond to AT commands, the page load will be delayed. Try refreshing the page or + waiting for the current operation to complete.
+The modem port (/dev/smd8) can only be accessed by one process at a time. If you see
+ this error, another process (possibly the stock firmware's modem handler) is using the port. The
+ firmware has built-in retry logic, but if the problem persists, try rebooting the device.
Connect to port 24 and run: pkill -f orbic_app. To permanently disable auto-start,
+ delete /data/dagshell_autostart.sh. Or just reboot to stop the current session.
Contributions are welcome! Check out the GitHub repository to submit issues, feature requests, or pull requests. +
+Great! Open an issue on GitHub describing your idea. Some things to keep in mind: the firmware is + designed to be lightweight and run on a resource-constrained embedded device. Features that + require large libraries may not be feasible.
++ ____ ____ _ _ _ +| _ \ __ _ __ / ___|| |__ ___| | | +| | | |/ _` |/ _\___ \| '_ \ / _ \ | | +| |_| | (_| | (_| |__) | | | | __/ | | +|____/ \__,_|\__, |___/|_| |_|\___|_|_| + |___/ ++
[ Orbic RCL400 Custom Firmware ]
+The home page provides an overview of your device and direct modem access.
+ +Displays how long the device has been running since last boot (in seconds).
+ +Send raw AT commands directly to the modem. Examples:
+| Command | +Description | +
|---|---|
ATI |
+ Display modem information | +
AT+CSQ |
+ Check signal strength | +
AT+COPS? |
+ Current network operator | +
AT+CREG? |
+ Network registration status | +
AT+CGSN |
+ Get IMEI number | +
AT+CIMI |
+ Get IMSI number | +
View detailed network information and manage TTL settings.
+ +Set a custom TTL (Time To Live) value for outgoing packets. This helps mask hotspot traffic from carriers + that detect tethering.
+Shows all network interfaces with their IP addresses, similar to running ip addr.
Displays the ARP table showing all devices connected to your hotspot, including their MAC and IP + addresses.
+ +Active TCP/UDP connections through the device, showing source, destination, and connection state.
+Protect your identity and block unwanted content.
+ +Change your device's MAC address to avoid tracking. Enter a new MAC in the format
+ XX:XX:XX:XX:XX:XX.
+
Enable DNS-level ad blocking by redirecting known ad domains to 0.0.0.0 via the hosts file.
+ This affects all devices connected to the hotspot.
Currently blocks:
+doubleclick.netThe hosts file can be extended by adding more domains to /data/hosts.
Send text messages using the hotspot's cellular modem.
+ +Enter a phone number (with country code for international) and your message. The firmware uses AT + commands to queue the SMS.
+ +To view received messages, use the stock Orbic portal at:
+http://192.168.1.1/common/shortmessage.html
+
+ Network reconnaissance and defense utilities.
+ +Monitors your cellular connection for signs of fake cell towers (IMSI catchers / Stingrays).
+Displays:
+Scan a target IP address for open ports. Enter the target IP and a comma-separated list of ports to scan. +
+Example: 192.168.1.1 with ports 22,80,443,8080
Uses netcat under the hood for connection testing.
Manage iptables rules through a simple interface.
+Current INPUT chain rules are displayed below the forms.
+Track your location using GPS from connected devices (phone/laptop) via browser geolocation.
+ +Since the Orbic RCL400 doesn't expose its internal GPS, DagShell uses a clever workaround:
+Browser geolocation requires HTTPS. For Chrome to work over HTTP:
+chrome://flagshttp://192.168.1.1:8081When no GPS fix is available, the page displays cell tower information (MCC, MNC, LAC, Cell ID) which can + be used for approximate location via services like OpenCellID.
+ +Access GPS data programmatically via:
+http://192.168.1.1:8081/?cmd=gps_json
+ Returns JSON: {"has_fix":1,"lat":"...","lon":"...","source":"Browser GPS"}
Scan and log WiFi networks in Wigle-compatible CSV format with GPS coordinates.
+ +The Wardrive page displays current GPS coordinates at the top. If no GPS fix is available:
+Performs a one-time WiFi scan using wlan1 and displays discovered networks with BSSID, SSID,
+ RSSI, and encryption type.
Starts continuous scanning in the background (every 5 seconds), logging new networks to a timestamped CSV
+ file in
+ /data/. Each scan uses the current GPS coordinates.
+
Logs are saved in Wigle-compatible format:
+MAC,SSID,AuthMode,FirstSeen,Channel,RSSI,Lat,Lon,Alt,Accuracy,Type
+
+ Browse, download, and delete files from the device's /data/ directory.
Click the "DL" button to download any file from the device (wardrive logs, etc.).
+ +Remove files using the "Del" button. Wardrive CSV files can be deleted immediately; other files will + prompt for confirmation.
+DagShell is a single-threaded HTTP server written in C. It listens on port 8081 and serves dynamically + generated HTML pages.
+ +AT commands are sent through /dev/smd8 (shared memory device). The port is opened with
+ retries to handle busy states.
The statically-linked ARM binary is approximately 200KB, allowing quick transfer over the network.
+ +| Component | +Details | +
|---|---|
| HTTP Port | +8081 | +
| Modem Port | +/dev/smd8 | +
| Binary | +/tmp/orbic_app | +
| Threading | +Single-threaded | +
| Linking | +Static (no libc dependency) | +
+ ____ ____ _ _ _ +| _ \ __ _ __ / ___|| |__ ___| | | +| | | |/ _` |/ _\___ \| '_ \ / _ \ | | +| |_| | (_| | (_| |__) | | | | __/ | | +|____/ \__,_|\__, |___/|_| |_|\___|_|_| + |___/ ++
[ Orbic RCL400 Custom Firmware ]
+A terminal-styled custom firmware for the Orbic RCL400 with hacking tools, privacy features, and full AT + command access.
+DagShell is a custom web-based control panel that runs on your rooted Orbic RCL400 LTE hotspot. It + provides tools and features not available in the stock firmware.
+ +System uptime monitoring and direct AT command interface for modem control.
+View interfaces, connected clients, active connections, and set custom TTL values.
+MAC address spoofing and DNS-level ad blocking to protect your identity.
+Send text messages directly through the hotspot's modem via AT commands.
+Monitor cell tower information to detect potential IMSI catchers.
+Block/unblock IPs and manage iptables rules through a simple interface.
+Get location from connected devices via browser geolocation with cell tower backup.
+Scan and log WiFi networks with GPS coordinates to Wigle-compatible CSV files.
+Browse, download, and delete files from the device storage.
+The firmware features a terminal/hacker aesthetic with ASCII art, scanline effects, and glowing text.
+
+ Your Orbic RCL400 must have root access via an exploit. This unlocks the diagnostic port (24). +
+cd orbic_fw_c
+.\build.ps1
+ python deploy_base64.py
+ Open your browser to: http://192.168.1.1:8081/
++ ____ ____ _ _ _ +| _ \ __ _ __ / ___|| |__ ___| | | +| | | |/ _` |/ _\___ \| '_ \ / _ \ | | +| |_| | (_| | (_| |__) | | | | __/ | | +|____/ \__,_|\__, |___/|_| |_|\___|_|_| + |___/ ++
[ Orbic RCL400 Custom Firmware ]
+arm-none-linux-gnueabihf-gcc (from ARM GNU
+ Toolchain)git clone https://github.com/dagnazty/DagShell.git
+cd DagShell
+ Download the ARM GNU Toolchain from ARM + Developer.
+Make sure arm-none-linux-gnueabihf-gcc is in your PATH, or edit the path in
+ build.ps1.
+
cd orbic_fw_c
+.\build.ps1
+ This produces orbic_app - a statically-linked ARM binary (~200KB).
Deployment requires two steps: enabling the shell, then deploying the firmware.
+ +Run our custom shell enabler script to open port 24 on the device:
+ +Connect via USB (RNDIS) or the Orbic's WiFi network. The device should be at
+ 192.168.1.1.
+
python enable_shell.py YOUR_ADMIN_PASSWORD
+ Replace YOUR_ADMIN_PASSWORD with your Orbic admin password (found on device
+ label or set in web UI).
This script:
+SetRemoteAccessCfg to start a netcat shellOnce the shell is enabled, deploy the DagShell firmware:
+ +python deploy_base64.py
+ The script will:
+/data/orbic_app and
+ configured
+ to auto-start on boot. It will survive reboots!
+ If you prefer to use HTTP download instead of base64 transfer:
+python deploy_net.py --host-ip YOUR_PC_IP
+ Once deployed and running, access DagShell by opening your browser to:
+ +http://192.168.1.1:8081/
+
+ http://192.168.1.1/ (port 80).
+ | URL | +Page | +
|---|---|
/ |
+ Dashboard (Home) | +
/?page=net |
+ Network Analysis | +
/?page=privacy |
+ Privacy Tools | +
/?page=sms |
+ SMS Manager | +
/?page=tools |
+ Hacking Tools | +
The shell isn't enabled yet. Run python enable_shell.py YOUR_PASSWORD first.
If the script shows "Login retcode = 102", double-check your admin password.
+ +If you see "connection closed before message completed":
+Make sure the binary was compiled statically for ARM. Check with file orbic_app - it
+ should
+ show ARM architecture.
Another process may be using /dev/smd8. The firmware will retry automatically, but if
+ persistent, reboot the device.
Verify DagShell is running: connect to port 24 and run ps | grep orbic.
If not running, manually start it: /data/orbic_app &
\xe2\x9c\x93 GPS Fix (%s)
" + "Latitude: %s
" + "Longitude: %s
" + "Updated %ds ago
", + gps_source, gps_lat, gps_lon, age); + } else { + snprintf(buffer, max_len, + "\xe2\x8f\xb3 No GPS Fix
" + "Cell: MCC=%s MNC=%s LAC=%s CID=%s
", + cell_mcc[0] ? cell_mcc : "?", + cell_mnc[0] ? cell_mnc : "?", + cell_lac[0] ? cell_lac : "?", + cell_cid[0] ? cell_cid : "?"); + } +} diff --git a/orbic_fw_c/gps.h b/orbic_fw_c/gps.h new file mode 100644 index 00000000..41cbec45 --- /dev/null +++ b/orbic_fw_c/gps.h @@ -0,0 +1,25 @@ +#ifndef GPS_H +#define GPS_H + +// Initialize GPS +void gps_init(); + +// Poll for updates +void gps_update(); + +// Update cell tower info from modem +void gps_update_cell_info(); + +// Receive GPS coordinates from a connected client browser +void gps_set_client_location(const char *lat, const char *lon); + +// Get current GPS coordinates (returns 0 if fix, -1 if no fix) +int gps_get_coords(char *lat, char *lon, int max_len); + +// Get JSON status +int gps_get_json(char *buffer, int max_len); + +// Get formatted HTML status for display +void gps_get_status_html(char *buffer, int max_len); + +#endif diff --git a/orbic_fw_c/main.c b/orbic_fw_c/main.c index 2a0b47aa..02413805 100644 --- a/orbic_fw_c/main.c +++ b/orbic_fw_c/main.c @@ -10,11 +10,14 @@ #include" - " ____ ____ _ _ _ \n" - "| _ \\ __ _ __ / ___|| |__ ___| | |\n" - "| | | |/ _` |/ _\\\\___ \\| '_ \\ / _ \\ | |\n" - "| |_| | (_| | (_| |__) | | | | __/ | |\n" - "|____/ \\__,_|\\__, |___/|_| |_|\\___|_|_|\n" - " |___/ \n" - "" - "
[ Orbic RCL400 Custom Firmware ]
" - "" + " ____ ____ _ _ _ \n" + "| _ \\ __ _ __ / ___|| |__ ___| | |\n" + "| | | |/ _` |/ _\\\\___ \\| '_ \\ / _ \\ | |\n" + "| |_| | (_| | (_| |__) | | | | __/ | |\n" + "|____/ \\__,_|\\__, |___/|_| |_|\\___|_|_|\n" + " |___/ \n" + "[ Orbic RCL400 Custom Firmware v2.0 ]
System Uptime: %.2f s
" - "%s", at_response); - strncat(body, safe_resp, sizeof(body) - strlen(body) - 1); + // Navigation + o += sprintf(body+o, + "", + strcmp(page,"home")==0?"active":"", strcmp(page,"net")==0?"active":"", + strcmp(page,"privacy")==0?"active":"", strcmp(page,"sms")==0?"active":"", + strcmp(page,"tools")==0?"active":"", strcmp(page,"gps")==0?"active":"", + strcmp(page,"wardrive")==0?"active":"", + strcmp(page,"files")==0?"active":""); + + // --- PAGE LOGIC --- + if (strcmp(page, "home") == 0) { + float up=0; FILE *f=fopen("/proc/uptime","r"); if(f){fscanf(f,"%f",&up);fclose(f);} + o += sprintf(body+o, + "
Uptime: %.2fs
%s
%s
", ttl_msg); - } else if (strcmp(page, "net") == 0) { - // ... (Network Page Code preserved by context match usually, but I must rewrite if I am replacing logic block) - // I will try to be efficient and assume previous logic is handled by Replace chunks if I can, but here I am replacing the whole handle_client struct mostly. - // Actually, to save tokens/errors, I will re-emit the pages since I replaced the start of handle_client. - - // --- NETWORK PAGE --- - char cmd_out[4096]; - char ttl_val[16] = {0}; - // Check for TTL set - char *ttl_ptr = strstr(buffer, "ttl="); - if (ttl_ptr) { - int val = atoi(ttl_ptr + 4); - if (val > 0 && val < 255) { - char ipt_cmd[256]; - system("iptables -t mangle -D POSTROUTING -j TTL --ttl-set 64 2>/dev/null"); - snprintf(ipt_cmd, sizeof(ipt_cmd), "iptables -t mangle -I POSTROUTING 1 -j TTL --ttl-set %d", val); - system(ipt_cmd); - snprintf(cmd_out, sizeof(cmd_out), "Applied TTL: %d", val); - } - } - body_off += snprintf(body + body_off, sizeof(body) - body_off, "%s", cmd_out); - // 2. ARP - run_command("cat /proc/net/arp", cmd_out, sizeof(cmd_out)); - body_off += snprintf(body + body_off, sizeof(body) - body_off, "
%s", cmd_out); - // 3. Netstat - run_command("/bin/netstat -ntu", cmd_out, sizeof(cmd_out)); - body_off += snprintf(body + body_off, sizeof(body) - body_off, "
%s", cmd_out); + // Interfaces + run_command("ip addr show wlan0", cmd_out, sizeof(cmd_out)); + o += sprintf(body+o, "
%s", cmd_out); - } else if (strcmp(page, "privacy") == 0) { - // --- PRIVACY PAGE --- - body_off += snprintf(body + body_off, sizeof(body) - body_off, "
%s", cmd_out); + + // ARP (Clients) - Show all + run_command("cat /proc/net/arp", cmd_out, sizeof(cmd_out)); + o += sprintf(body+o, "
%s", cmd_out); + + // Connections + run_command("netstat -ntu", cmd_out, sizeof(cmd_out)); + o += sprintf(body+o, "
%s
Send and receive text messages
"); - - // Status of send - if (strlen(at_response) > 0) { - body_off += snprintf(body + body_off, sizeof(body) - body_off, - "š„ View Inbox: Use Orbic's web portal at 192.168.1.1
" - "Network reconnaissance and defense
"); - - // === IMSI CATCHER DETECTOR === - body_off += snprintf(body + body_off, sizeof(body) - body_off, - "%s
%s
%s
ā No anomalies detected
" - "COPS: %s\nCREG: %s\nSIG: %s" + "
%s" + "
%s
Scanning %s ports %s...
", scan_ip, scan_ports);
-
- // Scan each port
- char port_buf[16];
- char *p = scan_ports;
- while (*p) {
- int port = 0;
- while (*p >= '0' && *p <= '9') { port = port * 10 + (*p - '0'); p++; }
- if (port > 0 && port < 65536) {
- char cmd[128];
- char result[256];
- snprintf(cmd, sizeof(cmd), "nc -zv -w1 %s %d 2>&1 || echo 'closed'", scan_ip, port);
- run_command(cmd, result, sizeof(result));
- if (strstr(result, "open") || strstr(result, "succeeded")) {
- body_off += snprintf(body + body_off, sizeof(body) - body_off,
- "Port %d: OPEN\n", port);
- }
- }
- if (*p == ',') p++;
- }
- body_off += snprintf(body + body_off, sizeof(body) - body_off, "");
+ // Parse bssid
+ p += 9;
+ char *e = strchr(p, '"');
+ if (e && (e-p) < 20) { strncpy(bssid, p, e-p); bssid[e-p]=0; }
+
+ // Parse ssid
+ char *sp = strstr(p, "\"ssid\":\"");
+ if (sp) {
+ sp += 8;
+ e = strchr(sp, '"');
+ if (e && (e-sp) < 64) { strncpy(ssid, sp, e-sp); ssid[e-sp]=0; }
}
- }
- body_off += snprintf(body + body_off, sizeof(body) - body_off, "ā Blocked %s
", block_ip); + + // Parse rssi + char *rp = strstr(p, "\"rssi\":"); + if (rp) { rssi = atoi(rp + 7); } + + // Parse enc + char *ep = strstr(p, "\"enc\":\""); + if (ep) { + ep += 7; + e = strchr(ep, '"'); + if (e && (e-ep) < 16) { strncpy(enc, ep, e-ep); enc[e-ep]=0; } } + + // Format line + char line[256]; + snprintf(line, sizeof(line), "%-18s | %-30s | %4d | %s\n", + bssid, ssid[0] ? ssid : "(hidden)", rssi, enc); + strcat(res, line); + + p++; // Move past to find next entry } - if (unblock_ip_ptr) { - char unblock_ip[32] = {0}; - char *end = strchr(unblock_ip_ptr + 11, '&'); - if (!end) end = strchr(unblock_ip_ptr + 11, ' '); - if (end) strncpy(unblock_ip, unblock_ip_ptr + 11, end - (unblock_ip_ptr + 11)); - if (strlen(unblock_ip) > 0) { - char cmd[128]; - snprintf(cmd, sizeof(cmd), "iptables -D INPUT -s %s -j DROP 2>/dev/null", unblock_ip); + } + + // Get current GPS for display and logging + char gps_lat[32] = "0", gps_lon[32] = "0"; + gps_update(); + int has_gps = (gps_get_coords(gps_lat, gps_lon, sizeof(gps_lat)) == 0); + + if (strstr(buffer,"action=log")) { + wifi_new_session(); + wifi_log_kml(gps_lat, gps_lon); + strcpy(res,"Logged to new file."); + } + if (strstr(buffer,"action=start")) { wifi_start_wardrive(); strcpy(res,"Loop Started."); } + if (strstr(buffer,"action=stop")) { wifi_stop_wardrive(); strcpy(res,"Loop Stopped."); } + + o += sprintf(body+o, "", + wifi_is_wardriving()?"RUNNING":"STOPPED", + has_gps ? "#0f0" : "#f66", + gps_lat, gps_lon, + has_gps ? "" : "(Set GPS)", + res); + } + + else if (strcmp(page, "files") == 0) { + char delete_msg[256] = ""; + + // Handle delete action + char *del_ptr = strstr(buffer, "delete="); + if (del_ptr) { + char raw_file[256] = {0}, filename[256] = {0}; + char *end = strchr(del_ptr + 7, ' '); + if (!end) end = strchr(del_ptr + 7, '&'); + if (!end) end = del_ptr + 7 + strlen(del_ptr + 7); + if ((end - (del_ptr + 7)) < 255) { + strncpy(raw_file, del_ptr + 7, end - (del_ptr + 7)); + url_decode(filename, raw_file); + // Only allow deleting files in /data/ + if (strncmp(filename, "/data/", 6) == 0 && strlen(filename) > 6) { + char cmd[512]; + snprintf(cmd, sizeof(cmd), "rm -f '%s'", filename); system(cmd); - body_off += snprintf(body + body_off, sizeof(body) - body_off, - "ā Unblocked %s
", unblock_ip); + snprintf(delete_msg, sizeof(delete_msg), "Deleted: %s", filename); } } + } + + o += sprintf(body+o, "Download/delete files from /data/
%s
", delete_msg); + } + + // List files in /data + FILE *ls = popen("ls -la /data/", "r"); + if (ls) { + o += sprintf(body+o, "Error listing directory
"); } - - strncat(body, "