利用 豆包AI 对学小易Protobuf协议逆向解析
Preface
早在2020年前,当时的学小易客户端到服务端通信并没有应用到protocol buffer,只是简单的https,后来不知过多久后,尝试对学小易进行抓包,发现响应数据无法被抓包工具解析(也就是乱码的状态),无法一眼看出猫腻。
如下图
利用 豆包AI 对学小易Protobuf协议逆向解析
Preface
早在2020年前,当时的学小易客户端到服务端通信并没有应用到protocol buffer,只是简单的https,后来不知过多久后,尝试对学小易进行抓包,发现响应数据无法被抓包工具解析(也就是乱码的状态),无法一眼看出猫腻。
如下图
什么是Protobuf
Protocol Buffers 是一种语言无关、平台无关、可扩展的序列化结构化数据的机制。说白点,就是能够把数据进行压缩,减轻server与client通信的压力,提升通信传输速率以及解析效率。
Step
1. 怎么知道响应数据是protobuf的,而不是响应数据被加密?
通过下图即可知道
2. 直接使用reqable将响应体保存下来
3. 使用
protoc.exe
对响应数据反解析
protoc.exe
E:Projectprotobufferprotoc-24.2-win64inprotoc.exe --decode_raw < E:Projectprotobufferprotoc-24.2-win64inpayload
下图为
对payload反解析的输出结果
protoc.exe
4. 对输出结果解析一下,这样好看点
raw_str =
"""
3: "20250913134146A26DBC552D7B2E9B0895"
4 {
1 {
1: "bb1c91fe574e4ca3958cf51f2e675c84"
2: 1
...
...
...
...
"""
string = raw_str.encode('latin-1').decode('utf-8')
print(string)
5. 将运行结果拷贝给豆包,让豆包根据结果来帮我们解析出
proto
语法
proto
syntax = "proto3"; // 现代protobuf默认使用proto3,字段默认optional,支持repeated
// 根消息:反序列化结果的最外层结构
message RootMessage {
// 字段3:字符串类型(值为"20250913134146A26DBC552D7B2E9B0895")
string field_3 = 3;
// 字段4:核心数据块(包含多个题目、搜索信息等,是复杂message类型)
省略号.....
省略号.....
省略号.....
豆包推导的结果不完全是正确的,需要手动修复字段定义等
6. 修复豆包推导出来的protoc
syntax = "proto3";
// ok
// 根消息:反序列化结果最外层结构(兼容所有同类数据场景)
message RootMessage {
// 字段3:唯一标识字符串(如"20250913195123C0ADF1A40DEDB38D0379",搜索/请求ID)
string field_3 = 3;
// 字段4:核心业务数据块(包含题目列表、搜索元信息等,核心嵌套消息)
MainData field_4 = 4;
// 字段5:全局反馈配置(固定结构,“对搜索结果不满意?点击反馈”及选项)
FeedbackConfig field_5 = 5;
// 字段6:引擎标识(固定值"saop",标识数据来源引擎)
string field_6 = 6;
// 字段7:分页/数量控制配置(存储分页相关数值,如总页数、每页条数)
PaginationConfig field_7 = 7;
// 字段8:预留空字符串(固定为空,可能用于扩展)
string field_8 = 8;
}
// ok
// 核心业务数据块:存储题目列表、搜索关键词、元信息等
message MainData {
// 字段1:条目列表(repeated,存储题目/广告等条目,新场景中为1道题目)
repeated Item field_1 = 1;
// 字段2:未知状态标识(新场景中未出现,可选,兼容历史有值场景)
optional int32 field_2 = 2;
// 字段3:有效题目总数(新场景中未出现,可选,历史场景为10)
optional int32 field_3 = 3;
// 字段4:总条目数(新场景中值为1,对应1道题目,int类型)
int32 field_4 = 4;
// 字段5:总条目计数(新场景中值为1,与field_4一致,可能为冗余计数)
int32 field_5 = 5;
// 字段7:搜索关键词(新场景中为"内存外挂解析",标识当前搜索主题)
string field_7 = 7;
// 字段51:全局元信息列表(repeated,存储search_id、query、engine等全局键值对)
repeated KeyValuePair field_51 = 51;
}
// 单个条目:可表示“题目”或“广告”,通过field_2区分类型
message Item {
// 字段1:条目唯一标识(仅题目有值,如"6b0445a2533c4c03bef2f965d7bf470c")
string field_1 = 1;
// 字段2:条目类型(1=题目,4=广告,新场景中为1,即题目类型)
int32 field_2 = 2;
// 字段3:题目详情(仅条目类型为1时存在,存储题干、答案、类型等核心信息)
QuestionDetail field_3 = 3;
// 字段6:广告配置(仅条目类型为4时存在,新场景中无广告,可选)
optional AdConfig field_6 = 6;
// 字段51:条目元信息列表(repeated,存储当前题目/广告的rank、engine等)
repeated KeyValuePair field_51 = 51;
// 字段52:分享配置(固定结构,“我向你分享了一道题”及分享链接、图片)
ShareConfig field_52 = 52;
// 字段53:答案反馈配置(固定结构,“此答案不满意?”及反馈选项)
AnswerFeedbackConfig field_53 = 53;
// 字段55:预留扩展字段(新场景中为空字符串,可选,兼容历史复杂结构)
optional string field_55 = 55;
// 字段56:预留扩展字段(新场景中为空字符串,可选,兼容历史复杂结构)
optional string field_56 = 56;
}
// 题目详情:存储单道题目的所有核心信息(题干、答案、类型、创建时间等)
message QuestionDetail {
// 字段1:题目类型编码(3=单选/多选混合,新场景中题干含单选内容,int类型)
int32 field_1 = 1;
// 字段2:题目类型名称(新场景中为"单选",字符串类型)
string field_2 = 2;
// 字段3:题目唯一标识(与Item.field_1一致,确保关联唯一性)
string field_3 = 3;
// 字段4:完整题干(含选项的完整题目内容,新场景中为“控制面板的添加删除程序...”)
string field_4 = 4;
// 字段5:题目答案(新场景中为多题答案集合,如“HKEY_USERSSoftware...”,字符串)
string field_5 = 5;
// 字段6:出题人/备注(新场景中为"嘻嘻嘻哈哈哈哈",固定备注内容)
string field_6 = 6;
// 字段7:题目创建时间(新场景中为"2024-05-01",日期字符串)
string field_7 = 7;
// 字段8:题目名称(新场景中为"题目一",标识题目序号)
string field_8 = 8;
// 字段9:精简题干(与field_4内容一致,可能为冗余存储,字符串)
string field_9 = 9;
}
// 广告配置:仅条目类型为4(广告)时存在,新场景中无广告,保留兼容
message AdConfig {
// 字段1:广告详情列表(repeated,存储广告类型、名称等)
repeated AdDetail field_1 = 1;
}
// 广告详情:广告的具体配置信息,保留兼容
message AdDetail {
// 字段1:广告类型编码(历史场景中为2,int类型)
int32 field_1 = 1;
// 字段2:广告名称(历史场景中为"穿山甲广告",字符串类型)
string field_2 = 2;
}
// 51
// 键值对消息:通用结构,存储各类元信息(如rank=0、query=内存外挂解析)
message KeyValuePair {
// 字段1:键(如"engine"、"query"、"search_id",字符串类型)
oneof union_data {
string field_1 = 1;
NestedKey field_14 = 14;
}
// 字段2:值(如"saop"、"内存外挂解析"、"20250913195123...",字符串类型)
string field_2 = 2;
}
// 嵌套键结构:承载KeyValuePair中特殊的嵌套键值(如14: 0x6e656c5f79726575)
message NestedKey {
// 字段14:64位整数值(新场景中为0x6e656c5f79726575,int64类型)
int64 field_14 = 14;
}
// 分享配置:固定结构,存储题目分享的文案、图片链接、跳转链接
message ShareConfig {
// 字段1:分享标题(固定为"我向你分享了一道题")
string field_1 = 1;
// 字段2:预留字段(新场景中未出现,可选,兼容历史有值场景)
optional string field_2 = 2;
// 字段3:分享副标题(固定为"常备学小易,大学少难题")
string field_3 = 3;
// 字段4:分享图片链接(固定为微信分享图片URL)
string field_4 = 4;
// 字段5:分享跳转链接(含题目唯一标识,如"https://xxy.51xuexiaoyi.com/...")
string field_5 = 5;
}
// 答案反馈配置:题目专属反馈(“此答案不满意?”及选项)
message AnswerFeedbackConfig {
// 字段1:反馈标题(固定为"此答案不满意?")
string field_1 = 1;
// 字段2:反馈选项列表(repeated,3个选项:答案解析错误、没有解析过程、输入正确答案)
repeated FeedbackItem field_2 = 2;
}
// 全局反馈配置:根消息的全局反馈(“对搜索结果不满意?点击反馈”及选项)
message FeedbackConfig {
// 字段1:反馈标题(固定为"对搜索结果不满意?点击反馈")
string field_1 = 1;
// 字段2:反馈选项列表(repeated,4个选项:没有我需要的答案、拍照识别不准确等)
repeated FeedbackItem field_2 = 2;
}
// 单个反馈项:承载反馈选项的名称、扩展标识和提示文案
message FeedbackItem {
// 字段1:反馈项名称(如"答案解析错误"、"其他",字符串类型)
string field_1 = 1;
// 字段3:扩展标识(仅“输入正确答案”“其他”项有值,为1,int类型,可选)
optional int32 field_3 = 3;
// 字段4:扩展提示文案(仅“输入正确答案”“其他”项有值,如“输入正确答案...”,可选)
optional string field_4 = 4;
}
// 分页配置:存储分页相关数值(新场景中为{1:9, 2:10},具体含义需结合业务)
message PaginationConfig {
// 字段1:分页参数1(新场景中为9,int类型)
int32 field_1 = 1;
// 字段2:分页参数2(新场景中为10,int类型)
int32 field_2 = 2;
}
7. 使用protoc.exe编译器按照protoc语法文件生成py脚本
E:Projectprotobufferprotoc-24.2-win64inprotoc.exe result.proto -I E:/Project/protobuffer/protoc-24.2-win64/bin/ --python_out=E:/Project/protobuffer/protoc-24.2-win64/bin/
运行以上命令,生成
,编译没有报错,proto语法文件是没有问题了。
result_pb2.py
利用
脚本尝试对
result_pb2.py
文件进行解析
payload
8. 解析payload
pip install protobuf
import result_pb2
def main():
with open("payload", "rb")as fp:
data = result_pb2.RootMessage()
data.ParseFromString(fp.read())
print(data.field_4)
if __name__ == "__main__":
main()
总结
protobuf协议推导,就是个体力活,还好有ai辅助一下
© 版权声明
文章版权归作者所有,未经允许请勿转载。
相关文章
暂无评论...