文章目录
- 一、前言
- 二、示例
- 三、python demo
一、前言
在很多场景,都有一个需求:
得到了一个编码后的protobuf数据(比如竞品调研的的数据包),需要逆向还原其proto结构文件。
有3种方案去做这件事情:
- 从编码入手,人工肉眼分析。
- 费时费力
- 容易出错(在一些嵌套结构,或者有优化编码的地方)
- 但,对理解 protobuf 编码原理非常有帮助
- 借助 protoc.exe
- protoc.exe --decode_raw < f1.bin
- 缺点:没有给出数据类型
# protoc.exe --decode_raw <f1.bin
1 {
1 {
1: "\004\032\224\354~m\350-?\266(qJ\264.0\031v\204I\364v,\001\341P\341\300\326\013!\351\014"
2 {
1892: "\000"
}
}
2: "\001d\2457\370\332\362\335\345e0\231\212(\007\2275d\330\025\327\000pk\254\361\213\020.N<\010"
}
- 借助在线工具先把protobuf的基本结构识别出来,然后结合对数据的理解进行修正。
- 为什么需要修正?
因为有些字段刚好符合 protobuf 的编码规则,被错误识别为结构- 对比 protoc --decode_raw 功能,优势是:
结构更加清晰
会提供Field 的数据类型
二、示例
这里介绍第三种方案,借助在线工具 + 修正。
- 拿到了一段数据
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F
00000000 0A 4D 0A 29 0A 21 04 1A 94 EC 7E 6D E8 2D 3F B6 M ) ! 旍~m???
00000010 28 71 4A B4 2E 30 19 76 84 49 F4 76 2C 01 E1 50 (qJ?0 v処魐, 酨
00000020 E1 C0 D6 0B 21 E9 0C 12 04 A2 76 01 00 12 20 01 崂?!?
00000030 64 A5 37 F8 DA F2 DD E5 65 30 99 8A 28 07 97 35 d?蜉錯0檴( ?
00000040 64 D8 15 D7 00 70 6B AC F1 8B 10 2E 4E 3C 08 d??pk?.N<
- 利用在线工具解析基本结构
Online Protobuf Decoder : https://protogen.marcgravell.com/decode
看到这个图,基本结构就可以还原出来了:
syntax = "proto2";
message Field1_1_2 {
optional bytes f1 = 1892;
}
message Field1_1 {
optional bytes f1 = 1;
optional Field1_1_2 f2 = 2;
}
message Field1 {
optional Field1_1 f1 = 1;
optional bytes f2 = 2;
}
message Sample {
optional Field1 f1 = 1;
}
这个结构,解析上面的数据是成功的
但是,可以注意一下,下面部分的定义不太常见,id比较特殊
message SampleField1_1_2 {
optional bytes f1 = 1892;
}
拿到了第二段数据,发现解码失败。
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F
00000000 0A 78 0A 29 0A 21 04 3B FC AF 46 BE E6 05 9B 88 x ) ! ;F炬 泩
00000010 D4 62 A6 96 AC 82 C7 EC 9D ED 96 BB BE 98 C9 18 詁瑐庆 頄痪樕
00000020 BB 7C 75 15 BB C8 38 12 04 00 00 50 00 0A 29 0A 粅u 蝗8 P )
00000030 21 04 09 AD 0C C5 B6 D8 E2 31 12 D9 86 7C 93 89 ! ?哦剽1 賳|搲
00000040 5D EB 4E 7C A8 87 E4 0F F2 7C E2 31 5B 99 B3 CD ]隢|▏?騶?[櫝?
00000050 B4 58 12 04 7C 2B 0D 00 12 20 4C 4C 90 8B 75 9A 碭 |+ LL 媢?
00000060 CD 3D 5C EC AD B7 6D 70 14 16 90 91 D2 09 DA A3 ?\飙穖p 懸 冢
00000070 F2 D6 53 FB 0A B4 46 E6 03 F2 蛑S?碏??
再利用在线工具校验一下结构:
这时候发现跟第一次的结构确实有差异,差异在于:
对比可以发现,第二次的结构可以兼容第一次,而且解析出来的数据明显更合理,于是可以得到修正后的结构:
syntax = "proto2";
message Field1_1 {
optional bytes f1 = 1;
optional bytes f2 = 2;
}
message Field1 {
optional Field1_1 f1 = 1;
optional bytes f2 = 2;
}
message Sample {
optional Field1 f1 = 1;
}
这样就完成了一次protobuf结构的还原与修订过程。
三、python demo
- 把以下结构保存为 sample.proto 文件:
syntax = "proto2";
message Field1_1 {
optional bytes f1 = 1;
optional bytes f2 = 2;
}
message Field1 {
optional Field1_1 f1 = 1;
optional bytes f2 = 2;
}
message Sample {
optional Field1 f1 = 1;
}
- 利用 protoc.exe 编译 sample.proto, 得到 sample_pb2.py
protoc.exe --I=. --python_out=. sample.proto
- 引用
import sample_pb2
# f1.bin
chunk1 = file_chunk_keys_pb2.FileChunkKeys()
with open("f1.bin", "rb") as f:
chunk1.ParseFromString(f.read())
print("#Field 1-1")
for i in range(len(chunk1.f1.f1)):
item1_1 = chunk1.f1.f1[i]
print("chunk.f1.f1[", i, "]", ".f1 =", item1_1.f1.hex(' '))
print("chunk.f1.f1[", i, "]", ".f2 =", item1_1.f2.hex(' '))
print("")
print("#Field 1-2")
print("chunk.f1.f2 =", chunk1.f1.f2.hex(' '))
# f4.bin
chunk2 = file_chunk_keys_pb2.FileChunkKeys()
with open("f4.bin", "rb") as f:
chunk2.ParseFromString(f.read())
print("#Field 1-1")
for i in range(len(chunk2.f1.f1)):
item1_1 = chunk2.f1.f1[i]
print("chunk.f1.f1[", i, "]", ".f1 =", item1_1.f1.hex(' '))
print("chunk.f1.f1[", i, "]", ".f2 =", item1_1.f2.hex(' '))
print("")
print("#Field 1-2")
print("chunk.f1.f2 =", chunk2.f1.f2.hex(' '))