1. 逆向分析环境搭建
搞逆向分析首先得把家伙事儿备齐。我习惯用JADX做静态反编译,配合Frida动态调试,再加上Unidbg模拟执行,这三件套基本能应付大多数场景。某宝APP建议选9.XX版本,太新的版本可能加了更强的防护。
装JADX没啥难度,官网下载解压就能用。重点说下Frida的环境配置:
pip install frida-tools adb push frida-server /data/local/tmp/ adb shell "chmod 755 /data/local/tmp/frida-server" adb shell "/data/local/tmp/frida-server &"Unidbg的配置稍微麻烦点,需要自己编译so库。我一般先拉取最新代码:
git clone https://github.com/zhkl0228/unidbg cd unidbg mvn clean package遇到过最坑的问题是libsgmainso的版本兼容性。某宝不同版本的so库加密逻辑可能有差异,建议用apktool解包后,把lib/arm64下的这些关键so文件都备份出来:
- libsgmainso-6.5.25.so
- libsgsecuritybodyso-6.5.33.so
- libsgmiddletierso-6.5.27.so
2. 关键算法定位技巧
抓包只是第一步,用Charles或Fiddler都能看到请求头里的x-sign、x-mini-wua这些参数。真正的挑战是怎么找到它们的生成位置。
我常用的三板斧:
- 字符串搜索法:在JADX里直接搜"x-sign",会找到网络请求封装类
- 堆栈回溯法:在Frida里hook网络库的发送函数,打印调用栈
- 黑盒调用法:用Unidbg直接调so导出函数
最近发现某宝把核心逻辑都移到了libsgmiddletierso里。比如x-sign的生成入口通常是doCommandNative这个JNI函数,对应命令号70102。用Frida hook的脚本长这样:
Interceptor.attach(Module.findExportByName("libsgmiddletierso", "doCommandNative"), { onEnter: function(args) { console.log("命令号:", args[2]); console.log("参数1:", Java.vm.getEnv().getStringUtfChars(args[3], null).readCString()); } });3. x-sign算法逆向实战
x-sign的生成其实是个多阶段加密过程。通过动态调试发现,它会先拼接以下参数:
- 设备ID
- 时间戳
- 接口名称
- 版本号
- 随机盐值
然后用HMAC-SHA256做签名,关键代码在libsgmainso的Java_com_taobao_wireless_security_adapter_SecurityGuardAdapter_doCommandNative函数里。用Unidbg模拟调用的代码结构如下:
public void generateXSign() { DalvikModule dm = vm.loadLibrary("libsgmainso", true); dm.callJNI_OnLoad(emulator); Object ret = JNICLibrary.callStaticJniMethodObject( emulator, "doCommandNative(I[Ljava/lang/Object;)Ljava/lang/Object;", 70102, new ArrayObject( new StringObject(vm, "21646297"), new StringObject(vm, "mtop.taobao.search.highway.upload"), // 其他参数... ) ); System.out.println(ret.getValue()); }实际测试发现,x-sign每30分钟会失效,因为服务端会校验时间戳的合理性。对抗方案可以考虑时间同步补偿,在生成签名时动态调整时间差。
4. x-mini-wua的破解之道
这个参数比x-sign更棘手,它包含了设备指纹信息。逆向发现主要来自libsgsecuritybodyso的getSecurityToken方法。
核心生成逻辑分三步:
- 采集设备特征(CPU序列号、传感器列表等)
- 用AES-CBC模式加密
- Base64编码后拼接版本号
用Frida dump内存中的加密密钥是个实用技巧:
var ptr = Module.findBaseAddress("libsgsecuritybodyso").add(0x123456); console.log(hexdump(ptr, { length: 32 }));在Unidbg中调用时要注意初始化上下文:
public void callSecurityBody() { DalvikModule dm = vm.loadLibrary("libsgsecuritybodyso", true); dm.callJNI_OnLoad(emulator); int ret = (Integer) JNICLibrary.callStaticJniMethodObject( emulator, "doCommandNative(I[Ljava/lang/Object;)Ljava/lang/Object;", 10102, new ArrayObject( new StringObject(vm, "securitybody"), new StringObject(vm, "6.5.33") ) ).getValue(); }5. x-sgext与x-umt的关联分析
这两个参数通常出现在搜索接口中,逆向发现它们其实是互补关系:
- x-sgext:包含搜索关键词等业务参数
- x-umt:携带用户身份令牌
关键生成函数在libsgmiddletierso的generateUT和generateSGEXT方法里。有意思的是,x-umt会用椭圆曲线加密,而x-sgext用的是CRC32+HMAC的组合算法。
用Unidbg黑盒调用时要注意参数顺序:
DvmObject<?> ret = JNICLibrary.callStaticJniMethodObject( emulator, "doCommandNative(I[Ljava/lang/Object;)Ljava/lang/Object;", 70102, new ArrayObject( new StringObject(vm, "pageId=http%3A%2F%2Fs.m.taobao.com%2Fh5entry"), new StringObject(vm, "mtop.relationrecommend.mtoprecommend.recommend"), DvmInteger.valueOf(vm, 27) ) );6. 对抗策略与防护升级
某宝的签名算法大概每3个月会有次大更新。最近发现他们开始用控制流混淆和符号表清除,给逆向增加了不少难度。
几个实用的对抗方案:
- 动态密钥:在so加载时通过JNI_OnLoad生成新密钥
- 环境检测:检查Frida等调试工具的存在
- 服务端协同:部分参数需要二次验证
对于控制流混淆,可以用angr做符号执行:
import angr proj = angr.Project('libsgmainso', auto_load_libs=False) cfg = proj.analyses.CFGFast()最关键的还是保持版本同步。建议建立自动化爬虫监控某宝的版本更新,及时获取新版so文件进行分析。