Fuzz 初探

更新中,未完待续

翻译自@h0mbre的两篇文章Fuzz Like A Caveman

介绍

本文将会展示如何创建一个非常简单的基于变异的fuzzer,理想情况下可以在开源的软件里找到一些crash。文章创建的fuzzer是基于油管上的@gynvael的fuzz教程,另外还要推荐一下Brandon Faulk的fuzz直播,他还有一个很有意思的介绍fuzz思想概念的视频

挑个目标

我们要找一个C/C++编写的、可以解析从文件里解析出来数据的软件。我找到的第一个软件是一个从图像里解析出Exif的软件,我们还将挑选一个几乎完全不处理安全问题的软件,因为我会直接公开这些漏洞研究。

通过这个文章,我们可以了解到一些Exif基础,Exif文件格式和JPEG文件相同,Exif按照JPEG的规范,将一些图像信息数据和缩略图,插入到JPEG文件中,因此你可以在兼容JPEG的浏览器/图片查看器/图片修改等软件中,如同浏览普通JPEG文件一样浏览Exif格式的图像文件。

因此Exif可以按照JPEG规范,把元数据类型信息插入到图像中,并且有很多软件可以用来解析这些数据。

开搞

我们将用Python3做一个基于突变样本的fuzzer,来调整合法的,填充了Exif数据的JPEG文件,然后把它们提供给解析器,看看能不能得到一个崩溃。

首先,我们需要一个填充了Exif数据的合法的JPEG文件,谷歌搜索Sample JPEG with Exif可以找到这个repo,我接下来会用Canon_40D.jpg来作为测试样本。

了解一下JPEG和Exif的规范

在开始写代码前,我们应该先了解一下JPEG和Exif规范,保证我们制作的样本都是符合规范的,这样可以防止浪费我们宝贵的fuzz循环。

通过这个文档我们可以知道JPEG文件以0xFFD8开始,以0xFFD9结束,前两个字节即操作系统用来标志文件类型的所谓的魔数

root@kali:~# file Canon_40D.jpg 
Canon_40D.jpg: JPEG image data, JFIF standard 1.01, resolution (DPI), density 72x72, segment length 16, Exif Standard: [TIFF image data, little-endian, direntries=11, manufacturer=Canon, model=Canon EOS 40D, orientation=upper-left, xresolution=166, yresolution=174, resolutionunit=2, software=GIMP 2.4.5, datetime=2008:07:31 10:38:11, GPS-Data], baseline, precision 8, 100x68, components 3

我们可以去掉.jpeg得到相同的输出,因为操作系统靠前文中的魔数来识别文件类型。

root@kali:~# file Canon
Canon: JPEG image data, JFIF standard 1.01, resolution (DPI), density 72x72, segment length 16, Exif Standard: [TIFF image data, little-endian, direntries=11, manufacturer=Canon, model=Canon EOS 40D, orientation=upper-left, xresolution=166, yresolution=174, resolutionunit=2, software=GIMP 2.4.5, datetime=2008:07:31 10:38:11, GPS-Data], baseline, precision 8, 100x68, components 3

用hexdump查看文件的话,可以看到开头和结尾是0xFFD80xFFD9

root@kali:~# hexdump Canon
0000000 d8ff e0ff 1000 464a 4649 0100 0101 4800
------SNIP------
0001f10 5aed 5158 d9ff 

文件规范中有个很有意思的信息是标记以0xFF开头,然后有几个静态标记:

  • 图像开始标记:0xFFD8
  • APP1标记:0xFFE1
  • 通用标记:0xFFXX
  • 图片结束标记:0xFFD9

由于我们不想改变图像的长度或者类型,所以让我们搞个计划,尽可能保持文件头尾不变,比如我们不希望把0xFFD9插入到图像中央,这样的话就会截断图像,导致图像解析器以非崩溃的方式出错。

开始我们的fuzzer

首先我们要提取出来我们要作为有效输入的JPEG样本中的所有字节,当然我们会对它进行一些修改。我们的代码开始是这样的:

#!/usr/bin/env python3

import sys

# 从合法JPEG文件中读取字节数据并保存在数组中
def get_bytes(filename):
f = open(filename, "rb").read()
return bytearray(f)

if len(sys.argv) < 2:
print("Usage: JPEGfuzz.py <valid_jpg>")

else:
filename = sys.argv[1]
data = get_bytes(filename)

如果想看一下读取到的数据是什么样的,可以输出一下前十个数据:

else:
filename = sys.argv[1]
data = get_bytes(filename)
counter = 10
for i in data:
if counter < 10:
print(i)
counter += 1

接下来试着用我们的字节数组创建一个新的有效的JPEG文件:

def create_new(data):
f = open("mutated.jpg", "wb+")
f.write(data)
f.close()

看一下md5:

root@kali:~# shasum Canon_40D.jpg mutated.jpg 
c3d98686223ad69ea29c811aaab35d343ff1ae9e  Canon_40D.jpg
c3d98686223ad69ea29c811aaab35d343ff1ae9e  mutated.jpg

文件变异

为了保持fuzzer的简单,接下来将仅使用两种突变方法:

  • 比特翻转
  • 用Gynvael的魔数来覆盖字节序列

我们先从比特翻转开始,如果255(0xFF)用二进制表示是11111111,随即反转一位比如下标是2的位,就会变成11011111,即223或者0xDF。