夺回平板所有权:讯飞 iClass C6 逆向记

3523 字
18 分钟
夺回平板所有权:讯飞 iClass C6 逆向记

设备:iFLYTEK iClass C6(CB-C6-STU),展锐 UD710,Android 9(SDK 28) 全程只读 + 本机操作,没有攻击任何服务器或者进行任何非法操作。


0. 先导言#

自3年前我在高二开发出平板看视频的功能后,我始终没有把破解大业向前推动一步,在高考毕业后,也是如愿收到官方所谓的原生安卓推送,在我拿到后始终感觉这个原生安卓并不原生,依旧是ifly的魔改版。

今天(已经大二了)正好借助Claude Code对这个系统进行一次改造,如果还有同辈手里有这台闲置设备又想让权限更上一层楼,或者为了隐私安全,可以一看。


1. 先把「所有权」拆成四层#

这是我一开始就想清楚的事,也是整篇文章的骨架。笼统地问「能不能刷成一个干净的系统」没有意义,因为不同层的答案完全不同:

层内容卡在哪
应用层禁应用、装 APK、断网需要 ADB
设置层开发者选项、USB 调试需要一个入口
系统层换原生 Settings、GMS 进 /system需要 root + 写 /system
引导层bootloader、自定义 ROM需要解锁,教育机基本不给

后来证明这个划分是对的:前两层可以全部拿下,后两层受限于个人能力和胆量一步没动。如果你像在其他安卓设备上一样,尝试多次点击版本号的话,会弹出一个需要输入暗码的窗口,这个后面会涉及。

先给结论,免得你抱错期望:

  • 展锐(Unisoc)教育机型的 bootloader 基本锁死,没有 TWRP、没有机型适配 ROM
  • ro.build.type=user + release-keys 意味着 adb root 直接拒绝,没有任何提权空间
  • 网上流传的「C6 刷机包」来源不明,踩到投毒固件的概率比原厂后门还高

所以这场改造的目标不是「换系统」,是「在现有系统上把被拿走的操作权限要回来」。


2. 第一关:设置里什么都没有#

现象很典型:

  • 设置不是原生 Android 设置,是深度定制的
  • 没有「开发者选项」
  • 没有 USB 调试开关
  • 拨号盘被删,所以 *#*#83781#*#* 这类工程码也敲不了
  • 有一个关不掉的「系统更新」通知

但有个有利条件:APK 可以随便装。

这就够了。第一步不是找漏洞,是找入口。

2.1 用 Activity Launcher 绕过菜单#

Android 的设置界面本质是一堆 Activity。厂商的做法通常是删掉菜单入口,而不是删除 Activity 本身。

只要 Activity 还在,就能绕过菜单直接启动它。

工具:Activity Launcher(F-Droid 开源)。

进去搜 com.android.settings,展开活动列表 —— 279 项。

279 这个数字本身就是情报:说明 AOSP 的整个 Settings 包还在,只是被挖了几个洞。如果只剩几十项,那才是真删干净了。


3. 三种报错,三种含义(这张表很值钱)#

在 279 项里乱点的时候,会遇到三种结果。学会分辨它们,能省掉大量瞎试:

报错含义还有救吗
ActivityNotFoundException这个类在 dex 里被删了彻底没戏
SecurityException: not exported类还在,但没对外开放可能有别的绕法
正常打开能用——

我第一次点 Settings$DevelopmentSettingsDashboardActivity,报的是 ActivityNotFoundException。 如果按这张表,结论应该是「类被删了」。但这个结论后来被推翻了 —— 原因在下面。

4. 意外之门:EngineerMode#

拨号被砍,工程码敲不了。那就换个思路:工程码最终只是跳转到一个 Activity,和拨号盘没有本质关系。

在 Activity Launcher 的包列表里翻,展锐平台通常有这个包:

com.sprd.engineermode

这是产线测试用的工具,厂商一般不敢删。展开它的 Activity,进去翻,找到了 USB Debug 开关。

4.1 为什么这个开关能用#

设置里那个「USB 调试」和工程模式里这个,底层是同一个东西:

Settings.Global.ADB_ENABLED

厂商只是把设置里的入口删了,没有(也无法轻易)删掉这个底层开关。工程模式是另一个入口。打开它、插线、平板上点「允许 USB 调试」:

Terminal window
adb devices
# Q102112224614763 device product:AIPad model:CB_C6_STU

第一层直接轻松拿下。

这一步是整个逆向的转折点。没有 ADB,后面所有分析几乎都做不了。


5. ADB 到手后,先读指纹#

连上第一件事不是急着改东西,是摸清底细。构建属性决定了一切可能性的边界:

