移动端协议逆向:从黑盒到白盒AES的完整链路剖析

发布时间:2026/7/30 1:20:29
移动端协议逆向:从黑盒到白盒AES的完整链路剖析 1. 项目概述一次对移动端协议算法的深度“考古”最近在分析一些移动端应用的数据交互时不可避免地会碰到像小红书这类头部App。它们的接口协议往往封装严密加密逻辑层层嵌套对于想要理解其数据流转机制或进行安全研究的人来说就像面对一个黑盒。这次我决定对这个“黑盒”进行一次彻底的逆向工程目标不仅仅是找到加密的入口而是完整地梳理出从应用启动、协议初始化到核心加密算法如AES被调用并最终生成请求参数的整个技术链路。这不仅仅是调用几个加密函数那么简单它涉及到对ARM指令集的理解、对现代App保护技术如混淆、加固的对抗以及对密码学算法在业务场景中具体实现方式的白盒化分析。这个过程我称之为“协议算法考古”。它适合对移动安全、逆向工程或协议分析感兴趣的开发者、安全研究员。通过这次拆解你不仅能获得针对特定App的分析方法更能掌握一套应对复杂、高保护强度客户端的技术思路和工具链。无论是为了学习先进的软件保护技术还是为了在合规前提下进行深度的产品交互逻辑研究这条从“跳转表”追踪到“白盒AES”的路径都充满了挑战和收获。2. 核心思路与逆向工程方法论选择面对一个像小红书这样体量的App直接进行无差别的静态分析或动态调试效率会非常低下且容易在庞大的代码海洋中迷失方向。因此一个清晰的、自上而下的分析策略至关重要。我的核心思路是“由外而内由果溯因”。2.1 切入点选择网络请求捕获与协议特征识别一切始于对网络流量的观察。使用代理工具如Charles或mitmproxy拦截App的HTTPS流量是第一步。你会发现即使安装了自定义证书许多关键请求的请求体或关键参数如x-sign,x-t,x-s等仍然是加密的或者是一长串无规律的字符。这就是我们的“果”——加密后的协议数据。我们的目标是找到生成这些数据的“因”。此时需要识别协议的特征。例如某些参数的长度固定如128位、256位暗示可能是AES/CBC的输出或者存在特定的前缀、编码格式Base64、Hex。同时观察请求的上下文哪些操作会触发加密登录、刷新feed、发布笔记建立操作与加密事件的关联能为后续的动态跟踪提供关键场景。2.2 逆向路径规划从动态跟踪到静态定位确定了加密发生的位置网络层和时机特定操作后下一步是找到代码中执行加密的逻辑。这里我选择“动态跟踪”先行。使用Frida、Xposed等运行时插桩框架Hook关键的网络库函数如OkHttp的Interceptor、RequestBody.writeTo或更底层的SSL_write。通过打印调用栈Stack Trace我们可以快速定位到是App中哪个模块、哪个类的方法最终负责组装和加密请求参数。这个过程就像在茫茫指令集中埋设探测器当加密事件被触发时探测器会告诉我们“爆炸点”的精确坐标类名、方法名。获得坐标后再转向静态分析。将App的安装包APK/IPA进行反编译使用Jadx、GDA for Android或IDA Pro、Hopper for iOS直接查看目标方法的代码。2.3 对抗代码混淆与加固静态分析很少会一帆风顺。你面对的很可能是经过严重混淆的代码类名、方法名、变量名被替换成无意义的a、b、c控制流被扁平化或虚假化。这时之前动态跟踪获得的信息就无比珍贵。即使方法名是a.a()但我们知道这个a.a()就是在我们Hook的点被调用的那么它很可能就是加密入口。对于加固情况更复杂。核心代码可能被加密或虚拟机保护。这就需要结合动态脱壳技术。在App运行时内存中的代码是解密后的原始状态。通过调试器如lldb、frida在合适时机如类加载器执行后对内存进行dump可以获取到可读的原始代码片段。这是一场与保护机制在时间线上的赛跑。注意所有分析工作应在拥有合法授权的设备上进行并严格遵守相关法律法规和服务条款仅用于安全研究和个人学习目的。3. 关键逆向技术点深度解析3.1 跳转表Jump Table分析与控制流还原在逆向高度混淆的代码时你经常会遇到一种结构一个大的switch-case或if-else链根据某个输入值通常是哈希值或索引跳转到不同的代码块。这就是“控制流扁平化”混淆技术的核心俗称“跳转表”。它破坏了代码原本清晰的逻辑结构将其打散成大量顺序执行的基本块并通过一个中央分发器来调度极大地增加了静态分析的难度。如何识别与破解定位分发器寻找一个函数它有一个主要的循环或switch内部包含大量看似无关的赋值和计算最终通过一个变量比如index来决定跳转目标。这个函数通常就是混淆后的原始函数体。动态追踪索引值在分发器入口处下断点或使用Frida Hook打印出每次执行时用于跳转的index或key的值。同时记录下这次执行对应的业务逻辑比如是在生成x-sign参数时调用的。通过多次操作你可以建立起index值与具体功能如MD5计算、AES加密、Base64编码的映射关系。手动/半自动还原有了映射关系后你可以将分散在各个基本块里的代码按照其对应的功能逻辑重新“拼接”起来。例如所有index在100-200范围内的块可能共同组成了AES的Key扩展逻辑。一些高级的逆向工具如IDA Pro的插件可以辅助进行这种控制流图CFG的还原。实操心得对付跳转表耐心和记录是关键。我通常会创建一个Excel表格记录下每个index值、触发该跳转的操作场景、以及该基本块内完成的主要操作如“调用了MessageDigest.getInstance(MD5)”。当记录了几十个映射后整个加密函数的逻辑轮廓就会逐渐清晰。3.2 加密算法识别与密钥定位找到疑似加密的函数后下一步是确认它使用了什么算法以及密钥在哪里。对于对称加密如AES寻找以下模式常量识别在代码中搜索算法相关的字符串常量如AES、AES/CBC/PKCS5Padding、RSA、MD5。即使字符串被混淆其对应的字节数组也可能在数据段中找到。此外AES的S盒Substitution Box是一个包含256个固定值的数组在二进制中具有很高的熵和特定模式是识别AES实现的强信号。API追踪对于使用标准库如Java的Javax.Crypto、iOS的CommonCrypto的情况直接HookCipher.getInstance(),Cipher.init()等方法可以快速获取算法模式、密钥和IV初始化向量。密钥来源分析密钥很少会硬编码在代码中。更常见的是动态生成由设备指纹IMEI、Android ID、用户Token、当前时间戳等因子通过特定算法如HMAC-SHA256计算得出。需要逆向这个生成算法。服务器下发在登录或会话初始化时从服务器响应中获取一个加密的密钥包客户端用本地固定的或非对称加密算法解密后使用。代码拼接与变换将密钥分段存储在不同位置或对原始字节进行简单的异或、加减操作后使用运行时再还原。案例分析在小红书的某个版本中我发现用于加密请求体的AES密钥是由用户的session_id和当前请求的path接口路径通过一个自定义的哈希函数计算出一个中间值再与该次请求的timestamp进行某种位运算后生成的。这意味着不同接口、不同时间、不同用户的加密密钥都不同极大地增加了逆向和重放的难度。3.3 白盒密码学White-box Cryptography应对策略这是本次逆向中最硬核的部分。传统的密码学假设攻击者无法接触密钥存储和运算过程黑盒。而白盒密码学则假设攻击者可以完全访问算法的执行环境和中间状态其目标是在这种“白盒”环境下仍能保护密钥安全。小红书等App很可能使用了商业的白盒AES库。白盒AES的特点查表操作T-Table将AES的轮运算字节替换、行移位、列混合预先计算并合并成若干张巨大的查找表通常为4张1024字节的表。加密过程不再是标准的AES步骤而是变成了对这几张表的一系列查表和异或操作。密钥与代码/数据融合原始密钥被编码并打散到这些查找表和程序的控制流中无法通过简单的内存扫描找到完整的密钥。外部编码输入输出数据可能经过随机化的线性或仿射变换使得直接观察到的输入输出与标准AES的输入输出不对应。逆向白盒AES的途径识别白盒实现在代码中看到大量通常是KB级别的静态字节数组并且加密函数的主要逻辑是围绕这些数组进行复杂的索引计算和异或基本可以判定是白盒实现。动态分析提取“等效密钥”虽然无法直接提取原始密钥但我们的目的往往是生成合法的加密数据。因此可以尝试输入输出对采集通过Hook收集大量的明文输入和对应的密文输出。符号执行或污点分析使用高级分析工具追踪输入数据在整个白盒计算过程中的传播路径。这非常困难但对简化模型有帮助。尝试“黑盒化”如果白盒算法本身没有与设备指纹等强绑定我们可以将整个加密函数包括它依赖的所有静态数据视为一个“黑盒函数”。在客户端模拟执行这个函数只要输入相同就能得到相同的输出。这可以通过用Frida调用原函数或者将其关键逻辑用Python/C重新实现来实现。寻找实现漏洞一些白盒实现可能存在设计或实现上的弱点例如编码变换是固定的、查找表保护不充分等可能为提取密钥或简化分析提供突破口。注意白盒逆向是极其耗时和需要深厚密码学及逆向功底的工作。对于大多数业务场景能够定位到加密函数并实现其“黑盒”调用已经足以完成协议模拟。4. 完整链路构建与协议复现4.1 链路串联从用户操作到加密请求将前面分析的点串联起来就形成了完整的协议链路。以下是一个简化但典型的流程触发用户在App点击“刷新”按钮。参数组装客户端代码组装请求参数包括业务参数如page_id,cursor、设备信息device_id,model、时间戳t等。排序与拼接将所有待签名的参数按照特定规则如字母序排序并拼接成字符串rawString。这个规则需要逆向确定。生成签名Sign对rawString使用某种哈希算法如MD5、SHA256或HMAC算法计算签名得到sig。密钥可能是固定的也可能是动态生成的。这个sig可能就是x-sign参数或者用于后续加密。构造待加密数据包将业务参数和签名sig组装成一个JSON或特定格式的字符串plainText。白盒AES加密根据当前会话、接口路径、时间戳等因子动态计算或获取本次加密使用的AES密钥key和初始化向量IV。调用白盒AES加密函数以CBC模式对plainText进行加密得到密文cipherBytes。编码与封装将cipherBytes进行Base64编码得到最终的payload字符串放入HTTP请求的body中。同时将t、key的索引或IV等必要但不敏感的参数放在请求头如x-t,x-iv中。发送发送HTTPS请求。4.2. 复现环境搭建与代码实现为了验证分析结果我们需要在PC如Python环境上复现整个加密流程。提取关键代码与数据从反编译的代码中提取出参数排序规则、哈希函数、密钥生成算法、以及白盒AES的查找表T-Tables和调度逻辑。对于白盒AES如果无法完全理解其内部变换可以考虑使用frida的Interceptor将整个加密函数在调用时的输入、输出以及必要的上下文如this指针记录下来然后在Python中直接用ctypes调用从内存中dump出来的so库函数或者使用unidbg这类模拟执行框架来调用这个原生函数。Python复现对于标准算法部分排序、HMAC直接用Python的hashlib、hmac库实现。对于自定义的密钥生成算法严格按照逆向出来的逻辑用Python重写。对于白盒AES如果成功提取了逻辑可以用Python实现查表运算如果选择调用so库则需使用ctypes进行绑定。验证用相同的输入业务参数、设备信息、时间戳运行自己的Python脚本将生成的payload与抓包得到的真实payload进行对比。如果完全一致恭喜你链路复现成功。一个简化的伪代码示例import hashlib import hmac import base64 import time from my_whitebox_aes import WhiteboxAES # 假设已实现的白盒AES模块 def generate_x_sign(params, secret_key): # 1. 参数排序并拼接 sorted_params sorted(params.items(), keylambda x: x[0]) raw_str .join([f{k}{v} for k, v in sorted_params]) # 2. 使用HMAC-SHA256生成签名 signature hmac.new(secret_key.encode(), raw_str.encode(), hashlib.sha256).hexdigest() return signature def encrypt_request_body(params, session_id, api_path): # 生成动态密钥和IV dynamic_key generate_dynamic_key(session_id, api_path, int(time.time())) iv generate_iv(dynamic_key) # 组装明文数据 plain_data { params: params, sign: generate_x_sign(params, dynamic_key), t: int(time.time() * 1000) } import json plain_text json.dumps(plain_data, separators(,, :)).encode() # 紧凑格式 # 白盒AES加密 cipher WhiteboxAES(dynamic_key, iv) cipher_bytes cipher.encrypt(plain_text) # Base64编码 payload base64.b64encode(cipher_bytes).decode() return payload, dynamic_key[:8] # 返回payload和key标识用于请求头 # 使用 params {page_id: home, cursor: 0} session user_session_123 path /api/sns/v1/feed payload, key_tag encrypt_request_body(params, session, path) print(fPayload: {payload}) print(fX-Key-Tag: {key_tag})5. 逆向过程中的典型问题与排查实录在长达数周的逆向过程中我遇到了无数坑点。这里记录几个最具代表性的问题及其解决方案。5.1 问题一动态Hook不到加密函数现象使用Frida Hook常见的Cipher.init,MessageDigest.update等函数在触发网络请求时没有任何输出。排查思路检查Hook时机App可能在后端线程或初始化时就完成了密码学实例的创建并缓存起来后续直接使用。尝试在App启动早期就附加Frida并Hook。App可能使用了自定义实现或BoringSSL没有使用标准JCA/JCE接口而是自己实现了加密算法或者使用了Google的BoringSSL库Native代码。需要将Hook目标转向Native层。解决方案使用Frida的Interceptor.attach在Native库如libcrypto.so,libssl.so中查找并Hook类似AES_encrypt,EVP_EncryptUpdate这样的符号。命令如frida-trace -U -i “AES_encrypt” com.xiaohongshu。存在反调试/反Hook检测App检测到了Frida等工具的存在主动崩溃或跳过了加密逻辑。解决方案使用更隐蔽的Hook方式如修改frida-gadget的配置名或者使用基于内核的调试手段也可以尝试在模拟器或root后的真机中使用强隐藏工具。5.2 问题二白盒AES的查找表巨大且访问模式复杂现象成功定位到一个巨大的白盒AES函数内部有数个KB级别的静态数组T-Tables但代码逻辑极其复杂难以人工还原其加密流程。排查与解决确认是否为标准白盒查阅一些开源的白盒AES实现如Chows White-Box AES对比其查表结构和算法步骤。如果结构相似可以尝试将目标App的查找表dump出来与标准实现进行对比分析其编码变换。采用“函数级”黑盒复用如果我们的目标不是破解白盒而是复现协议可以放弃理解内部逻辑。将包含这个白盒函数及其所有静态数据的整个Native库.so文件dump出来。然后在自己的程序中通过dlopen和dlsym或Python的ctypes加载这个库直接调用这个加密函数。这需要精确知道函数的签名参数类型、顺序、返回值。如何获取函数签名通过静态分析调用该函数的上级Java/JNI代码或者通过Frida的Interceptor在调用时打印参数的内存地址和值然后分析其数据结构。使用Unidbg模拟执行这是一个强大的模拟执行框架可以模拟运行Android的so库。你可以编写Java代码加载目标so调用目标函数并指定输入参数。Unidbg会处理复杂的系统调用和内存映射让你能在PC上直接运行手机里的加密算法。这是目前处理复杂Native代码尤其是白盒密码学的利器。5.3 问题三密钥动态生成且与多因素绑定现象每次请求的加密密钥都不同且似乎与时间、设备ID、用户ID等多个因素有关规律难以捉摸。排查思路数据关联分析收集大量数百条不同时间、不同操作下的请求。记录每条请求的明文参数如果能拿到、加密后的payload、请求头中的所有字段特别是疑似与密钥相关的、以及当时的时间戳和设备信息。逆向密钥生成函数在代码中搜索与这些因素时间戳、设备ID相关的操作尤其是哈希、异或、拼接等操作。动态Hook这些操作观察其输入输出。假设与验证提出密钥生成算法的假设例如key HMAC-SHA256(主密钥, 时间戳分钟数 设备ID后8位)然后用Python实现该假设算法用收集到的数据验证其输出的密钥是否与真实请求能对应上可以通过尝试用假设的密钥解密payload来验证。寻找“密钥种子”最终的动态密钥可能由一个相对固定的“种子”生成。这个种子可能在登录时从服务器下发并保存在本地数据库或KeyChain中。逆向登录流程和本地存储逻辑是关键。我的实操心得面对复杂的协议日志和记录是你的最佳盟友。我从项目一开始就建立了一个结构化的日志系统记录每一次Hook的输出、每一个猜测的算法、每一次失败的解密尝试。这些数据在后期进行模式识别和算法验证时提供了不可替代的价值。不要怕走弯路每一个被证伪的猜想都让你离真相更近一步。逆向工程七分靠耐心两分靠技术还有一分靠运气。而充分的准备和严谨的方法能为你争取到那“一分运气”。

相关新闻

最新新闻

日新闻

周新闻

月新闻