206 lines
7.0 KiB
Python
206 lines
7.0 KiB
Python
"""
|
|
Debug Flash — chạy đầy đủ 3 bước flash và log chi tiết từng bước.
|
|
|
|
Usage:
|
|
python debug_full.py <IP> <firmware.bin>
|
|
python debug_full.py 192.168.11.17 V3.0.6p5.bin
|
|
"""
|
|
|
|
import sys
|
|
import os
|
|
import requests
|
|
import re
|
|
|
|
|
|
def debug_flash(ip, firmware_path, username="root", password=""):
|
|
base_url = f"http://{ip}"
|
|
session = requests.Session()
|
|
|
|
print(f"{'='*65}")
|
|
print(f" Full Flash Debug — {ip}")
|
|
print(f" Firmware: {os.path.basename(firmware_path)}")
|
|
print(f" Size: {os.path.getsize(firmware_path) / 1024 / 1024:.2f} MB")
|
|
print(f"{'='*65}")
|
|
|
|
# ═══════════════════════════════════
|
|
# STEP 1: Login
|
|
# ═══════════════════════════════════
|
|
print(f"\n{'─'*65}")
|
|
print(f" STEP 1: Login")
|
|
print(f"{'─'*65}")
|
|
|
|
login_url = f"{base_url}/cgi-bin/luci"
|
|
|
|
print(f" → GET {login_url}")
|
|
try:
|
|
r = session.get(login_url, timeout=10)
|
|
print(f" Status: {r.status_code}")
|
|
except Exception as e:
|
|
print(f" ❌ Cannot connect: {e}")
|
|
return
|
|
|
|
# Detect form fields
|
|
if 'name="luci_username"' in r.text:
|
|
login_data = {"luci_username": username, "luci_password": password}
|
|
print(f" Fields: luci_username / luci_password")
|
|
else:
|
|
login_data = {"username": username, "password": password}
|
|
print(f" Fields: username / password")
|
|
|
|
print(f"\n → POST {login_url}")
|
|
print(f" Data: {login_data}")
|
|
resp = session.post(login_url, data=login_data,
|
|
timeout=10, allow_redirects=True)
|
|
|
|
print(f" Status: {resp.status_code}")
|
|
print(f" Cookies: {dict(session.cookies)}")
|
|
|
|
# Extract stok
|
|
stok = None
|
|
for source in [resp.url, resp.text]:
|
|
match = re.search(r";stok=([a-f0-9]+)", source)
|
|
if match:
|
|
stok = match.group(1)
|
|
break
|
|
if not stok:
|
|
for hist in resp.history:
|
|
match = re.search(r";stok=([a-f0-9]+)",
|
|
hist.headers.get("Location", ""))
|
|
if match:
|
|
stok = match.group(1)
|
|
break
|
|
|
|
print(f" stok: {stok}")
|
|
has_cookie = "sysauth" in str(session.cookies)
|
|
print(f" sysauth: {'✅ YES' if has_cookie else '❌ NO'}")
|
|
|
|
if not stok and not has_cookie:
|
|
print(f"\n ❌ LOGIN FAILED — no stok, no sysauth cookie")
|
|
return
|
|
|
|
print(f"\n ✅ LOGIN OK")
|
|
|
|
# Build flash URL
|
|
if stok:
|
|
flash_url = f"{base_url}/cgi-bin/luci/;stok={stok}/admin/system/flashops"
|
|
else:
|
|
flash_url = f"{base_url}/cgi-bin/luci/admin/system/flashops"
|
|
|
|
# ═══════════════════════════════════
|
|
# STEP 2: Upload firmware
|
|
# ═══════════════════════════════════
|
|
print(f"\n{'─'*65}")
|
|
print(f" STEP 2: Upload firmware")
|
|
print(f"{'─'*65}")
|
|
|
|
print(f" → POST {flash_url}")
|
|
print(f" File field: image")
|
|
print(f" File name: {os.path.basename(firmware_path)}")
|
|
print(f" keep: NOT sent (unchecked)")
|
|
print(f" Uploading...")
|
|
|
|
filename = os.path.basename(firmware_path)
|
|
with open(firmware_path, "rb") as f:
|
|
resp = session.post(
|
|
flash_url,
|
|
data={}, # No keep = uncheck Keep Settings
|
|
files={"image": (filename, f, "application/octet-stream")},
|
|
timeout=300,
|
|
)
|
|
|
|
print(f" Status: {resp.status_code}")
|
|
print(f" URL: {resp.url}")
|
|
|
|
# Check response content
|
|
resp_lower = resp.text.lower()
|
|
has_verify = "verify" in resp_lower
|
|
has_proceed = "proceed" in resp_lower
|
|
has_checksum = "checksum" in resp_lower
|
|
has_image_form = 'name="image"' in resp.text and 'type="file"' in resp.text
|
|
|
|
print(f"\n Response analysis:")
|
|
print(f" Has 'verify': {'✅' if has_verify else '❌'}")
|
|
print(f" Has 'proceed': {'✅' if has_proceed else '❌'}")
|
|
print(f" Has 'checksum': {'✅' if has_checksum else '❌'}")
|
|
print(f" Has flash form: {'⚠️ (upload ignored!)' if has_image_form else '✅ (not flash form)'}")
|
|
|
|
# Extract checksum from verification page
|
|
checksum = re.search(r'Checksum:\s*<code>([a-f0-9]+)</code>', resp.text)
|
|
size_info = re.search(r'Size:\s*([\d.]+\s*MB)', resp.text)
|
|
if checksum:
|
|
print(f" Checksum: {checksum.group(1)}")
|
|
if size_info:
|
|
print(f" Size: {size_info.group(1)}")
|
|
|
|
# Check for keep settings status
|
|
if "will be erased" in resp.text:
|
|
print(f" Config: Will be ERASED ✅ (keep=off)")
|
|
elif "will be kept" in resp.text:
|
|
print(f" Config: Will be KEPT ⚠️ (keep=on)")
|
|
|
|
if not has_verify or not has_proceed:
|
|
print(f"\n ❌ Did NOT get verification page!")
|
|
# Show cleaned response
|
|
text = re.sub(r'<[^>]+>', ' ', resp.text)
|
|
text = re.sub(r'\s+', ' ', text).strip()
|
|
print(f" Response: {text[:500]}")
|
|
return
|
|
|
|
# Show the Proceed form
|
|
print(f"\n Proceed form (from HTML):")
|
|
forms = re.findall(r'<form[^>]*>(.*?)</form>', resp.text, re.DOTALL)
|
|
for i, form_body in enumerate(forms):
|
|
if 'value="Proceed"' in form_body or 'step' in form_body:
|
|
inputs = re.findall(r'<input[^>]*/?\s*>', form_body)
|
|
for inp in inputs:
|
|
print(f" {inp.strip()}")
|
|
|
|
print(f"\n ✅ UPLOAD OK — Verification page received")
|
|
|
|
# ═══════════════════════════════════
|
|
# STEP 3: Proceed (confirm flash)
|
|
# ═══════════════════════════════════
|
|
print(f"\n{'─'*65}")
|
|
print(f" STEP 3: Proceed (confirm flash)")
|
|
print(f"{'─'*65}")
|
|
|
|
confirm_data = {
|
|
"step": "2",
|
|
"keep": "",
|
|
}
|
|
|
|
print(f" → POST {flash_url}")
|
|
print(f" Data: {confirm_data}")
|
|
|
|
try:
|
|
resp = session.post(flash_url, data=confirm_data, timeout=300)
|
|
print(f" Status: {resp.status_code}")
|
|
# Show cleaned response
|
|
text = re.sub(r'<[^>]+>', ' ', resp.text)
|
|
text = re.sub(r'\s+', ' ', text).strip()
|
|
print(f" Response: {text[:300]}")
|
|
except requests.ConnectionError:
|
|
print(f" ✅ Connection dropped — device is REBOOTING!")
|
|
except requests.ReadTimeout:
|
|
print(f" ✅ Timeout — device is REBOOTING!")
|
|
|
|
print(f"\n{'='*65}")
|
|
print(f" 🎉 FLASH COMPLETE")
|
|
print(f"{'='*65}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
if len(sys.argv) < 3:
|
|
print("Usage: python debug_full.py <IP> <firmware.bin>")
|
|
print("Example: python debug_full.py 192.168.11.17 V3.0.6p5.bin")
|
|
sys.exit(1)
|
|
|
|
ip = sys.argv[1]
|
|
fw = sys.argv[2]
|
|
|
|
if not os.path.exists(fw):
|
|
print(f"❌ File not found: {fw}")
|
|
sys.exit(1)
|
|
|
|
debug_flash(ip, fw)
|