Terminal window
for p in ro.build.type ro.build.tags ro.debuggable ro.secure \
ro.board.platform ro.build.version.release ro.build.fingerprint; do
printf "%-28s %s\n" "$p" "$(adb shell getprop $p)"
done

输出:

ro.build.type user
ro.build.tags release-keys
ro.debuggable 0
ro.secure 1
ro.board.platform ud710
ro.build.version.release 9
ro.build.fingerprint SPRD/ud710_2h10_native_noorca/ud710_2h10:9/...:user/release-keys

翻译一下:

  • user + release-keys → 正式发布构建。adb root 会被直接拒绝(实测:adbd cannot run as root in production builds)
  • ro.debuggable=0 → 没有调试后门
  • ud710 → 展锐 UD710 平台

结论很干脆:root 无望,不过 ADB 已经够用了。

顺手确认一下有没有 MDM 锁:

Terminal window
adb shell dumpsys device_policy

结果是空的 —— 没有启用的 Device Admin,没有 Device Owner。这意味着 pm disable-user 可以自由使用。这是一个好消息。


6. 逆向第一步:把 Settings.apk 拉下来#

有了 ADB,就能读 /system(shell 用户对它可读,不需要 root):

/system/priv-app/Settings/Settings.apk
adb shell pm path com.android.settings
adb pull /system/priv-app/Settings/Settings.apk .
# 54 MB

现在目标很明确:在这个 APK 里找到那个「暗码」。

因为现象里有一条关键线索:连点设置内的版本号好几次后,ifly弹窗要求输入暗码。这说明:

  1. 存在一个硬编码或可推导的校验值
  2. 它就在这个 APK 里(或者它请求的某个服务里)

先赌在 APK 里。


7. 在 54MB 里找一个中文词#

7.1 先试试用 grep#

APK 就是 zip。先解开,用最原始的办法:

Terminal window
unzip -o Settings.apk -d settings_x
grep -al "暗码" settings_x/*.dex settings_x/resources.arsc

结果很有意思:

settings_x/resources.arsc ← 命中
(dex 里没有)

这一条信息量很大:「暗码」只存在于资源文件里,不存在于代码里。

在 Android 里这意味着:

  • resources.arsc 存的是界面文案(strings.xml 编译后的形态)
  • classes.dex 存的是逻辑(常量、比较、跳转)

「暗码」是 UI 文案(多半就是那个输入框的提示),真正的校验逻辑在 dex 里,只是它没有把密码本身写成明文字符串 —— 否则 dex 里也会命中。

7.2 用 Python 挖上下文#

grep 只能告诉你「有」,看不出「是什么」。用几行 Python 把前后的字节挖出来:

import glob, os
for f in glob.glob('settings_x/**/*', recursive=True):
if not os.path.isfile(f): continue
data = open(f, 'rb').read()
b = '暗码'.encode('utf-8')
i = data.find(b)
if i != -1:
print(f, data[max(0,i-160):i+240].decode('utf-8', 'replace'))

(resources.arsc 里的字符串是 UTF-8 池,注意还要试 utf-16-le。)

输出里除了「请输入暗码」,还有一条同样扎眼的:

启用开发者选项

这说明开发者选项的界面资源还在,只是入口被关了。这就推翻了第三节那个 ActivityNotFoundException 的结论。


8. 可疑的wind_ 前缀#

strings.xml 里找到资源名:

<string name="wind_delelopment_password_hint">请输入暗码</string>
<string name="wind_delelopment_title">开启开发者模式</string>

wind_ 是讯飞的定制前缀(顺带一提,delelopment 是他们的拼写错误,这种瑕疵反而好认)。

于是问题变成:谁用了这个字符串?

这时候装反编译器:

Terminal window
brew install jadx
jadx -d settings_java Settings.apk

然后搜:

Terminal window
grep -rn "wind_delelopment" settings_java/sources/

直接指到一个文件:

com/android/settings/WindDeviceInfo.java:119

9. 柳暗花明:WindDeviceInfo.java#

打开一看,密码就明明白白写在第 111 行,不能更扎眼了:

if (developDilaog.getPassword() != null
&& developDilaog.getPassword().equals("IFlyCBaistudy5121")) {
toDevelopmentMode();
}

暗码 = IFlyCBaistudy5121。

而且同一个方法里还藏着完整的触发条件:

if (CLICK_COUNT >= 6 && CLICK_COUNT > 0) {
// 从第 6 次开始显示倒数
Toast.makeText(..., plurals.show_dev_countdown, 12 - CLICK_COUNT, ...);
}
if (CLICK_COUNT >= 12) {
// 第 12 次才弹暗码框
developDilaog.showDialog(...);
}

所以这里不是原生 Android 的连点 7 次,而是连点 12 次。

再看 toDevelopmentMode():

