ProtoFuzz: 一个Protobuf模糊测试器
Google的Protocol Buffers(protobuf)是一种常见的数据序列化方法,通常用于分布式应用程序中。Protobuf通过让开发者定义数据类型,并让protobuf编译器(protoc)自动生成所有序列化和反序列化代码,简化了通常容易出错的二进制数据解析任务。
直接对期望protobuf编码结构的服务进行模糊测试不太可能达到满意的代码覆盖率。首先,protobuf反序列化代码相当成熟且经过严格审查。其次,我们通常对protobuf实现本身的缺陷不感兴趣。我们的主要目标是针对protobuf解码背后的代码。我们的目标是创建由恶意值组成的有效protobuf编码结构。
由于Protobuf的使用足够广泛,我们觉得创建一个通用的Protobuf消息生成器来帮助评估是值得的。该消息生成器是一个Python3库,具有简单的接口:提供一个protobuf定义,它为所有定义的消息的各种排列创建Python生成器。我们称之为ProtoFuzz。
对于数据本身,我们使用fuzzdb数据库作为生成值的来源,但定义自己的值集合相对简单。
安装
在Ubuntu中安装:
1
2
3
4
5
6
7
|
pip install py3-protobuffers
sudo add-apt-repository -y ppa:5-james-t/protobuf-ppa
sudo apt-get -qq update
sudo apt-get -y install protobuf-compiler
git clone --recursive git@github.com:trailofbits/protofuzz.git
cd protofuzz/
python3 setup.py install
|
使用
消息生成由ProtobufGenerator实例处理。每个实例支持一个Protobuf生成的类。该类有两个功能:创建模糊测试策略和创建字段依赖关系。
模糊测试策略定义了字段如何排列。目前只定义了两个:线性和排列。线性策略创建一个protobuf对象流,相当于Python的zip()函数在所有可生成值上的应用。排列产生一个流,它是所有可生成值的笛卡尔积。可以使用linear()排列来了解将生成的值类型,而无需创建大量值。
字段依赖通过任何可调用对象强制某些字段的值从其他字段的值创建。这用于可能不应该被模糊测试的字段,如长度、CRC校验和、魔术值等。
库的入口点是protofuzz.protofuzz
模块。它定义了三个函数:
protofuzz.from_description_string()
从字符串Protobuf定义创建ProtobufGenerator对象的字典。
1
2
3
4
5
6
7
8
9
|
from protofuzz import protofuzz
message_fuzzers = protofuzz.from_description_string("""
message Address {
required int32 house = 1;
required string street = 2;
}
""")
for obj in message_fuzzers['Address'].permute():
print("Generated object: {}".format(obj))
|
输出:
1
2
3
4
5
6
7
8
|
Generated object: house: -1
street: "!"
Generated object: house: 0
street: "!"
Generated object: house: 256
street: "!"
|
protofuzz.from_file()
从.proto文件的路径创建ProtobufGenerator对象的字典。
1
2
3
4
|
from protofuzz import protofuzz
message_fuzzers = protofuzz.from_file('test.proto')
for obj in message_fuzzers['Person'].permute():
print("Generated object: {}".format(obj))
|
输出:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
Generated object: name: "!"
id: -1
email: "!"
phone {
number: "!"
type: MOBILE
}
Generated object: name: "!'"
id: -1
email: "!"
phone {
number: "!"
type: MOBILE
}
...
|
protofuzz.from_protobuf_class()
从已加载的Protobuf类创建ProtobufGenerator。
创建链接字段
某些字段不应该被模糊测试。例如,像魔术值、校验和和长度这样的字段不应该被变异。为此,protofuzz支持从其他字段解析选定的字段值。要创建链接字段,请使用ProtobufGenerator的add_dependency方法。依赖关系也可以在嵌套对象之间创建。例如,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
fuzzer = protofuzz.from_description_string('''
message Contents {
required string header = 1;
required string body = 2;
}
message Payload {
required int32 length = 1;
required Contents contents = 2;
}
''')
fuzzer['Payload'].add_dependency('length', 'contents.body', len)
for idx, obj in zip(range(3), fuzzer['Payload'].permute()):
print("Generated object: {}".format(obj))
|
输出:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
Generated object: length: 1
contents {
header: "!"
body: "!"
}
Generated object: length: 2
contents {
header: "!"
body: "!'"
}
Generated object: length: 29
contents {
header: "!"
body: "!@#$%%^#$%#$@#$%$$@#$%^^**(()"
}
...
|
杂项
虽然与模糊测试没有直接关系,但Protofuzz还包括一个简单的日志记录类,它被实现为环形缓冲区,以帮助模糊测试活动。请参阅protobuf.log。
结论
我们创建了Protofuzz来协助安全评估。它使我们能够以最小的准备时间快速测试消息处理代码。
该库本身以最少的依赖关系实现,使其适合与持续集成(CI)和测试工具集成。
如果您有任何问题,请随时通过yan@trailofbits.com联系我们或提交问题。