# 某航空 APP wToken 生成机制深度分析报告(v3.0 整合版)
整合来源:看雪社区 shmilyaxy、博客园 🐳.城南、GitHub wtoken2023、CSDN 等多篇逆向分析文章
一、wToken 架构总览
某航空 APP 的 wToken 参数并非单一算法产物,而是阿里聚安全(SecurityGuard)多阶段初始化 + AVMP虚拟化签名的结果。整个调用链涉及6次 doCommandNative 调用,最终通过命令码 60902 生成 wToken。
┌──────────────────────────────────────────────────────────────────┐
│ wToken 生成完整调用链 │
├─────────┬──────────────────────────────────┬─────────────────────┤
│ 命令码 │ 功能 │ 关键输入参数 │
├─────────┼──────────────────────────────────┼─────────────────────┤
│ 10101 │ SDK主初始化 │ Context, 3, "", │
│ │ │ /app_SGLib, "" │
│ 10102 │ libsgmainso 插件初始化 │ "main", 5.4.193, │
│ │ │ so路径 │
│ 10102 │ libsgsecuritybodyso 初始化 │ "securitybody", │
│ │ │ 5.4.112, so路径 │
│ 10102 │ libsgavmpso 插件初始化 │ "avmp", 5.4.1002, │
│ │ │ so路径 │
│ 60901 │ AVMP 实例创建 │ "0335_mwua", │
│ │ │ "sgcipher" │
│ 60902 │ ★ wToken 签名生成 ★ │ AVMP实例ID, "sign", │
│ │ │ 请求体JSON, 参数数组 │
└─────────┴──────────────────────────────────┴─────────────────────┘
二、各阶段详细分析
阶段1: SDK主初始化 (10101)
调用方法: JNICLibrary.doCommandNative(10101, args)
输入参数:
args[0] = Context对象 (com.rytong.ceair.CeairApp)
args[1] = 3 (SDK版本标志)
args[2] = "" (空字符串)
args[3] = "/data/user/0/com.rytong.ceair/app_SGLib" (数据目录)
args[4] = "" (空字符串)
返回值: 0 (成功)
关键说明: 此阶段为SecurityGuard SDK的核心初始化,低版本unidbg(0.8.0)返回null而非0,需手动补环境。建议使用 unidbg 0.9.5 版本,可避免 SecException(0xC7) 错误。
阶段2: 插件链初始化 (10102 × 3)
此命令码被连续调用3次,分别初始化3个不同的安全插件SO:
2.1 libsgmainso-5.4.193.so
args[0] = "main"
args[1] = "5.4.193"
args[2] = "/data/app/.../lib/arm64/libsgmainso-5.4.193.so"
2.2 libsgsecuritybodyso-5.4.112.so
args[0] = "securitybody"
args[1] = "5.4.112"
args[2] = "/data/app/.../lib/arm64/libsgsecuritybodyso-5.4.112.so"
2.3 libsgavmpso-5.4.1002.so
args[0] = "avmp"
args[1] = "5.4.1002"
args[2] = "/data/user/0/.../app_SGLib/.../main/libsgavmpso-5.4.1002.so"
返回值: 全部为 0
注意: 初始化顺序不可更改,AVMP实例依赖于前序插件的注册。
阶段3: AVMP实例创建 (60901)
调用方法: JNICLibrary.doCommandNative(60901, args)
输入参数:
args[0] = "0335_mwua" (业务标识/场景码)
args[1] = "sgcipher" (加密类型标识)
返回值: 475517112478 (AVMP实例ID,需 & 0xffffffffL 处理)
作用: 创建一个AVMP(阿里虚拟机保护)实例,用于后续的加密签名操作。0335_mwua 是某航APP的业务标识,不同APP此值不同。
阶段4: wToken 生成 (60902) ★
调用方法: JNICLibrary.doCommandNative(60902, args)
外层参数:
args[0] = AVMP实例ID (475517112478)
args[1] = "sign" (操作类型)
args[2] = byte[].class (返回类型声明)
args[3] = innerArgs (内层参数数组)
内层参数 (aryobj1):
innerArgs[0] = 3 (标志位)
innerArgs[1] = 请求体JSON的字节数组 (如: {"req":"S6TVWfiiBE2Z..."})
innerArgs[2] = 请求体JSON的长度
innerArgs[3] = "" (空字符串)
innerArgs[4] = new byte[4] (4字节空数组)
innerArgs[5] = 0 (标志位)
Hook返回结果:
QllUUl9yUncwbkFVNFNpWTg5WnlnVldBcnZ2bmZuYXhVZnY4WHE4WDREcGhvNmNY
SkdzM2dMMjZ5YUV6dndoRUorSjF5V1ZRVHNxZnBndk1PdVW5zNVdyMzg3cFM2VTJi
akFPZTExZ1NzakNSRWplcEJ0NmVYU2w0eHNtWll2UFV0MmYxVTM5TEFyN01ZbzVo
ZDMzS0gzKzQ1aXd4aDY0TGdVaVViPaXRTRjExTmNoRnpjcHlyOFE3Q2NZPSZBV0VS
X2EwMDEzOTIzNzc5M2I1YWE5YjJiYWVkZWI4NWYwOWM1ZDAwYWYxZDU3ZTE4NQ==
Base64解码后:
BYTR_rRw0nAU4SiY89ZyGVArvvnfnaxUfv8Xq8X4Dpho6cXJGs3gL26yaEvzwhE
J+J1yWVPQsqfpgvMOuUns5Wr387pS6U2bjAOe11gSsjCREjepBt6eXSl4xsmZYvP
Ut2f1U39LAr7MYo5hd33KH3+45iwxh64LgUiUbPiTSF11NchFzcpyr8Q7CcY==&A
WER_a00139237793b5aa9b2baedeb85f09c5d00af1d57e185
wToken 结构解析:
┌────────────────────────────────────────────────────────────────────┐
│ wToken 格式分解 │
├────────────────────────────────────────────────────────────────────┤
│ BYTR_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx== │
│ └┬┘ └─────────────────── 签名主体 ────────────────────────┘ │
│ │ │ │
│ └─ 签名类型标识 (Base64+RSA密文) │ │
│ │ │
│ &AWER_a00139237793b5aa9b2baedeb85f09c5d00af1d57e185 │ │
│ └┬─┘ └─────────────────── 设备指纹 ────────────────────────┘ │
│ │ │
│ └─ 设备标识符 (a001 = appKey前缀, 后跟设备哈希) │
└────────────────────────────────────────────────────────────────────┘
三、Java 层调用链
3.1 完整调用栈(从Frida Hook获取)
acc.intercept(EncodeRequestInterceptor.kt:92) ← 加密拦截器注入点
└── aej.a(AliPreWorm.java:86) ← 阿里安全壳封装
└── avmpSign(AVMPInstance, "sign", data, args) ← AVMP签名入口
└── invokeAVMP(instance, "sign", byte[], args) ← JNI调用
└── JNICLibrary.doCommandNative(60902, ...) ← Native层入口
└── libsgmainso ← 主安全库
└── libsgavmpso ← AVMP虚拟机
3.2 关键类文件定位
| 类文件 | 功能 | 行号 |
|---|---|---|
EncodeRequestInterceptor.kt |
OkHttp请求拦截器,注入wToken | 92 |
AliPreWorm.java |
阿里安全壳调用封装 | 54(初始化), 86(签名) |
TransactionIdInterceptor.kt |
交易ID注入 | 59 |
SplashActivity.kt |
启动页,触发SDK初始化 | 477 |
CeairApp.java |
Application类,同意隐私后初始化 | 86 |
四、Unidbg 复现方案
4.1 版本选择
| 版本 | 结果 | 说明 |
|---|---|---|
| 0.8.0 | ⚠️ 需大量补环境 | 无自动环境修补,但可逐一手动补 |
| 0.9.5 | ✓ 推荐 | 社区验证的最佳版本,SecException最少 |
| 0.9.6+ | ✗ SecException(0xC7) | 自动补环境反而导致问题 |
| 0.9.7 | ✗ 需特殊处理 | null初始化SecException可绕过 |
4.2 SO 文件选择
必须使用 32 位 (armeabi-v7a) SO 文件,64位在unidbg中兼容性差。
需要提取的SO文件:
libsgmainso-5.4.193.so (主安全库)
libsgsecuritybodyso-5.4.112.so (安全体插件)
libsgavmpso-5.4.1002.so (AVMP虚拟机)
4.3 完整 Unidbg 代码
基于看雪 shmilyaxy 的分析,核心调用顺序如下:
// 1. 创建模拟器(32位)
emulator = new AndroidARMEmulator("com.rytong.ceair");
// 2. 创建DalvikVM
vm = emulator.createDalvikVM(new File("base.apk"));
vm.setJni(this);
// 3. 加载主SO
DalvikModule dm = vm.loadLibrary(new File("libsgmainso-5.4.193.so"), true);
dm.callJNI_OnLoad(emulator);
// 4. 10101 - SDK初始化
ArrayObject init_arg = new ArrayObject(
vm.resolveClass("android/content/Context").newObject(null),
DvmInteger.valueOf(vm, 3),
new StringObject(vm, ""),
new StringObject(vm, "/data/user/0/com.rytong.ceair/app_SGLib"),
new StringObject(vm, "")
);
DvmObject<?> res1 = JNICLibrary.callStaticJniMethodObject(
emulator, "doCommandNative(I[Ljava/lang/Object;)Ljava/lang/Object;",
10101, init_arg
);
// 5. 10102 - 初始化3个插件(依次执行)
// ... libsgmainso, libsgsecuritybodyso, libsgavmpso
// 6. 60901 - 创建AVMP实例
ArrayObject vmp_arg = new ArrayObject(
new StringObject(vm, "0335_mwua"),
new StringObject(vm, "sgcipher")
);
DvmObject<?> vmp_res = JNICLibrary.callStaticJniMethodObject(..., 60901, vmp_arg);
long avmpId = Long.valueOf(vmp_res.getValue().toString()) & 0xffffffffL;
// 7. 60902 - 生成wToken
String requestBody = "{\"req\":\"...base64...\"}";
ArrayObject inner = new ArrayObject(
DvmInteger.valueOf(vm, 3),
new ByteArray(vm, requestBody.getBytes()),
DvmInteger.valueOf(vm, requestBody.length()),
new StringObject(vm, ""),
new ByteArray(vm, new byte[4]),
DvmInteger.valueOf(vm, 0)
);
ArrayObject outer = new ArrayObject(
DvmLong.valueOf(vm, avmpId),
new StringObject(vm, "sign"),
vm.resolveClass("[B"),
inner
);
DvmObject<?> wToken = JNICLibrary.callStaticJniMethodObject(..., 60902, outer);
System.out.println(wToken); // 输出: BYTR_...&AWER_...
4.4 必补 JNIEnv 环境
@Override
public DvmObject<?> callObjectMethod(VM vm, DvmObject<?> dvmObject,
String signature, VarArg varArg) {
switch (signature) {
// 应用路径
case "android/content/Context->getPackageCodePath()Ljava/lang/String;":
return new StringObject(vm, "/data/app/com.rytong.ceair.apk");
// 文件目录
case "android/content/Context->getFilesDir()Ljava/io/File;":
return vm.resolveClass("java/io/File").newObject(
new File("/data/data/com.rytong.ceair/files"));
// WiFi MAC地址(固定值)
case "android/net/wifi/WifiInfo->getMacAddress()Ljava/lang/String;":
return new StringObject(vm, "02:00:00:00:00:00");
}
return super.callObjectMethod(vm, dvmObject, signature, varArg);
}
@Override
public DvmObject<?> getObjectField(VM vm, DvmObject<?> dvmObject,
String signature) {
switch (signature) {
case "android/content/pm/ApplicationInfo->nativeLibraryDir:Ljava/lang/String;":
return new StringObject(vm, "/data/app/.../lib/arm");
}
return super.getObjectField(vm, dvmObject, signature);
}
@Override
public DvmObject<?> callStaticObjectMethod(VM vm, DvmClass dvmClass,
String signature, VarArg varArg) {
switch (signature) {
case "android/app/ActivityThread->currentPackageName()Ljava/lang/String;":
return new StringObject(vm, "com.rytong.ceair");
case "android/os/SystemProperties->get(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;":
String key = (String) varArg.getObject(0).getValue();
if (key.contains("ro.serialno")) return new StringObject(vm, "94CX1Z56A");
return new StringObject(vm, "");
}
return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);
}
@Override
public int getStaticIntField(VM vm, DvmClass dvmClass, String signature) {
switch (signature) {
case "android/os/Build$VERSION->SDK_INT:I":
return 23; // Android 6.0
}
return super.getStaticIntField(vm, dvmClass, signature);
}
4.5 SecException(1910) 解决方案
错误含义: 非法的AVMP实例(Invalid AVMP instance)
根本原因: 60901创建的AVMP实例ID未正确传递,或实例在后续调用时已被释放。
解决步骤:
- 确认60901返回非null值(应为一个Long数值)
- 对返回值执行
& 0xffffffffL转换为有效实例ID - 确认使用 32位 SO文件
- 如仍报错,尝试降级到 unidbg 0.9.5
- 极端方案:自行构造SecException对象返回,跳过异常中断
五、梆梆加固与阿里安全的关系
┌────────────────────────────────────────────────────────────────────┐
│ 某航APP 安全架构全景图 │
├────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 梆梆加固企业版 │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │ │
│ │ │ libDexHelper │ │ libdexjni.so │ │ CheckCode (MD5) │ │ │
│ │ │ (SO加载) │ │ (JNI桥接) │ │ 设备指纹校验 │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────────┘ │ │
│ │ ↓ 透明代理加密请求体 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 阿里聚安全 (SecurityGuard) │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │ │
│ │ │ libsgmainso │ │libsgsecurity │ │ libsgavmpso │ │ │
│ │ │ (主引擎) │ │ bodyso │ │ (AVMP虚拟机) │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────────┘ │ │
│ │ ↓ doCommandNative(60902) │ │
│ │ ↓ 生成 wToken │ │
│ │ 格式: BYTR_{签名}==&AWER_{设备ID} │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ ↓ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 其他风控组件 │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │ │
│ │ │ 同盾 SDK │ │ 极光推送 │ │ 支付宝风控 │ │ │
│ │ │ (libtdvm.so) │ │(libjiguang) │ │ (apdidToken) │ │ │
│ │ │ ★ 未启用 │ │ 独立运行 │ │ color令牌 │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────────────────┘
分工说明
| 组件 | 功能 | 状态 |
|---|---|---|
| 梆梆加固 | SO保护、代码混淆、请求体透明加密 | 主防线 |
| 阿里聚安全 | wToken生成、设备指纹、反篡改 | 风控核心 |
| CheckCode | 设备信息MD5校验(梆梆) | 辅助校验 |
| 同盾SDK | 备用风控(初始化失败) | 未实际工作 |
| 支付宝 | apdidToken设备标识 | 辅助风控 |
六、关键数据汇总
6.1 插件版本号(随APP版本变化)
| 插件 | 版本 | 文件路径示例 |
|---|---|---|
| libsgmainso | 5.4.193 | .../lib/arm64/libsgmainso-5.4.193.so |
| libsgsecuritybodyso | 5.4.112 | .../lib/arm64/libsgsecuritybodyso-5.4.112.so |
| libsgavmpso | 5.4.1002 | .../app_SGLib/.../main/libsgavmpso-5.4.1002.so |
6.2 AVMP业务标识
场景码: 0335_mwua
加密类型: sgcipher
6.3 wToken 返回示例(真实Hook数据)
BYTR_rRw0nAU4SiY89ZyGVArvvnfnaxUfv8Xq8X4Dpho6cXJGs3gL26yaEvzwhEJ+
J1yWVPQsqfpgvMOuUns5Wr387pS6U2bjAOe11gSsjCREjepBt6eXSl4xsmZYvPUt2
f1U39LAr7MYo5hd33KH3+45iwxh64LgUiUbPiTSF11NchFzcpyr8Q7CcY==&AWER_
a00139237793b5aa9b2baedeb85f09c5d00af1d57e185
BYTR = 阿里签名标识,AWER 后的 a001 为APP密钥前缀,后续为设备哈希。
七、总结与建议
7.1 核心结论
- wToken来源: 100% 确认来自阿里SecurityGuard SDK的
doCommandNative(60902),非梆梆生成 - 调用入口:
EncodeRequestInterceptor.kt:92→AliPreWorm.java:86 - 虚拟化保护: 使用AVMP(阿里虚拟机保护),直接逆向native代码极难
- Unidbg可行: 通过unidbg模拟调用可复现完整wToken生成流程
- 环境敏感: 对Android环境参数(SDK版本、MAC地址、序列号等)有强校验
7.2 实战建议
| 方案 | 难度 | 成功率 | 说明 |
|---|---|---|---|
| Unidbg模拟调用 | 中 | 高 | 推荐方案,需补全JNIEnv环境 |
| Frida Hook注入 | 中 | 中 | 需绕过梆梆反Frida检测 |
| 算法还原 | 极高 | 低 | AVMP虚拟化保护,不建议 |
| 真机RPC转发 | 低 | 高 | 最稳定方案,需维护设备池 |
7.3 Unidbg版本建议
强烈推荐使用 unidbg 0.9.5,该版本经社区多人大规模验证,对阿里SecurityGuard系列的兼容性最佳。高于此版本(0.9.6+)的自动环境修补机制反而会导致 SecException(0xC7/1910) 系列错误。
八、参考资料
| 来源 | 作者 | 链接 | 核心贡献 |
|---|---|---|---|
| 看雪社区 | shmilyaxy | bbs.kanxue.com/thread-277006.htm | 完整doCommandNative调用链 + Unidbg代码 |
| 博客园 | 🐳.城南 | cnblogs.com/dockers/p/19238044 | 梆梆去壳 + Frida分析 |
| GitHub | irabbit666666 | github.com/irabbit666666/wtoken2023 | wToken2023通用库 |
| 91fans | - | 91fans.com.cn/post/iboxone/ | genericNt3 Frida Hook |
| 看雪社区 | - | bbs.kanxue.com/thread-279374.htm | 梆梆企业版加固分析 |
| CSDN | 编程达人_study | blog.csdn.net/weixin_44251614 | ibox wToken加密分析 |
本报告仅供安全研究和学习交流使用,请遵守相关法律法规。

浙公网安备 33010602011771号