DevelopmentSettingsEnabler.setDevelopmentSettingsEnabled(mContext, true);
ComponentName componentName = new ComponentName(
"com.android.settings",
"com.android.settings.Settings$DevelopmentSettingsDashboardActivity");
setTileEnabled(componentName, true); // ← 关键
mContext.startActivity(intent);

10. 解开 enabled="false" 的谜#

回到第三节那个矛盾:Activity Launcher 说 ActivityNotFoundException,但代码里明明在跳转它。

答案在 AndroidManifest.xml 里:

<activity
android:name="Settings$DevelopmentSettingsDashboardActivity"
android:enabled="false" ← 就是这里
...>

组件被声明成禁用状态。

在 Android 里,启动一个 enabled=false 的组件,抛出的正是 ActivityNotFoundException —— 和「类不存在」的报错一模一样。这就是为什么那张分诊表也会骗人:这个报错有两种成因,但大不相同。

厂商的设计是:先用 DevelopmentSettingsEnabler 打开全局开关,再由 Settings 自己的 uid 调 setComponentEnabledSetting 把组件启用。两步都对,才能进去。

10.1 用 ADB 直接绕#

我们有 shell,能不能直接启用组件?

Terminal window
adb shell pm enable 'com.android.settings/...Settings$DevelopmentSettingsDashboardActivity'

被拒了:

SecurityException: Shell cannot change component state for ... to 1

Android 9 限制了 shell 修改非调试应用的组件状态。这条路封死。

好在 am start 能行:

Terminal window
adb shell "am start -n 'com.android.settings/com.android.settings.Settings\$DevelopmentSettingsDashboardActivity'"

为什么?shell 用户持有 START_ANY_ACTIVITY 一类的特权,Activity 启动和组件启用是两套不同的权限检查。禁用组件拦的是普通应用,却拦不住 shell。

注意这里有个坑:$ 会被远程 shell 吃掉,组件名会被截成 .Settings。一定要用单引号包住组件名,或者在 $ 前加转义。容易踩坑,报错信息里的组件名少了半截,非常容易看岔。

结果:

mResumedActivity: ActivityRecord{... com.android.settings/.Settings$DevelopmentSettingsDashboardActivity}

正式确认,开发者选项已经打开了。

10.2 让它永久生效#

am start 只是这次启动成功,组件本身还是 enabled=false,重启后会丢。

正确的做法是走一遍官方流程:连点版本号 12 次 → 输入 IFlyCBaistudy5121。这样由 Settings 自己调 setTileEnabled,持久化写入。

逆向的终点,往往是回到那个原本就该有的按钮上。


11. 顺手解剖 OTA 包#

开发者选项搞定后,还剩那个关不掉的更新通知。

先定位:

Terminal window
adb shell pm list packages -s | grep -iE "ota|update|fota"
# com.iflytek.study.ota
# com.sprd.systemupdate

第一个是讯飞的教育机 OTA,第二个是展锐原生的更新器。两个要区别对待。

把讯飞那个拉下来:

/system/app/IFlyOTA/IFlyOTA.apk
adb shell pm path com.iflytek.study.ota
adb pull /system/app/IFlyOTA/IFlyOTA.apk .

11.1 从 dex 里榨 URL#

不反编译也能拿到线索 —— dex 里的字符串常量是明文的:

Terminal window
LC_ALL=C grep -aoh 'https\?://[A-Za-z0-9._~:/?#@!$&()*+,;=%-]\{6,200\}' classes*.dex \
| LC_ALL=C sort -u

(LC_ALL=C 是必须的,否则中文环境下的 sort 会报 Illegal byte sequence。)

结果比预想的热闹:

https://hsmp.changyan.com/ ← 自己家的
https://byod.xunfeixxj.com/byod/
https://k12-api.openspeech.cn/
https://aiapi.changyan.com/coep-rest/
http://coop.voicecloud.cn/activity/ms/uploadSnapshot ← 这个名字一言难尽
https://android.bugly.qq.com/rqd/async ← 腾讯 Bugly 崩溃上报
https://astat.bugly.qcloud.com/rqd/async
https://h.trace.qq.com/kv

一个「系统更新」应用,捆了崩溃上报、行为统计、日志服务和一个叫 uploadSnapshot 的上传接口。

11.2 为什么是禁用而不是删除#

Terminal window
adb shell pm disable-user --user 0 com.iflytek.study.ota

选禁用不选卸载,理由有三个:

  1. 可逆。pm enable 就回来了
  2. 系统分区删不掉。/system/app 没 root 写不了,「卸载」对系统应用只能是 --user 0,本质还是禁用
  3. 退路拆了容易玩儿完

