设备:Redmi K70 Pro(manet)
芯片:骁龙 8 Gen 3(SM8650 / pineapple)
系统:HyperOS OS3.0.302.0.WNMCNXM
分析日期:2026-07-11(博客整理:2026-07-17)
打游戏一热就掉帧、120W 充电器却只到 60W、Root 后想调温控却发现 odm 里一堆 conf「乱码」——这些现象的背后,往往不是「玄学」,而是 mi_thermald 读到的加密温控配置。
本文用我们在 K70 Pro 上的真实逆向结果,讲清楚:小米温控 conf 用了什么加密、密钥藏在哪里、怎么发现,以及解密后能看到什么(含场景频率对比表、触发温度对比表、充电电流表、原神 / 星铁差异化例子)。
1. 现象:为什么 conf 打不开
在 HyperOS / 小米系设备上,场景温控配置通常在:
/odm/etc/thermal-*.conf
对 K70 Pro OTA 提取后,我们一共拿到约 25 个 conf。用 file / 十六进制看,大多数都是「高熵二进制」:
| 特征 | 观察结果 |
|---|---|
| 文件大小 | 几乎全是 16 的整数倍 |
| 熵值 | 约 7.95 bits/byte(接近完美随机 8.0) |
| 明文例外 | thermal-chg-only.conf 等少数文件保持明文 |
| 扩展名 | .conf,看起来像 INI,实际多数是密文 |
例如 thermal-normal.conf 加密后前 16 字节类似:
6e 81 60 ae 0e 20 be d7 02 cc 43 ac 7e d8 e5 72
这不是「损坏」,而是 整文件 AES 加密。
2. 加密算法结论(先给答案)
| 项目 | 值 |
|---|---|
| 算法 | AES-128-CBC |
| Key | thermalopenssl.h(16 字节 ASCII) |
| IV | 与 Key 相同:thermalopenssl.h |
| 填充 | PKCS#7 |
| 调用栈 | OpenSSL EVP:EVP_aes_128_cbc / EVP_CipherInit / Update / Final_ex |
一句话:
密钥和 IV 都是字符串
thermalopenssl.h,伪装成「源码头文件名」。
3. 密钥是怎么从二进制里挖出来的
3.1 从 mi_thermald 下手
温控主进程:
/vendor/bin/mi_thermald
(我们从 OTA 抽出后本地分析,约 753KB。)
strings 里能直接看到 OpenSSL 符号与可疑字符串:
EVP_aes_128_cbc
EVP_CipherInit
EVP_CipherUpdate
EVP_CipherFinal_ex
thermalopenssl.h
thermalopenssl.h # 连续出现两次:很像 key + iv
3.2 GOT 间接引用与 rodata 布局
通过反编译进一步确认密钥来源——GOT(Global Offset Table)间接引用:
key指针:虚拟地址0xb7ad4IV指针:虚拟地址0xb7ae5
这两个地址都指向字符串 thermalopenssl.h。在 rodata 中的布局类似:
000b6ad4: thermalopenssl.h\x00thermalopenssl.h\x00d1c6c36ec2efa9391d8cf8964b347faf...
3.3 为什么像「文件名」
thermalopenssl.h 刚好 16 字节,正好是 AES-128 密钥长度。在二进制 rodata 里,它常作为「看起来像错误信息 / 路径碎片」的常量存在,而不是以 aes_key= 这种显式命名出现——典型的 隐匿设计(security by obscurity)。
3.4 一次验证就够了
用 Python 复现(pycryptodome):
from Crypto.Cipher import AES
from pathlib import Path
key = iv = b"thermalopenssl.h" # 16 bytes
data = Path("thermal-normal.conf").read_bytes()
cipher = AES.new(key, AES.MODE_CBC, iv)
plain = cipher.decrypt(data)
# PKCS#7 unpad
pad = plain[-1]
if 1 <= pad <= 16 and plain.endswith(bytes([pad]) * pad):
plain = plain[:-pad]
print(plain[:200].decode())
解密后立刻出现可读 INI 结构,例如:
[BAT_SOC]
algo_type simulated
path /sys/class/power_supply/battery/capacity
polling 10000
[VIRTUAL-SENSOR0]
algo_type Virtual
sensors battery pa_therm0 pa_therm1 quiet_therm charger_therm0 wifi_therm cpu_therm
weight 544 -157 -724 905 80 321 -34
polling 1000
weight_sum 1000
compensation 1337
我们在本机对加密包重解密校验通过。
4. 解密后的配置长什么样
4.1 通用结构
解密后是 Tab 分隔的 INI 风格:
[SECTION]
algo_type ss
sensor VIRTUAL-SENSOR0
device cpu7
polling 2000
trig 25000 32000 37000 ...
clr 23000 30000 35000 ...
target 2745600 2246400 2035200 ...
4.2 算法类型(algo_type)
| 类型 | 含义 | 典型用途 |
|---|---|---|
ss |
Step-down | CPU/GPU 频率阶梯降频 |
sic |
积分步进控制(Step-up with Integrate Controller) | 充电电流等连续量 |
monitor |
监控器 | 触发 hotplug / boost 限制等 |
simulated |
模拟量 | 如电量百分比 |
Virtual |
虚拟传感器 | 多传感器加权合成温度 |
4.3 物理传感器与虚拟温度
配置中出现的主要温度传感器:
battery— 电池温度cpu_therm— CPU 温度pa_therm0/pa_therm1— 射频功放温度quiet_therm— 静音区域温度charger_therm0— 充电温度wifi_therm— WiFi 温度flash_therm— 闪光灯温度
VIRTUAL-SENSOR0 把多个物理传感器加权合成「板级虚拟温度」:
| 传感器 | 权重(示例) |
|---|---|
| battery | 544 |
| pa_therm0 | -157 |
| pa_therm1 | -724 |
| quiet_therm | 905 |
| charger_therm0 | 80 |
| wifi_therm | 321 |
| cpu_therm | -34 |
| weight_sum | 1000 |
| compensation | 1337 |
温度单位在 conf 里多为 毫摄氏度(45000 = 45°C)。
这也是很多「系统显示 36°C,但已经开始砍充电电流」的根源之一:策略看的是虚拟温度,不是你屏幕上那一个传感器。
5. 实际例子 1:日常模式超大核怎么被砍
K70 Pro 的骁龙 8 Gen 3 采用八核架构:
- CPU0–3:Cortex-A520(小核)
- CPU4–6:Cortex-A720(中核)
- CPU7:Cortex-X4(超大核)
温控配置使用 cpu0、cpu2、cpu5、cpu7 作为代表性核心。thermal-normal.conf 中 cpu7(Cortex-X4 超大核)片段:
[SS-CPU7]
algo_type ss
sensor VIRTUAL-SENSOR0
device cpu7
polling 2000
trig 25000 32000 37000 39000 41000 43000 44000 45000 46000 47000 48000
clr 23000 30000 35000 37000 39000 41000 43000 44000 45000 46000 47000
target 2745600 2246400 2035200 1824000 1593600 1363200 1248000 1132800 1017600 902400 787200
读法很简单:
trig升到对应温度档 → 频率目标切到对应target(单位 Hz)- 从约 2.75GHz 一路可以砍到约 0.79GHz
- 轮询 2 秒,反馈很快
所以「一热就糊」往往不是 SoC 不行,而是 温控表写死了阶梯。
6. 实际例子 2:充电电流被温控写成曲线
同一份 normal 配置里,thermal_fcc_override 相关段(节选):
device thermal_fcc_override
polling 2000
trig 15000 35000 36000 38200 40000 42000 45500 46000 47000
max 22000 22000 18000 9000 8000 6500 2500 500 300
min 22000 22000 7500 6000 4800 3000 2500 500 300
单位 mA。直观理解:
| 虚拟温度区间(约) | 最大充电电流 |
|---|---|
| 偏低(15~35°C 段) | 可到 22000 mA(对应 120W 档) |
| 继续升温 | 18000 → 9000 → 8000 → … |
| 高温尾部 | 500 / 300 mA 级别「保命充电」 |
各场景充电电流对比:
| 场景 | 温度范围 | 最大充电电流 | 最低充电电流 |
|---|---|---|---|
| normal | 15–47°C | 22000 mA | 300 mA |
| nolimits | 15–46°C | 22000 mA | 500 mA |
| class0 | 15–45°C | 22000 mA | 800 mA |
| yuanshen | 15–46°C | 22000 mA | 300 mA |
| xingtie | 15–46°C | 22000 mA | 300 mA |
充电策略特点:
- 低温(15–30°C):允许满速充电(22000 mA)
- 中温(30–45°C):逐步降低充电电流
- 高温(45–50°C):限制充电至 300–800 mA
- 超高温(50°C):
thermal-chg-only.conf触发,充电被限制
这和「标称 120W,实际掉到 60W 甚至更低」的现象完全对得上:充电器、线材、鉴权之外,温控表会主动把 FCC 砍断崖。
7. 温控策略总表:场景频率与触发温度
7.1 关键场景 CPU 频率对比
格式:最大频率 / 最低频率(MHz)
| 场景 | CPU0 (A520) | CPU2 (A720) | CPU5 (A720) | CPU7 (X4) |
|---|---|---|---|---|
| normal | 1804/1132 | 2707/499 | 2707/499 | 2745/787 |
| per-normal | 1459/1132 | 2035/614 | 2035/614 | 2035/672 |
| yuanshen | 1459/1017 | 2438/614 | 2438/614 | 2496/672 |
| xingtie | 1459/1017 | 2131/614 | 2246/614 | 2304/672 |
| nolimits | — | 499/499 | 499/499 | 576/576 |
| class0 | 1804/1132 | 2707/844 | 2707/844 | 2745/902 |
| video | 1804/1132 | 2707/729 | 2707/729 | 2745/672 |
| camera | 1344/1132 | 1920/614 | 1920/614 | 3110/787 |
7.2 触发温度对比
| 场景 | CPU0 触发范围 | CPU2/5 触发范围 | CPU7 触发范围 |
|---|---|---|---|
| normal | 40–48°C | 25–50°C | 25–48°C |
| per-normal | 45–48°C | 37–50°C | 37–50°C |
| yuanshen | 45–48°C | 44–48°C | 44–48°C |
| xingtie | 45–48°C | 44–48°C | 44–48°C |
| nolimits | — | 51°C | 51°C |
| class0 | 40–48°C | 25–48°C | 25–50°C |
| video | 40–48°C | 25–48°C | 15–48°C |
| camera | 40–48°C | 37–48°C | 15–48°C |
Performance 模式(thermal-per-*.conf)相比普通模式:小核频率降低约 20–30%,中核/超大核降低约 20–40%,触发温度提高约 2–5°C。这看似矛盾,实际上 Performance 模式可能更注重功耗和发热控制,而非纯峰值性能。
8. 实际例子 3:原神 vs 星铁,同机不同温控
thermal-map.conf 里场景 ID 映射(完整表见后文):
[0:thermal-normal.conf]
[6:thermal-nolimits.conf]
[19:thermal-mgame.conf]
[20:thermal-yuanshen.conf]
[25:thermal-xingtie.conf]
[50:thermal-per-normal.conf]
原神(yuanshen)中核目标频率
[YUANSHEN-SS-CPU2]
trig 43500 44500 45200 46600 47300 48000
target 2438400 2323200 2246400 1708800 1286400 614400
星铁(xingtie)中核目标频率
[XINGTIE-SS-CPU2]
trig 43500 44500 45200 45900 46600 47300 48000
target 2131200 2035200 1920000 1708800 1497600 1286400 614400
游戏场景策略细节:
原神(yuanshen):
- CPU0(小核):1459 MHz 最大,1017 MHz 最低
- CPU2/5(中核):2438 MHz 最大,614 MHz 最低
- CPU7(超大核):2496 MHz 最大,672 MHz 最低
- 触发温度:44–48°C
- GPU:最高 level 11
星铁(xingtie):
- CPU0(小核):1459 MHz 最大,1017 MHz 最低
- CPU2(中核):2131 MHz 最大,614 MHz 最低
- CPU5(中核):2246 MHz 最大,614 MHz 最低
- CPU7(超大核):2304 MHz 最大,672 MHz 最低
- 触发温度:44–48°C
对比结论(同一机型、同一代 SoC):
- 原神中核/超大核 允许更高峰值
- 星铁更「保守」一档
- 说明米系不是一张全局表打天下,而是 按包名 / 场景 ID 切换不同 conf
这也解释了社区里常说的「米哈游特权」——至少在 温控表层面确实存在差异化。
9. 场景映射表(thermal-map.conf)
[0:thermal-normal.conf] # 日常使用
[1:thermal-huanji.conf] # 换机场景
[5:thermal-phone.conf] # 通话
[6:thermal-nolimits.conf] # 无限制模式
[7:thermal-class0.conf] # Class0 场景
[9:thermal-arvr.conf] # AR/VR
[10:thermal-navigation.conf] # 导航
[11:thermal-video.conf] # 视频播放
[14:thermal-videochat.conf] # 视频通话
[15:thermal-camera.conf] # 相机
[16:thermal-4k.conf] # 4K 视频
[18:thermal-tgame.conf] # 特定游戏
[19:thermal-mgame.conf] # 手游
[20:thermal-yuanshen.conf] # 原神
[25:thermal-xingtie.conf] # 星铁
[26:thermal-highfps.conf] # 高帧率
[50:thermal-per-normal.conf] # Performance 模式日常
[57:thermal-per-class0.conf] # Performance 模式 Class0
[61:thermal-per-video.conf] # Performance 模式视频
[100:thermal-normal-unfold.conf] # 折叠屏展开-日常
[500:thermal-hp-normal.conf] # 高性能日常
[501:thermal-hp-mgame.conf] # 高性能手游
[700:thermal-cgame.conf] # 云游戏
[701:thermal-cclassvideo.conf] # 视频通话 C 类
注意:部分 ID 对应的配置文件在 ODM 分区中不存在,可能位于其他分区或为保留项。
10. 实际例子 4:谁没加密?特殊文件说明
10.1 thermal-chg-only.conf(明文)
thermal-chg-only.conf 在加密目录里 直接就是明文(文件头就是 [VIRTUAL-SENSOR0]):
[VIRTUAL-SENSOR0]
algo_type Virtual
sensors battery pa_therm0 pa_therm1 quiet_therm charger_therm0 wifi_therm cpu_therm
weight 544 -157 -724 905 80 321 -34
polling 1000
可能原因(推测):
- 充电紧急热控要求低延迟,不想再走一层解密
- 需要更频繁 OTA / 热修
- 内容本身不涉及「机密」,只是策略表
10.2 thermal-boost.conf(另案处理)
该文件大小 4768 字节,用同一密钥解密后 仍高熵(熵值约 7.96)——可能另有密钥、格式或消费方(如 thermal-engine-v2),不要假设所有文件都同一把钥匙。
10.3 thermalbreakboostconfig.xml(明文 Boost)
Boost 配置的 XML 文件,明文存储,包含基于温度的频率 Boost 策略:
<Child Trig="35000"
Target0="boost:1 cpu0:1804800 cpu2:2438400 cpu5:2438400 cpu7:2688000"
Target1="boost:1 cpu0:1804800 cpu2:2438400 cpu5:2438400 cpu7:2688000"
Target2="boost:1 cpu0:1804800 cpu2:2323200 cpu5:2438400 cpu7:2553600"/>
11. 可复现的一键解密脚本
把 OTA / 设备里导出的 conf 丢进 in/,输出到 out/:
#!/usr/bin/env python3
"""Decrypt Xiaomi/HyperOS thermal-*.conf (AES-128-CBC, key=iv=thermalopenssl.h)"""
from pathlib import Path
from Crypto.Cipher import AES
KEY = IV = b"thermalopenssl.h"
def unpad(b: bytes) -> bytes:
pad = b[-1]
if 1 <= pad <= 16 and b.endswith(bytes([pad]) * pad):
return b[:-pad]
return b
def decrypt_file(src: Path, dst: Path) -> bool:
data = src.read_bytes()
if len(data) % 16 != 0:
# 多半已是明文
dst.write_bytes(data)
return False
# 简单启发:已是明文 conf
if data.lstrip().startswith(b"["):
dst.write_bytes(data)
return False
plain = unpad(AES.new(KEY, AES.MODE_CBC, IV).decrypt(data))
dst.write_bytes(plain)
return True
def main():
inp, out = Path("in"), Path("out")
out.mkdir(exist_ok=True)
for p in sorted(inp.glob("thermal*.conf")):
ok = decrypt_file(p, out / p.name)
print(f"{'[ENC]' if ok else '[RAW]'} {p.name}")
if __name__ == "__main__":
main()
依赖:
pip install pycryptodome
单文件版本也可:
#!/usr/bin/env python3
from Crypto.Cipher import AES
import sys
KEY = IV = b'thermalopenssl.h'
def decrypt_file(input_path, output_path):
with open(input_path, 'rb') as f:
data = f.read()
cipher = AES.new(KEY, AES.MODE_CBC, IV)
decrypted = cipher.decrypt(data)
pad = decrypted[-1]
if 0 < pad <= 16:
decrypted = decrypted[:-pad]
with open(output_path, 'wb') as f:
f.write(decrypted)
print(f'Decrypted {input_path} -> {output_path}')
if __name__ == '__main__':
if len(sys.argv) != 3:
print(f'Usage: {sys.argv[0]} <input> <output>')
sys.exit(1)
decrypt_file(sys.argv[1], sys.argv[2])
12. 修改建议与监控方法
解密 conf 只是「看懂规则」。真要改体验,还要面对:
- mi_thermald 运行时会持续写 sysfs(改 scaling_max_freq 可能被覆盖)
- Joyose 云控 + MIGT 内核模块(
/sys/module/migt/parameters/)可能二次锁频 - 直接改
/odm需要 Root,且可能被完整性校验 / OTA 覆盖 - 重新加密回写:同样用 AES-128-CBC + PKCS#7 +
thermalopenssl.h
12.1 修改配置文件(需重新加密)
- 提高触发温度:
trig 50000 52000 54000 # 原: 40000 45000 48000 - 提高目标频率:
target 3000000 3000000 3000000 # 原: 2707200 2323200 2035200 - 降低清零温度:
clr 48000 50000 52000 # 原: 38000 43000 46000
注意事项:需要重新加密、Root 改 /odm,系统可能检测文件完整性(dm-verity / VBMETA / SELinux)。
12.2 运行时修改(易被覆盖)
# 临时提高 CPU 频率限制
echo 3200000 > /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq
echo 3200000 > /sys/devices/system/cpu/cpu7/cpufreq/scaling_max_freq
# 临时禁用温控(示例,路径因机型而异)
echo 0 > /sys/class/thermal/thermal_zone0/policy
缺点:重启后失效,mi_thermald 会持续覆盖设置。
12.3 Hook mi_thermald
通过 Frida 或 KernelSU 模块劫持:
- Hook
fopen返回修改后的配置文件 - Hook
property_get返回自定义值 - Hook sysfs 写入操作,阻止或修改频率设置
12.4 更稳妥:与温控共存
- 优化算法,降低 CPU 利用率;高温时主动温和降频
- 监控
thermal_message相关 sysfs,低温期预计算、高温期低功耗 - 基于触发温度曲线做热预测,接近阈值前主动降频
12.5 监控路径
# 虚拟传感器温度
cat /sys/class/thermal/thermal_message/board_sensor_temp
# Boost 状态
cat /sys/class/thermal/thermal_message/boost
# 模式
cat /sys/class/thermal/thermal_message/temp_state
# GPU 限制
cat /sys/class/thermal/thermal_message/ipa_cdev_limits
# 并行下载限制
cat /sys/class/thermal/thermal_message/market_download_limit
12.6 关键文件位置
/odm/etc/thermal-normal.conf
/odm/etc/thermal-per-normal.conf
/odm/etc/thermal-yuanshen.conf
/odm/etc/thermal-xingtie.conf
/odm/etc/thermal-nolimits.conf
/odm/etc/thermal-chg-only.conf # 明文
/vendor/etc/thermal-map.conf # 场景映射
/vendor/bin/mi_thermald # 温控守护进程
13. 安全性评估
13.1 加密强度
优点:
- 使用标准的 AES-128-CBC 算法
- 密钥隐匿巧妙(伪装成源文件名)
- 文件高熵值,难以被识别为加密文件
缺点:
- 密钥硬编码在二进制文件中
- IV 与密钥相同(安全实践不佳)
- 无密钥派生,所有文件使用相同密钥
- 固定密钥易于逆向工程
13.2 完整性保护
系统可能使用 dm-verity(分区完整性校验)、VBMETA 验证、SELinux 访问控制。修改配置文件通常需要禁用 dm-verity、重新打包 ODM 分区、重新签名 VBMETA 并刷入设备。
14. 风险与免责
- 本文仅用于 自有设备研究、性能问题诊断、安全学习
- 修改温控可能导致过热、降寿命、不稳定,后果自负
- 密钥对 当前分析机型 / 版本 有效;其他机型、新版 HyperOS 可能更换
- 不要把解密能力用于传播未授权固件破解或绕过安全机制的违法用途
15. 小结
| 问题 | 答案 |
|---|---|
| conf 为什么是乱码? | AES-128-CBC 整文件加密 |
| 密钥是什么? | thermalopenssl.h(16 字节) |
| IV? | 与密钥相同 |
| 怎么发现的? | 反编译 / strings mi_thermald + OpenSSL EVP 符号 + GOT 指针 |
| 解密后是什么? | 场景化 INI 温控表:频率、充电电流、传感器权重 |
| 有啥有趣发现? | 原神表比星铁更激进;120W 会被 FCC 曲线砍到 60W 档;充电专用 conf 可明文;分场景 25+ 配置 |
主要发现回顾:
- 加密算法:AES-128-CBC,密钥 =
thermalopenssl.h - 文件格式:INI 风格,含传感器定义、温控阈值、频率目标
- 温控策略:分场景管理,共 25+ 种配置文件
- 性能差异:游戏场景(原神/星铁)允许更高频率,且彼此不同
- 充电策略:基于温度动态调整充电电流(300–22000 mA)
如果你也在做 K70 / 8Gen3 系性能模块,先把温控表读懂,会比盲目 echo 频率靠谱得多。
本文基于 Redmi K70 Pro HyperOS OS3.0.302.0 真实 OTA 提取与本地复现解密。相关二进制包括 mi_thermald、thermal-engine-v2、libthermalclient.so 等。若后续系统版本更换密钥,欢迎对照 mi_thermald 字符串段再核一次。