
1. 项目概述与核心挑战最近在分析一些网络请求时又遇到了老熟人——Protobuf。抓包工具里看到的不再是熟悉的JSON或者XML而是一堆看似乱码的二进制数据Content-Type 赫然写着application/x-protobuf。这种情况在涉及数据压缩和传输效率的移动端App、游戏后端或者微服务通信中越来越常见。传统的玩法是你得有对应的.proto文件定义然后用官方的protoc编译器去解析。但逆向的场景里上哪去找这个定义文件难道就束手无策了这个项目的核心就是解决这个“无米之炊”的问题在没有原始.proto文件的情况下逆向解析未知结构的 Protobuf 数据。我们瞄准的工具是blackboxprotobuf这个Python库它就像个“黑盒解码器”试图从二进制数据本身推断出结构。听起来很美好但实际用起来坑不少比如网络热词里提到的google.protobuf.runtime_version.versionerror: detected incompatible protobuf这种版本兼容性问题就够喝一壶的。本文将带你从零开始搭建环境、解析数据、处理常见错误并附上能直接跑通的完整代码让你下次遇到Protobuf乱码时心里有底手上有招。2. 逆向解析的核心思路与方案选型面对一串未知的Protobuf二进制数据我们首先要理解逆向解析的本质是什么。Protobuf序列化后的数据本身包含了字段编号field number和对应的值但不包含字段名和明确的消息类型名。.proto文件的作用就是提供这份“解码字典”。逆向解析就是要尝试从数据流中反推出这份字典的近似版本至少是字段的类型和结构关系。2.1 常见逆向方法对比目前主流的无原型文件解析方法主要有两种各有优劣。方法一使用官方protoc工具的--decode_raw模式这是Protocol Buffers编译器自带的一个基础功能。当你执行protoc --decode_raw message.bin时它会尝试将二进制数据解析成一种内部的、未命名的表示形式。它的优点是直接、官方不需要额外依赖。输出结果会显示字段编号如1: 12345和推断出的值类型如变长整型、字符串、嵌套消息等。但是它的局限性非常明显首先它只能解析不能生成可用的.proto文件你看到的是“裸”的编号和值对于复杂嵌套结构理解起来依然困难。其次正如很多实践者遇到的它经常抛出Failed to parse input错误对非标准或包含未知字段的数据容错性较差。最后它完全无法处理字段名所有输出都是匿名编号对于后续的自动化处理或代码生成帮助有限。方法二使用第三方库blackboxprotobuf这正是我们项目要深入使用的方案。blackboxprotobuf是一个Python库它的目标更远大不仅解析数据还尝试推断出一个完整的类型定义包括消息类型、字段类型、甚至嵌套关系并能够将解析出的结构重新序列化为二进制形成一个“猜测-验证”的闭环。它的工作原理可以概括为“试探性解析与类型推断”。库会遍历二进制数据根据Protobuf的编码规则如Varint、64-bit、Length-delimited等识别出一个个字段。对于基本类型整数、字符串它能较好地区分。对于嵌套消息message和数组repeated它会通过分析数据格式进行递归推断。最终它输出两个东西一是解析后的Python字典包含字段名占位符如field1二是一个“类型定义”字典描述了每个字段编号对应的猜测类型。这个类型定义可以被保存、修改并用于后续对同类数据的解析是实现自动化逆向的关键。为什么选择blackboxprotobuf在逆向工程和动态分析场景下blackboxprotobuf的优势是决定性的。第一它输出结构化信息生成的类型定义是后续分析和代码生成的基石。第二它支持交互和修正。当自动推断不准时比如把bytes误判为string或者搞错了嵌套结构我们可以手动修改类型定义重新加载并解析不断逼近真实结构。第三它易于集成到Python自动化脚本中方便我们批量处理抓包数据或集成到爬虫、调试工具链里。虽然它也不是万能的会遇到版本兼容、复杂类型推断错误等问题但通过一些技巧和我们的代码封装可以极大地提高成功率和可用性。3. 环境搭建与核心工具解析工欲善其事必先利其器。在开始实战前我们需要一个稳定、兼容的环境。网络热词中提到的versionerror: detected incompatible protobuf错误其根源往往就在于环境中存在多个、版本冲突的Protobuf相关库。3.1 创建隔离的Python虚拟环境强烈建议使用虚拟环境来管理项目依赖这是避免库冲突的最佳实践。# 使用 venv 创建虚拟环境 python -m venv protobuf-reverse-env # 激活虚拟环境 # 在 Windows 上 protobuf-reverse-env\Scripts\activate # 在 macOS/Linux 上 source protobuf-reverse-env/bin/activate激活后你的命令行提示符前会出现环境名表示你已进入一个干净的Python沙箱。3.2 安装与版本控制接下来安装核心库。这里有一个关键点blackboxprotobuf对protobuf库Google官方Python库的版本有特定要求。直接pip install blackboxprotobuf可能会拉取不兼容的版本组合导致运行时错误。一个经过验证的、稳定的版本组合如下pip install protobuf3.20.3 pip install blackboxprotobuf这里我们先将官方的protobuf库固定到3.20.3这个版本然后再安装blackboxprotobuf后者会自动安装其兼容的依赖。这个组合在我多次逆向项目中表现稳定能有效规避google.protobuf.runtime_version.versionerror这类错误。注意如果你已经安装了其他版本的protobuf请先卸载 (pip uninstall protobuf) 再按上述顺序安装。有时blackboxprotobuf会尝试安装一个兼容的protobuf版本但手动指定可以更可控。3.3 验证安装与基础测试安装完成后写一个简单的测试脚本验证功能是否正常。import blackboxprotobuf # 准备一个最简单的Protobuf二进制数据 (例如: 字段1整数150) # 这个数据可以通过一个有proto文件的环境序列化得到用于验证 test_data b\x08\x96\x01 try: decoded, typedef blackboxprotobuf.decode_message(test_data) print(解码成功) print(解码数据:, decoded) print(类型定义:, typedef) except Exception as e: print(f解码失败错误信息: {e}) print(请检查protobuf库版本兼容性。)如果运行成功输出应该类似于解码成功 解码数据: {1: 150} 类型定义: {1: {type: int, name: }}这证明你的环境已经就绪。typedef中的type: int表示库正确地将字段1推断为整数类型。4. 实战解析未知Protobuf数据的完整流程现在我们进入核心实战环节。假设你从网络抓包中获取了一段十六进制字符串表示的Protobuf数据0a0766616e74617379221b0880a2dac90510e8072a094d617263204a6f6e6573。我们的目标是将它还原成可读的数据结构。4.1 数据预处理从抓包格式到Python字节流抓包工具如Fiddler, Charles, Wireshark导出的数据格式五花八门可能是十六进制字符串、Base64或者直接显示为乱码。第一步是将其统一转换为Python的bytes对象。import base64 def prepare_data(raw_data, input_formathex): 将不同格式的原始数据转换为 bytes。 :param raw_data: 原始数据字符串 :param input_format: 输入格式hex 或 base64 :return: bytes 对象 if input_format hex: # 移除可能存在的空格、0x前缀等 hex_string raw_data.strip().replace( , ).replace(0x, ) # 确保十六进制字符串长度为偶数 if len(hex_string) % 2 ! 0: raise ValueError(无效的十六进制字符串长度) return bytes.fromhex(hex_string) elif input_format base64: # 处理可能存在的URL安全的Base64或带换行符的Base64 std_b64 raw_data.strip().replace(-, ).replace(_, /) # 补全可能的等号 padding 4 - len(std_b64) % 4 if padding ! 4: std_b64 * padding return base64.b64decode(std_b64) else: raise ValueError(f不支持的输入格式: {input_format}) # 使用示例 hex_data 0a0766616e74617379221b0880a2dac90510e8072a094d617263204a6f6e6573 byte_data prepare_data(hex_data, hex) print(f原始十六进制: {hex_data}) print(f转换后字节: {byte_data}) print(f字节长度: {len(byte_data)})4.2 首次解码与初步分析将处理好的字节数据送入blackboxprotobuf.decode_message进行首次尝试。import blackboxprotobuf import json def first_pass_decode(byte_data): 首次解码尝试输出原始结果。 try: decoded_dict, type_definition blackboxprotobuf.decode_message(byte_data) print( 首次解码结果 ) print(解析后的字典:) # 使用json.dumps美化输出处理可能存在的bytes类型 def bytes_to_str(obj): if isinstance(obj, bytes): return obj.hex() # 将bytes显示为十六进制 raise TypeError print(json.dumps(decoded_dict, defaultbytes_to_str, indent2)) print(\n推断的类型定义:) print(json.dumps(type_definition, indent2)) return decoded_dict, type_definition except Exception as e: print(f首次解码失败: {e}) return None, None # 执行解码 decoded, typedef first_pass_decode(byte_data)运行上述代码你可能会得到类似下面的输出 首次解码结果 解析后的字典: { 1: fantasy, 2: { 1: 1234567890, 2: 2024, 3: Marc Jones } } 推断的类型定义: { 1: { type: bytes, name: }, 2: { type: message, message_typedef: { 1: { type: int, name: }, 2: { type: int, name: }, 3: { type: bytes, name: } }, name: } }结果解读解码字典Protobuf消息被解析成一个Python字典。键1,2对应原始数据中的字段编号。1的值是字符串fantasy。2的值是另一个字典说明它是一个嵌套消息Nested Message内部包含编号为1、2、3的子字段。类型定义blackboxprotobuf推断出了每个字段的类型。注意它把字段1和嵌套消息中的字段3都判断为bytes类型但实际值看起来是字符串。这是因为Protobuf中的字符串在传输时就是作为bytes长度分隔类型处理的库的推断从语法上是正确的。但在语义上我们知道它很可能是一个string。4.3 优化类型定义与二次解码首次推断的类型定义可能不完美如bytesvsstring。我们可以手动优化这个类型定义然后使用blackboxprotobuf.decode_message的另一个形式进行二次解码以得到更符合直觉的结果。def refine_and_redecode(byte_data, initial_typedef): 优化类型定义并重新解码。 # 复制初始类型定义避免修改原对象 refined_typedef initial_typedef.copy() # 优化点1将看起来像UTF-8字符串的bytes类型改为string # 假设我们根据业务逻辑或字段值判断字段1和嵌套消息的字段3是字符串 if refined_typedef.get(1, {}).get(type) bytes: refined_typedef[1][type] string if 2 in refined_typedef and refined_typedef[2][type] message: nested_typedef refined_typedef[2][message_typedef] if nested_typedef.get(3, {}).get(type) bytes: nested_typedef[3][type] string print( 优化后的类型定义 ) print(json.dumps(refined_typedef, indent2)) # 使用优化后的类型定义进行解码 try: # 注意这里使用了decode_message的另一个签名传入已知的typedef refined_decoded, _ blackboxprotobuf.decode_message(byte_data, typedefrefined_typedef) print(\n 使用优化类型定义解码的结果 ) print(json.dumps(refined_decoded, defaultbytes_to_str, indent2)) return refined_decoded, refined_typedef except Exception as e: print(f使用优化类型定义解码失败: {e}) return None, refined_typedef # 执行优化解码 refined_decoded, refined_typedef refine_and_redecode(byte_data, typedef)这次输出中字段值将直接显示为字符串fantasy和Marc Jones而不是十六进制的bytes表示可读性大大增强。4.4 生成近似.proto文件虽然blackboxprotobuf不能直接生成完美的.proto文件但我们可以根据优化后的类型定义生成一个近似的、可读的版本这对于理解数据结构和与开发人员沟通非常有帮助。def generate_proto_skeleton(typedef, message_nameDecodedMessage): 根据类型定义生成一个近似的.proto文件骨架。 注意这是一个近似版本字段名需要手动补充。 lines [] lines.append(fsyntax proto3;\n) lines.append(fmessage {message_name} {{\n) def _generate_fields(field_typedef, indent_level1): indent * indent_level for field_num_str, type_info in sorted(field_typedef.items(), keylambda x: int(x[0])): field_num int(field_num_str) field_type type_info[type] # 映射 blackboxprotobuf 类型到 proto 类型 type_map { int: int64, # Protobuf varint 可能对应 int32/int64这里用int64更安全 uint: uint64, sint: sint64, float: float, double: double, bytes: bytes, string: string, bool: bool } proto_type type_map.get(field_type, field_type) # 默认为原类型 if field_type message: # 嵌套消息需要递归生成子消息类型这里简化处理 nested_msg_name f{message_name}_Field{field_num} lines.append(f{indent}// 字段 {field_num}: 嵌套消息详见下方 message {nested_msg_name}) lines.append(f{indent}{nested_msg_name} field_{field_num} {field_num};) # 递归生成嵌套消息的定义这里简化实际可能需要更复杂的处理避免命名冲突 nested_lines _generate_nested_message(type_info[message_typedef], nested_msg_name, indent_level) lines.extend(nested_lines) elif field_type packed_primitive: # 打包的重复基本类型如 repeated int32 packed_type type_info.get(packed_type, int64) proto_packed_type type_map.get(packed_type, packed_type) lines.append(f{indent}repeated {proto_packed_type} field_{field_num} {field_num} [packedtrue];) else: # 普通字段 lines.append(f{indent}{proto_type} field_{field_num} {field_num};) return lines def _generate_nested_message(nested_typedef, nested_name, base_indent): # 在实际完整实现中这里应该递归生成完整的嵌套message定义。 # 为简洁起见这里只输出一个占位符注释。 return [f\nmessage {nested_name} {{\n // 嵌套字段定义需根据具体类型信息补充\n}}\n] # 生成主消息的字段 _generate_fields(typedef, 1) lines.append(}\n) return \n.join(lines) # 生成并打印.proto骨架 proto_skeleton generate_proto_skeleton(refined_typedef, MyDecodedData) print(\n 生成的近似 .proto 文件骨架 ) print(proto_skeleton)生成的骨架文件会像这样syntax proto3; message MyDecodedData { string field_1 1; MyDecodedData_Field2 field_2 2; } message MyDecodedData_Field2 { // 嵌套字段定义需根据具体类型信息补充 }这个骨架文件给出了字段编号和推断的类型字段名如field_1是占位符需要你根据上下文如接口URL、响应内容将其替换为有意义的名称如username,user_info。5. 封装完整工具类与脚本将上述步骤封装成一个工具类方便复用。这个类将处理从原始数据到最终解析结果的全流程并加入错误处理和日志。import json import base64 from typing import Optional, Dict, Any, Tuple import blackboxprotobuf class ProtobufReverseParser: Protobuf逆向解析工具类。 def __init__(self): self.decoded_data None self.type_definition None self.refined_type_definition None def load_data(self, raw_data: str, input_format: str hex) - bytes: 加载并预处理原始数据。 if input_format hex: hex_str raw_data.strip().replace( , ).replace(0x, ) if len(hex_str) % 2 ! 0: raise ValueError(十六进制字符串长度必须为偶数。) return bytes.fromhex(hex_str) elif input_format base64: # 处理标准Base64和URL安全的Base64 std_b64 raw_data.strip().replace(-, ).replace(_, /) # 补足等号 pad_len 4 - len(std_b64) % 4 if pad_len ! 4: std_b64 * pad_len return base64.b64decode(std_b64) elif input_format bytes: # 如果输入直接是bytes或bytearray if isinstance(raw_data, (bytes, bytearray)): return raw_data else: raise TypeError(input_format为bytes时raw_data必须是bytes或bytearray类型。) else: raise ValueError(f不支持的输入格式: {input_format}。支持 hex, base64, bytes。) def decode_first_pass(self, byte_data: bytes) - Tuple[Optional[Dict], Optional[Dict]]: 首次解码尝试。 try: self.decoded_data, self.type_definition blackboxprotobuf.decode_message(byte_data) print([INFO] 首次解码成功。) return self.decoded_data, self.type_definition except Exception as e: print(f[ERROR] 首次解码失败: {e}) return None, None def _convert_bytes_to_str(self, obj): 辅助函数用于JSON序列化时将bytes转换为十六进制字符串。 if isinstance(obj, bytes): return obj.hex() elif isinstance(obj, dict): return {k: self._convert_bytes_to_str(v) for k, v in obj.items()} elif isinstance(obj, list): return [self._convert_bytes_to_str(i) for i in obj] else: return obj def print_results(self, decoded: Dict, typedef: Dict, title: str 结果): 格式化打印解码结果和类型定义。 print(f\n {title} ) print(解析数据:) print(json.dumps(self._convert_bytes_to_str(decoded), indent2)) print(\n类型定义:) print(json.dumps(typedef, indent2)) def refine_typedef(self, typedef: Dict) - Dict: 手动优化类型定义。 这是一个示例函数实际优化逻辑需根据具体数据调整。 常见优化将 bytes 改为 string修正数字类型等。 refined json.loads(json.dumps(typedef)) # 深拷贝 def _traverse_and_refine(td): for key, type_info in td.items(): if isinstance(type_info, dict): current_type type_info.get(type) # 示例规则如果字段值是纯ASCII字符的bytes可能是个string # 注意这是一个启发式规则不一定准确。 if current_type bytes: # 这里无法直接访问值所以这个规则需要结合decoded_data使用。 # 更实际的优化是在看到decoded_data后手动进行。 pass # 如果遇到嵌套消息递归处理 if current_type message and message_typedef in type_info: _traverse_and_refine(type_info[message_typedef]) # 处理 packed_primitive 等其它结构 # ... _traverse_and_refine(refined) self.refined_type_definition refined print([INFO] 类型定义优化完成示例函数实际需手动调整。) return refined def decode_with_typedef(self, byte_data: bytes, typedef: Dict) - Optional[Dict]: 使用指定的类型定义进行解码。 try: decoded, _ blackboxprotobuf.decode_message(byte_data, typedeftypedef) print([INFO] 使用自定义类型定义解码成功。) return decoded except Exception as e: print(f[ERROR] 使用自定义类型定义解码失败: {e}) return None def save_typedef(self, filepath: str, typedef: Dict): 将类型定义保存为JSON文件方便后续加载。 with open(filepath, w, encodingutf-8) as f: json.dump(typedef, f, indent2) print(f[INFO] 类型定义已保存至: {filepath}) def load_typedef(self, filepath: str) - Dict: 从JSON文件加载类型定义。 with open(filepath, r, encodingutf-8) as f: typedef json.load(f) print(f[INFO] 类型定义已从 {filepath} 加载。) return typedef # 使用示例 if __name__ __main__: parser ProtobufReverseParser() # 示例数据 hex_str 0a0766616e74617379221b0880a2dac90510e8072a094d617263204a6f6e6573 # 1. 加载数据 data_bytes parser.load_data(hex_str, hex) # 2. 首次解码 decoded, typedef parser.decode_first_pass(data_bytes) if decoded and typedef: parser.print_results(decoded, typedef, 首次解码) # 3. 可选优化类型定义 - 这里需要你根据decoded_data手动判断 # 例如我们发现字段1和嵌套字段3看起来是字符串 if typedef.get(1, {}).get(type) bytes: typedef[1][type] string if typedef.get(2, {}).get(type) message: nested typedef[2][message_typedef] if nested.get(3, {}).get(type) bytes: nested[3][type] string # 4. 使用优化后的类型定义重新解码 refined_decoded parser.decode_with_typedef(data_bytes, typedef) if refined_decoded: parser.print_results(refined_decoded, typedef, 优化后解码) # 5. 保存类型定义供以后使用 parser.save_typedef(message_typedef.json, typedef)6. 常见问题、错误排查与实战技巧在实际操作中你肯定会遇到各种问题。下面是我踩过坑后总结的一些常见错误和解决思路。6.1 版本兼容性错误google.protobuf.runtime_version.versionerror这是最常见的拦路虎。错误表现在导入blackboxprotobuf或调用decode_message时抛出类似AttributeError: module google.protobuf.internal.api_implementation has no attribute Type或明确提示版本不兼容的错误。根本原因你的Python环境中存在多个版本冲突的protobuf库包。可能通过pip全局安装了一个版本而blackboxprotobuf依赖或又安装了另一个版本。解决方案使用虚拟环境如前所述这是最根本的解决方案。在一个全新的虚拟环境中操作。检查并固定版本在虚拟环境中执行pip list | grep protobuf查看已安装版本。确保只安装了我们推荐的protobuf3.20.3和blackboxprotobuf。彻底清理如果问题依旧尝试pip uninstall protobuf blackboxprotobuf -y pip cache purge # 清理pip缓存 pip install protobuf3.20.3 pip install blackboxprotobuf注意IDE环境如果你使用PyCharm、VSCode等IDE确保其终端或运行配置使用的是你激活的虚拟环境下的Python解释器而不是系统全局的。6.2 解码失败或结果异常现象decode_message抛出异常或者返回的字典看起来杂乱无章字段编号顺序错乱值明显不对。可能原因与排查数据不完整或损坏抓包时可能只截取到了部分TCP流或者数据被加密、压缩过。确保你获取的是完整的、未经过额外处理的Protobuf负载。可以尝试对比多个同类请求/响应的数据看结构是否一致。非标准Protobuf有些实现可能使用了Protobuf的某些非标准特性或自定义扩展。blackboxprotobuf主要支持标准语法。包含未知字段或复杂类型对于enum、oneof或非常复杂的嵌套库的推断可能失败。数据预处理错误十六进制字符串中包含空格、换行符、0x前缀未去除或者Base64解码不正确。务必使用我们工具类中的prepare_data或load_data函数进行严格的格式清洗。调试技巧先用protoc --decode_raw试一下如果官方的raw decode都能失败那数据本身可能就有问题。分片解码如果数据很长可以尝试截取前一小段比如前50个字节进行解码看是否能成功。如果能说明问题可能出在后半部分的复杂结构上。打印字节流print(byte_data.hex())仔细核对与你抓到的原始十六进制是否完全一致。6.3 类型推断不准现象库将string推断为bytes将sint32推断为普通的int或者将repeated字段识别错了。处理策略结合上下文修正这是最主要的方法。观察解码后的值。如果某个bytes类型的值用value.decode(utf-8)能得到有意义的字符串那它就应该被修正为string。如果某个int的值看起来特别大或有正负考虑它是否是uint64或sint32。修改typedef重试这正是我们refine_and_redecode函数在做的事情。不要害怕手动修改那个JSON格式的类型定义字典然后重新解码。这是一个迭代的过程。理解Protobuf编码有助于你判断类型。例如Varint编码用于int32,int64,uint32,uint64,sint32,sint64,bool,enum。Length-delimited (字节流) 用于string,bytes, 嵌套消息以及repeated的非packed编码。知道这些当你看到一个字段是长度分隔类型时你就需要根据内容判断它具体是哪种。6.4 处理repeated字段和嵌套消息blackboxprotobuf对repeated数组字段的推断有时会不太明确尤其是在非packed编码的情况下。在类型定义中repeated字段可能会被表示为一个包含message_typedef的特殊结构或者直接推断为bytes然后需要你手动解释。实战心得当你发现一个字段的值是一个列表Pythonlist但在typedef中它的类型是message且message_typedef只有一个子字段时这很可能就是一个repeated基本类型或repeated消息。你需要根据列表里元素的类型手动将typedef中该字段的type改为packed_primitive如果是数字、布尔值并指定packed_type或者理解为一个repeated消息。对于深度嵌套的消息blackboxprotobuf的递归推断可能会在某一层失去准确性。这时需要你一层一层地解码和修正。可以先解码最外层得到一个包含嵌套bytes的字典然后单独提取那个bytes值将其作为新的输入再次调用decode_message逐步深入。7. 进阶应用与脚本集成掌握了基础解析后我们可以将这个能力集成到更强大的工作流中。7.1 批量处理抓包数据如果你有大量抓包文件如.har文件或Fiddler/Charles的导出文件可以编写脚本自动提取其中的Protobuf响应进行批量解析。import json import re from your_parser_module import ProtobufReverseParser # 导入上面封装的类 def extract_protobuf_from_har(har_file_path): 从HAR文件中提取Content-Type为protobuf的响应体。 parser ProtobufReverseParser() with open(har_file_path, r, encodingutf-8) as f: har_data json.load(f) for entry in har_data.get(log, {}).get(entries, []): response entry.get(response, {}) content response.get(content, {}) mime_type content.get(mimeType, ).lower() text content.get(text, ) # 判断是否为protobuf (常见类型) if protobuf in mime_type or x-protobuf in mime_type or grpc in mimeType: print(f\n发现Protobuf响应: {entry[request][url]}) # HAR中text可能是base64编码的 encoding content.get(encoding, ) if encoding base64: byte_data parser.load_data(text, base64) else: # 尝试作为十六进制或原始文本处理这里需要根据实际情况调整 # 有时text就是十六进制字符串 try: byte_data parser.load_data(text, hex) except: # 如果不是hex可能已经是乱码文本尝试直接encode byte_data text.encode(latin-1) # 谨慎使用 if byte_data: decoded, typedef parser.decode_first_pass(byte_data) if decoded: parser.print_results(decoded, typedef, fURL: {entry[request][url]}) # 可以在这里保存typedef或decoded数据到文件7.2 与动态调试工具结合在逆向Android或iOS应用时可以使用Frida、Xposed等工具Hook网络库函数直接获取到序列化前的对象或序列化后的字节数组。你可以将获取到的字节数组通过Socket发送到你的Python解析脚本实现实时解码和分析极大提升动态分析的效率。7.3 生成Python数据类对于需要反复交互的接口手动操作字典很不方便。我们可以基于最终确定的typedef生成一个简单的Python数据类或使用dataclass让后续的数据构造和解析更类型安全。import dataclasses from typing import Optional, List def generate_dataclass_from_typedef(typedef, class_nameProtobufMessage): 根据类型定义生成dataclass代码字符串简化版。 这是一个概念性示例实际生成需要处理嵌套等复杂情况。 lines [] lines.append(fdataclasses.dataclass) lines.append(fclass {class_name}:) field_num 1 # 假设从字段1开始实际需要从typedef解析 # 这里需要遍历typedef字典根据类型映射到Python类型 # 例如: {type: string} - field_1: Optional[str] None # 这是一个复杂的递归过程此处省略具体实现。 lines.append(f field_{field_num}: Optional[str] None) lines.append(f # ... 其他字段根据typedef生成) return \n.join(lines) # 生成的代码可以复制到你的项目中用于创建和操作对象。逆向解析Protobuf就像是在解一个没有图纸的拼图。blackboxprotobuf给了我们一个很好的起点和一套工具但最终的完成度取决于你的耐心、对数据的观察力以及对Protobuf协议本身的理解。每次成功解析一个未知结构都是对协议细节和业务逻辑的一次深刻理解。