-
-
[翻译]野蛮fuzz - part1:梦开始的地方
-
发表于: 2024-8-28 10:17 3831
-
在过去的几个月里,我一直在被动地吸收大量与模糊测试相关的材料,因为我主要尝试将我的 Windows 利用技能从菜鸟级别提升到略微高级的水平,我发现它非常有趣。在这篇文章中,我将向你展示如何创建一个非常简单的变异模糊测试器,并希望我们能用它在一些开源项目中找到一些崩溃。
我们将要创建的模糊测试器是通过跟随 @gynvael 在 YouTube 上的模糊测试教程来实现的。我之前不知道 Gynvael 有视频流,现在我有了更多的内容可以添加到永无止境的观看/阅读列表中。
我还必须提到 Brandon Faulk 的模糊测试视频流非常棒。我大约不理解 Brandon 所说的 99% 的内容,但这些视频流非常吸引人。我个人最喜欢的是他对 calc.exe
和 c-tags
的模糊测试。他还有一个非常棒的模糊测试概念介绍视频:NYU Fuzzing Talk。
我想找到一个用 C 或 C++ 编写并从文件中解析数据的二进制文件。我首先接触到的是解析图像中 Exif 数据的二进制文件。我们还希望选择一个几乎没有安全隐患的目标,因为我会实时发布这些发现。
根据 https://www.media.mit.edu/pia/Research/deepview/exif.html
的描述:基本上,Exif 文件格式与 JPEG 文件格式相同。Exif 根据 JPEG 规范将一些图像/数码相机信息数据和缩略图图像插入到 JPEG 中。因此,你可以像查看普通 JPEG 图像文件一样,通过符合 JPEG 规范的互联网浏览器/图片查看器/照片修饰软件等查看 Exif 格式图像文件。
所以,Exif 根据 JPEG 规范将元数据类型信息插入到图像中,并且有很多程序/工具可以解析这些数据。
我们将使用 Python3 构建一个基本的变异模糊测试器,它会微妙地(或不那么微妙地)修改有效的包含 Exif 的 JPEG,并将其提供给解析器,希望引发崩溃。我们还将在 x86 Kali Linux 发行版上进行操作。
首先,我们需要一个有效的包含 Exif 的 JPEG。谷歌搜索“带有 Exif 的样本 JPEG”会将我们引导到这个仓库。我将使用 Canon_40D.jpg
图像进行测试。
在我们开始随便在 Sublime Text 中编写 Python 之前,让我们先花点时间了解 JPEG 和 Exif 规范,这样我们可以避免一些更明显的陷阱,比如将图像损坏到解析器不尝试解析它并浪费宝贵的模糊测试周期。
从之前引用的规范概述中可以知道,所有 JPEG 图像都以字节值 0xFFD8
开头,以字节值 0xFFD9
结尾。这前几个字节被称为“magic bytes”。这使得在 *Nix 系统上可以直接进行文件类型识别。
我们可以去掉 .jpg
扩展名,得到相同的输出。
如果我们对图像进行十六进制转储,可以看到第一个和最后一个字节实际上是 0xFFD8
和 0xFFD9
。
在规范概述中,另一个有趣的信息是“标记”以 0xFF 开头。有几个已知的静态标记,例如:
由于我们不想更改图像长度或文件类型,让我们尽量保持 SOI 和 EOI 标记不变。例如,我们不希望在图像中间插入 0xFFD9
,因为这会截断图像或导致解析器以非崩溃的方式出现异常。“非崩溃”是一个真实的词。此外,这样的做法可能是错误的,也许我们应该随机地在字节流中插入 EOI 标记?让我们看看。
我们首先需要做的是从我们要用作“有效”输入样本的 JPEG 中提取所有字节,当然我们会对其进行变异。
我们的代码将如下开始:
如果我们想看看这些数据的样子,可以打印数组中的前 10 个左右的字节值,看看我们将如何与它们交互。我们可以临时添加类似以下的代码:
运行这段代码显示我们在处理整齐转换的十进制整数,这让我觉得一切都变得更加容易。
让我们快速验证一下是否可以从我们的字节数组创建一个新的有效 JPEG 文件。我们会将这个函数添加到代码中并运行它。
现在我们在目录中有了一个 mutated.jpg
文件,让我们对两个文件进行哈希处理,看看它们是否匹配。
太棒了,我们有两个相同的文件。现在我们可以在创建 mutated.jpg
之前开始变异数据了。
我们将保持我们的模糊测试器相对简单,并只实现两种不同的变异方法。这些方法是:
让我们从位翻转开始。255
(或 0xFF
)在二进制中是 11111111
,如果我们随机翻转这个数字中的一个位,例如在索引号 2 处,我们会得到 11011111
。这个新数字将是 223
或 0xDF
。
我不完全确定这种变异方法与从 0
到 255
随机选择一个值并用它覆盖一个随机字节有多大的不同。我的直觉告诉我,位翻转与随机用任意字节覆盖字节非常相似。
让我们假设我们只想在 1% 的字节中翻转一个位。我们可以在 Python 中通过以下方式得到这个数字:
我们希望从 bytearray 的长度中减去 4,因为我们不希望计算数组中的前 2 个字节或最后 2 个字节,因为它们是 SOI 和 EOI 标记,我们希望保持这些标记不变。
接下来,我们需要随机选择这么多的索引,并针对这些索引进行位翻转。我们将创建一个可以更改的可能索引范围,然后选择 num_of_flips
个索引进行随机位翻转。
让我们在脚本中添加 import random
,并添加这些调试打印语句,以确保一切正常工作。
我们现在的函数看起来像这样:
如果我们运行这个函数,我们会得到一个预期的漂亮输出:
接下来我们需要实际对这些索引处的字节进行变异。我们需要对它们进行位翻转。我选择了一种非常简单的方法来实现这一点,您可以自由实现自己的解决方案。我们将把这些索引处的字节转换为二进制字符串,并填充它们,使其长度为 8 位。让我们添加这段代码,看看我在说什么。我们将把字节值(记住是十进制的)转换为二进制字符串,然后如果长度小于 8 位,则用前导零填充。最后一行是用于调试的临时打印语句。
如您所见,我们得到了漂亮的二进制数字字符串输出。
现在对于每一个,我们将随机选择一个索引并翻转它。以第一个 10100110
为例,如果选择索引 0,我们有一个 1
,我们将其翻转为 0
。
最后要考虑的是,这些是字符串而不是整数。所以我们需要做的最后一件事是将翻转后的二进制字符串转换为整数。
我们将创建一个空列表,将每个数字添加到列表中,翻转我们随机选择的数字,然后从所有列表成员构造一个新的字符串。(我们必须使用这个中间列表步骤,因为字符串是不可变的)。最后,我们将其转换为整数,并将数据返回到我们的 create_new()
函数,以创建一个新的 JPEG。
我们的脚本现在总体上看起来是这样的:
如果我们运行脚本,可以对输出文件进行 shasum
并与原始 JPEG 进行比较。
这看起来很有希望,因为它们现在有不同的哈希值。我们可以通过使用名为 Beyond Compare 或 bcompare
的程序进一步分析它们。我们将得到两个十六进制转储,其中差异部分会被突出显示。
正如你所见,仅在这一屏幕分享中,我们有 3 个不同的字节被翻转了位。原始文件在左边,变异样本在右边。
这种变异方法似乎有效。让我们继续实现第二种变异方法。
在前面提到的 GynvaelColdwind 的“模糊测试基础”直播中,他列举了几种可能对程序产生破坏性影响的“Magic Numbers”。通常,这些数字与数据类型大小和算术引发的错误有关。讨论的数字包括:
如果在 malloc()
或其他类型的操作过程中对这些类型的值执行任何算术运算,溢出是常见的。例如,如果在一个字节寄存器上将 0x1
加到 0xFF
,它会滚动到 0x00
,这可能是意外行为。HEVD 实际上有一个类似概念的整数溢出漏洞。
假设我们的模糊测试器选择了 0x7FFFFFFF
作为它想要使用的Magic Numbers,该值是 4 个字节长,所以我们需要在数组中找到一个字节索引,并覆盖该字节及其后的三个字节。让我们开始在我们的模糊测试器中实现这一点。
首先,我们需要像 Gynvael 那样创建一个元组列表,其中元组的第一个数字是Magic Numbers的字节大小,第二个数字是第一个字节的十进制值。
如果我们运行这个脚本,可以看到它随机选择一个Magic Numbers元组。
现在我们需要在 JPEG 文件中用这个新的 1 到 4 字节的Magic Numbers值覆盖一个随机的 1 到 4 字节的值。我们将像之前的方法一样设置可能的索引,选择一个索引,然后用我们picked_magic
覆盖该索引处的字节。
例如,如果我们得到 (4, 128)
,我们知道它是 4 个字节长,Magic Numbers是 0x80000000
。因此,我们会做类似以下的操作:
总体来说,我们的函数看起来像这样:
现在运行我们的脚本并在 Beyond Compare 中分析结果,可以看到一个两个字节的值 0xA6 0x76
被 0xFF 0xFF
覆盖了。
这正是我们想要实现的目标。
现在我们有了两种可靠的数据变异方法,我们需要:
对于我们的受害者程序,我们将在 Google 上搜索 site:github.com "exif" language:c
以查找用 C 语言编写并引用了‘exif’的 Github 项目。
快速浏览一下,我们找到了这个项目:https://github.com/mkttanabe/exif。
我们可以通过 git 克隆该仓库,并按照 README 中包含的 gcc 构建说明进行安装。(为了方便,我将编译后的二进制文件放在 /usr/bin
中。)
让我们先看看该程序如何处理我们有效的 JPEG。
我们看到程序正在解析标签并显示与其关联的字节值。这正是我们想要找到的。
理想情况下,我们希望向这个二进制文件输入一些变异数据并让它发生段错误,这意味着我们发现了一个漏洞。我遇到的问题是,当我监控 stdout 和 stderr 以获取Segmentation fault
消息时,它从未出现。这是因为Segmentation fault
消息来自我们的命令 shell 而不是二进制文件。这意味着 shell 收到了一个 SIGSEGV 信号,并响应打印了该消息。
我找到的一种监控方法是使用 pexpect
Python 模块中的 run()
方法和 pipes
Python 模块中的 quote()
方法。
我们将添加一个新函数,该函数将接收一个计数器参数,表示我们处于哪个模糊测试迭代中,还将接收另一个参数中的变异数据。如果我们在 run()
命令的输出中看到 Segmentation,我们将把变异数据写入文件并保存,以便我们拥有导致二进制文件崩溃的 JPEG 图像。
让我们创建一个名为 crashes
的新文件夹,并将导致崩溃的 JPEG 图像保存在该文件夹中,格式为 crash.<fuzzing iteration (counter)>.jpg
。因此,如果模糊测试迭代 100 导致崩溃,我们应该得到一个类似 /crashes/crash.100.jpg
的文件。
我们将在终端的同一行上打印,以便每 100 次模糊测试迭代进行计数。我们的函数如下所示:
接下来,我们将在脚本底部修改我们的执行存根,使其在计数器上运行。一旦达到 1000 次迭代,我们将停止模糊测试。我们还将让我们的模糊测试器随机选择一种变异方法。因此,它可能会进行比特翻转,也可能会使用Magic Numbers。让我们运行它,然后在完成后检查我们的 crashes 文件夹。
模糊测试器完成后,你可以看到我们大约有 30 次崩溃!
我们现在可以使用一个简单的单行命令来确认结果:root@kali:~/crashes# for i in *.jpg; do exif "$i" -verbose > /dev/null 2>&1; done
。记住,我们可以将 STDOUT 和 STDERR 都重定向到 /dev/null
,因为“Segmentation fault”来自 shell,而不是二进制文件。
我们运行这个命令,输出如下:
你无法看到所有的崩溃,但确实有 30 次段错误,所以一切似乎都按计划进行!
现在我们有大约 30 次崩溃和导致这些崩溃的 JPEG 文件,下一步是分析这些崩溃并确定它们中有多少是独特的。这时我们将利用我在观看 Brandon Faulk 的直播时学到的一些东西。快速查看 Beyond Compare 中的崩溃样本告诉我,大多数崩溃是由我们的 bit_flip()
变异引起的,而不是 magic()
变异方法。有趣。作为测试,在我们继续的过程中,我们可以关闭函数选择的随机性,并仅使用 magic()
变异器运行 100,000 次迭代,看看是否会有任何崩溃。
ASan 是“Address Sanitizer”,它是较新版本的 gcc 附带的一个工具,允许用户使用 -fsanitize=address
开关编译二进制文件,并在发生内存访问错误时获取非常详细的信息,甚至包括那些导致崩溃的错误。显然,我们在这里已经预先选择了导致崩溃的输入,所以我们将错过这个工具的部分功能,但或许我们可以留到下次使用。
为了使用 ASan,我按照 Fuzzing Project 的步骤,使用 cc -fsanitize=address -ggdb -o exifsan sample_main.c exif.c
命令重新编译了 exif
然后我将 exifsan
移动到 /usr/bin
以便于使用。如果我们在崩溃样本上运行这个新编译的二进制文件,让我们看看输出。
这真是太好了。我们不仅得到了详细的信息,ASan 还为我们分类了错误类型,告诉我们崩溃地址,并提供了一个漂亮的堆栈跟踪。如你所见,我们在 exif.c
文件的 parseIFD
函数中执行了一个 4 字节的读取操作。
由于这些现在都是标准的二进制输出,我们实际上可以对这些崩溃进行分类并尝试理解它们。让我们首先尝试去重这些崩溃。这里有可能我们所有的 30 次崩溃都是同一个错误。也有可能我们有 30 个独特的崩溃(不太可能,哈哈)。所以我们需要整理清楚。
让我们再次求助于一个 Python 脚本,我们将遍历这个文件夹,针对每个崩溃运行启用 ASan 的二进制文件,并记录每个崩溃的地址。我们还会尝试捕捉它是 'READ'
还是 'WRITE'
操作。例如,对于 crash.252.jpg
,我们将日志文件格式化为:crash.252.HBO.b4f00758.READ
,并将 ASan 输出写入日志。这样我们就知道导致崩溃的图像、错误类型、地址和操作,而无需打开日志。(我会在最后发布分类脚本,它真的很糟糕,呃,我讨厌它。)
在我们的crashes
文件夹上运行分类脚本后,我们现在可以看到我们已经对崩溃进行了分类,并且有一些非常有趣的发现。
经过一大段省略后,在我的 30 次崩溃中,我只有一个是 WRITE 操作。你无法从省略的输出中看出,但我也有很多 SEGV 错误,其中引用了 NULL 地址(0x00000000
)。
让我们也检查一下仅使用 magic()
变异器运行了 100,000 次迭代的修改后的模糊测试器,看看它是否发现了任何错误。
这真是大量的崩溃!
这个模糊测试器可以进行大量优化,目前它真的很粗糙,只是为了演示非常基础的变异模糊测试。错误分类过程也是一团糟,整个过程感觉非常临时,我想我需要多看一些 @gamozolabs 的直播。也许下次我们进行模糊测试时,我们会尝试一个更难的目标,用 Rust 或 Go 这样的酷语言编写模糊测试器,并真正改进分类过程/利用其中一个错误!
感谢博客文章中提到的每一个人,非常感谢。
下次见!
JPEGfuzz.py
triage.py
文章目前一共有6篇,我会陆续翻译
内容使用chatGPT-4o翻译,如有错误之处请斧正
原文链接:https://h0mbre.github.io/Fuzzing-Like-A-Caveman/
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
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
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
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
root@kali:~
# hexdump Canon
0000000 d8ff e0ff 1000 464a 4649 0100 0101 4800
------SNIP------
0001f10 5aed 5158 d9ff
root@kali:~
# hexdump Canon
0000000 d8ff e0ff 1000 464a 4649 0100 0101 4800
------SNIP------
0001f10 5aed 5158 d9ff
#!/usr/bin/env python3
import
sys
# read bytes from our valid JPEG and return them in a mutable bytearray
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)
#!/usr/bin/env python3
import
sys
# read bytes from our valid JPEG and return them in a mutable bytearray
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
=
0
for
x
in
data:
if
counter <
10
:
print
(x)
counter
+
=
1
else
:
filename
=
sys.argv[
1
]
data
=
get_bytes(filename)
counter
=
0
for
x
in
data:
if
counter <
10
:
print
(x)
counter
+
=
1
root@kali:~
# python3 fuzzer.py Canon_40D.jpg
255
216
255
224
0
16
74
70
73
70
root@kali:~
# python3 fuzzer.py Canon_40D.jpg
255
216
255
224
0
16
74
70
73
70
def
create_new(data):
f
=
open
(
"mutated.jpg"
,
"wb+"
)
f.write(data)
f.close()
def
create_new(data):
f
=
open
(
"mutated.jpg"
,
"wb+"
)
f.write(data)
f.close()
root@kali:~
# shasum Canon_40D.jpg mutated.jpg
c3d98686223ad69ea29c811aaab35d343ff1ae9e Canon_40D.jpg
c3d98686223ad69ea29c811aaab35d343ff1ae9e mutated.jpg
root@kali:~
# shasum Canon_40D.jpg mutated.jpg
c3d98686223ad69ea29c811aaab35d343ff1ae9e Canon_40D.jpg
c3d98686223ad69ea29c811aaab35d343ff1ae9e mutated.jpg
num_of_flips
=
int
((
len
(data)
-
4
)
*
.
01
)
num_of_flips
=
int
((
len
(data)
-
4
)
*
.
01
)
indexes
=
range
(
4
, (
len
(data)
-
4
))
chosen_indexes
=
[]
# iterate selecting indexes until we've hit our num_of_flips number
counter
=
0
while
counter < num_of_flips:
chosen_indexes.append(random.choice(indexes))
counter
+
=
1
indexes
=
range
(
4
, (
len
(data)
-
4
))
chosen_indexes
=
[]
# iterate selecting indexes until we've hit our num_of_flips number
counter
=
0
while
counter < num_of_flips:
chosen_indexes.append(random.choice(indexes))
counter
+
=
1
print
(
"Number of indexes chosen: "
+
str
(
len
(chosen_indexes)))
print
(
"Indexes chosen: "
+
str
(chosen_indexes))
print
(
"Number of indexes chosen: "
+
str
(
len
(chosen_indexes)))
print
(
"Indexes chosen: "
+
str
(chosen_indexes))
def
bit_flip(data):
num_of_flips
=
int
((
len
(data)
-
4
)
*
.
01
)
indexes
=
range
(
4
, (
len
(data)
-
4
))
chosen_indexes
=
[]
# iterate selecting indexes until we've hit our num_of_flips number
counter
=
0
while
counter < num_of_flips:
chosen_indexes.append(random.choice(indexes))
counter
+
=
1
print
(
"Number of indexes chosen: "
+
str
(
len
(chosen_indexes)))
print
(
"Indexes chosen: "
+
str
(chosen_indexes))
def
bit_flip(data):
num_of_flips
=
int
((
len
(data)
-
4
)
*
.
01
)
indexes
=
range
(
4
, (
len
(data)
-
4
))
chosen_indexes
=
[]
# iterate selecting indexes until we've hit our num_of_flips number
counter
=
0
while
counter < num_of_flips:
chosen_indexes.append(random.choice(indexes))
counter
+
=
1
print
(
"Number of indexes chosen: "
+
str
(
len
(chosen_indexes)))
print
(
"Indexes chosen: "
+
str
(chosen_indexes))
root@kali:~
# python3 fuzzer.py Canon_40D.jpg
Number of indexes chosen: 79
Indexes chosen: [6580, 930, 6849, 6007, 5020, 33, 474, 4051, 7722, 5393, 3540, 54, 5290, 2106, 2544, 1786, 5969, 5211, 2256, 510, 7147, 3370, 625, 5845, 2082, 2451, 7500, 3672, 2736, 2462, 5395, 7942, 2392, 1201, 3274, 7629, 5119, 1977, 2986, 7590, 1633, 4598, 1834, 445, 481, 7823, 7708, 6840, 1596, 5212, 4277, 3894, 2860, 2912, 6755, 3557, 3535, 3745, 1780, 252, 6128, 7187, 500, 1051, 4372, 5138, 3305, 872, 6258, 2136, 3486, 5600, 651, 1624, 4368, 7076, 1802, 2335, 3553]
root@kali:~
# python3 fuzzer.py Canon_40D.jpg
Number of indexes chosen: 79
Indexes chosen: [6580, 930, 6849, 6007, 5020, 33, 474, 4051, 7722, 5393, 3540, 54, 5290, 2106, 2544, 1786, 5969, 5211, 2256, 510, 7147, 3370, 625, 5845, 2082, 2451, 7500, 3672, 2736, 2462, 5395, 7942, 2392, 1201, 3274, 7629, 5119, 1977, 2986, 7590, 1633, 4598, 1834, 445, 481, 7823, 7708, 6840, 1596, 5212, 4277, 3894, 2860, 2912, 6755, 3557, 3535, 3745, 1780, 252, 6128, 7187, 500, 1051, 4372, 5138, 3305, 872, 6258, 2136, 3486, 5600, 651, 1624, 4368, 7076, 1802, 2335, 3553]
for
x
in
chosen_indexes:
current
=
data[x]
current
=
(
bin
(current).replace(
"0b"
,""))
current
=
"0"
*
(
8
-
len
(current))
+
current
for
x
in
chosen_indexes:
current
=
data[x]
current
=
(
bin
(current).replace(
"0b"
,""))
current
=
"0"
*
(
8
-
len
(current))
+
current
root@kali:~
# python3 fuzzer.py Canon_40D.jpg
10100110
10111110
10010010
00110000
01110001
00110101
00110010
-----SNIP-----
root@kali:~
# python3 fuzzer.py Canon_40D.jpg
10100110
10111110
10010010
00110000
01110001
00110101
00110010
-----SNIP-----
#!/usr/bin/env python3
import
sys
import
random
# read bytes from our valid JPEG and return them in a mutable bytearray
def
get_bytes(filename):
f
=
open
(filename,
"rb"
).read()
return
bytearray(f)
def
bit_flip(data):
num_of_flips
=
int
((
len
(data)
-
4
)
*
.
01
)
indexes
=
range
(
4
, (
len
(data)
-
4
))
chosen_indexes
=
[]
# iterate selecting indexes until we've hit our num_of_flips number
counter
=
0
while
counter < num_of_flips:
chosen_indexes.append(random.choice(indexes))
counter
+
=
1
for
x
in
chosen_indexes:
current
=
data[x]
current
=
(
bin
(current).replace(
"0b"
,""))
current
=
"0"
*
(
8
-
len
(current))
+
current
indexes
=
range
(
0
,
8
)
picked_index
=
random.choice(indexes)
new_number
=
[]
# our new_number list now has all the digits, example: ['1', '0', '1', '0', '1', '0', '1', '0']
for
i
in
current:
new_number.append(i)
# if the number at our randomly selected index is a 1, make it a 0, and vice versa
if
new_number[picked_index]
=
=
"1"
:
new_number[picked_index]
=
"0"
else
:
new_number[picked_index]
=
"1"
# create our new binary string of our bit-flipped number
current
=
''
for
i
in
new_number:
current
+
=
i
# convert that string to an integer
current
=
int
(current,
2
)
# change the number in our byte array to our new number we just constructed
data[x]
=
current
return
data
# create new jpg with mutated data
def
create_new(data):
f
=
open
(
"mutated.jpg"
,
"wb+"
)
f.write(data)
f.close()
if
len
(sys.argv) <
2
:
print
(
"Usage: JPEGfuzz.py <valid_jpg>"
)
else
:
filename
=
sys.argv[
1
]
data
=
get_bytes(filename)
mutated_data
=
bit_flip(data)
create_new(mutated_data)
#!/usr/bin/env python3
import
sys
import
random
# read bytes from our valid JPEG and return them in a mutable bytearray
def
get_bytes(filename):
f
=
open
(filename,
"rb"
).read()
return
bytearray(f)
def
bit_flip(data):
num_of_flips
=
int
((
len
(data)
-
4
)
*
.
01
)
indexes
=
range
(
4
, (
len
(data)
-
4
))
chosen_indexes
=
[]
# iterate selecting indexes until we've hit our num_of_flips number
counter
=
0
while
counter < num_of_flips:
chosen_indexes.append(random.choice(indexes))
counter
+
=
1
for
x
in
chosen_indexes:
current
=
data[x]
current
=
(
bin
(current).replace(
"0b"
,""))
current
=
"0"
*
(
8
-
len
(current))
+
current
indexes
=
range
(
0
,
8
)
picked_index
=
random.choice(indexes)
new_number
=
[]
# our new_number list now has all the digits, example: ['1', '0', '1', '0', '1', '0', '1', '0']
for
i
in
current:
new_number.append(i)
# if the number at our randomly selected index is a 1, make it a 0, and vice versa
if
new_number[picked_index]
=
=
"1"
:
new_number[picked_index]
=
"0"
else
:
new_number[picked_index]
=
"1"
# create our new binary string of our bit-flipped number
current
=
''
for
i
in
new_number:
current
+
=
i
# convert that string to an integer
current
=
int
(current,
2
)
# change the number in our byte array to our new number we just constructed
data[x]
=
current
return
data
# create new jpg with mutated data
def
create_new(data):
f
=
open
(
"mutated.jpg"
,
"wb+"
)
f.write(data)
f.close()
if
len
(sys.argv) <
2
:
print
(
"Usage: JPEGfuzz.py <valid_jpg>"
)
else
:
filename
=
sys.argv[
1
]
data
=
get_bytes(filename)
mutated_data
=
bit_flip(data)
create_new(mutated_data)
root@kali:~
# shasum Canon_40D.jpg mutated.jpg
c3d98686223ad69ea29c811aaab35d343ff1ae9e Canon_40D.jpg
a7b619028af3d8e5ac106a697b06efcde0649249 mutated.jpg
root@kali:~
# shasum Canon_40D.jpg mutated.jpg
c3d98686223ad69ea29c811aaab35d343ff1ae9e Canon_40D.jpg
a7b619028af3d8e5ac106a697b06efcde0649249 mutated.jpg
def
magic(data):
magic_vals
=
[
(
1
,
255
),
(
1
,
255
),
(
1
,
127
),
(
1
,
0
),
(
2
,
255
),
(
2
,
0
),
(
4
,
255
),
(
4
,
0
),
(
4
,
128
),
(
4
,
64
),
(
4
,
127
)
]
picked_magic
=
random.choice(magic_vals)
print
(picked_magic)
def
magic(data):
magic_vals
=
[
(
1
,
255
),
(
1
,
255
),
(
1
,
127
),
(
1
,
0
),
(
2
,
255
),
(
2
,
0
),
(
4
,
255
),
(
4
,
0
),
(
4
,
128
),
(
4
,
64
),
(
4
,
127
)
]
picked_magic
=
random.choice(magic_vals)
print
(picked_magic)
root@kali:~
# python3 fuzzer.py Canon_40D.jpg
(4, 64)
root@kali:~
# python3 fuzzer.py Canon_40D.jpg
(4, 128)
root@kali:~
# python3 fuzzer.py Canon_40D.jpg
(4, 0)
root@kali:~
# python3 fuzzer.py Canon_40D.jpg
(2, 255)
root@kali:~
# python3 fuzzer.py Canon_40D.jpg
(4, 0)
root@kali:~
# python3 fuzzer.py Canon_40D.jpg
(4, 64)
root@kali:~
# python3 fuzzer.py Canon_40D.jpg
(4, 128)
root@kali:~
# python3 fuzzer.py Canon_40D.jpg
(4, 0)
root@kali:~
# python3 fuzzer.py Canon_40D.jpg
(2, 255)
root@kali:~
# python3 fuzzer.py Canon_40D.jpg
(4, 0)
byte[x]
=
128
byte[x
+
1
]
=
0
byte[x
+
2
]
=
0
byte[x
+
3
]
=
0
byte[x]
=
128
byte[x
+
1
]
=
0
byte[x
+
2
]
=
0
byte[x
+
3
]
=
0
def
magic(data):
magic_vals
=
[
(
1
,
255
),
(
1
,
255
),
(
1
,
127
),
(
1
,
0
),
(
2
,
255
),
(
2
,
0
),
(
4
,
255
),
(
4
,
0
),
(
4
,
128
),
(
4
,
64
),
(
4
,
127
)
]
picked_magic
=
random.choice(magic_vals)
length
=
len
(data)
-
8
index
=
range
(
0
, length)
picked_index
=
random.choice(index)
# here we are hardcoding all the byte overwrites for all of the tuples that begin (1, )
if
picked_magic[
0
]
=
=
1
:
if
picked_magic[
1
]
=
=
255
:
# 0xFF
data[picked_index]
=
255
elif
picked_magic[
1
]
=
=
127
:
# 0x7F
data[picked_index]
=
127
elif
picked_magic[
1
]
=
=
0
:
# 0x00
data[picked_index]
=
0
# here we are hardcoding all the byte overwrites for all of the tuples that begin (2, )
elif
picked_magic[
0
]
=
=
2
:
if
picked_magic[
1
]
=
=
255
:
# 0xFFFF
data[picked_index]
=
255
data[picked_index
+
1
]
=
255
elif
picked_magic[
1
]
=
=
0
:
# 0x0000
data[picked_index]
=
0
data[picked_index
+
1
]
=
0
# here we are hardcoding all of the byte overwrites for all of the tuples that being (4, )
elif
picked_magic[
0
]
=
=
4
:
if
picked_magic[
1
]
=
=
255
:
# 0xFFFFFFFF
data[picked_index]
=
255
data[picked_index
+
1
]
=
255
data[picked_index
+
2
]
=
255
data[picked_index
+
3
]
=
255
elif
picked_magic[
1
]
=
=
0
:
# 0x00000000
data[picked_index]
=
0
data[picked_index
+
1
]
=
0
data[picked_index
+
2
]
=
0
data[picked_index
+
3
]
=
0
elif
picked_magic[
1
]
=
=
128
:
# 0x80000000
data[picked_index]
=
128
data[picked_index
+
1
]
=
0
data[picked_index
+
2
]
=
0
data[picked_index
+
3
]
=
0
elif
picked_magic[
1
]
=
=
64
:
# 0x40000000
data[picked_index]
=
64
data[picked_index
+
1
]
=
0
data[picked_index
+
2
]
=
0
data[picked_index
+
3
]
=
0
elif
picked_magic[
1
]
=
=
127
:
# 0x7FFFFFFF
data[picked_index]
=
127
data[picked_index
+
1
]
=
255
data[picked_index
+
2
]
=
255
data[picked_index
+
3
]
=
255
return
data
def
magic(data):
magic_vals
=
[
(
1
,
255
),
(
1
,
255
),
(
1
,
127
),
(
1
,
0
),
(
2
,
255
),
(
2
,
0
),
(
4
,
255
),
(
4
,
0
),
(
4
,
128
),
(
4
,
64
),
(
4
,
127
)
]
picked_magic
=
random.choice(magic_vals)
length
=
len
(data)
-
8
index
=
range
(
0
, length)
picked_index
=
random.choice(index)
# here we are hardcoding all the byte overwrites for all of the tuples that begin (1, )
if
picked_magic[
0
]
=
=
1
:
if
picked_magic[
1
]
=
=
255
:
# 0xFF
data[picked_index]
=
255
elif
picked_magic[
1
]
=
=
127
:
# 0x7F
data[picked_index]
=
127
elif
picked_magic[
1
]
=
=
0
:
# 0x00
data[picked_index]
=
0
# here we are hardcoding all the byte overwrites for all of the tuples that begin (2, )
elif
picked_magic[
0
]
=
=
2
:
if
picked_magic[
1
]
=
=
255
:
# 0xFFFF
data[picked_index]
=
255
data[picked_index
+
1
]
=
255
elif
picked_magic[
1
]
=
=
0
:
# 0x0000
data[picked_index]
=
0
data[picked_index
+
1
]
=
0
# here we are hardcoding all of the byte overwrites for all of the tuples that being (4, )
elif
picked_magic[
0
]
=
=
4
:
if
picked_magic[
1
]
=
=
255
:
# 0xFFFFFFFF
data[picked_index]
=
255
data[picked_index
+
1
]
=
255
data[picked_index
+
2
]
=
255
data[picked_index
+
3
]
=
255
elif
picked_magic[
1
]
=
=
0
:
# 0x00000000
data[picked_index]
=
0
data[picked_index
+
1
]
=
0
data[picked_index
+
2
]
=
0
data[picked_index
+
3
]
=
0
elif
picked_magic[
1
]
=
=
128
:
# 0x80000000
data[picked_index]
=
128
data[picked_index
+
1
]
=
0
data[picked_index
+
2
]
=
0
data[picked_index
+
3
]
=
0
elif
picked_magic[
1
]
=
=
64
:
# 0x40000000
data[picked_index]
=
64
data[picked_index
+
1
]
=
0
data[picked_index
+
2
]
=
0
data[picked_index
+
3
]
=
0
elif
picked_magic[
1
]
=
=
127
:
# 0x7FFFFFFF
data[picked_index]
=
127
data[picked_index
+
1
]
=
255
data[picked_index
+
2
]
=
255
data[picked_index
+
3
]
=
255
return
data
root@kali:~
# exif Canon_40D.jpg -verbose
system: little-endian
data: little-endian
[Canon_40D.jpg] createIfdTableArray: result=5
{0TH IFD} tags=11
tag[00] 0x010F Make
type
=2 count=6 val=[Canon]
tag[01] 0x0110 Model
type
=2 count=14 val=[Canon EOS 40D]
tag[02] 0x0112 Orientation
type
=3 count=1 val=1
tag[03] 0x011A XResolution
type
=5 count=1 val=72
/1
tag[04] 0x011B YResolution
type
=5 count=1 val=72
/1
tag[05] 0x0128 ResolutionUnit
type
=3 count=1 val=2
tag[06] 0x0131 Software
type
=2 count=11 val=[GIMP 2.4.5]
tag[07] 0x0132 DateTime
type
=2 count=20 val=[2008:07:31 10:38:11]
tag[08] 0x0213 YCbCrPositioning
type
=3 count=1 val=2
tag[09] 0x8769 ExifIFDPointer
type
=4 count=1 val=214
tag[10] 0x8825 GPSInfoIFDPointer
type
=4 count=1 val=978
{EXIF IFD} tags=30
tag[00] 0x829A ExposureTime
type
=5 count=1 val=1
/160
tag[01] 0x829D FNumber
type
=5 count=1 val=71
/10
tag[02] 0x8822 ExposureProgram
type
=3 count=1 val=1
tag[03] 0x8827 PhotographicSensitivity
type
=3 count=1 val=100
tag[04] 0x9000 ExifVersion
type
=7 count=4 val=0 2 2 1
tag[05] 0x9003 DateTimeOriginal
type
=2 count=20 val=[2008:05:30 15:56:01]
tag[06] 0x9004 DateTimeDigitized
type
=2 count=20 val=[2008:05:30 15:56:01]
tag[07] 0x9101 ComponentsConfiguration
type
=7 count=4 val=0x01 0x02 0x03 0x00
tag[08] 0x9201 ShutterSpeedValue
type
=10 count=1 val=483328
/65536
tag[09] 0x9202 ApertureValue
type
=5 count=1 val=368640
/65536
tag[10] 0x9204 ExposureBiasValue
type
=10 count=1 val=0
/1
tag[11] 0x9207 MeteringMode
type
=3 count=1 val=5
tag[12] 0x9209 Flash
type
=3 count=1 val=9
tag[13] 0x920A FocalLength
type
=5 count=1 val=135
/1
tag[14] 0x9286 UserComment
type
=7 count=264 val=0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 (omitted)
tag[15] 0x9290 SubSecTime
type
=2 count=3 val=[00]
tag[16] 0x9291 SubSecTimeOriginal
type
=2 count=3 val=[00]
tag[17] 0x9292 SubSecTimeDigitized
type
=2 count=3 val=[00]
tag[18] 0xA000 FlashPixVersion
type
=7 count=4 val=0 1 0 0
tag[19] 0xA001 ColorSpace
type
=3 count=1 val=1
tag[20] 0xA002 PixelXDimension
type
=4 count=1 val=100
tag[21] 0xA003 PixelYDimension
type
=4 count=1 val=68
tag[22] 0xA005 InteroperabilityIFDPointer
type
=4 count=1 val=948
tag[23] 0xA20E FocalPlaneXResolution
type
=5 count=1 val=3888000
/876
tag[24] 0xA20F FocalPlaneYResolution
type
=5 count=1 val=2592000
/583
tag[25] 0xA210 FocalPlaneResolutionUnit
type
=3 count=1 val=2
tag[26] 0xA401 CustomRendered
type
=3 count=1 val=0
tag[27] 0xA402 ExposureMode
type
=3 count=1 val=1
tag[28] 0xA403 WhiteBalance
type
=3 count=1 val=0
tag[29] 0xA406 SceneCaptureType
type
=3 count=1 val=0
{Interoperability IFD} tags=2
tag[00] 0x0001 InteroperabilityIndex
type
=2 count=4 val=[R98]
tag[01] 0x0002 InteroperabilityVersion
type
=7 count=4 val=0 1 0 0
{GPS IFD} tags=1
tag[00] 0x0000 GPSVersionID
type
=1 count=4 val=2 2 0 0
{1ST IFD} tags=6
tag[00] 0x0103 Compression
type
=3 count=1 val=6
tag[01] 0x011A XResolution
type
=5 count=1 val=72
/1
tag[02] 0x011B YResolution
type
=5 count=1 val=72
/1
tag[03] 0x0128 ResolutionUnit
type
=3 count=1 val=2
tag[04] 0x0201 JPEGInterchangeFormat
type
=4 count=1 val=1090
tag[05] 0x0202 JPEGInterchangeFormatLength
type
=4 count=1 val=1378
0th IFD : Model = [Canon EOS 40D]
Exif IFD : DateTimeOriginal = [2008:05:30 15:56:01]
root@kali:~
# exif Canon_40D.jpg -verbose
system: little-endian
data: little-endian
[Canon_40D.jpg] createIfdTableArray: result=5
{0TH IFD} tags=11
tag[00] 0x010F Make
type
=2 count=6 val=[Canon]
tag[01] 0x0110 Model
type
=2 count=14 val=[Canon EOS 40D]
tag[02] 0x0112 Orientation
type
=3 count=1 val=1
tag[03] 0x011A XResolution
type
=5 count=1 val=72
/1
tag[04] 0x011B YResolution
type
=5 count=1 val=72
/1
tag[05] 0x0128 ResolutionUnit
type
=3 count=1 val=2
tag[06] 0x0131 Software
type
=2 count=11 val=[GIMP 2.4.5]
tag[07] 0x0132 DateTime
type
=2 count=20 val=[2008:07:31 10:38:11]
tag[08] 0x0213 YCbCrPositioning
type
=3 count=1 val=2
tag[09] 0x8769 ExifIFDPointer
type
=4 count=1 val=214
tag[10] 0x8825 GPSInfoIFDPointer
type
=4 count=1 val=978
{EXIF IFD} tags=30
tag[00] 0x829A ExposureTime
type
=5 count=1 val=1
/160
tag[01] 0x829D FNumber
type
=5 count=1 val=71
/10
tag[02] 0x8822 ExposureProgram
type
=3 count=1 val=1
tag[03] 0x8827 PhotographicSensitivity
type
=3 count=1 val=100
tag[04] 0x9000 ExifVersion
type
=7 count=4 val=0 2 2 1
tag[05] 0x9003 DateTimeOriginal
type
=2 count=20 val=[2008:05:30 15:56:01]
tag[06] 0x9004 DateTimeDigitized
type
=2 count=20 val=[2008:05:30 15:56:01]
tag[07] 0x9101 ComponentsConfiguration
type
=7 count=4 val=0x01 0x02 0x03 0x00
tag[08] 0x9201 ShutterSpeedValue
type
=10 count=1 val=483328
/65536
tag[09] 0x9202 ApertureValue
type
=5 count=1 val=368640
/65536
tag[10] 0x9204 ExposureBiasValue
type
=10 count=1 val=0
/1
tag[11] 0x9207 MeteringMode
type
=3 count=1 val=5
tag[12] 0x9209 Flash
type
=3 count=1 val=9
tag[13] 0x920A FocalLength
type
=5 count=1 val=135
/1
tag[14] 0x9286 UserComment
type
=7 count=264 val=0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 (omitted)
tag[15] 0x9290 SubSecTime
type
=2 count=3 val=[00]
tag[16] 0x9291 SubSecTimeOriginal
type
=2 count=3 val=[00]
tag[17] 0x9292 SubSecTimeDigitized
type
=2 count=3 val=[00]
tag[18] 0xA000 FlashPixVersion
type
=7 count=4 val=0 1 0 0
tag[19] 0xA001 ColorSpace
type
=3 count=1 val=1
tag[20] 0xA002 PixelXDimension
type
=4 count=1 val=100
tag[21] 0xA003 PixelYDimension
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!