ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

短视频App mssdk get_token逆向实战:从Java到SO加密还原全解析

短视频App mssdk get_token逆向实战:从Java到SO加密还原全解析 做短视频平台App的逆向分析最绕不开的就是mssdk里那个get_token接口。我最初接触这个接口是在分析一款短视频App的采集链路时发现所有针对用户维度、推荐流和评论列表的请求都要带上token参数而这个token每次请求都在变而且不同接口拿到的token规格还有细微差别。单纯靠抓包改参数根本追不动它的生成逻辑于是我把mssdk从整个App里拆了出来专门啃了它的协议与加密流程。这篇文章就把整个逆向过程完整复盘一遍包含我从Java层定位到SO层算法还原的完整链路以及踩过的几个比较隐蔽的坑。定位解决的是什么问题呢get_token这个接口到底怎么触发、token怎么生成、哪些参数参与了加密、如何脱离App在外部环境复现生成逻辑。适合正在做安卓逆向、爬虫参数分析、以及想搞懂短视频SDK加密体系的朋友。如果你只是临时想绕过某个接口的token校验这篇文章的排查思路也能给你一些切入点。1. 拿到目标之后先别急着逆向信息收集与方案选型很多新手拿到一个App就直接丢进jadx开始翻代码翻了一整天也不知道从哪下手。我的习惯是先把APK当作一个“黑盒”做一轮静态侦察搞清楚SDK的分布形态和可用于hook的入口点再决定静态还是动态优先。1.1 mssdk在APK中的分布形态与快速定位短视频App经过多渠道打包后mssdk一般以两种形式存在一种是以独立dex文件或分包形式集成在主APK里另一种是作为动态特征so模块配合assets目录或云端下发的配置文件共同生效。所谓mssdk其实是一个面向客户端的功能集合里面包含了网络通信、用户态鉴权、数据上报以及一批加解密组件。我在实战中定位get_token的方法很简单先把APK解包用grep快速扫一下字符串看主dex和lib目录下是否有明显指向mssdk的特征量。常见的特征包括com.**.mssdk包名、libmssdk.so之类的so文件以及在jadx里搜get_token就能看到的RN或Java桥接层代码。拿到这些特征之后用jadx打开主dex定位get_token对应的类和方法。实际App里往往不是直接暴露一个同名Java方法而是通过JNI调用SO层方法Java层只留一个native声明。如果你的动态调试经验不足不妨先在Java层把所有调用get_token的前置条件摸清楚比如入口参数里有没有appkey、设备指纹、时间戳、会话态等这些参数往往就是后续加密算法的“原料”。1.2 工具链选型与前置准备逆向工具链的选择直接影响整个项目的推进速度。我这里用到的组合比较常规但都是经过多次实战验证的jadx 1.4负责静态还原Java层逻辑重点是调用链和参数传递。IDA Pro 8.x处理libmssdk.so或对应的vmp/ollvm变种so定位JNI导出函数。Frida 16.x动态hook的核心工具能实时打印Java层方法的入参与返回值也能attach到native层看寄存器与内存。objection用于快速绕过root检测、SSL pinning等常规防护。Python 3.9 requests pycryptodome协议复现和加密算法验证的工具最终要在这里把整个get_token算法跑通。工具选型上有一条心得能动态调试的不要先静态硬啃能hook Java层解决的不要一上来就动IDA。因为不少短视频SDK里的核心加密算法会做两级封装Java层拿到的是一个已经处理过的“半成品”如果一上来就追so很容易被混淆代码带偏。1.3 逆向路线图从Java到SO的完整链路我习惯先把整个链路画成一个线性图方便后续不迷路链路大致是网络请求触发 - Java层接口调用get_token- 收集当前设备信息和请求上下文 - 拼装原始字符串 - JNI调用进入so - 在so内部完成核心加密 - 返回token字符串 - Java层包装进业务请求头。从这个链路里能明显看出需要重点攻克的是两个环节第一个是Java层收集了哪些参数第二个是so层用什么算法把这些参数变成了token。前者决定你的“输入”能不能凑齐后者决定你的“算法”能不能复现两者缺一不可。实际操作时我会先在jadx里标记所有调用get_token的调用点然后逐个查看每个调用点前置的数据构造过程同时在Frida里hook这些方法把入参和返回值全部打出来两者对照着看很快就能把Java层拼参数的方式梳理清楚。2. get_token 协议流程拆解与算法定位到了这一步我们已经完成了信息收集和链路绘制下面就开始真正动手拆协议流程。整个拆解的核心目标是回答三个问题谁在调用get_token、参数从哪里来、加密结果长什么样。2.1 静态分析用jadx还原Java层调用逻辑用jadx直接搜索“get_token”会出现一系列结果其中关键的通常是类似MSSDKTokenService或者TokenManager这样的类。我建议先看这个类的方法签名和字段定义不要急着看实现。举个例子方法签名往往是public String getToken(String str, String str2)这里的str可能是设备指纹的json串str2可能是时间戳或者随机数。字段定义里往往藏着SDK版本号、加密开关、密钥下标等关键信息。跟着调用链追下去你往往会发现token的生成并不直接由业务方触发而是经过了一层层封装。比如业务层调用MSSDK.getInstance().getToken(context)然后内部又会调用TokenBuilder.buildToken(context)最终才走到native方法。这里要特别留意一个“参数透传”的细节native层拿到的参数未必和Java层业务方传入的参数完全一致。中间有些参数是SDK内部根据包名、签名、设备ID自动补充的这些参数才是加密的核心。所以静态分析时建议对每个关键方法都做一次“入参记录”把所有输入的来源字段标出来最后汇总成一个参数表。2.2 动态HookFrida实战定位关键参数静态分析只能告诉你“应该有哪些参数”但参数的真实格式和取值必须靠动态hook来验证。Frida在这一步的作用非常大。我在实际hook中采用的方法是先hook Java层入口点打印入参和返回结果再hook native层对应的JNI函数打印so层拿到的一手输入。Java.perform(function() { var MSSDKTokenService Java.use(com.xxx.mssdk.token.MSSDKTokenService); MSSDKTokenService.getToken.overload(java.lang.String, java.lang.String) .implementation function(str, str2) { console.log([Java Hook] getToken called); console.log(arg1 str); console.log(arg2 str2); var ret this.getToken(str, str2); console.log(ret ret); return ret; }; });这段脚本并不复杂但排查效果明显。从脚本输出里能直接看到token生成前后的数据形态。比如有一次我hook后发现str参数里除了明文设备信息还包含一个sign字段这个sign明显不是Java层生成的而是SO层回填进来的。这就意味着实际算法是Java和native交替配合的不能只看单侧。Frida hook有一个细节经验建议只hook自己关心的少数几个方法不要全量hook。短视频App里SDK内部逻辑多数据上报频繁全量hook会严重拖慢运行速度甚至触发SDK内部的耗时检测导致线程被强制终止。2.3 算法初步判定Hash还是对称加密拿到一批token样本后第一件事不是急着还原算法而是先做“数据特征初步判定”。我总结了一套简单的判定维度看长度32位hex大概率是MD5或某类hash44位含等号的可能是Base64编码后的密文64位hex可能是SHA256或AES后的hex形式。看字符集全hex字符0-9a-f偏向hash或者AES转为hex含和/的偏向标准Base64含-和_的可能是URL-safe Base64。看jwt特征token里如果出现两个点.第一段是header第二段是payload第三段是签名那就是jwt结构。get_token返回的token格式不同App差异很大。我遇到过一个情况它在不同环境下返回长度还不一样——真机返回96位模拟器返回64位。后来复现源码逻辑才发现真实签名版本不同加密块的数量不同导致长度不同。所以看到长度不一时先别怀疑算法判断错了可以先考虑是不是版本分支不同。3. 加密流程深度拆解核心案例静态和动态分析都做完下面进入整个逆向过程中最有价值的部分加密流程的完整还原。这一部分我以一次实际分析的mssdk为样本把从SO层定位到函数还原再到外部复现的完整过程拉一遍。3.1 从Java层到JNIso文件里的真正加密逻辑主力so文件一般是libmssdk.so或libxxxms.so在IDA里打开之后最关键的一步是定位导出函数JNI_OnLoad以及get_token对应的native方法。找native方法对应的导出函数名有一个固定套路Java层的native方法声明是public native String nativeGetToken(String str);那么在so里对应的导出符号一般是Java_包名_类名_nativeGetToken。比如Java_com_example_mssdk_token_TokenManager_nativeGetToken。不过现在主流SDK一般都用RegisterNatives动态注册这时候不靠导出名得通过JNI_OnLoad的代码逻辑来找注册表。我用IDA打开so后先看JNI_OnLoad的伪代码在里面找RegisterNatives的调用把第二个参数指向的注册表结构dump出来。注册表是JNINativeMethod数组里面每一项包含方法名、签名和函数指针拿到函数指针后跳到对应地址就是nativeGetToken的真实实现。这一步是整个逆向的分水岭如果so没做深度混淆直接F5看伪代码就能找到加密函数如果做了Ollvm控制流平坦化那F5出来的代码基本不能看需要走动态调试或者用去混淆插件。实际上很多短视频SDK的so都做了不同程度的混淆所以我会预先准备好Frida的Native hook能力来辅助判断。3.2 加密算法还原与参数计算过程我以其中一次分析为例说明还原过程。native函数内部看起来像这样第一步获取Java层传入的字符串把它转成UTF-8字节数组。第二步从so的只读数据段取出一个16字节的固定key。第三步拼接三个元素当前毫秒级时间戳字符串、设备ID哈希、固定key。第四步用AES-CBC模式加密拼接后的数据IV是硬编码的16字节。第五步把加密结果做一轮自定义的字符错位置换再转成Base64返回。只看伪代码无法确认每一步的算法类型我当时用了一个比较笨但很有效的方法——在IDA里给关键函数下断点然后用Frida主动触发get_token调用在断点处dump内存数据。比如我在疑似AES加密函数前后分别dump了一段数据加密前的明文长度是48字节加密后的密文长度也是48字节按块对齐后结合它调用了某个置换表就能确认是AES-CBC并且后续的自定义置换也能通过对比输入输出来还原。这里我尤其要提一下IV和key的提取技巧key和iv在IDA的只读数据段里很明显但有的是被分割存储的需要看交叉引用才能拼起来。有一个便于排查的点是——如果你发现某块数据的交叉引用特别多且多次被加载到内存中作为参数传入加密函数那它有大概率就是密钥材料。我习惯用shiftF12打开Strings窗口先过滤可能的hex字符串再逐一跳到引用位置核对。3.3 完整复现用Python还原get_token加密流程还原出算法之后最激动人心的环节是外部环境完整复现。这一步能把逆向结论直接验证掉也能为后续脚本化调用铺路。下面是一个简化的Python复现示例核心逻辑与我在样本里还原出的流程保持一致import base64 import hashlib import time from Crypto.Cipher import AES SECRET_KEY b\x9c\xd3\xf1\x2a... # 从so里提取的16字节key SECRET_IV b\x0f\xe2\xab\x3c... # 从so里提取的16字节IV def custom_permute(data): # 还原出来的自定义字节错位逻辑 output bytearray(len(data)) for i in range(len(data)): output[(i * 7 3) % len(data)] data[i] return bytes(output) def generate_token(device_id, appkey): ts str(int(time.time() * 1000)).encode() device_hash hashlib.md5(device_id.encode()).hexdigest().encode() raw ts b| device_hash b| appkey.encode() cipher AES.new(SECRET_KEY, AES.MODE_CBC, SECRET_IV) pad_len 16 - (len(raw) % 16) raw_padded raw bytes([pad_len]) * pad_len encrypted cipher.encrypt(raw_padded) return base64.b64encode(custom_permute(encrypted)).decode()这个脚本跑通后我拿它生成了一批token再和App真实生成的token做了对比一段时间窗口内完全一致。这就证明了算法还原正确。注意在真实业务里token还会有过期时间、接口维度绑定等额外逻辑这些需要在Python侧按接口维度维护不能只依赖一个静态算法。4. 反调试绕过与常见问题排查实录整个逆向过程不是一路顺风的mssdk级别的SDK基本都做了反调试、root检测和so加固。这里把我实际遇到的最典型的几类问题整理成一份排查速查方便后来的人少走弯路。4.1 反调试与so加固的对抗思路短视频SDK里常见的反调试手段包括检测/proc/self/status里的TracerPid、检查常用hook端口、检测Frida服务进程名、以及关键so自定义ptrace自身。我碰到的反调试强度属于中等Java层有root检测so层有TracerPid检测。绕过思路分成两步第一步用objection或者Magisk隐藏模块隐藏root环境。这一步相对简单主要是为了过掉Java层的环境检测。第二步针对so层的TracerPid检测在IDA里定位检测函数后直接patch掉判断分支或者用Frida在native层hookfgets等文件读取函数把读到的status内容里的TracerPid篡改为0。Frida绕过TracerPid检测的脚本写法比较固定Interceptor.attach(Module.findExportByName(null, fgets), { onLeave: function(retval) { var buf Memory.readUtf8String(retval); if (buf buf.indexOf(TracerPid:) ! -1) { Memory.writeUtf8String(retval, buf.replace(/TracerPid:\s\d/, TracerPid: 0)); } } });需要提醒的是这类绕过只用于本地学习调试如果目标App有更硬核的保护不建议强行对抗否则很容易被风控系统识别甚至导致账号和设备异常。4.2 Ollvm混淆下的算法还原经验当so启用了Ollvm控制流平坦化之后IDA的F5伪代码会变成一坨“状态机”所有基本块都放在一个while循环里通过状态变量跳来跳去。直接看这种代码非常痛苦我的经验是不要让AI或静态工具硬读优先靠动态trace。具体做法是在关键函数入口处用Frida Stalker做指令级trace把执行过的基本块顺序完整记录下来然后对照汇编代码还原数据流。虽然trace数据量大但目标函数往往集中在加密逻辑里输入输出特征明显通过观察哪些基本块访问过密钥和密文缓冲区通常能锁定算法的核心路径。另一个更省力的思路是关注JNI函数传入的JNIEnv*指针调用关系有时候关键流程会直接间接调用GetStringUTFChars、NewStringUTF等函数这些函数名称在IDA里能看到顺着它们切入比硬啃状态机能更快找到核心加密函数。因为JNI交互函数难以被Ollvm完全平坦化它们常常是整个混淆代码里为数不多的“灯塔”。4.3 常见问题速查表问get_token生成的token在外部复现时总是校验不过但App内是正常的怎么排查答优先对比三块内容——参与加密的参数是否完整尤其是设备ID和包名、IV和key是否提取正确、Base64编码前是否做了额外转换比如URL-safe。我遇到过一次外部复现失败排查了很久发现是参数里多了一个“空字符串字段”Java层拼接时忽略掉了空值而我的复现脚本保留了空值导致加密结果完全不同。问Java层找不到get_token调用点但抓包能看到token参数怎么办答这种情况多半是token由native层直接生成后塞进了请求头Java层只是透传。建议直接搜请求头字段名比如x-tt-token、x-ms-token在jadx里跳转到字段定义处再找到写入该字段的位置顺藤摸瓜就能找到源头。问Frida attach后App直接闪退是什么原因答大概率是遇到了Frida检测。这种情况下建议尝试Frida的gadget模式或者改用另一种hook方式——不推荐直接硬怼可以先跑一下frida-ps -U确认Frida服务进程是否被隐藏再换用objection执行绕过脚本。如果还是闪退就考虑用模拟器里的xpose框架代替Frida因为很多App对xpose的检测比对Frida弱。问算法还原后token有效期只有几分钟定时刷新导致请求频率高容易被风控怎么办答token的过期逻辑通常在服务端客户端无法直接延长。实际处理上可以动态调用App内部的刷新接口来获取新token而不是每次重新计算加密参数。更稳妥的方案是分析刷新接口的触发条件做到“业务请求前才刷新”不要开后台线程高频预取。问so文件被加固了脱壳后IDA打开显示全是乱码怎么办答这是so加固的典型特征脱壳后的so需要用sofixer这类工具做修复修复后再用IDA加载。如果加固厂商做了自解密那么更适合的方式是运行时dump内存中的so在Frida里用Process.findModuleByName定位模块基址然后手动dump完整的内存片段用dd或者脚本整理成文件后再分析。5. 最后再聊聊协议逆向的长期维护思路get_token这类接口和普通业务接口不太一样它属于SDK底层协议一般不会频繁变动但一旦变动就极有可能是整体加密框架升级。如果只是做短期研究逆向一次获取算法就够了。如果要做长期的项目还需要搭建一个“算法独立层”把密钥提取、参数拼接、加密计算、token刷新全部独立成模块方便App升级后快速对比差异。我在实际项目里会保留一份“加密特征记录”包含key长度、iv内容、密码算法模式、base64规则、自定义置换表等关键指纹。每次App更新后只需要重新提取这些指纹然后和上一版做diff就能快速定位变动的点不用每次都从零开始逆向。整体来说mssdk中get_token的逆向难度属于中上主要难点不在算法本身而在定位过程。只要把Java层调用链路摸清楚再用Frida完成动态验证最后在IDA里用JNI函数和字符串交叉引用作为索引去定位核心逻辑大部分定制化加密都可以被还原出来。希望这篇文章的拆解思路能给你正在折腾的协议分析项目带来一点参考价值。
返回列表