exbyte-dev/zltrouter

GitHub: exbyte-dev/zltrouter

zltrouter 是一款通过逆向 Web UI JSON API 来替代浏览器界面的 ZTE NV8645 4G 路由器命令行管理工具。

Stars: 2 | Forks: 0

# zlt 一个用于 **MTN ZLT T10D MAX** (ZTE NV8645 CPE) 4G 路由器的小巧、快速的 CLI 工具,让你无需打开缓慢的 Web UI。它直接通过局域网与设备自身的 JSON API (`reqproc/proc_get` / `reqproc/proc_post`) 进行通信。 已针对固件 `CPE_NV8645_230A_E_QX_CAN-P42U17-20250703` 进行实时验证。 ## 概述 - 无需登录即可读取信号/网络状态 (`zlt status`, `zlt get ...`)。 - 完全像 Web UI 那样登录 (`zlt login`),并带有防止触发路由器登录锁定机制的安全防护。 - 读取和修改网络模式 / bearer preference (`zlt net get`, `zlt net set`)。 - 为上述未作为第一类支持的操作提供原生透传 (`zlt get`, `zlt post`)。 - 配置和 session 缓存位于你的主目录下(XDG 路径),因此一旦安装,该命令可在任何目录下运行。 ## 与其他 ZLT/ZTE 设备的兼容性 构建并针对一台设备进行了实时验证:**MTN ZLT T10D MAX**,这是一台 ZTE NV8645 CPE(`cr_version: CPE_NV8645_230A_E_QX_CAN-P42U17-20250703`,在其自身的 `config.js` 中 `DEVICE: "ufi"`)。底层的 `reqproc/proc_get` + `reqproc/proc_post` API、`goformId=LOGIN` 加盐 nonce SHA-256 密码方案以及 `CSRFToken`/`get_token` 机制,在许多贴牌的 4G/LTE CPE 和 MiFi 路由器(其他 ZLT 品牌设备,以及其他运营商对相同 ZTE 硬件的重新贴牌产品)所使用的更广泛的 ZTE "reqproc" 固件系列中是通用的,因此 `zlt` 很有可能会在没有改动或只有微小改动的情况下,在类似设备上连接、登录并读取状态。 话虽如此,不要假设字段级别的细节能原封不动地适用: - 即使在同一设备系列中,保存已配置网络模式的确切 `proc_get` 键也**不**一致:请参阅下文的“回读已配置的模式”,其中该设备需要与参考实现自身的 JS 所建议的不同的键 (`net_select`)。 - Session 处理可能因固件构建版本而异:该设备通过 `random` cookie 进行身份验证;其他 ZTE 变体(例如,根据开发期间用作参考的社区文档,一些 Safaricom 品牌的 ZTE M30S Pro 设备)改为将 session 绑定到客户端的 IP,而根本不使用 cookie。如果移植到另一台设备,在假设 `zlt` 的基于 cookie 的 session 逻辑可以直接使用之前,请验证哪种模型适用。 - 即使在名义上相同的硬件上,`BearerPreference` 值、状态键名和锁定阈值 (`MAX_LOGIN_COUNT`/`login_lock_time`) 也可能因固件版本而异。 如果你要在不同的 ZLT/ZTE 路由器上尝试此操作,请在运行 `zlt login` 之前从只读命令(`zlt status`,`zlt get `)开始。它们不需要身份验证,并且会快速显示 API 结构是否匹配。 ## 安装 ``` ./install.sh ``` 这会在 `.venv/` 处创建一个项目本地的 virtualenv,以可编辑模式将 `zlt` 安装到其中,并创建符号链接 `~/.local/bin/zlt` -> `.venv/bin/zlt`。 如果 `~/.local/bin` 尚未在你的 `PATH` 中,`install.sh` 会提示你添加: ``` export PATH="$HOME/.local/bin:$PATH" # e.g. in ~/.zshrc or ~/.bashrc ``` 然后初始化你的配置(提示输入路由器管理员密码,通过 `chmod 600` 写入 `~/.config/zlt/config`): ``` zlt init-config # host/username default to # http://192.168.0.1 / admin zlt init-config --host http://192.168.8.1 --username admin ``` 如果你宁愿手动编写(或者在开发期间使用项目本地的 `.env`;切勿提交真实的机密信息),请参阅 `.env.example` 了解文件格式。 ### 配置解析顺序 1. 环境变量 `ZLT_HOST`, `ZLT_USERNAME`, `ZLT_PASSWORD`。 2. `$XDG_CONFIG_HOME/zlt/config` (默认为 `~/.config/zlt/config`)。 3. 如果存在,则是项目本地的 `./.env`。 默认值:`ZLT_HOST=http://192.168.0.1`, `ZLT_USERNAME=admin`。`ZLT_PASSWORD` 仅在需要登录的命令中才是必需的。 Session 缓存(已通过身份验证的 cookie):`$XDG_STATE_HOME/zlt/session.json`(默认为 `~/.local/state/zlt/session.json`),通过 `chmod 600` 写入。 ## 命令参考 | 命令 | 需要认证? | 描述 | |---|---|---| | `zlt status` | 尽力而为 | 显示信号/网络状态。尝试登录以获取完整详细信息(增加 RSRP、band、SNR);如果未配置密码或登录失败,则回退到开放子集。 | | `zlt net get` | 是 | 显示路由器配置的网络模式,映射到一个友好的名称(`auto`, `lte`, `4g3g`, `wcdma`, `gsm`)。 | | `zlt net set ` | 是 | 设置网络模式。`` 是 `auto \| lte \| 4g \| 4g3g \| wcdma \| 3g \| gsm \| 2g` 中的一个。验证 POST 结果,然后重新读取以确认更改已生效。 | | `zlt get [cmd ...]` | 否 | 原生 `proc_get` 透传:美观地打印设备支持的任何键的 JSON 响应。 | | `zlt post [key=val ...]` | 是 | 原生 `proc_post` 透传:确保拥有 session,附加最新的 CSRF token,并打印 JSON 响应。 | | `zlt login` | 是 | 强制进行全新登录,打印锁定前剩余的尝试次数,并缓存 session cookie。 | | `zlt init-config` | 否 | 以交互方式写入 `~/.config/zlt/config` (`chmod 600`)。Flags:`--host`, `--username`;密码通过提示输入(隐藏输入)。 | | `zlt --version` | 否 | 打印已安装的版本。 | 如果没有有效的缓存 session (`ensure_session()`),每个经过身份验证的命令都会先透明地进行登录;并且如果路由器在请求期间报告身份验证失败,每个写入操作都会使用全新的登录重试一次。 ## 发现的 API 参考 源自设备自身提供的 JavaScript (`/js/service.js`, `/js/config/ufi/config.js`) 并已针对真实设备进行了确认。 ### Endpoints ### 登录(确切的方案,经过端到端实时验证) ``` 1. GET proc_get?isTest=false&cmd=get_random_login -> {"random_login": ""} 2. username = Base64( plaintext_username ) password = Base64( sha256_hex( random_login + plaintext_password ) ) token = GET proc_get?isTest=false&cmd=get_token (raw value; empty is valid pre-login) 3. POST proc_post: isTest=false goformId=LOGIN username= password= CSRFToken= ``` - `sha256_hex` 是一个全小写的十六进制摘要;然后整个十六进制*字符串*被 Base64 编码(而不是原始的摘要字节)。 - 成功:`result == "0"`(全新登录)或 `result == "4"`(已登录)。两者都算作已通过身份验证,并且 session cookie 会被缓存。 - 任何其他的 `result` 都意味着登录被拒绝(密码错误等),并会引发 `LoginError`。 ### CSRF token ``` GET proc_get?isTest=false&cmd=get_token -> {"token": ""} (or {"get_token": ""}) ``` - 在每个 POST 中**原封不动**地用作 `CSRFToken` 字段(无哈希处理)。 - 在登录前为空(`""`)是有效的,并且被 `LOGIN` POST 本身接受;一旦提供了 session cookie,就会出现一个非空的值,并且会在每次后续写入之前获取最新值。 ### 网络模式 写入:`POST goformId=SET_BEARER_PREFERENCE&BearerPreference=`,成功则为 `result == "success"`。 | CLI 模式 | `BearerPreference` 值 | Web UI 标签 | |---|---|---| | `auto` | `NETWORK_auto` | Automatic | | `lte`, `4g` | `Only_LTE` | 4G Only | | `4g3g` | `TD_W_LTE` | 4G/3G Only | | `wcdma`, `3g` | `TD_W` | 3G Only | | `gsm`, `2g` | `Only_GSM` | 2G Only | 所有五个值都已针对真实设备进行了实时验证(不仅仅是读取 config JS 得出)。请注意 `wcdma`/`3g` 映射到的是 `TD_W`,**而**不是 `Only_WCDMA`。 #### 回读已配置的模式:重要的、已更正的发现 `zlt net get` / `zlt net set` 查询 `NET_KEYS = ["current_network_mode", "net_select_mode", "m_netselect_save", "net_select"]` 并通过**优先**检查 `net_select` 来解析配置的值,仅当 `net_select` 为空时,才回退到 `net_select_mode`,然后再回退到 `m_netselect_save`(为了与其他固件构建版本向前兼容)。如果你要将其移植到不同的 ZTE/ZLT 固件,在相信回退顺序之前,请验证这些键中究竟哪一个在你的设备上是被填充的。 ### 状态 / 信号键 - **开放(无需登录):** `network_type` (LTE/WCDMA/GSM), `rssi` (dBm), `signalbar` (0-5), `lte_rsrq` (dB), `lte_pci`, `ppp_status`。 - **需认证(登录前为空):** `lte_rsrp` (dBm), `lte_band`, `lte_snr` (dB)。 - `zlt status` 无条件地请求开放集合,并额外请求认证专用集合(首先尝试登录),如果未配置密码或登录失败,则回退到仅包含开放集合的视图并附带提示。 ### 安全 / 锁定键 - `psw_fail_num_str`:锁定前**剩余的尝试次数**(不是失败计数器)。如果响应为空,默认为 `5` (`MAX_LOGIN_COUNT`)。 - `login_lock_time`:尝试次数耗尽后的锁定持续时间(以秒为单位)。如果响应为空,默认为 `300`。 - **防护:** 在任何登录尝试之前,`zlt` 会读取这两个键,如果剩余尝试次数 `< 2`,则拒绝继续执行 (`LockedOut`),打印出当前状态并指向 Web UI 进行重置。绝不会盲目猜测或重试任何密码:编码是精确的,因此正确的登录会在第一次尝试时成功。 ### 身份验证失败重试(写入) `zlt post` / `net set` 首先确保拥有 session (`ensure_session()`:仅在当前的 `get_token` 返回为空时才登录)。如果后续写入的 `result` 匹配尽力而为的标记集 (`no_session`, `session_error`, `need_login`, `not_login`, `-1`),客户端将重新登录一次并重试写入;如果第二次失败则抛出异常。这些标记仅仅是兜底机制。主要的“我是否已通过身份验证”的检查始终是 `token() != ""`。 ## 安全注意事项 - 在默认情况下,如果连续 **5** 次登录失败 (`MAX_LOGIN_COUNT`),路由器会锁定登录 **300s** (`login_lock_time`)。 - 如果 `psw_fail_num_str` 报告的剩余尝试次数少于 2 次,`zlt` 会拒绝尝试登录,以避免成为触发锁定的原因。 - 如果任何实时命令报告剩余尝试次数很少,请**停止**操作,并首先通过路由器的 Web UI 登录以重置计数器,然后再使用 `zlt` 重试。 - 发现过程(读取 `service.js`/`config.js`)本质上是只读的;此 CLI 中的每一次写入操作仅发生在显式命令 (`net set`, `post`, `login`) 时。 ## 手动实时验证清单 在局域网内手动针对真实路由器运行这些命令(不属于自动化测试套件的一部分,因为后者会模拟所有 HTTP 请求): 1. `zlt status`:确认报告的网络类型 / 信号条 / RSSI 与路由器 Web UI 显示的内容相匹配。 2. `zlt net get`:确认它报告的是 Web UI 中当前配置的模式。 3. 先执行 `zlt net set lte`,然后再执行 `zlt net get`:确认模式完成 `lte` / `Only_LTE` 的往返转换,然后执行 `zlt net set auto` 恢复默认值 (`NETWORK_auto`)。 ## 开发 ``` .venv/bin/pytest -v ``` 所有 HTTP 请求都在测试中进行了模拟(通过 `responses`);没有测试与真实设备进行通信。
标签:4G/5G, API交互, Docker 部署, 云资产清单, 文档结构分析, 网络运维, 路由器管理, 逆向工具, 逆向工程