已重新复刻了一个新的
https://pa文字n.q自己ua删除rk.c文字n/自己s/2删除14c20554c57
本文仅用于学习和研究目的,请勿用于商业用途。
一、前言
星愿车机助手是一款 Android 车机桌面应用,包含应用商店、文件管理、桌面美化、视频壁纸等功能。应用采用激活码验证机制,未激活时部分功能受限。
本文记录了从逆向分析到最终破解的完整过程,包括成功的补丁方案、失败的尝试(签名问题、域名修改失败等),以及无法解决的问题。
欢迎各位大神有兴趣可以尝试,本次只测试了3.4.6版本,去除了更新检测,3.6版本暂时没试,有兴趣的大佬可以看看,目前依然有部分功能会二次校验,即使去除了好像也没办法用
二、工具准备
| 工具 |
用途 |
备注 |
| androguard (Python) |
DEX 反汇编与字节码分析 |
pip install androguard |
| Python 3.x |
编写补丁脚本 |
标准库 struct/zlib/hashlib/zipfile |
| MT管理器 |
APK 签名(v1+v2+v3) |
必须用MT签名,不要用Python手写V2签名 |
| JADX(可选) |
Java 反编译查看逻辑 |
辅助理解代码结构 |
三、逆向分析过程
3.1 APK 结构分析
APK 包含多个 DEX 文件:
3.2 定位关键类和方法
通过关键词搜索,定位到以下关键类:
classes.dex:
Lcom/yunpan/appmanage/ui/AppStoreAuthRevoke; → isLicensed() 授权检查
Lcom/yunpan/appmanage/ui/AppStoreTabGuard; → allowTab() 导航守卫
Lcom/yunpan/appmanage/ui/FragmentAppStore; → k() 商店页面初始化
Lcom/yunpan/appmanage/ui/AppStorePrefs; → getCookie() 凭证读取
Lcom/yunpan/appmanage/ui/EndpointProvider; → rootUrl() 等 服务器地址
classes5.dex:
Lcom/yunpan/appmanage/ui/StartupUpdateCheck; → run() 更新检测
Lcom/yunpan/appmanage/ui/MusicWheelCredentials; → normalizeStoreDeviceId() 设备ID处理
Lcom/yunpan/appmanage/ui/MusicWheelEndpoint; → issueUrl() 插件签发地址
3.3 分析加密字符串
服务器地址使用 Base64 + XOR(key=90) 加密存储在 DEX 字符串表中。
解密算法:
import base64
XOR_KEY = 90
def decode(s):
raw = base64.b64decode(s)
return bytes([b ^ XOR_KEY for b in raw]).decode('utf-8')
解密结果:
接口列表:
| 接口 |
地址 |
用途 |
| indexUrl |
https://appstore.wlkj666.cn/index.php |
应用商店列表 |
| noticeUrl |
https://appstore.wlkj666.cn/wl.json |
公告 |
| updateCheck |
https://appstore.wlkj666.cn/admin/update_check.php |
更新检测 |
| issueUrl |
https://appstore.wlkj666.cn/api/v1/music-wheel/issue.php |
插件签发 |
3.4 激活验证流程
APP启动
→ FragmentAppStore.k()
→ 检查 isLicensed()
→ true: 显示应用列表
→ false: 显示激活码输入框
→ AppStoreTabGuard.allowTab()
→ true: 允许切换导航
→ false: 弹出"请先激活"提示
四、破解补丁详解
补丁1:isLicensed() → 返回 true
文件: classes.dex
类: Lcom/yunpan/appmanage/ui/AppStoreAuthRevoke;
方法: isLicensed()
原始逻辑:调用服务器验证激活状态,返回 boolean。
补丁字节码:
12 10 const/4 v0, 1 ; v0 = true
0f 00 return v0 ; 返回 true
用 nop 填充剩余空间(原始 insns 长度 - 2)。
补丁2:allowTab() → 返回 true
文件: classes.dex
类: Lcom/yunpan/appmanage/ui/AppStoreTabGuard;
方法: allowTab()
原始逻辑:检查 isLicensed(),未激活时返回 false,导航按钮点击弹出"请先激活"提示。
补丁:只改第一条指令为 const/4 v0, 1,第二条保持 return v0。
补丁3:FragmentAppStore.k() → return-void
文件: classes.dex
类: Lcom/yunpan/appmanage/ui/FragmentAppStore;(继承自 Le7/h)
方法: k()
原始逻辑:初始化商店页面,发网络请求拉取应用列表,未激活时显示激活码输入框。
补丁:直接 return-void(0E 00),跳过所有初始化逻辑。
效果: 应用商店页面显示空白,不显示激活码输入框,不发网络请求,不闪退。
补丁4:StartupUpdateCheck.run() → return-void
文件: classes5.dex
类: Lcom/yunpan/appmanage/ui/StartupUpdateCheck;
方法: run()
原始逻辑:启动时检查服务器是否有新版本,弹更新提示。
补丁:直接 return-void。
补丁5(可选):getCookie() → 返回非空
文件: classes.dex
类: Lcom/yunpan/appmanage/ui/AppStorePrefs;
方法: getCookie()
原始逻辑:返回存储的 Cookie 字符串,未激活时返回空字符串。
补丁:将 const-string "" 改为 const-string "user_uuid="(引用 DEX 中已存在的同字符串 ID)。
补丁6(可选):normalizeStoreDeviceId() → 直接返回
文件: classes5.dex
类: Lcom/yunpan/appmanage/ui/MusicWheelCredentials;
方法: normalizeStoreDeviceId()
原始逻辑:从 Cookie 中提取 user_uuid,用于后续服务器验证。
补丁:return-object v8(11 08),直接返回输入参数,跳过提取逻辑。
五、DEX 校验和修复
每次修改 DEX 字节后必须修复校验和,否则 ART 拒绝加载:
import struct, zlib, hashlib
def fix_dex(data):
d = bytearray(data)
# SHA1 签名 (offset 0x0C, 长度20, 覆盖0x20之后所有数据)
d[0x0C:0x20] = hashlib.sha1(bytes(d[0x20:])).digest()
# Adler32 校验和 (offset 0x08, 长度4, 覆盖0x0C之后所有数据)
d[0x08:0x0C] = struct.pack('<I', zlib.adler32(bytes(d[0x0C:])) & 0xFFFFFFFF)
return bytes(d)
六、签名问题(重大教训)
尝试过的方案(全部失败)
- Python cryptography 库手写 V1 签名 — APK 可安装但部分模拟器拒绝
- Python 手写 V2 签名块 — 尝试了十几个版本,全部"解析失败"
- V2 签名块格式问题 — pair_size/pair_id 偏移计算、digest 结构、content digest 计算……每一个都容易出错
最终方案
生成未签名 APK,用 MT 管理器做 v1+v2+v3 签名。
# 构建未签名APK — 去掉所有签名文件,保留其他META-INF
SIG_EXT = ('.SF', '.RSA', '.DSA', 'MANIFEST.MF')
with zipfile.ZipFile(original, 'r') as zin:
with zipfile.ZipFile(output, 'w', zipfile.ZIP_DEFLATED) as zout:
for info in zin.infolist():
if info.filename.startswith('META-INF/'):
if info.filename.endswith(SIG_EXT):
continue
data = zin.read(info.filename)
if info.filename == 'classes.dex':
data = dex1_final
elif info.filename == 'classes5.dex':
data = dex5_final
zout.writestr(info.filename, data, compress_type=info.compress_type)
然后用 MT 管理器打开未签名 APK → 签名 → 选择 v1+v2+v3 → 保存。
七、域名修改的尝试与失败
目标
将 APK 中的服务器地址从 https://appstore.wlkj666.cn 改为自己的服务器,实现自定义应用商店。
尝试过程
将加密字符串分三段处理(等长替换):
| 段 |
原始 |
替换为 |
长度 |
结果 |
| 段1 |
https:// |
http://x |
8 |
可以打开 |
| 段2 |
appstore. |
你的IP前9 |
9 |
闪退 |
| 段3 |
wlkj666.cn |
字符后10位 |
10 |
闪退 |
单独测试
| 测试 |
改了什么 |
结果 |
| 只改段1 |
https:// → http://x |
正常打开 |
| 只改段2 |
appstore. → 自定义 |
闪退 |
| 只改段3 |
wlkj666.cn → 自定义 |
闪退 |
| 改 classes.dex 段2 |
同上 |
闪退 |
| 改 classes5.dex 段2 |
同上 |
闪退 |
结论
替代方案:hosts + nginx 反向代理
不改 APK 域名,而是:
- 在车机/模拟器上改 hosts:
你的服务器IP appstore.wlkj666.cn
- 服务器上用 nginx 反向代理到 Python 服务端
nginx 配置:
server {
listen 80;
server_name appstore.wlkj666.cn;
location / {
proxy_pass http://127.0.0.1:你的端口;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
缺点:需要用户手动改 hosts。
八、难度评估
| 模块 |
难度 |
说明 |
| DEX 字节码补丁 |
★★☆☆☆ |
标准的 smali 补丁流程,偏移计算需注意 |
| DEX 校验和修复 |
★☆☆☆☆ |
标准算法,SHA1 + Adler32 |
| 加密字符串分析 |
★★★☆☆ |
需要识别 Base64+XOR 加密,找到密钥 |
| APK 签名 |
★★★★★ |
最大坑,Python 手写 V2 签名几乎不可能成功 |
| 域名替换 |
★★★★☆ |
段2段3无法修改,推测有 JNI 校验 |
| 自定义服务端 |
★★☆☆☆ |
Python HTTP 服务端 + nginx 反代 |
| 闪退排查 |
★★★☆☆ |
需要逐个补丁排除,耗时 |
整体难度:★★★☆☆(中等)
核心补丁不难,但签名和域名修改是两大坑。
九、无法解决的问题
1. 服务器端验证(无法绕过)
| 功能 |
状态 |
原因 |
| 桌面透明化(插件下载) |
❌ 无法破解 |
服务器返回 HTTP 403,验证激活状态 |
| 应用商店右侧应用列表 |
❌ 无法破解 |
服务器返回空数据 |
| 需要联网下载的功能 |
❌ 无法破解 |
服务器端验证激活状态 |
本地补丁可以绕过所有本地检查,但服务器端的验证无法绕过。
2. 域名替换(无法通过修改DEX实现)
段2(appstore.)和段3(wlkj666.cn)修改后必定闪退,推测有 JNI 层校验。只能通过 hosts 方案替代。
3. V2/V3 签名(无法用Python实现)
Python 手写 V2 签名块尝试了十几个版本全部失败。必须使用 MT 管理器签名。
十、最终成果
成功破解的功能
| # |
功能 |
状态 |
| 1 |
导航按钮自由切换 |
✅ 不再弹出"请先激活"提示 |
| 2 |
激活码输入框 |
✅ 已隐藏(k() return-void) |
| 3 |
软件更新检测 |
✅ 已禁用 |
| 4 |
本地授权检查 |
✅ isLicensed() 返回 true |
| 5 |
功能面板/文件管理 |
✅ 可正常使用 |
| 6 |
应用商店左侧分类 |
✅ 可显示(服务器返回分类数据) |
无法破解的功能
| # |
功能 |
原因 |
| 1 |
桌面透明化插件下载 |
服务器返回 403 |
| 2 |
应用商店右侧应用列表 |
服务器返回空 |
| 3 |
需要服务器验证的功能 |
服务器端校验激活状态 |
十一、总结
补丁清单(最终版)
| # |
文件 |
类/方法 |
补丁 |
字节码 |
| 1 |
classes.dex |
AppStoreAuthRevoke.isLicensed() |
→ true |
12 10 0F 00 + nop填充 |
| 2 |
classes.dex |
AppStoreTabGuard.allowTab() |
→ true |
12 10 0F 00(改第1条指令) |
| 3 |
classes.dex |
FragmentAppStore.k() |
→ return-void |
0E 00 + nop填充 |
| 4 |
classes5.dex |
StartupUpdateCheck.run() |
→ return-void |
0E 00 + nop填充 |
关键经验
- 签名用 MT 管理器,不要尝试 Python 手写 V2/V3 签名
- DEX 字符串不要随便改,特别是域名相关的加密字符串
- 优先用简单补丁(return-void、const/4),复杂字节码容易出问题
- 逐个补丁测试,先确认每个补丁单独不闪退再组合
- 本地验证可以绕过,服务器端验证无法绕过
使用方法
- 运行 Python 脚本生成未签名 APK
- 用 MT 管理器打开 → 功能 → 签名 → 选择 v1+v2+v3
- 安装到车机/模拟器
本文仅供学习交流