最后这点很重要:com.sprd.systemupdate 我没动。 它很可能就是「从 SD 卡本地升级」的执行者 —— 那是这台机器唯一还能刷回原厂的能力。禁掉它,等于彻底把安全绳剪了。

禁用后通知立刻消失,剩余进程只剩输入法和相册。


12. 哪些做成了,哪些没做#

目标结果原因
开发者选项✅EngineerMode + 暗码
ADB✅同上,adb_enabled=1 持久化
停掉更新通知✅disable-user
停掉后台遥测✅随 OTA 包一起停
原生系统设置❌换 APK 要写 /system → 要 root

关于在这台机器上装 GMS 补一句:Android 9 上把 GSF/GMS 当普通 APK 装是能装上的,但账号登录必然失败 —— GSF 需要 /system/priv-app 位置和签名权限。没有 root,这条路是死胡同。要 GMS 体验,换一台能解锁 bootloader 的机器(Pixel、部分小米/红米)比在这台上耗着划算得多。

除此之外,我还把默认浏览器换成了Via(原版CVE太多了),用小企鹅替换了原版输入法等等,剩下的都是应用问题了。


13. 方法论#

回头看,真正起作用的是这几个思路:

  1. 先分层,再谈可行性。 「能不能刷干净」是伪问题,「哪一层能拿到、卡在哪」才是真问题。
  2. 厂商删入口不删实现。 菜单项删了,Activity 还在;设置项藏了,底层 Settings.Global 还在。找另一个入口,比找漏洞容易得多。
  3. 报错信息要分级。 NotFound / SecurityException 是两个完全不同的世界。而且要警惕同一个报错有多种成因 —— enabled=false 也能伪装成「类不存在」。
  4. 资源文件和代码要分开搜。 中文文案在 resources.arsc,逻辑在 dex。搜错了地方会得出「这里什么都没有」的错误结论。
  5. 壳可以伪装,常量不行。 一个应用可以声称自己是「系统更新」,但它的 dex 里躺着 Bugly 和 uploadSnapshot。URL 不会撒谎。
  6. 变化要可逆。 每一步都问一句「怎么退回去」。禁用而不是删除,留着 com.sprd.systemupdate,都是这个原则。

这次能成,一半靠技术,一半靠厂商的帮衬。 工程模式没删、暗码硬编码在客户端、组件只是禁用没有移除,不过这个平板本身就是学校发的学生机。一毕业,客户残值也就没了。


附录:完整命令清单#

Terminal window
# ---------- 连接 ----------
adb devices -l
# ---------- 摸清底细 ----------
for p in ro.build.type ro.build.tags ro.debuggable ro.secure \
ro.board.platform ro.build.version.release ro.build.fingerprint; do
printf "%-28s %s\n" "$p" "$(adb shell getprop $p)"
done
adb shell dumpsys device_policy
# ---------- 拉包 ----------
adb shell pm path com.android.settings
adb pull /system/priv-app/Settings/Settings.apk .
adb shell pm path com.iflytek.study.ota
adb pull /system/app/IFlyOTA/IFlyOTA.apk .
# ---------- 静态分析 ----------
unzip -o Settings.apk -d settings_x
grep -al "暗码" settings_x/*.dex settings_x/resources.arsc
brew install jadx
jadx -d settings_java Settings.apk
grep -rn "wind_delelopment" settings_java/sources/
# ---------- 启动被禁用的组件 ----------
adb shell "am start -n 'com.android.settings/com.android.settings.Settings\$DevelopmentSettingsDashboardActivity'"
# ---------- 停掉 OTA ----------
adb shell pm disable-user --user 0 com.iflytek.study.ota
adb shell pm list packages -d # 查看已禁用的包
# ---------- 榨 URL ----------
LC_ALL=C grep -aoh 'https\?://[A-Za-z0-9._~:/?#@!$&()*+,;=%-]\{6,200\}' classes*.dex \
| LC_ALL=C sort -u

这篇结束的时候,开发者选项已经能正常打开了。距离第一次在设置里翻不到任何东西的高中毕业季,过去了一年多。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!

打赏
夺回平板所有权:讯飞 iClass C6 逆向记
https://hecloud.top/posts/essays/2026/夺回平板所有权讯飞-iclass-c6-逆向记/
作者
贺小云
发布于
2026-11-07
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
贺小云
热爱技术与折腾的博客 Dancing with uncertainty. 与不确定性共舞
公告
出于成本考虑,贺云已全面转型Serverless架构
分类
标签
碎碎念
站点统计
文章
97
分类
5
标签
45
总字数
117,395
运行时长
0 天
最后活动
0 天前
站点信息
构建平台
GitHub Actions
博客版本
Firefly v6.16.5
文章许可
CC BY-NC-SA 4.0