逆向分析Protobuf协议时遇到的3个典型问题及解决方案

张开发
2026/4/14 17:25:46 15 分钟阅读

分享文章

逆向分析Protobuf协议时遇到的3个典型问题及解决方案
逆向工程中Protobuf协议解析的三大实战难题与突破策略Protobuf作为Google开源的高效序列化协议在移动端应用中被广泛采用。但当我们尝试逆向分析这些应用时Protobuf协议却常常成为一道难以逾越的屏障——二进制数据流、嵌套结构、动态字段编号等特性让传统的抓包分析手段失效。本文将聚焦逆向工程师在实际工作中最常遇到的三个Protobuf解析痛点通过真实案例演示如何突破这些技术瓶颈。1. 识别与解压gzip压缩的Protobuf数据流现代移动应用为了优化传输效率普遍会对Protobuf数据进行gzip压缩。当你在Charles中看到如下所示的乱码数据时很可能就遇到了这种情况1f 8b 08 00 00 00 00 00 00 03 ed 57 4d 6f db 38 10 bd fb 57 10 3d 25 40 25 3b 4e 9b 1e 7a 28 50判断压缩类型的三个关键线索HTTP头信息检查查看Content-Encoding字段是否包含gzip魔数特征识别gzip文件开头固定为1f 8b 08数据熵值分析压缩后的数据熵值明显高于原始Protobuf解压操作可以使用Python的gzip模块简单实现import gzip def decompress_protobuf(raw_data): try: return gzip.decompress(raw_data) except OSError: return raw_data # 非压缩数据直接返回注意部分应用会自定义压缩方式此时需要逆向分析app的网络库实现实战中我曾遇到一个电商app它在传输层对Protobuf进行了分块压缩。解决方案是通过hook网络库的压缩函数直接获取未压缩数据frida-trace -U -f com.example.app -j *!*compress*2. 解析嵌套消息结构的实用技巧Protobuf允许消息嵌套定义这给逆向分析带来了巨大挑战。当遇到类似如下的数据时如何确定其结构0a 1b 0a 08 4a 6f 68 6e 20 44 6f 65 10 01 1a 09 0a 07 41 64 64 72 65 73 73嵌套消息的逆向分析方法论字段编号追踪法奇数编号通常表示嵌套开始字段类型3length-delimited往往包含子消息上下文关联分析通过URL路径推测可能的数据结构对比不同请求的相似字段模式动态插桩技术使用Frida hook Protobuf的解析函数获取运行时类型信息这里有一个我总结的嵌套消息解析流程表步骤操作工具预期结果1定位网络请求Charles/Fiddler获取原始二进制数据2提取字段模式010 Editor识别重复结构3逆向关键函数IDA/Ghidra找到消息定义4动态验证Frida/Xposed确认字段类型一个真实案例在分析某社交app的私信功能时发现其消息结构嵌套了5层。通过hookMessageLite#parseFrom方法成功获取了完整的proto定义Interceptor.attach(Module.findExportByName(libprotobuf.so, _ZN6google8protobuf11MessageLite11ParseFromERKNS0_10DescriptorEPNS0_7MessageE), { onEnter: function(args) { console.log(Parsing message with descriptor at args[1]); } });3. 字段编号匹配与proto文件还原Protobuf使用数字编号而非字段名进行序列化这导致原始.proto文件的还原成为逆向工程中最关键的环节。以下是经过多个项目验证的有效方法字段编号匹配四步法基础信息收集捕获多个相似请求的样本数据统计字段出现频率和位置类型推断技巧字段编号后的第一个字节表示类型常见类型对应关系0: VARINT2: LENGTH_DELIMITED5: 32-bit固定长度上下文关联验证对比不同操作产生的数据变化结合UI显示内容反向推导自动化辅助工具使用protod来自pbtk工具集生成初始proto人工校验和修正字段含义一个典型的.proto文件还原过程// 初始版本 - 自动生成 message Msg1 { optional int32 field1 1; optional bytes field2 2; } // 优化版本 - 人工分析后 message UserProfile { optional int32 user_id 1; // 通过注册流程确认 optional string nickname 2; // 对比修改昵称请求 }提示字段命名不必完全还原但编号必须准确。建议建立编号-功能映射表辅助分析在某金融app的逆向项目中我们发现字段编号会根据版本动态变化。解决方案是通过特征码定位到编号映射表LDR R0, [R4,#0x10] ; 加载字段编号 ADD R1, PC, #0x1234 ; 指向映射表 LDRB R0, [R1,R0] ; 获取实际编号4. 进阶技巧处理动态修改的Protobuf协议一些安全意识较强的应用会采用动态Protobuf策略这给逆向工程带来了新的挑战。以下是几种应对方案动态Protobuf的破解之道运行时协议捕获使用Frida dump内存中的Descriptor对象拦截MessageParser的初始化过程差分分析对比不同时段捕获的数据样本识别可变部分与固定部分代码定位法搜索特征字符串DescriptorPool跟踪ProtocolBuffer的注册流程一个有效的hook脚本示例const Protobuf Module.findExportByName(libprotobuf.so, _ZN6google8protobuf10Descriptor12DebugStringEv); Interceptor.attach(Protobuf, { onEnter: function(args) { this.descriptor args[0]; }, onLeave: function(retval) { console.log(ptr(retval).readCString()); } });在最近的一个车联网app逆向中我们发现其每24小时会变更字段编号。通过定时dump描述符信息最终破解了其轮换算法def predict_field_id(base_id, timestamp): seed (timestamp // 86400) % 256 return (base_id seed) % 512逆向工程本质上是一场与开发者的智力博弈。Protobuf协议虽然增加了分析难度但通过系统性的方法和持续的经验积累这些技术障碍终将被一一突破。每次成功解析出复杂数据结构时的成就感正是驱动我们不断探索的动力源泉。

更多文章